diff --git a/README.md b/README.md index c04c646d..a789f5d3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ 想快速体验完整商城,不需要分别部署后端、PC、商家端、运营端、IM、H5、MySQL 和 Redis。安装 Docker Desktop 后,直接执行下面脚本,按提示输入 IP/域名、端口、密码和数据目录即可启动单镜像单容器环境。 +数据库口径:常规部署和项目技术栈仍以 MySQL 为准;All-In-One 镜像内置 MariaDB 只是单容器本地体验中的 MySQL 协议兼容实现,不代表生产部署或常规部署已经切换为 MariaDB。 + macOS / Linux: ```bash @@ -26,7 +28,7 @@ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass .\install-lilishop.ps1 ``` -默认镜像:`ccr.ccs.tencentyun.com/lilishop/lilishop-all-in-one:lite`。启动后可访问买家 PC、商家端、运营端、IM Web、uniapp H5 和后端 API。更多说明见 [docker/all-in-one 文档](https://gitee.com/beijing_hongye_huicheng/docker/tree/allinone-lite-mysql-redis/all-in-one)。 +默认镜像:`ccr.ccs.tencentyun.com/lilishop/lilishop-all-in-one:lite`。启动后可访问买家 PC、商家端、运营端、IM Web、uniapp H5,统一后端由 All-In-One 的同域 `/api/` 入口代理。更多说明见 [docker/all-in-one 文档](https://gitee.com/beijing_hongye_huicheng/docker/tree/allinone-lite-mysql-redis/all-in-one)。 LILISHOP 是基于 Spring Boot / Spring Cloud / Vue / Uniapp 开发的 Java 开源商城系统,支持 B2B2C 多商户商城、小程序商城、微服务商城、直播电商、分销返佣、秒杀活动、Docker 私有化部署。 @@ -80,7 +82,7 @@ Lilishop 由 4 个独立仓库组成,均同步托管于 GitHub 与 Gitee: - **全端覆盖**: 一套代码库支持PC、H5、小程序、APP,降低开发和维护成本。 - **商家入驻**: 支持多商家入驻,构建平台化电商生态。 -- **分布式架构**: 后端API服务化,支持独立部署和弹性伸缩。 +- **All-In-One 后端**: 当前分支推荐使用 `lilishop-all` 统一后端入口,降低本地体验和私有化部署门槛。 - **前后端分离**: 清晰的职责划分,便于团队协作和独立开发。 - **容器化支持**: 提供Docker镜像和docker-compose配置,实现一键部署。 - **功能完善**: 涵盖客户、订单、商品、促销、店铺、运营、统计等完整电商业务模块。 diff --git a/buyer/public/config.js b/buyer/public/config.js index 2272ff76..37fa5df8 100644 --- a/buyer/public/config.js +++ b/buyer/public/config.js @@ -3,15 +3,15 @@ var BASE = { * @description api请求基础路径 */ API_DEV: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn" + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api" }, API_PROD: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn" + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api" }, }; diff --git a/im/.env b/im/.env index 070b82b5..aaf0b890 100644 --- a/im/.env +++ b/im/.env @@ -1,10 +1,10 @@ NODE_ENV=production VUE_APP_PREVIEW=false -VUE_APP_API_BASE_URL=https://im-api.pickmall.cn -VUE_APP_WEB_SOCKET_URL=wss://im-api.pickmall.cn/lili/webSocket -VUE_APP_COMMON=https://common-api.pickmall.cn -VUE_APP_BUYER=https://buyer-api.pickmall.cn -VUE_APP_SELLER=https://store-api.pickmall.cn +VUE_APP_API_BASE_URL=/api +VUE_APP_WEB_SOCKET_URL=/lili/webSocket +VUE_APP_COMMON=/api +VUE_APP_BUYER=/api +VUE_APP_SELLER=/api VUE_APP_WEBSITE_NAME="LiLi IM" VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/ -VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/ \ No newline at end of file +VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/ diff --git a/im/.env.development b/im/.env.development index 2b51a5e6..7e9445f3 100644 --- a/im/.env.development +++ b/im/.env.development @@ -1,10 +1,10 @@ NODE_ENV=development VUE_APP_PREVIEW=false -VUE_APP_API_BASE_URL=https://im-api.pickmall.cn -VUE_APP_WEB_SOCKET_URL=wss://im-api.pickmall.cn/lili/webSocket -VUE_APP_COMMON=https://common-api.pickmall.cn -VUE_APP_BUYER=https://buyer-api.pickmall.cn -VUE_APP_SELLER=https://store-api.pickmall.cn +VUE_APP_API_BASE_URL=/api +VUE_APP_WEB_SOCKET_URL=/lili/webSocket +VUE_APP_COMMON=/api +VUE_APP_BUYER=/api +VUE_APP_SELLER=/api VUE_APP_WEBSITE_NAME="LiLi IM" VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/ -VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/ \ No newline at end of file +VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/ diff --git a/im/README.md b/im/README.md index 6ac928fc..f13e9600 100644 --- a/im/README.md +++ b/im/README.md @@ -43,7 +43,7 @@ proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - server_name im-api.pickmall.cn; + server_name shop.example.com; location / { proxy_pass http://127.0.0.1:8088; } @@ -68,4 +68,4 @@ try_files $uri $uri/ /index.html; root /home/im/im/dist; } -```` \ No newline at end of file +```` diff --git a/im/src/config/config.js b/im/src/config/config.js index fe283a94..f42cf217 100644 --- a/im/src/config/config.js +++ b/im/src/config/config.js @@ -22,13 +22,24 @@ const normalizeUrl = (url) => { return url.endsWith("/") ? url : `${url}/`; }; +const normalizeWsUrl = (url) => { + if (!url || /^wss?:\/\//.test(url)) { + return url || ""; + } + if (typeof window !== "undefined" && url.startsWith("/")) { + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + return `${protocol}//${window.location.host}${url}`; + } + return url; +}; + export default { // 网站名称 WEBSITE_NAME: process.env.VUE_APP_WEBSITE_NAME || "LiLi IM", // 默认请求IM的API BASE_API_URL: runtimeApi || process.env.VUE_APP_API_BASE_URL || "", // 默认请求的WS - BASE_WS_URL: runtimeBase.IM_WS_URL || process.env.VUE_APP_WEB_SOCKET_URL || "", + BASE_WS_URL: normalizeWsUrl(runtimeBase.IM_WS_URL || process.env.VUE_APP_WEB_SOCKET_URL || ""), // 默认请求公有接口相关 API BASE_COMMON: apiProd.common || apiDev.common || process.env.VUE_APP_COMMON || "", // 默认请求用户相关API diff --git a/manager/public/config.js b/manager/public/config.js index 9f51858a..ed8f21ce 100644 --- a/manager/public/config.js +++ b/manager/public/config.js @@ -3,16 +3,16 @@ var BASE = { * @description api请求基础路径 */ API_DEV: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn", + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api", }, API_PROD: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn" + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api" }, /** * @description // 跳转买家端地址 pc端 diff --git a/manager/src/views/order/after-order/afterSaleOrderDetail.vue b/manager/src/views/order/after-order/afterSaleOrderDetail.vue index eea1eb73..91b9be4e 100644 --- a/manager/src/views/order/after-order/afterSaleOrderDetail.vue +++ b/manager/src/views/order/after-order/afterSaleOrderDetail.vue @@ -272,12 +272,12 @@
物流公司
-
{{ afterSaleInfo.mlogisticsName }}
+
{{ buyerReturnLogisticsName }}
物流单号
- {{ afterSaleInfo.mlogisticsNo }} + {{ buyerReturnLogisticsNo }}
@@ -312,13 +312,13 @@
物流公司:
-
{{ afterSaleInfo.mlogisticsName }}
+
{{ buyerReturnLogisticsName }}
物流单号:
-
{{ afterSaleInfo.mlogisticsNo }}
+
{{ buyerReturnLogisticsNo }}
@@ -410,7 +410,24 @@ export default { ], }; }, + computed: { + buyerReturnLogisticsName() { + return this.pickAfterSaleField("mlogisticsName", "mLogisticsName", "MLogisticsName", "m_logistics_name"); + }, + buyerReturnLogisticsNo() { + return this.pickAfterSaleField("mlogisticsNo", "mLogisticsNo", "MLogisticsNo", "m_logistics_no"); + }, + }, methods: { + pickAfterSaleField(...fields) { + for (const field of fields) { + const value = this.afterSaleInfo && this.afterSaleInfo[field]; + if (value !== undefined && value !== null && value !== "") { + return value; + } + } + return ""; + }, // 获取售后详情 getDetail() { this.loading = true; diff --git a/seller/public/config.js b/seller/public/config.js index 87161bcb..57ff2cc3 100644 --- a/seller/public/config.js +++ b/seller/public/config.js @@ -3,16 +3,16 @@ var BASE = { * @description api请求基础路径 */ API_DEV: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn", + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api", }, API_PROD: { - common: "https://common-api.pickmall.cn", - buyer: "https://buyer-api.pickmall.cn", - seller: "https://store-api.pickmall.cn", - manager: "https://admin-api.pickmall.cn", + common: "/api", + buyer: "/api", + seller: "/api", + manager: "/api", }, /** * @description // 跳转买家端地址 pc端 diff --git a/seller/src/api/order.contract.test.mjs b/seller/src/api/order.contract.test.mjs new file mode 100644 index 00000000..80144eb5 --- /dev/null +++ b/seller/src/api/order.contract.test.mjs @@ -0,0 +1,19 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const orderApi = readFileSync(new URL('./order.js', import.meta.url), 'utf8'); +const afterSaleDetail = readFileSync(new URL('../views/order/after-order/reurnGoodsOrderDetail.vue', import.meta.url), 'utf8'); +const managerAfterSaleDetail = readFileSync(new URL('../../../manager/src/views/order/after-order/afterSaleOrderDetail.vue', import.meta.url), 'utf8'); + +test('complaint reply posts to the exact seller-api communication route', () => { + assert.match(orderApi, /postRequest\(`\/order\/complain\/communication`, params\)/); + assert.doesNotMatch(orderApi, /\/order\/complain\/communication\//); +}); + +test('after-sale detail displays buyer return logistics from backend JSON fields', () => { + assert.match(afterSaleDetail, /buyerReturnLogisticsName/); + assert.match(afterSaleDetail, /buyerReturnLogisticsNo/); + assert.match(managerAfterSaleDetail, /buyerReturnLogisticsName/); + assert.match(managerAfterSaleDetail, /buyerReturnLogisticsNo/); +}); diff --git a/seller/src/api/order.js b/seller/src/api/order.js index 601e18c4..85f16449 100644 --- a/seller/src/api/order.js +++ b/seller/src/api/order.js @@ -73,7 +73,7 @@ export const getComplainDetail = id => { //添加交易投诉对话 export const addOrderComplaint = params => { - return postRequest(`/order/complain/communication/`, params); + return postRequest(`/order/complain/communication`, params); }; //添加交易投诉对话 diff --git a/seller/src/api/shops.contract.test.mjs b/seller/src/api/shops.contract.test.mjs new file mode 100644 index 00000000..45a28976 --- /dev/null +++ b/seller/src/api/shops.contract.test.mjs @@ -0,0 +1,11 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const shopsApi = readFileSync(new URL('./shops.js', import.meta.url), 'utf8'); + +test('store pickup address list and create use backend route without trailing slash', () => { + assert.match(shopsApi, /getRequest\(`\/member\/storeAddress`, params\)/); + assert.match(shopsApi, /postRequest\(`\/member\/storeAddress`, params\)/); + assert.doesNotMatch(shopsApi, /\/member\/storeAddress\/`/); +}); diff --git a/seller/src/api/shops.js b/seller/src/api/shops.js index c5c2f695..5efb8d62 100644 --- a/seller/src/api/shops.js +++ b/seller/src/api/shops.js @@ -71,7 +71,7 @@ export const logisticsUnChecked = (id, params) => { } // 获取商家自提点 export const getShopAddress = (id, params) => { - return getRequest(`/member/storeAddress/`, params) + return getRequest(`/member/storeAddress`, params) } // 修改商家自提点 @@ -81,7 +81,7 @@ export const editShopAddress = (id, params) => { // 添加商品自提点 export const addShopAddress = (params) => { - return postRequest(`/member/storeAddress/`, params) + return postRequest(`/member/storeAddress`, params) } // 添加商品自提点 @@ -140,4 +140,3 @@ export const editChecked = (logisticsId,params) => { return putRequest(`/other/logistics/${logisticsId}/updateStoreLogistics`,params) } - diff --git a/seller/src/libs/axios.contract.test.mjs b/seller/src/libs/axios.contract.test.mjs new file mode 100644 index 00000000..1c0cfa2e --- /dev/null +++ b/seller/src/libs/axios.contract.test.mjs @@ -0,0 +1,12 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +const source = readFileSync("seller/src/libs/axios.js", "utf8"); + +test("seller token refresh waits on the refresh request promise", () => { + assert.match(source, /let refreshPromise = null;/); + assert.match(source, /refreshPromise = handleRefreshToken\(oldRefreshToken\)/); + assert.match(source, /return refreshPromise;/); + assert.doesNotMatch(source, /setInterval\(/, "refresh should not depend on polling shared state"); +}); diff --git a/seller/src/libs/axios.js b/seller/src/libs/axios.js index a61bcf61..96492122 100644 --- a/seller/src/libs/axios.js +++ b/seller/src/libs/axios.js @@ -136,45 +136,29 @@ service.interceptors.response.use( // 防抖闭包来一波 function getTokenDebounce() { - let lock = false; - let success = false; + let refreshPromise = null; return function() { - if (!lock) { - lock = true; + if (!refreshPromise) { let oldRefreshToken = getStore("refreshToken"); - handleRefreshToken(oldRefreshToken) - .then(res => { + refreshPromise = handleRefreshToken(oldRefreshToken) + .then((res) => { if (res.success) { let { accessToken, refreshToken } = res.result; setStore("accessToken", accessToken); setStore("refreshToken", refreshToken); - - success = true; - lock = false; + return "success"; } else { - success = false; - lock = false; - // router.push('/login') + return "fail"; } }) - .catch(err => { - success = false; - lock = false; + .catch(() => { + return "fail"; + }) + .finally(() => { + refreshPromise = null; }); } - return new Promise(resolve => { - // 一直看lock,直到请求失败或者成功 - const timer = setInterval(() => { - if (!lock) { - clearInterval(timer); - if (success) { - resolve("success"); - } else { - resolve("fail"); - } - } - }, 500); // 轮询时间间隔 - }); + return refreshPromise; }; } @@ -410,4 +394,3 @@ export const postRequestWithNoToken = (url, params) => { data: params }); }; - diff --git a/seller/src/utils/file-url.contract.test.mjs b/seller/src/utils/file-url.contract.test.mjs new file mode 100644 index 00000000..7c2ba962 --- /dev/null +++ b/seller/src/utils/file-url.contract.test.mjs @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +const source = readFileSync("seller/src/utils/file-url.js", "utf8"); + +test("file url normalizer targets local storage placeholder paths only", () => { + assert.match(source, /LOCAL_FILE_PREFIX\s*=\s*["']\/files["']/, "should declare the local file placeholder prefix"); + assert.match(source, /startsWith\(LOCAL_FILE_PREFIX \+ ["']\/["']\)/, "should only rewrite /files/... urls"); + assert.match(source, /\^\(https\?:\)\?\\\/\\\//, "should keep absolute http urls"); + assert.match(source, /data\|blob\|mailto\|tel/, "should keep non-http browser urls"); +}); + +test("file url normalizer uses common api origin for resource records", () => { + assert.match(source, /function normalizeFileUrl\(url,\s*apiBase\)/, "should accept api origin explicitly"); + assert.match(source, /String\(apiBase \|\| ["']["']\)\.replace\(\/\\\/\+\$\/,\s*["']["']\)/, "should trim trailing slashes from api origin"); + assert.match(source, /return `\$\{base\}\$\{trimmed\}`/, "should prepend api origin to local file paths"); + assert.match(source, /function normalizeFileRecords\(records,\s*apiBase\)/, "should normalize resource record lists"); + assert.match(source, /url:\s*normalizeFileUrl\(item\.url,\s*apiBase\)/, "should normalize each record url"); +}); + +test("file url normalizer parses selected resource values into objects", () => { + assert.match(source, /function parseSelectedFileValue\(value\)/, "should expose a parser for checkbox values"); + assert.match(source, /value\.indexOf\(["'],["']\)/, "should split only at the id/url separator"); + assert.match(source, /id:\s*value\.slice\(0,\s*separatorIndex\)/, "should preserve selected resource id"); + assert.match(source, /url:\s*value\.slice\(separatorIndex \+ 1\)/, "should preserve the whole selected url"); + assert.match(source, /function normalizeSelectedFileValues\(values\)/, "should expose selected value list normalization"); +}); diff --git a/seller/src/utils/file-url.js b/seller/src/utils/file-url.js new file mode 100644 index 00000000..2d50d91d --- /dev/null +++ b/seller/src/utils/file-url.js @@ -0,0 +1,80 @@ +const LOCAL_FILE_PREFIX = "/files"; + +export function normalizeFileUrl(url, apiBase) { + if (!url || typeof url !== "string") { + return url; + } + + const trimmed = url.trim(); + if ( + /^(https?:)?\/\//i.test(trimmed) || + /^(data|blob|mailto|tel):/i.test(trimmed) + ) { + return url; + } + + if ( + trimmed !== LOCAL_FILE_PREFIX && + !trimmed.startsWith(LOCAL_FILE_PREFIX + "/") + ) { + return url; + } + + const base = String(apiBase || "").replace(/\/+$/, ""); + if (!base) { + return url; + } + + return `${base}${trimmed}`; +} + +export function normalizeFileRecords(records, apiBase) { + if (!Array.isArray(records)) { + return records; + } + + return records.map((item) => { + if (!item) { + return item; + } + return { + ...item, + url: normalizeFileUrl(item.url, apiBase), + }; + }); +} + +export function parseSelectedFileValue(value) { + if (!value) { + return null; + } + if (typeof value === "object") { + return value.url ? value : null; + } + if (typeof value !== "string") { + return null; + } + + const separatorIndex = value.indexOf(","); + if (separatorIndex < 0) { + return { + id: "", + url: value, + }; + } + + return { + id: value.slice(0, separatorIndex), + url: value.slice(separatorIndex + 1), + }; +} + +export function normalizeSelectedFileValues(values) { + if (!Array.isArray(values)) { + return []; + } + + return values + .map((value) => parseSelectedFileValue(value)) + .filter((item) => item && item.url); +} diff --git a/seller/src/views/goods/goods-seller/goodsOperationSec.contract.test.mjs b/seller/src/views/goods/goods-seller/goodsOperationSec.contract.test.mjs new file mode 100644 index 00000000..b12461c2 --- /dev/null +++ b/seller/src/views/goods/goods-seller/goodsOperationSec.contract.test.mjs @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const component = readFileSync(new URL('./goodsOperationSec.vue', import.meta.url), 'utf8'); + +test('resource-library image import stores only image urls in goodsGalleryFiles', () => { + assert.match(component, /normalizeImageUrl/); + assert.match(component, /appendSelectedImages/); + assert.doesNotMatch(component, /baseInfoForm\[this\.selectedFormBtnName\] = \[\.\.\.this\.baseInfoForm\[this\.selectedFormBtnName\], \.\.\.this\.selectedImage\]/); +}); + +test('goods submit filters invalid gallery entries before building goodsGalleryList', () => { + assert.match(component, /filter\(Boolean\)/); + assert.match(component, /submit\.goodsGalleryFiles = submit\.goodsGalleryFiles/); +}); + +test('category parameter validation reset guards missing clearValidate method', () => { + assert.match(component, /typeof this\.\$refs\.baseInfoForm\.clearValidate === ["']function["']/); +}); + +test('direct goods uploads always send a fallback directory path', () => { + assert.match(component, /uploadDirectoryData:\s*\{\s*directoryPath:\s*["']default["']/); + assert.match(component, /:data=["']uploadDirectoryData["']/); +}); diff --git a/seller/src/views/goods/goods-seller/goodsOperationSec.vue b/seller/src/views/goods/goods-seller/goodsOperationSec.vue index de1bf0ec..deb4c737 100644 --- a/seller/src/views/goods/goods-seller/goodsOperationSec.vue +++ b/seller/src/views/goods/goods-seller/goodsOperationSec.vue @@ -156,7 +156,8 @@
- - this.normalizeImageUrl(image)) + .filter(Boolean); + return [...current, ...selected]; + }, // 局部刷新 refresh(v) { if (v == 'template') { @@ -1272,7 +1293,7 @@ export default { // 确保表单验证能正确初始化 this.$nextTick(() => { - if (this.$refs.baseInfoForm) { + if (this.$refs.baseInfoForm && typeof this.$refs.baseInfoForm.clearValidate === "function") { this.$refs.baseInfoForm.clearValidate(); } }); @@ -2035,6 +2056,14 @@ export default { this.$Message.error("请上传商品图片"); return; } + submit.goodsGalleryFiles = submit.goodsGalleryFiles + .map((image) => this.normalizeImageUrl(image)) + .filter(Boolean); + if (submit.goodsGalleryFiles.length <= 0) { + this.submitLoading = false; + this.$Message.error("请上传商品图片"); + return; + } if (submit.templateId === "") submit.templateId = 0; let flag = false; let paramValue = ""; diff --git a/seller/src/views/lili-components/editor/upload-image.contract.test.mjs b/seller/src/views/lili-components/editor/upload-image.contract.test.mjs new file mode 100644 index 00000000..809f8710 --- /dev/null +++ b/seller/src/views/lili-components/editor/upload-image.contract.test.mjs @@ -0,0 +1,11 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +const source = readFileSync("seller/src/views/lili-components/editor/upload-image.vue", "utf8"); + +test("editor resource import is confirmed once from selected resource objects", () => { + assert.doesNotMatch(source, /@callback=["']handleCallback["']/, "resource import should not also add images on checkbox callback"); + assert.match(source, /normalizeSelectedImages\(this\.selectedImage\)/, "confirm should normalize selected resource objects"); + assert.match(source, /this\.images\.push\(\.\.\.selectedImages\)/, "confirm should append normalized images once"); +}); diff --git a/seller/src/views/lili-components/editor/upload-image.vue b/seller/src/views/lili-components/editor/upload-image.vue index 44ee38f7..dde6b3de 100644 --- a/seller/src/views/lili-components/editor/upload-image.vue +++ b/seller/src/views/lili-components/editor/upload-image.vue @@ -57,13 +57,14 @@ - +