mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-06-21 09:30:24 +08:00
Fix seller all-in-one image and order flows
This commit is contained in:
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
想快速体验完整商城,不需要分别部署后端、PC、商家端、运营端、IM、H5、MySQL 和 Redis。安装 Docker Desktop 后,直接执行下面脚本,按提示输入 IP/域名、端口、密码和数据目录即可启动单镜像单容器环境。
|
想快速体验完整商城,不需要分别部署后端、PC、商家端、运营端、IM、H5、MySQL 和 Redis。安装 Docker Desktop 后,直接执行下面脚本,按提示输入 IP/域名、端口、密码和数据目录即可启动单镜像单容器环境。
|
||||||
|
|
||||||
|
数据库口径:常规部署和项目技术栈仍以 MySQL 为准;All-In-One 镜像内置 MariaDB 只是单容器本地体验中的 MySQL 协议兼容实现,不代表生产部署或常规部署已经切换为 MariaDB。
|
||||||
|
|
||||||
macOS / Linux:
|
macOS / Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -26,7 +28,7 @@ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
|||||||
.\install-lilishop.ps1
|
.\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 私有化部署。
|
LILISHOP 是基于 Spring Boot / Spring Cloud / Vue / Uniapp 开发的 Java 开源商城系统,支持 B2B2C 多商户商城、小程序商城、微服务商城、直播电商、分销返佣、秒杀活动、Docker 私有化部署。
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ Lilishop 由 4 个独立仓库组成,均同步托管于 GitHub 与 Gitee:
|
|||||||
|
|
||||||
- **全端覆盖**: 一套代码库支持PC、H5、小程序、APP,降低开发和维护成本。
|
- **全端覆盖**: 一套代码库支持PC、H5、小程序、APP,降低开发和维护成本。
|
||||||
- **商家入驻**: 支持多商家入驻,构建平台化电商生态。
|
- **商家入驻**: 支持多商家入驻,构建平台化电商生态。
|
||||||
- **分布式架构**: 后端API服务化,支持独立部署和弹性伸缩。
|
- **All-In-One 后端**: 当前分支推荐使用 `lilishop-all` 统一后端入口,降低本地体验和私有化部署门槛。
|
||||||
- **前后端分离**: 清晰的职责划分,便于团队协作和独立开发。
|
- **前后端分离**: 清晰的职责划分,便于团队协作和独立开发。
|
||||||
- **容器化支持**: 提供Docker镜像和docker-compose配置,实现一键部署。
|
- **容器化支持**: 提供Docker镜像和docker-compose配置,实现一键部署。
|
||||||
- **功能完善**: 涵盖客户、订单、商品、促销、店铺、运营、统计等完整电商业务模块。
|
- **功能完善**: 涵盖客户、订单、商品、促销、店铺、运营、统计等完整电商业务模块。
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ var BASE = {
|
|||||||
* @description api请求基础路径
|
* @description api请求基础路径
|
||||||
*/
|
*/
|
||||||
API_DEV: {
|
API_DEV: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn"
|
manager: "/api"
|
||||||
},
|
},
|
||||||
API_PROD: {
|
API_PROD: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn"
|
manager: "/api"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
12
im/.env
12
im/.env
@@ -1,10 +1,10 @@
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
VUE_APP_PREVIEW=false
|
VUE_APP_PREVIEW=false
|
||||||
VUE_APP_API_BASE_URL=https://im-api.pickmall.cn
|
VUE_APP_API_BASE_URL=/api
|
||||||
VUE_APP_WEB_SOCKET_URL=wss://im-api.pickmall.cn/lili/webSocket
|
VUE_APP_WEB_SOCKET_URL=/lili/webSocket
|
||||||
VUE_APP_COMMON=https://common-api.pickmall.cn
|
VUE_APP_COMMON=/api
|
||||||
VUE_APP_BUYER=https://buyer-api.pickmall.cn
|
VUE_APP_BUYER=/api
|
||||||
VUE_APP_SELLER=https://store-api.pickmall.cn
|
VUE_APP_SELLER=/api
|
||||||
VUE_APP_WEBSITE_NAME="LiLi IM"
|
VUE_APP_WEBSITE_NAME="LiLi IM"
|
||||||
VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/
|
VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/
|
||||||
VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/
|
VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
VUE_APP_PREVIEW=false
|
VUE_APP_PREVIEW=false
|
||||||
VUE_APP_API_BASE_URL=https://im-api.pickmall.cn
|
VUE_APP_API_BASE_URL=/api
|
||||||
VUE_APP_WEB_SOCKET_URL=wss://im-api.pickmall.cn/lili/webSocket
|
VUE_APP_WEB_SOCKET_URL=/lili/webSocket
|
||||||
VUE_APP_COMMON=https://common-api.pickmall.cn
|
VUE_APP_COMMON=/api
|
||||||
VUE_APP_BUYER=https://buyer-api.pickmall.cn
|
VUE_APP_BUYER=/api
|
||||||
VUE_APP_SELLER=https://store-api.pickmall.cn
|
VUE_APP_SELLER=/api
|
||||||
VUE_APP_WEBSITE_NAME="LiLi IM"
|
VUE_APP_WEBSITE_NAME="LiLi IM"
|
||||||
VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/
|
VUE_APP_PC_URL=https://pc-b2b2c.pickmall.cn/
|
||||||
VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/
|
VUE_APP_PC_STORE=https://store-b2b2c.pickmall.cn/
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
server_name im-api.pickmall.cn;
|
server_name shop.example.com;
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:8088;
|
proxy_pass http://127.0.0.1:8088;
|
||||||
}
|
}
|
||||||
@@ -68,4 +68,4 @@
|
|||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
root /home/im/im/dist;
|
root /home/im/im/dist;
|
||||||
}
|
}
|
||||||
````
|
````
|
||||||
|
|||||||
@@ -22,13 +22,24 @@ const normalizeUrl = (url) => {
|
|||||||
return url.endsWith("/") ? url : `${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 {
|
export default {
|
||||||
// 网站名称
|
// 网站名称
|
||||||
WEBSITE_NAME: process.env.VUE_APP_WEBSITE_NAME || "LiLi IM",
|
WEBSITE_NAME: process.env.VUE_APP_WEBSITE_NAME || "LiLi IM",
|
||||||
// 默认请求IM的API
|
// 默认请求IM的API
|
||||||
BASE_API_URL: runtimeApi || process.env.VUE_APP_API_BASE_URL || "",
|
BASE_API_URL: runtimeApi || process.env.VUE_APP_API_BASE_URL || "",
|
||||||
// 默认请求的WS
|
// 默认请求的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
|
// 默认请求公有接口相关 API
|
||||||
BASE_COMMON: apiProd.common || apiDev.common || process.env.VUE_APP_COMMON || "",
|
BASE_COMMON: apiProd.common || apiDev.common || process.env.VUE_APP_COMMON || "",
|
||||||
// 默认请求用户相关API
|
// 默认请求用户相关API
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ var BASE = {
|
|||||||
* @description api请求基础路径
|
* @description api请求基础路径
|
||||||
*/
|
*/
|
||||||
API_DEV: {
|
API_DEV: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn",
|
manager: "/api",
|
||||||
},
|
},
|
||||||
API_PROD: {
|
API_PROD: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn"
|
manager: "/api"
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @description // 跳转买家端地址 pc端
|
* @description // 跳转买家端地址 pc端
|
||||||
|
|||||||
@@ -272,12 +272,12 @@
|
|||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>物流公司</dt>
|
<dt>物流公司</dt>
|
||||||
<dd>{{ afterSaleInfo.mlogisticsName }}</dd>
|
<dd>{{ buyerReturnLogisticsName }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>物流单号</dt>
|
<dt>物流单号</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ afterSaleInfo.mlogisticsNo }}
|
{{ buyerReturnLogisticsNo }}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
@@ -312,13 +312,13 @@
|
|||||||
<dl>
|
<dl>
|
||||||
<dt>物流公司:</dt>
|
<dt>物流公司:</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div class="text-box">{{ afterSaleInfo.mlogisticsName }}</div>
|
<div class="text-box">{{ buyerReturnLogisticsName }}</div>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>物流单号:</dt>
|
<dt>物流单号:</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div class="text-box">{{ afterSaleInfo.mlogisticsNo }}</div>
|
<div class="text-box">{{ buyerReturnLogisticsNo }}</div>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<div class="div-express-log">
|
<div class="div-express-log">
|
||||||
@@ -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: {
|
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() {
|
getDetail() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ var BASE = {
|
|||||||
* @description api请求基础路径
|
* @description api请求基础路径
|
||||||
*/
|
*/
|
||||||
API_DEV: {
|
API_DEV: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn",
|
manager: "/api",
|
||||||
},
|
},
|
||||||
API_PROD: {
|
API_PROD: {
|
||||||
common: "https://common-api.pickmall.cn",
|
common: "/api",
|
||||||
buyer: "https://buyer-api.pickmall.cn",
|
buyer: "/api",
|
||||||
seller: "https://store-api.pickmall.cn",
|
seller: "/api",
|
||||||
manager: "https://admin-api.pickmall.cn",
|
manager: "/api",
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @description // 跳转买家端地址 pc端
|
* @description // 跳转买家端地址 pc端
|
||||||
|
|||||||
19
seller/src/api/order.contract.test.mjs
Normal file
19
seller/src/api/order.contract.test.mjs
Normal file
@@ -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/);
|
||||||
|
});
|
||||||
@@ -73,7 +73,7 @@ export const getComplainDetail = id => {
|
|||||||
|
|
||||||
//添加交易投诉对话
|
//添加交易投诉对话
|
||||||
export const addOrderComplaint = params => {
|
export const addOrderComplaint = params => {
|
||||||
return postRequest(`/order/complain/communication/`, params);
|
return postRequest(`/order/complain/communication`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
//添加交易投诉对话
|
//添加交易投诉对话
|
||||||
|
|||||||
11
seller/src/api/shops.contract.test.mjs
Normal file
11
seller/src/api/shops.contract.test.mjs
Normal file
@@ -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\/`/);
|
||||||
|
});
|
||||||
@@ -71,7 +71,7 @@ export const logisticsUnChecked = (id, params) => {
|
|||||||
}
|
}
|
||||||
// 获取商家自提点
|
// 获取商家自提点
|
||||||
export const getShopAddress = (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) => {
|
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)
|
return putRequest(`/other/logistics/${logisticsId}/updateStoreLogistics`,params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
seller/src/libs/axios.contract.test.mjs
Normal file
12
seller/src/libs/axios.contract.test.mjs
Normal file
@@ -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");
|
||||||
|
});
|
||||||
@@ -136,45 +136,29 @@ service.interceptors.response.use(
|
|||||||
|
|
||||||
// 防抖闭包来一波
|
// 防抖闭包来一波
|
||||||
function getTokenDebounce() {
|
function getTokenDebounce() {
|
||||||
let lock = false;
|
let refreshPromise = null;
|
||||||
let success = false;
|
|
||||||
return function() {
|
return function() {
|
||||||
if (!lock) {
|
if (!refreshPromise) {
|
||||||
lock = true;
|
|
||||||
let oldRefreshToken = getStore("refreshToken");
|
let oldRefreshToken = getStore("refreshToken");
|
||||||
handleRefreshToken(oldRefreshToken)
|
refreshPromise = handleRefreshToken(oldRefreshToken)
|
||||||
.then(res => {
|
.then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
let { accessToken, refreshToken } = res.result;
|
let { accessToken, refreshToken } = res.result;
|
||||||
setStore("accessToken", accessToken);
|
setStore("accessToken", accessToken);
|
||||||
setStore("refreshToken", refreshToken);
|
setStore("refreshToken", refreshToken);
|
||||||
|
return "success";
|
||||||
success = true;
|
|
||||||
lock = false;
|
|
||||||
} else {
|
} else {
|
||||||
success = false;
|
return "fail";
|
||||||
lock = false;
|
|
||||||
// router.push('/login')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(() => {
|
||||||
success = false;
|
return "fail";
|
||||||
lock = false;
|
})
|
||||||
|
.finally(() => {
|
||||||
|
refreshPromise = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return new Promise(resolve => {
|
return refreshPromise;
|
||||||
// 一直看lock,直到请求失败或者成功
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
if (!lock) {
|
|
||||||
clearInterval(timer);
|
|
||||||
if (success) {
|
|
||||||
resolve("success");
|
|
||||||
} else {
|
|
||||||
resolve("fail");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500); // 轮询时间间隔
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,4 +394,3 @@ export const postRequestWithNoToken = (url, params) => {
|
|||||||
data: params
|
data: params
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
28
seller/src/utils/file-url.contract.test.mjs
Normal file
28
seller/src/utils/file-url.contract.test.mjs
Normal file
@@ -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");
|
||||||
|
});
|
||||||
80
seller/src/utils/file-url.js
Normal file
80
seller/src/utils/file-url.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -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["']/);
|
||||||
|
});
|
||||||
@@ -156,7 +156,8 @@
|
|||||||
<video :src="baseInfoForm.goodsVideo" class="video" controls style="max-width: 300px;" />
|
<video :src="baseInfoForm.goodsVideo" class="video" controls style="max-width: 300px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Upload ref="upload" :action="uploadFileUrl" :format="['avi', 'wmv', 'mpeg', 'mp4', 'mov']"
|
<Upload ref="upload" :action="uploadFileUrl" :data="uploadDirectoryData"
|
||||||
|
:format="['avi', 'wmv', 'mpeg', 'mp4', 'mov']"
|
||||||
:headers="{ ...accessToken }" :max-size="10240" :on-error="() => { loadingVideo = false }"
|
:headers="{ ...accessToken }" :max-size="10240" :on-error="() => { loadingVideo = false }"
|
||||||
:on-exceeded-size="handleVideoMaxSize" :on-format-error="handleFormatError"
|
:on-exceeded-size="handleVideoMaxSize" :on-format-error="handleFormatError"
|
||||||
:on-progress="() => { loadingVideo = true }" :on-success="handleSuccessGoodsVideo"
|
:on-progress="() => { loadingVideo = true }" :on-success="handleSuccessGoodsVideo"
|
||||||
@@ -239,7 +240,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</vuedraggable>
|
</vuedraggable>
|
||||||
<Upload ref="uploadSku" :action="uploadFileUrl" v-if="val.images < 1"
|
<Upload ref="uploadSku" :action="uploadFileUrl" :data="uploadDirectoryData"
|
||||||
|
v-if="val.images < 1"
|
||||||
:before-upload="handleBeforeUpload" :format="['jpg', 'jpeg', 'png', 'webp']"
|
:before-upload="handleBeforeUpload" :format="['jpg', 'jpeg', 'png', 'webp']"
|
||||||
:headers="{ ...accessToken }" :max-size="2048" :on-error="() => { $Spin.hide(); }"
|
:headers="{ ...accessToken }" :max-size="2048" :on-error="() => { $Spin.hide(); }"
|
||||||
:on-exceeded-size="handleMaxSize" :on-format-error="handleFormatError"
|
:on-exceeded-size="handleMaxSize" :on-format-error="handleFormatError"
|
||||||
@@ -496,6 +498,9 @@ export default {
|
|||||||
submitLoading: false,
|
submitLoading: false,
|
||||||
//上传图片路径
|
//上传图片路径
|
||||||
uploadFileUrl: uploadFile,
|
uploadFileUrl: uploadFile,
|
||||||
|
uploadDirectoryData: {
|
||||||
|
directoryPath: "default",
|
||||||
|
},
|
||||||
// 预览图片路径
|
// 预览图片路径
|
||||||
previewPicture: "",
|
previewPicture: "",
|
||||||
//商品图片
|
//商品图片
|
||||||
@@ -696,19 +701,35 @@ export default {
|
|||||||
callbackSelected(val) {
|
callbackSelected(val) {
|
||||||
this.picModelFlag = false;
|
this.picModelFlag = false;
|
||||||
if (val && this.selectedFormBtnName == 'selectedSkuImages') {
|
if (val && this.selectedFormBtnName == 'selectedSkuImages') {
|
||||||
this.selectedSku.images.push(val);
|
this.selectedSku.images = this.appendSelectedImages(this.selectedSku.images, [val]);
|
||||||
} else {
|
} else {
|
||||||
this.baseInfoForm[this.selectedFormBtnName].push(val.url);
|
this.baseInfoForm[this.selectedFormBtnName] = this.appendSelectedImages(this.baseInfoForm[this.selectedFormBtnName], [val]);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
confirmUrls() {
|
confirmUrls() {
|
||||||
if (this.selectedImage && this.selectedFormBtnName == 'selectedSkuImages') {
|
if (this.selectedImage && this.selectedFormBtnName == 'selectedSkuImages') {
|
||||||
this.selectedSku.images = [...this.selectedSku.images, ...this.selectedImage];
|
this.selectedSku.images = this.appendSelectedImages(this.selectedSku.images, this.selectedImage);
|
||||||
} else {
|
} else {
|
||||||
this.baseInfoForm[this.selectedFormBtnName] = [...this.baseInfoForm[this.selectedFormBtnName], ...this.selectedImage];
|
this.baseInfoForm[this.selectedFormBtnName] = this.appendSelectedImages(this.baseInfoForm[this.selectedFormBtnName], this.selectedImage);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
normalizeImageUrl(image) {
|
||||||
|
if (!image) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (typeof image === "string") {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
return image.url || image.original || image.thumbnail || image.fileUrl || "";
|
||||||
|
},
|
||||||
|
appendSelectedImages(target, images) {
|
||||||
|
const current = Array.isArray(target) ? target : [];
|
||||||
|
const selected = (Array.isArray(images) ? images : [images])
|
||||||
|
.map((image) => this.normalizeImageUrl(image))
|
||||||
|
.filter(Boolean);
|
||||||
|
return [...current, ...selected];
|
||||||
|
},
|
||||||
// 局部刷新
|
// 局部刷新
|
||||||
refresh(v) {
|
refresh(v) {
|
||||||
if (v == 'template') {
|
if (v == 'template') {
|
||||||
@@ -1272,7 +1293,7 @@ export default {
|
|||||||
|
|
||||||
// 确保表单验证能正确初始化
|
// 确保表单验证能正确初始化
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.baseInfoForm) {
|
if (this.$refs.baseInfoForm && typeof this.$refs.baseInfoForm.clearValidate === "function") {
|
||||||
this.$refs.baseInfoForm.clearValidate();
|
this.$refs.baseInfoForm.clearValidate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2035,6 +2056,14 @@ export default {
|
|||||||
this.$Message.error("请上传商品图片");
|
this.$Message.error("请上传商品图片");
|
||||||
return;
|
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;
|
if (submit.templateId === "") submit.templateId = 0;
|
||||||
let flag = false;
|
let flag = false;
|
||||||
let paramValue = "";
|
let paramValue = "";
|
||||||
|
|||||||
@@ -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");
|
||||||
|
});
|
||||||
@@ -57,13 +57,14 @@
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal width="1000" v-model="showOssManager" @on-ok="confirmUrls">
|
<Modal width="1000" v-model="showOssManager" @on-ok="confirmUrls">
|
||||||
<OssManage ref="ossManage" :isComponent="true" :initialize="showOssManager" @selected="(list)=>{ selectedImage = list}" @callback="handleCallback" />
|
<OssManage ref="ossManage" :isComponent="true" :initialize="showOssManager" @selected="(list)=>{ selectedImage = list}" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import vuedraggable from "vuedraggable";
|
import vuedraggable from "vuedraggable";
|
||||||
import {uploadFile} from "@/libs/axios";
|
import {uploadFile} from "@/libs/axios";
|
||||||
|
import { parseSelectedFileValue } from "@/utils/file-url";
|
||||||
// import OssManage from "@/views/sys/oss-manage/ossManage";
|
// import OssManage from "@/views/sys/oss-manage/ossManage";
|
||||||
import OssManage from "@/views/shop/ossManage";
|
import OssManage from "@/views/shop/ossManage";
|
||||||
export default {
|
export default {
|
||||||
@@ -123,14 +124,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmUrls(){
|
confirmUrls(){
|
||||||
this.selectedImage.length ? this.selectedImage.forEach(element => {
|
const selectedImages = this.normalizeSelectedImages(this.selectedImage);
|
||||||
this.images.push({ url: element.url })
|
if (selectedImages.length) {
|
||||||
}):''
|
this.images.push(...selectedImages);
|
||||||
|
}
|
||||||
|
this.selectedImage = [];
|
||||||
this.showOssManager = false
|
this.showOssManager = false
|
||||||
},
|
},
|
||||||
handleCallback(val){
|
normalizeSelectedImages(images) {
|
||||||
this.$Message.success("导入成功")
|
const list = Array.isArray(images) ? images : [images];
|
||||||
this.images.push({url:val.url})
|
return list
|
||||||
|
.map((image) => parseSelectedFileValue(image))
|
||||||
|
.filter((image) => image && image.url)
|
||||||
|
.map((image) => ({ url: image.url }));
|
||||||
},
|
},
|
||||||
// 从资源库中导入图片
|
// 从资源库中导入图片
|
||||||
importOSS(){
|
importOSS(){
|
||||||
|
|||||||
@@ -336,6 +336,7 @@ export default {
|
|||||||
this.$Message.error("请填写对话内容");
|
this.$Message.error("请填写对话内容");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.submitLoading = true;
|
||||||
this.params.complainId = this.id;
|
this.params.complainId = this.id;
|
||||||
API_Order.addOrderComplaint(this.params).then((res) => {
|
API_Order.addOrderComplaint(this.params).then((res) => {
|
||||||
this.submitLoading = false;
|
this.submitLoading = false;
|
||||||
@@ -344,6 +345,8 @@ export default {
|
|||||||
this.params.content = "";
|
this.params.content = "";
|
||||||
this.getDetail();
|
this.getDetail();
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.submitLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
//申诉
|
//申诉
|
||||||
|
|||||||
@@ -196,14 +196,14 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="div-form-default" v-if="afterSaleInfo.serviceStatus =='BUYER_RETURN' || afterSaleInfo.serviceStatus =='COMPLETE' && afterSaleInfo.serviceType !='RETURN_MONEY'">
|
<div class="div-form-default" v-if="showBuyerReturnLogistics">
|
||||||
<h3>回寄物流信息</h3>
|
<h3>回寄物流信息</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
物流公司
|
物流公司
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ afterSaleInfo.mlogisticsName }}
|
{{ buyerReturnLogisticsName }}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
物流单号
|
物流单号
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ afterSaleInfo.mlogisticsNo }}
|
{{ buyerReturnLogisticsNo }}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
@@ -408,7 +408,28 @@ export default {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
buyerReturnLogisticsName() {
|
||||||
|
return this.pickAfterSaleField("mlogisticsName", "mLogisticsName", "MLogisticsName", "m_logistics_name");
|
||||||
|
},
|
||||||
|
buyerReturnLogisticsNo() {
|
||||||
|
return this.pickAfterSaleField("mlogisticsNo", "mLogisticsNo", "MLogisticsNo", "m_logistics_no");
|
||||||
|
},
|
||||||
|
showBuyerReturnLogistics() {
|
||||||
|
const status = this.afterSaleInfo.serviceStatus;
|
||||||
|
return this.afterSaleInfo.serviceType !== "RETURN_MONEY" && (status === "BUYER_RETURN" || status === "COMPLETE");
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
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() {
|
getDetail() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
const component = readFileSync(new URL('./full-discount-add.vue', import.meta.url), 'utf8');
|
||||||
|
|
||||||
|
test('full discount submit clears mutually exclusive discount flags before saving', () => {
|
||||||
|
assert.match(component, /normalizeDiscountFlags/);
|
||||||
|
assert.match(component, /params\.fullMinusFlag = params\.discountType === "fullMinusFlag"/);
|
||||||
|
assert.match(component, /params\.fullRateFlag = params\.discountType === "fullRateFlag"/);
|
||||||
|
});
|
||||||
@@ -314,11 +314,7 @@ export default {
|
|||||||
});
|
});
|
||||||
params.scopeId = scopeId.toString();
|
params.scopeId = scopeId.toString();
|
||||||
}
|
}
|
||||||
if (params.discountType == "fullMinusFlag") {
|
this.normalizeDiscountFlags(params);
|
||||||
params.fullMinusFlag = true;
|
|
||||||
} else {
|
|
||||||
params.fullRateFlag = true;
|
|
||||||
}
|
|
||||||
delete params.rangeTime;
|
delete params.rangeTime;
|
||||||
this.submitLoading = true;
|
this.submitLoading = true;
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
@@ -351,6 +347,16 @@ export default {
|
|||||||
// 已选商品批量选择
|
// 已选商品批量选择
|
||||||
this.selectedGoods = e;
|
this.selectedGoods = e;
|
||||||
},
|
},
|
||||||
|
normalizeDiscountFlags(params) {
|
||||||
|
params.fullMinusFlag = params.discountType === "fullMinusFlag";
|
||||||
|
params.fullRateFlag = params.discountType === "fullRateFlag";
|
||||||
|
if (!params.fullMinusFlag) {
|
||||||
|
params.fullMinus = null;
|
||||||
|
}
|
||||||
|
if (!params.fullRateFlag) {
|
||||||
|
params.fullRate = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
delSelectGoods () {
|
delSelectGoods () {
|
||||||
// 多选删除商品
|
// 多选删除商品
|
||||||
if (this.selectedGoods.length <= 0) {
|
if (this.selectedGoods.length <= 0) {
|
||||||
|
|||||||
47
seller/src/views/shop/ossManage.contract.test.mjs
Normal file
47
seller/src/views/shop/ossManage.contract.test.mjs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
"seller/src/views/shop/ossManage.vue",
|
||||||
|
"seller/src/views/shop/ossManages.vue",
|
||||||
|
"seller/src/views/sys/oss-manage/ossManage.vue",
|
||||||
|
];
|
||||||
|
|
||||||
|
test("seller resource manager exposes owner name search", () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const source = readFileSync(file, "utf8");
|
||||||
|
assert.match(source, /label=["']上传人["']/, `${file} should show uploader search field`);
|
||||||
|
assert.match(source, /v-model=["']searchForm\.ownerName["']/, `${file} should bind ownerName`);
|
||||||
|
assert.match(source, /ownerName:\s*["']["']/, `${file} should initialize ownerName query param`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("seller resource manager never uploads with an undefined directory path", () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const source = readFileSync(file, "utf8");
|
||||||
|
assert.match(source, /uploadDirectoryData/, `${file} should use computed upload directory data`);
|
||||||
|
assert.match(source, /directoryPath:\s*this\.searchForm\.fileDirectoryId\s*\|\|\s*["']default["']/, `${file} should fallback to default directory`);
|
||||||
|
assert.doesNotMatch(source, /:data="\{\s*directoryPath:\s*searchForm\.fileDirectoryId,\s*\}"/, `${file} should not bind raw fileDirectoryId`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("seller resource manager normalizes local file urls before rendering and selecting", () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const source = readFileSync(file, "utf8");
|
||||||
|
assert.match(source, /normalizeFileRecords/, `${file} should import the local file url normalizer`);
|
||||||
|
assert.match(source, /this\.data\s*=\s*normalizeFileRecords\(res\.result\.records,\s*commonUrl\)/, `${file} should normalize resource list urls with common api origin`);
|
||||||
|
assert.match(source, /<img :src="item\.url"/, `${file} should render the normalized url`);
|
||||||
|
assert.match(source, /:label="item\.id\+','\+item\.url"/, `${file} should select the normalized url`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("seller resource manager emits selected resources as objects", () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const source = readFileSync(file, "utf8");
|
||||||
|
assert.match(source, /normalizeSelectedFileValues/, `${file} should import selected value normalization`);
|
||||||
|
assert.match(source, /const selectedFiles\s*=\s*normalizeSelectedFileValues\(e\)/, `${file} should parse checkbox values into selected file objects`);
|
||||||
|
assert.match(source, /this\.selectList\s*=\s*selectedFiles\.map\(item => \{\s*return \{\s*id:\s*item\.id\s*\}\s*\}\)/, `${file} should keep delete ids from parsed selected objects`);
|
||||||
|
assert.match(source, /this\.\$emit\(["']selected["'],\s*selectedFiles\)/, `${file} should emit selected file objects, not raw id,url strings`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -55,6 +55,15 @@
|
|||||||
@on-change="selectDateRange"
|
@on-change="selectDateRange"
|
||||||
></DatePicker>
|
></DatePicker>
|
||||||
</Form-item>
|
</Form-item>
|
||||||
|
<Form-item label="上传人" prop="ownerName">
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.ownerName"
|
||||||
|
clearable
|
||||||
|
placeholder="图片拥有者名称"
|
||||||
|
style="width: 240px"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</Form-item>
|
||||||
<Button
|
<Button
|
||||||
class="search-btn"
|
class="search-btn"
|
||||||
icon="ios-search"
|
icon="ios-search"
|
||||||
@@ -87,9 +96,7 @@
|
|||||||
<Upload
|
<Upload
|
||||||
ref="up"
|
ref="up"
|
||||||
:action="commonUrl + '/common/common/upload/file'"
|
:action="commonUrl + '/common/common/upload/file'"
|
||||||
:data="{
|
:data="uploadDirectoryData"
|
||||||
directoryPath: searchForm.fileDirectoryId,
|
|
||||||
}"
|
|
||||||
:headers="accessToken"
|
:headers="accessToken"
|
||||||
:max-size="20480"
|
:max-size="20480"
|
||||||
:on-error="handleError"
|
:on-error="handleError"
|
||||||
@@ -413,6 +420,7 @@
|
|||||||
} from "@/api/index";
|
} from "@/api/index";
|
||||||
import DPlayer from "dplayer";
|
import DPlayer from "dplayer";
|
||||||
import { commonUrl } from "@/libs/axios";
|
import { commonUrl } from "@/libs/axios";
|
||||||
|
import { normalizeFileRecords, normalizeSelectedFileValues, parseSelectedFileValue } from "@/utils/file-url";
|
||||||
|
|
||||||
const config = require("@/config/index");
|
const config = require("@/config/index");
|
||||||
|
|
||||||
@@ -474,6 +482,7 @@
|
|||||||
name: "",
|
name: "",
|
||||||
fileKey: "",
|
fileKey: "",
|
||||||
fileType: "",
|
fileType: "",
|
||||||
|
ownerName: "",
|
||||||
pageNumber: 1, // 当前页数
|
pageNumber: 1, // 当前页数
|
||||||
pageSize: 27, // 页面大小
|
pageSize: 27, // 页面大小
|
||||||
sort: "createTime", // 默认排序字段
|
sort: "createTime", // 默认排序字段
|
||||||
@@ -869,11 +878,21 @@
|
|||||||
if (val) this.selectImage = val
|
if (val) this.selectImage = val
|
||||||
},
|
},
|
||||||
selectedOss(val) {
|
selectedOss(val) {
|
||||||
if (val && val.length > 0 && val[val.length-1].split(',')[1]) {
|
if (val && val.length > 0) {
|
||||||
this.$emit("callback", {url: val[val.length-1].split(',')[1]});
|
const selectedFile = parseSelectedFileValue(val[val.length - 1]);
|
||||||
|
if (selectedFile && selectedFile.url) {
|
||||||
|
this.$emit("callback", selectedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
uploadDirectoryData() {
|
||||||
|
return {
|
||||||
|
directoryPath: this.searchForm.fileDirectoryId || "default",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onMouseOver(item, index) {
|
onMouseOver(item, index) {
|
||||||
@@ -886,12 +905,13 @@
|
|||||||
// 复选框值改变时触发
|
// 复选框值改变时触发
|
||||||
selectOssChange(e) {
|
selectOssChange(e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.selectList = e.map(item => {return { id: item.split(',')[0]}});
|
const selectedFiles = normalizeSelectedFileValues(e);
|
||||||
this.selectCount = e.length;
|
this.selectList = selectedFiles.map(item => {return { id: item.id }});
|
||||||
|
this.selectCount = selectedFiles.length;
|
||||||
// let size = 0;
|
// let size = 0;
|
||||||
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
||||||
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
||||||
this.$emit("selected", e);
|
this.$emit("selected", selectedFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 页码改变时回调
|
// 页码改变时回调
|
||||||
@@ -1176,7 +1196,7 @@
|
|||||||
getFileListData(this.searchForm).then((res) => {
|
getFileListData(this.searchForm).then((res) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
this.data = res.result.records;
|
this.data = normalizeFileRecords(res.result.records, commonUrl);
|
||||||
this.total = res.result.total;
|
this.total = res.result.total;
|
||||||
if (type === 'refresh') {
|
if (type === 'refresh') {
|
||||||
this.$Message.success('刷新成功!');
|
this.$Message.success('刷新成功!');
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
<Form-item label="上传时间">
|
<Form-item label="上传时间">
|
||||||
<DatePicker v-model="selectDate" clearable format="yyyy-MM-dd" placeholder="选择起始时间" style="width: 200px" type="daterange" @on-change="selectDateRange"></DatePicker>
|
<DatePicker v-model="selectDate" clearable format="yyyy-MM-dd" placeholder="选择起始时间" style="width: 200px" type="daterange" @on-change="selectDateRange"></DatePicker>
|
||||||
</Form-item>
|
</Form-item>
|
||||||
|
<Form-item label="上传人" prop="ownerName">
|
||||||
|
<Input v-model="searchForm.ownerName" clearable placeholder="图片拥有者名称" style="width: 200px" type="text" />
|
||||||
|
</Form-item>
|
||||||
<Button class="search-btn" icon="ios-search" type="primary" @click="handleSearch">搜索
|
<Button class="search-btn" icon="ios-search" type="primary" @click="handleSearch">搜索
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -37,9 +40,7 @@
|
|||||||
<Upload
|
<Upload
|
||||||
ref="up"
|
ref="up"
|
||||||
:action="commonUrl + '/common/common/upload/file'"
|
:action="commonUrl + '/common/common/upload/file'"
|
||||||
:data="{
|
:data="uploadDirectoryData"
|
||||||
directoryPath: searchForm.fileDirectoryId,
|
|
||||||
}"
|
|
||||||
:headers="accessToken"
|
:headers="accessToken"
|
||||||
:max-size="20480"
|
:max-size="20480"
|
||||||
:on-error="handleError"
|
:on-error="handleError"
|
||||||
@@ -203,6 +204,7 @@
|
|||||||
} from "@/api/index";
|
} from "@/api/index";
|
||||||
import DPlayer from "dplayer";
|
import DPlayer from "dplayer";
|
||||||
import { commonUrl } from "@/libs/axios";
|
import { commonUrl } from "@/libs/axios";
|
||||||
|
import { normalizeFileRecords, normalizeSelectedFileValues, parseSelectedFileValue } from "@/utils/file-url";
|
||||||
|
|
||||||
const config = require("@/config/index");
|
const config = require("@/config/index");
|
||||||
|
|
||||||
@@ -268,6 +270,7 @@
|
|||||||
name: "",
|
name: "",
|
||||||
fileKey: "",
|
fileKey: "",
|
||||||
fileType: "",
|
fileType: "",
|
||||||
|
ownerName: "",
|
||||||
pageNumber: 1, // 当前页数
|
pageNumber: 1, // 当前页数
|
||||||
pageSize: 27, // 页面大小
|
pageSize: 27, // 页面大小
|
||||||
sort: "createTime", // 默认排序字段
|
sort: "createTime", // 默认排序字段
|
||||||
@@ -665,7 +668,10 @@
|
|||||||
},
|
},
|
||||||
selectedOss(val) {
|
selectedOss(val) {
|
||||||
if (val && val.length) {
|
if (val && val.length) {
|
||||||
this.$emit("callback", {url: val[val.length-1].split(',')[1]});
|
const selectedFile = parseSelectedFileValue(val[val.length - 1]);
|
||||||
|
if (selectedFile && selectedFile.url) {
|
||||||
|
this.$emit("callback", selectedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 初始化监听 是否清空所选图片
|
// 初始化监听 是否清空所选图片
|
||||||
@@ -675,6 +681,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
uploadDirectoryData() {
|
||||||
|
return {
|
||||||
|
directoryPath: this.searchForm.fileDirectoryId || "default",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onMouseOver(item, index) {
|
onMouseOver(item, index) {
|
||||||
@@ -687,12 +700,13 @@
|
|||||||
// 复选框值改变时触发
|
// 复选框值改变时触发
|
||||||
selectOssChange(e) {
|
selectOssChange(e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.selectList = e.map(item => {return { id: item.split(',')[0]}});
|
const selectedFiles = normalizeSelectedFileValues(e);
|
||||||
this.selectCount = e.length;
|
this.selectList = selectedFiles.map(item => {return { id: item.id }});
|
||||||
|
this.selectCount = selectedFiles.length;
|
||||||
// let size = 0;
|
// let size = 0;
|
||||||
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
||||||
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
||||||
this.$emit("selected", e);
|
this.$emit("selected", selectedFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 页码改变时回调
|
// 页码改变时回调
|
||||||
@@ -977,7 +991,7 @@
|
|||||||
getFileListData(this.searchForm).then((res) => {
|
getFileListData(this.searchForm).then((res) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
this.data = res.result.records;
|
this.data = normalizeFileRecords(res.result.records, commonUrl);
|
||||||
this.total = res.result.total;
|
this.total = res.result.total;
|
||||||
if (type === 'refresh') {
|
if (type === 'refresh') {
|
||||||
this.$Message.success('刷新成功!');
|
this.$Message.success('刷新成功!');
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export default {
|
|||||||
UNPAID: "未付款",
|
UNPAID: "未付款",
|
||||||
PAID: "已付款",
|
PAID: "已付款",
|
||||||
DELIVERED: "已发货",
|
DELIVERED: "已发货",
|
||||||
|
PARTS_DELIVERED: "部分发货",
|
||||||
CANCELLED: "已取消",
|
CANCELLED: "已取消",
|
||||||
COMPLETED: "已完成",
|
COMPLETED: "已完成",
|
||||||
TAKE: "已完成",
|
TAKE: "已完成",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export default {
|
|||||||
UNPAID: "未付款",
|
UNPAID: "未付款",
|
||||||
PAID: "已付款",
|
PAID: "已付款",
|
||||||
DELIVERED: "已发货",
|
DELIVERED: "已发货",
|
||||||
|
PARTS_DELIVERED: "部分发货",
|
||||||
CANCELLED: "已取消",
|
CANCELLED: "已取消",
|
||||||
COMPLETED: "已完成",
|
COMPLETED: "已完成",
|
||||||
TAKE: "已完成",
|
TAKE: "已完成",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
"seller/src/views/statistics/order.vue",
|
||||||
|
"seller/src/views/statistics/order/orderDetail.vue",
|
||||||
|
"seller/src/views/statistics/order/refundOrder.vue",
|
||||||
|
];
|
||||||
|
|
||||||
|
test("order statistics pages map PARTS_DELIVERED to 部分发货", () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const source = readFileSync(file, "utf8");
|
||||||
|
assert.match(
|
||||||
|
source,
|
||||||
|
/PARTS_DELIVERED:\s*["']部分发货["']/,
|
||||||
|
`${file} should render partial delivery status instead of blank text`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -82,6 +82,7 @@ export default {
|
|||||||
UNPAID: "未付款",
|
UNPAID: "未付款",
|
||||||
PAID: "已付款",
|
PAID: "已付款",
|
||||||
DELIVERED: "已发货",
|
DELIVERED: "已发货",
|
||||||
|
PARTS_DELIVERED: "部分发货",
|
||||||
CANCELLED: "已取消",
|
CANCELLED: "已取消",
|
||||||
COMPLETED: "已完成",
|
COMPLETED: "已完成",
|
||||||
TAKE: "已完成",
|
TAKE: "已完成",
|
||||||
|
|||||||
@@ -93,9 +93,7 @@
|
|||||||
<Upload
|
<Upload
|
||||||
ref="up"
|
ref="up"
|
||||||
:action="commonUrl + '/common/common/upload/file'"
|
:action="commonUrl + '/common/common/upload/file'"
|
||||||
:data="{
|
:data="uploadDirectoryData"
|
||||||
directoryPath: searchForm.fileDirectoryId,
|
|
||||||
}"
|
|
||||||
:headers="accessToken"
|
:headers="accessToken"
|
||||||
:max-size="20480"
|
:max-size="20480"
|
||||||
:on-error="handleError"
|
:on-error="handleError"
|
||||||
@@ -257,6 +255,7 @@ import {
|
|||||||
} from "@/api/index";
|
} from "@/api/index";
|
||||||
import DPlayer from "dplayer";
|
import DPlayer from "dplayer";
|
||||||
import { commonUrl } from "@/libs/axios";
|
import { commonUrl } from "@/libs/axios";
|
||||||
|
import { normalizeFileRecords, normalizeSelectedFileValues, parseSelectedFileValue } from "@/utils/file-url";
|
||||||
|
|
||||||
const config = require("@/config/index");
|
const config = require("@/config/index");
|
||||||
|
|
||||||
@@ -720,7 +719,10 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedOss(val) {
|
selectedOss(val) {
|
||||||
if (val && val.length) {
|
if (val && val.length) {
|
||||||
this.$emit("callback", {url: val[val.length-1].split(',')[1]});
|
const selectedFile = parseSelectedFileValue(val[val.length - 1]);
|
||||||
|
if (selectedFile && selectedFile.url) {
|
||||||
|
this.$emit("callback", selectedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 初始化监听 是否清空所选图片
|
// 初始化监听 是否清空所选图片
|
||||||
@@ -735,6 +737,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
uploadDirectoryData() {
|
||||||
|
return {
|
||||||
|
directoryPath: this.searchForm.fileDirectoryId || "default",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onMouseOver(item, index) {
|
onMouseOver(item, index) {
|
||||||
@@ -747,12 +756,13 @@ export default {
|
|||||||
// 复选框值改变时触发
|
// 复选框值改变时触发
|
||||||
selectOssChange(e) {
|
selectOssChange(e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.selectList = e.map(item => {return { id: item.split(',')[0]}});
|
const selectedFiles = normalizeSelectedFileValues(e);
|
||||||
this.selectCount = e.length;
|
this.selectList = selectedFiles.map(item => {return { id: item.id }});
|
||||||
|
this.selectCount = selectedFiles.length;
|
||||||
// let size = 0;
|
// let size = 0;
|
||||||
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
// e.forEach((item) => {size += item.fileSize * 1.0;});
|
||||||
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
// this.totalSize = ((size * 1.0) / (1024 * 1024)).toFixed(2) + " MB";
|
||||||
this.$emit("selected", e);
|
this.$emit("selected", selectedFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 页码改变时回调
|
// 页码改变时回调
|
||||||
@@ -1043,7 +1053,7 @@ export default {
|
|||||||
getFileListData(this.searchForm).then((res) => {
|
getFileListData(this.searchForm).then((res) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
this.data = res.result.records;
|
this.data = normalizeFileRecords(res.result.records, commonUrl);
|
||||||
this.total = res.result.total;
|
this.total = res.result.total;
|
||||||
if (type === 'refresh') {
|
if (type === 'refresh') {
|
||||||
this.$Message.success('刷新成功!');
|
this.$Message.success('刷新成功!');
|
||||||
|
|||||||
Reference in New Issue
Block a user