mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 21:40:50 +00:00
fix(礼物海报): 修复保存海报空白并同源化绘制图片
- 组件内置离屏画布并按组件实例创建上下文,补齐缺失的海报画布 - 头像/二维码缺失时跳过绘制,二维码异步生成等待后再绘制 - 失败时提示海报生成失败,关闭弹窗重置海报状态 - 开发环境代理 /uploads、/statics,规避 canvas 跨域污染
This commit is contained in:
parent
db3870fdbd
commit
d2d1305d26
@ -4,16 +4,39 @@
|
||||
v-if="aleartStatus"
|
||||
:style="'background-image: url(' + giftbag + ');'"
|
||||
>
|
||||
<!-- 海报绘制画布:尺寸与海报一致,移出可视区;组件内 canvas 需配合组件实例使用 -->
|
||||
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
|
||||
<template v-if="!posterImageStatus">
|
||||
<text class="iconfont icon-cha2 close" @click="posterImageClose"></text>
|
||||
<view class="from">{{ $t(`赠送给好友一份礼物`) }}</view>
|
||||
<view class="message">{{ giftData.message }}</view>
|
||||
<view class="aleart-body">
|
||||
<image class="goods-img" :src="giftData.image" mode=""></image>
|
||||
<image class="goods-img" :src="giftData.image" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="title line1">
|
||||
{{ $t(giftData.title) }}
|
||||
</view>
|
||||
<!-- 二维码仅用于生成海报,不在弹窗中展示 -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<view class="qrcode-hidden">
|
||||
<zb-code
|
||||
ref="qrcode"
|
||||
:show="codeShow"
|
||||
:cid="cid"
|
||||
:val="codeVal"
|
||||
:size="size"
|
||||
:unit="unit"
|
||||
:background="background"
|
||||
:foreground="foreground"
|
||||
:pdground="pdground"
|
||||
:icon="codeIcon"
|
||||
:iconSize="iconsize"
|
||||
:onval="onval"
|
||||
:loadMake="loadMake"
|
||||
@result="qrR"
|
||||
/>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="btn" @click="copyLink()">
|
||||
{{ $t("复制礼物链接") }}
|
||||
@ -35,29 +58,11 @@
|
||||
<view class="keep">{{ $t(`长按图片可以保存到手机`) }}</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<zb-code
|
||||
ref="qrcode"
|
||||
:show="codeShow"
|
||||
:cid="cid"
|
||||
:val="codeVal"
|
||||
:size="size"
|
||||
:unit="unit"
|
||||
:background="background"
|
||||
:foreground="foreground"
|
||||
:pdground="pdground"
|
||||
:icon="codeIcon"
|
||||
:iconSize="iconsize"
|
||||
:onval="onval"
|
||||
:loadMake="loadMake"
|
||||
@result="qrR"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, getCurrentInstance } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { HTTP_REQUEST_URL } from "@/config/app";
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
@ -81,6 +86,8 @@ const emit = defineEmits(['close', 'shareH5']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { uid } = storeToRefs(appStore);
|
||||
// 组件内 canvas(小程序端 createCanvasContext/canvasToTempFilePath 必须传组件实例)
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
// 原代码未在 data 声明、通过 this.shareQrcode 访问的字段(undefined)
|
||||
let shareQrcode;
|
||||
@ -89,12 +96,12 @@ const aleartData = ref({});
|
||||
const bag = HTTP_REQUEST_URL + "/statics/images/canvas-bag.png";
|
||||
const giftBorder = HTTP_REQUEST_URL + "/statics/images/gift-border.png";
|
||||
const giftbag = HTTP_REQUEST_URL + "/statics/images/gift-bag.png";
|
||||
//二维码参数
|
||||
//二维码参数(仅供海报生成,弹窗不展示)
|
||||
const codeShow = ref(false);
|
||||
const cid = "1";
|
||||
const codeVal = ref(""); // 要生成的二维码值
|
||||
const size = 200; // 二维码大小
|
||||
const unit = "upx"; // 单位
|
||||
const unit = "px"; // 单位
|
||||
const background = "#FFF"; // 背景色
|
||||
const foreground = "#000"; // 前景色
|
||||
const pdground = "#000"; // 角标色
|
||||
@ -110,6 +117,9 @@ const posterImage = ref("");
|
||||
watch(() => props.aleartStatus, (status) => {
|
||||
if (!status) {
|
||||
aleartData.value = {};
|
||||
// 关闭时重置海报状态,避免下次打开直接显示上一次的旧海报
|
||||
posterImageStatus.value = false;
|
||||
posterImage.value = "";
|
||||
} else {
|
||||
// #ifdef H5
|
||||
codeVal.value =
|
||||
@ -156,6 +166,14 @@ function posterImageClose() {
|
||||
emit("close", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制海报
|
||||
* 头像、二维码可能缺失(订单详情不返回赠送人头像、二维码异步生成),缺失时跳过对应绘制
|
||||
* @param {Array} loadedImages [背景, 商品边框, 商品图, 头像, 二维码]
|
||||
* @param {string} name 赠送人昵称(可能为空)
|
||||
* @param {string} store_name 商品标题
|
||||
* @return {Promise<string>} 海报临时文件路径
|
||||
*/
|
||||
function drawPoster(loadedImages, name, store_name) {
|
||||
// 截断标题函数
|
||||
function truncateTitle(title, maxLength) {
|
||||
@ -164,140 +182,146 @@ function drawPoster(loadedImages, name, store_name) {
|
||||
}
|
||||
return title;
|
||||
}
|
||||
// 获取canvas上下文
|
||||
const ctx = uni.createCanvasContext("posterCanvas");
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 获取canvas上下文(组件内 canvas 需传组件实例)
|
||||
const ctx = uni.createCanvasContext("posterCanvas", instance);
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src: loadedImages[0],
|
||||
success: (res) => {
|
||||
// 海报尺寸
|
||||
const posterWidth = 375;
|
||||
const posterHeight = 579;
|
||||
// const posterWidth = res.width / 2;
|
||||
// const posterHeight = res.height / 2;
|
||||
// 绘制背景图
|
||||
ctx.drawImage(loadedImages[0], 0, 0, posterWidth, posterHeight);
|
||||
ctx.save();
|
||||
// 头像和标题的布局
|
||||
fail: (err) => reject(err),
|
||||
success: () => {
|
||||
try {
|
||||
// 海报尺寸
|
||||
const posterWidth = 375;
|
||||
const posterHeight = 579;
|
||||
// 绘制背景图
|
||||
ctx.drawImage(loadedImages[0], 0, 0, posterWidth, posterHeight);
|
||||
ctx.save();
|
||||
|
||||
const avatarSize = 22; // 头像尺寸
|
||||
const nickname = name; // 昵称
|
||||
const title = i18n.t(`赠送给好友一份礼物`); // 标题文字
|
||||
const titleFontSize = 14; // 标题字号
|
||||
const nicknameFontSize = 14; // 昵称字号
|
||||
const padding = 10; // 元素之间的间距
|
||||
// 计算标题宽度
|
||||
ctx.setFontSize(titleFontSize);
|
||||
const titleWidth = ctx.measureText(title).width;
|
||||
const nicknameWidth = ctx.measureText(nickname).width;
|
||||
// 计算头像和标题的总宽度
|
||||
const totalWidth =
|
||||
avatarSize + padding + nicknameWidth + padding + titleWidth;
|
||||
// 头像和标题的布局
|
||||
const avatarSize = 22; // 头像尺寸
|
||||
const nickname = name || ""; // 昵称
|
||||
const title = i18n.t(`赠送给好友一份礼物`); // 标题文字
|
||||
const titleFontSize = 14; // 标题字号
|
||||
const nicknameFontSize = 14; // 昵称字号
|
||||
const padding = 10; // 元素之间的间距
|
||||
const avatarImage = loadedImages[3]; // 头像(可能为空)
|
||||
const hasAvatar = !!avatarImage;
|
||||
|
||||
// 计算起始绘制位置(水平居中)
|
||||
const startX = (posterWidth - totalWidth) / 2;
|
||||
const startY = 77; // 距离顶部的距离
|
||||
// 计算标题/昵称宽度,按实际存在的元素计算总宽并居中
|
||||
ctx.setFontSize(titleFontSize);
|
||||
const titleWidth = ctx.measureText(title).width;
|
||||
const nicknameWidth = nickname ? ctx.measureText(nickname).width : 0;
|
||||
const totalWidth =
|
||||
(hasAvatar ? avatarSize + padding : 0) +
|
||||
(nickname ? nicknameWidth + padding : 0) +
|
||||
titleWidth;
|
||||
|
||||
// 绘制头像
|
||||
// ctx.drawImage(loadedImages[3], startX, startY, avatarSize, avatarSize);
|
||||
const avatarX = startX + avatarSize / 2; // 头像中心点 X
|
||||
const avatarY = startY + avatarSize / 2; // 头像中心点 Y
|
||||
ctx.save(); // 保存画布状态
|
||||
ctx.beginPath();
|
||||
ctx.arc(avatarX, avatarY, avatarSize / 2, 0, Math.PI * 2); // 绘制圆形路径
|
||||
ctx.clip(); // 裁剪圆形区域
|
||||
ctx.drawImage(
|
||||
loadedImages[3],
|
||||
startX,
|
||||
startY,
|
||||
avatarSize,
|
||||
avatarSize
|
||||
); // 绘制头像
|
||||
ctx.restore(); // 恢复画布状态
|
||||
// 计算起始绘制位置(水平居中)
|
||||
const startX = (posterWidth - totalWidth) / 2;
|
||||
const startY = 77; // 距离顶部的距离
|
||||
let cursorX = startX;
|
||||
|
||||
// 绘制昵称
|
||||
ctx.setFontSize(nicknameFontSize);
|
||||
ctx.setTextAlign("left");
|
||||
ctx.fillText(
|
||||
nickname,
|
||||
startX + avatarSize + padding,
|
||||
startY + avatarSize - 5
|
||||
); // 调整文字垂直居中
|
||||
// 绘制标题
|
||||
ctx.setFontSize(titleFontSize);
|
||||
ctx.fillText(
|
||||
title,
|
||||
startX + avatarSize + padding + nicknameWidth + padding,
|
||||
startY + avatarSize - 5
|
||||
);
|
||||
// 绘制头像(无头像时不占位,标题自动居中)
|
||||
if (hasAvatar) {
|
||||
const avatarX = startX + avatarSize / 2; // 头像中心点 X
|
||||
const avatarY = startY + avatarSize / 2; // 头像中心点 Y
|
||||
ctx.save(); // 保存画布状态
|
||||
ctx.beginPath();
|
||||
ctx.arc(avatarX, avatarY, avatarSize / 2, 0, Math.PI * 2); // 绘制圆形路径
|
||||
ctx.clip(); // 裁剪圆形区域
|
||||
ctx.drawImage(avatarImage, startX, startY, avatarSize, avatarSize); // 绘制头像
|
||||
ctx.restore(); // 恢复画布状态
|
||||
cursorX += avatarSize + padding;
|
||||
}
|
||||
|
||||
// 商品图尺寸
|
||||
const productImageSize = 225; // 商品图尺寸为 225px x 225px
|
||||
// 绘制昵称(可能为空)
|
||||
if (nickname) {
|
||||
ctx.setFontSize(nicknameFontSize);
|
||||
ctx.setTextAlign("left");
|
||||
ctx.fillText(nickname, cursorX, startY + avatarSize - 5); // 调整文字垂直居中
|
||||
cursorX += nicknameWidth + padding;
|
||||
}
|
||||
|
||||
// 绘制商品图边框
|
||||
const productBorderX = (posterWidth - productImageSize) / 2; // 水平居中
|
||||
const productBorderY = startY + avatarSize + 31; // 距离头像和标题的间距
|
||||
ctx.drawImage(
|
||||
loadedImages[1],
|
||||
productBorderX,
|
||||
productBorderY,
|
||||
productImageSize,
|
||||
productImageSize
|
||||
);
|
||||
// 绘制标题
|
||||
ctx.setFontSize(titleFontSize);
|
||||
ctx.setTextAlign("left");
|
||||
ctx.fillText(title, cursorX, startY + avatarSize - 5);
|
||||
|
||||
// 绘制商品图
|
||||
const productImagePadding = 10; // 商品图与边框的内边距
|
||||
const productImageX = productBorderX + productImagePadding;
|
||||
const productImageY = productBorderY + productImagePadding + 11;
|
||||
const productImageInnerSize =
|
||||
productImageSize - 2 * productImagePadding; // 商品图实际绘制尺寸
|
||||
ctx.drawImage(
|
||||
loadedImages[2],
|
||||
productImageX,
|
||||
productImageY,
|
||||
productImageInnerSize,
|
||||
productImageInnerSize - 10
|
||||
);
|
||||
// 商品图尺寸
|
||||
const productImageSize = 225; // 商品图尺寸为 225px x 225px
|
||||
|
||||
// 绘制商品标题
|
||||
const productTitle = store_name;
|
||||
const maxTitleLength = 20; // 标题最大长度
|
||||
const truncatedTitle = truncateTitle(productTitle, maxTitleLength); // 截断标题
|
||||
ctx.setFontSize(14);
|
||||
ctx.setTextAlign("center");
|
||||
ctx.fillText(
|
||||
truncatedTitle,
|
||||
posterWidth / 2,
|
||||
productBorderY + productImageSize + 26
|
||||
);
|
||||
// 绘制分享二维码
|
||||
const qrCodeSize = 100;
|
||||
const qrCodeX = (posterWidth - qrCodeSize) / 2;
|
||||
const qrCodeY = productBorderY + productImageSize + 63; // 距离商品标题的间距
|
||||
ctx.drawImage(
|
||||
loadedImages[4],
|
||||
qrCodeX,
|
||||
qrCodeY,
|
||||
qrCodeSize,
|
||||
qrCodeSize
|
||||
);
|
||||
// 绘制商品图边框
|
||||
const productBorderX = (posterWidth - productImageSize) / 2; // 水平居中
|
||||
const productBorderY = startY + avatarSize + 31; // 距离头像和标题的间距
|
||||
ctx.drawImage(
|
||||
loadedImages[1],
|
||||
productBorderX,
|
||||
productBorderY,
|
||||
productImageSize,
|
||||
productImageSize
|
||||
);
|
||||
|
||||
// 绘制完成
|
||||
ctx.draw(false, () => {
|
||||
// 生成海报图片
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: "posterCanvas",
|
||||
width: posterWidth,
|
||||
height: posterHeight,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
// 绘制商品图
|
||||
const productImagePadding = 10; // 商品图与边框的内边距
|
||||
const productImageX = productBorderX + productImagePadding;
|
||||
const productImageY = productBorderY + productImagePadding + 11;
|
||||
const productImageInnerSize =
|
||||
productImageSize - 2 * productImagePadding; // 商品图实际绘制尺寸
|
||||
ctx.drawImage(
|
||||
loadedImages[2],
|
||||
productImageX,
|
||||
productImageY,
|
||||
productImageInnerSize,
|
||||
productImageInnerSize - 10
|
||||
);
|
||||
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
// 绘制商品标题
|
||||
const productTitle = store_name || "";
|
||||
const maxTitleLength = 20; // 标题最大长度
|
||||
const truncatedTitle = truncateTitle(productTitle, maxTitleLength); // 截断标题
|
||||
ctx.setFontSize(14);
|
||||
ctx.setTextAlign("center");
|
||||
ctx.fillText(
|
||||
truncatedTitle,
|
||||
posterWidth / 2,
|
||||
productBorderY + productImageSize + 26
|
||||
);
|
||||
|
||||
// 绘制分享二维码(异步生成,可能为空)
|
||||
if (loadedImages[4]) {
|
||||
const qrCodeSize = 100;
|
||||
const qrCodeX = (posterWidth - qrCodeSize) / 2;
|
||||
const qrCodeY = productBorderY + productImageSize + 63; // 距离商品标题的间距
|
||||
ctx.drawImage(
|
||||
loadedImages[4],
|
||||
qrCodeX,
|
||||
qrCodeY,
|
||||
qrCodeSize,
|
||||
qrCodeSize
|
||||
);
|
||||
}
|
||||
|
||||
// 绘制完成
|
||||
ctx.draw(false, () => {
|
||||
// 生成海报图片
|
||||
uni.canvasToTempFilePath(
|
||||
{
|
||||
canvasId: "posterCanvas",
|
||||
width: posterWidth,
|
||||
height: posterHeight,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
},
|
||||
instance
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -319,32 +343,89 @@ function share() {
|
||||
emit("shareH5");
|
||||
}
|
||||
|
||||
function getPoster() {
|
||||
let images = [
|
||||
bag,
|
||||
giftBorder,
|
||||
props.giftData.image,
|
||||
props.giftData.avatar,
|
||||
PromotionCode.value,
|
||||
];
|
||||
let postImg = ["", "", "", "", ""];
|
||||
// #ifdef MP
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
uni.downloadFile({
|
||||
url: images[i],
|
||||
success: (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
postImg[i] = res.tempFilePath;
|
||||
}
|
||||
const allNotEmpty = postImg.every((item) => item !== "");
|
||||
if (allNotEmpty) goPoster(postImg);
|
||||
},
|
||||
fail: () => {
|
||||
PromotionCode.value = "";
|
||||
},
|
||||
});
|
||||
/**
|
||||
* 等待二维码生成(zb-code 异步出图,避免绘制时图片地址为空)
|
||||
* @param {number} timeout 超时毫秒
|
||||
* @return {Promise<string>} 二维码图片地址,超时返回空串
|
||||
*/
|
||||
function waitForPromotionCode(timeout = 3000) {
|
||||
return new Promise((resolve) => {
|
||||
if (PromotionCode.value) {
|
||||
resolve(PromotionCode.value);
|
||||
return;
|
||||
}
|
||||
const start = Date.now();
|
||||
const timer = setInterval(() => {
|
||||
if (PromotionCode.value || Date.now() - start >= timeout) {
|
||||
clearInterval(timer);
|
||||
resolve(PromotionCode.value || "");
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 海报图片同源化(H5)
|
||||
* 跨域图片会污染 canvas,导致 toDataURL/getImageData 抛 SecurityError;
|
||||
* 后端自身域名(HTTP_REQUEST_URL)的图片改为相对路径,由开发环境 vite 代理走同源,
|
||||
* 生产同源部署下同样可用;其他 CDN 域名保持原样,由其自行提供 CORS。
|
||||
* @param {string} url 原始图片地址
|
||||
* @return {string} 可用于绘制/下载的图片地址
|
||||
*/
|
||||
function toSameOriginUrl(url) {
|
||||
if (!url) return "";
|
||||
// #ifdef H5
|
||||
if (/^https?:\/\//i.test(url)) {
|
||||
try {
|
||||
const target = new URL(url, window.location.origin);
|
||||
if (target.origin === window.location.origin) {
|
||||
return target.href;
|
||||
}
|
||||
const apiOrigin = new URL(HTTP_REQUEST_URL).origin;
|
||||
if (target.origin === apiOrigin) {
|
||||
return target.pathname + target.search;
|
||||
}
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
return url;
|
||||
}
|
||||
|
||||
async function getPoster() {
|
||||
// 顺序固定:[背景, 商品边框, 商品图, 头像, 二维码];空项在绘制时跳过
|
||||
const qrCode = await waitForPromotionCode();
|
||||
const images = [
|
||||
toSameOriginUrl(bag),
|
||||
toSameOriginUrl(giftBorder),
|
||||
toSameOriginUrl(props.giftData.image || ""),
|
||||
toSameOriginUrl(props.giftData.avatar || ""),
|
||||
toSameOriginUrl(qrCode),
|
||||
];
|
||||
// #ifdef MP
|
||||
const postImg = ["", "", "", "", ""];
|
||||
await Promise.all(
|
||||
images.map(
|
||||
(src, index) =>
|
||||
new Promise((resolve) => {
|
||||
if (!src) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
uni.downloadFile({
|
||||
url: src,
|
||||
success: (res) => {
|
||||
if (res.statusCode == 200) postImg[index] = res.tempFilePath;
|
||||
resolve();
|
||||
},
|
||||
fail: () => resolve(),
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
goPoster(postImg);
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
goPoster(images);
|
||||
// #endif
|
||||
@ -363,6 +444,7 @@ function goPoster(postImg) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("海报生成失败:", err);
|
||||
uni.showToast({ title: i18n.t(`海报生成失败`), icon: "none" });
|
||||
});
|
||||
}
|
||||
|
||||
@ -438,7 +520,8 @@ function savePic(url) {
|
||||
height: 419rpx;
|
||||
background-image: url("../../static/gift-border.png");
|
||||
background-size: 100% 100%;
|
||||
margin-bottom: 32rpx;
|
||||
margin-bottom: 16rpx;
|
||||
overflow: hidden;
|
||||
.goods-img {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
@ -450,7 +533,7 @@ function savePic(url) {
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 52rpx;
|
||||
margin-bottom: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.btn,
|
||||
@ -490,4 +573,24 @@ function savePic(url) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* 海报画布:尺寸与海报一致,移出可视区但仍参与布局(canvas 需要有效宽高) */
|
||||
.poster-canvas {
|
||||
position: fixed;
|
||||
left: -2000px;
|
||||
top: 0;
|
||||
width: 375px;
|
||||
height: 579px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 二维码画布只服务海报生成,视觉上不出现 */
|
||||
.qrcode-hidden {
|
||||
position: absolute !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -11,8 +11,19 @@ export default defineConfig(({ mode }) => {
|
||||
// 必须在 uni() 首次读取 pages.json 之前执行
|
||||
installAutoSubpackages();
|
||||
|
||||
// 开发环境后端地址:海报等场景需要同源读取后端静态资源(避免 canvas 跨域污染)
|
||||
const apiTarget = env.UNI_APP_API_URL || 'http://127.0.0.1:8011';
|
||||
|
||||
return {
|
||||
plugins: [uniAutoSubpackagesPlugin(), uni()],
|
||||
server: {
|
||||
proxy: {
|
||||
// 后台上传文件(商品图等)
|
||||
'/uploads': { target: apiTarget, changeOrigin: true },
|
||||
// 后端系统静态图(海报背景、礼物边框等)
|
||||
'/statics': { target: apiTarget, changeOrigin: true }
|
||||
}
|
||||
},
|
||||
define: {
|
||||
// 注入 src/config/app.js 中使用的接口地址变量;.env 未配置时置空串,代码内回退到各端默认值
|
||||
'process.env.UNI_APP_API_URL': JSON.stringify(env.UNI_APP_API_URL || '')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user