fix(shop): 修复商城商品导出及专区商品分页问题
- 修复专区商品抽屉分页中20/100条/页按键无效,增加动态分页参数支持 - 修复商城商品导出功能无反应问题,调整loading初始值保证导出流程执行 - 支持商品导出携带当前筛选条件,保证导出数据符合筛选结果 - 商品导出增加封面图路径列,导出URL文本方式避免跨域限制 - 优化UserSelectModal手机号码展示,改为优先展示真实号码字段 - 相关组件调整分页及导出事件参数,提升交互准确性与稳定性
This commit is contained in:
@@ -141,3 +141,32 @@
|
|||||||
- 根因:系统 User 实体 `mobile` 是 `@TableField(exist=false)` 的脱敏字段(`getMobile()` 返回 `DesensitizedUtil.mobilePhone(phone)`);`phone` 才是 DB 真实号码,无 `@JsonIgnore`、无全局脱敏 → 接口同时返回 `phone`(真实) 与 `mobile`(脱敏)。
|
- 根因:系统 User 实体 `mobile` 是 `@TableField(exist=false)` 的脱敏字段(`getMobile()` 返回 `DesensitizedUtil.mobilePhone(phone)`);`phone` 才是 DB 真实号码,无 `@JsonIgnore`、无全局脱敏 → 接口同时返回 `phone`(真实) 与 `mobile`(脱敏)。
|
||||||
- 改动:`UserSelectModal.vue` 两处展示由 `u.mobile` 改为 `u.phone`(候选行 `u.phone || u.mobile || '-'`;表格列 `dataIndex:'phone'` + `customRender` 回退 `record.mobile`),后端无需改动。
|
- 改动:`UserSelectModal.vue` 两处展示由 `u.mobile` 改为 `u.phone`(候选行 `u.phone || u.mobile || '-'`;表格列 `dataIndex:'phone'` + `customRender` 回退 `record.mobile`),后端无需改动。
|
||||||
- 部署:重新构建 admin 前端即可。
|
- 部署:重新构建 admin 前端即可。
|
||||||
|
|
||||||
|
## 专区商品抽屉分页「20/100 条/页」不可用修复
|
||||||
|
- 现象:专区商品抽屉右下角切 20/100 条/页不生效,始终只展示 10 条。
|
||||||
|
- 根因:`src/views/special/zone/index.vue` 中商品表格的 `:pagination` 把 `pageSize: 10` 写死,且 `onChange` 只接收 `page` 参数、未处理 `pageSize`;翻页事件里还误写成 `goodsPage = page`(应 `.value`)。
|
||||||
|
- 修复:
|
||||||
|
- 新增 `goodsPageSize = ref(10)`;
|
||||||
|
- `loadGoods` 请求参数 `limit` 改为 `goodsPageSize.value`;
|
||||||
|
- 分页配置改为 `pageSize: goodsPageSize`、`showSizeChanger: true`,`onChange(page, pageSize)` 同时更新 `goodsPage.value` 与 `goodsPageSize.value` 后调 `loadGoods`。
|
||||||
|
- 改动文件:`src/views/special/zone/index.vue`。
|
||||||
|
- 部署:重新构建 admin 前端即可。
|
||||||
|
|
||||||
|
## 商城商品导出「完全无反应」修复(/shop/shopGoods 导出功能不可用)
|
||||||
|
- 现象:/shop/shopGoods 点「导出xls」完全没反应(无下载、无报错提示)。
|
||||||
|
- 根因:`src/views/shop/shopGoods/index.vue` 第236行 `const loading = ref(true);`,而 `handleExport` 第一行 `if (loading.value) return;`。`loading` 这个变量只有 `handleExport` 自己会改(导出中设 true / 结束或失败设 false),列表加载的 `reload()`(只调 `tableRef.reload`)、`query()`(只加载分类树)从不碰它 → 页面加载后 loading 永远是初始的 `true` → 导出首行被永久拦截、逻辑从未执行。
|
||||||
|
- 已排除项:按钮 emit、后端 `/shop/shop-goods` 返回 `ApiResult<List<ShopGoods>>`、xlsx@0.18.5 依赖、vite `optimizeDeps.include:['xlsx']` 均正常——不是接口/依赖/打包问题。
|
||||||
|
- 修复(按用户"修守卫+带筛选导出"):
|
||||||
|
1. `index.vue` 第236行 `ref(true)` → `ref(false)`(loading 恢复成"导出中防重复点击"语义,首击可进入)。
|
||||||
|
2. 导出携带当前筛选:`search.vue` emit 签名 `(e:'export', where?: ShopGoodsParam)`,`handleExport` emit('export', where);`index.vue` `handleExport(where?)` 调 `listShopGoods(where || {})`(原 `listShopGoods({})` 导出全部、忽略筛选)。where 来自 search.vue 的 `useSearch<ShopGoodsParam>`,与列表 datasource 用的 where 同源,完全复现筛选。
|
||||||
|
- 改动文件:`src/views/shop/shopGoods/index.vue`、`src/views/shop/shopGoods/components/search.vue`(共5处小改)。
|
||||||
|
- 部署:重新构建 admin 前端(npm run dev 热更新或 npm run build)即可,无需后端改动。
|
||||||
|
- 验证清单:① 进 /shop/shopGoods 点「导出xls」应弹"正在准备导出数据"并下载 xlsx;② 先筛选分类/关键词/状态再导出,xlsx 内容应与筛选结果一致;③ 导出中连点不应重复触发(loading 防重生效)。
|
||||||
|
|
||||||
|
## 商城商品导出增加「封面图」路径列(用户改主意:不内嵌图片,只导出路径)
|
||||||
|
- 背景:用户原想 Excel 内嵌封面图(前端 fetch OSS 图→base64→xlsx !images),但 OSS(oss.wsdns.cn) 与 admin(websoft.top) 跨域,浏览器 fetch 会被 CORS 拦截;且逐张下载会导致导出变慢、xlsx 体积暴涨。用户明确改为「图片导出路径就行了」——即 Excel 里加一列封面图 URL 文本。
|
||||||
|
- 实现:`src/views/shop/shopGoods/index.vue` 的 `handleExport` 导出列末尾新增「封面图」列,值为 `ensureFullUrl(goods.image)`(`@/utils/image` 的 ensureFullUrl:相对路径补成 `https://oss.wsdns.cn/...` 完整 URL,已是 http(s) 原样返回)。表头/每行/`!cols` 列数均由 10 增到 11,封面图列宽 wch:50。
|
||||||
|
- 补 import:`import { ensureFullUrl } from '@/utils/image';`(紧接 xlsx 的 import 之后)。
|
||||||
|
- 改动文件:`src/views/shop/shopGoods/index.vue`(handleExport 内共 4 处:import / 表头 / forEach / !cols)。
|
||||||
|
- 说明:Excel 单元格中是 URL 文本,运营复制/点开即可在浏览器看原图;不是内嵌缩略图。OSS 图片无 CORS 依赖,导出不再受跨域影响。
|
||||||
|
- 部署:重新构建 admin 前端即可,无需后端改动。
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
(e: 'imageImport'): void;
|
(e: 'imageImport'): void;
|
||||||
(e: 'detailImport'): void;
|
(e: 'detailImport'): void;
|
||||||
(e: 'convertContent'): void;
|
(e: 'convertContent'): void;
|
||||||
(e: 'export'): void;
|
(e: 'export', where?: ShopGoodsParam): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 新增
|
// 新增
|
||||||
@@ -207,9 +207,9 @@
|
|||||||
emit('convertContent');
|
emit('convertContent');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导出
|
// 导出(携带当前筛选条件 where)
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
emit('export');
|
emit('export', where);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
const handleSearch = (e) => {
|
||||||
|
|||||||
@@ -211,6 +211,7 @@
|
|||||||
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
|
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
|
||||||
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory';
|
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory';
|
||||||
import { utils, writeFile } from 'xlsx';
|
import { utils, writeFile } from 'xlsx';
|
||||||
|
import { ensureFullUrl } from '@/utils/image';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
// 表格实例
|
// 表格实例
|
||||||
@@ -233,7 +234,7 @@
|
|||||||
// 是否显示批量移动弹窗
|
// 是否显示批量移动弹窗
|
||||||
const showMove = ref(false);
|
const showMove = ref(false);
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(true);
|
const loading = ref(false);
|
||||||
|
|
||||||
// 表格数据源
|
// 表格数据源
|
||||||
const datasource: DatasourceFunction = ({
|
const datasource: DatasourceFunction = ({
|
||||||
@@ -429,8 +430,8 @@
|
|||||||
: base;
|
: base;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 导出商品列表 */
|
/* 导出商品列表(可携带当前筛选条件) */
|
||||||
const handleExport = async () => {
|
const handleExport = async (where?: ShopGoodsParam) => {
|
||||||
if (loading.value) {
|
if (loading.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -448,11 +449,12 @@
|
|||||||
'库存',
|
'库存',
|
||||||
'状态',
|
'状态',
|
||||||
'推荐',
|
'推荐',
|
||||||
'创建时间'
|
'创建时间',
|
||||||
|
'封面图'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
const list = await listShopGoods({});
|
const list = await listShopGoods(where || {});
|
||||||
if (!list || list.length === 0) {
|
if (!list || list.length === 0) {
|
||||||
message.warning('没有数据可以导出');
|
message.warning('没有数据可以导出');
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -471,7 +473,8 @@
|
|||||||
`${goods.stock || 0}`,
|
`${goods.stock || 0}`,
|
||||||
statusMap[goods.status || 0] || '',
|
statusMap[goods.status || 0] || '',
|
||||||
goods.recommend === 1 ? '是' : '否',
|
goods.recommend === 1 ? '是' : '否',
|
||||||
`${goods.createTime || ''}`
|
`${goods.createTime || ''}`,
|
||||||
|
ensureFullUrl(`${goods.image || ''}`)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -493,7 +496,8 @@
|
|||||||
{ wch: 10 },
|
{ wch: 10 },
|
||||||
{ wch: 12 },
|
{ wch: 12 },
|
||||||
{ wch: 8 },
|
{ wch: 8 },
|
||||||
{ wch: 20 }
|
{ wch: 20 },
|
||||||
|
{ wch: 50 }
|
||||||
];
|
];
|
||||||
|
|
||||||
message.destroy();
|
message.destroy();
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
{ title: '姓名', dataIndex: 'realName', key: 'realName', align: 'center' },
|
{ title: '姓名', dataIndex: 'realName', key: 'realName', align: 'center' },
|
||||||
{ title: '手机号码', dataIndex: 'phone', key: 'phone', align: 'center', customRender: ({ text, record }: any) => text || record.mobile || '-' },
|
{ title: '手机号码', dataIndex: 'phone', key: 'phone', align: 'center', customRender: ({ text, record }: any) => text || record.mobile || '-' },
|
||||||
{ title: '昵称', dataIndex: 'nickname', key: 'nickname', align: 'center' },
|
{ title: '昵称', dataIndex: 'nickname', key: 'nickname', align: 'center' },
|
||||||
{ title: '所属部门', dataIndex: 'organizationName', key: 'organizationName', align: 'center' },
|
// { title: '所属部门', dataIndex: 'organizationName', key: 'organizationName', align: 'center' },
|
||||||
{ title: '操作', key: 'action', align: 'center', width: 90 }
|
{ title: '操作', key: 'action', align: 'center', width: 90 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -148,9 +148,11 @@
|
|||||||
:pagination="{
|
:pagination="{
|
||||||
total: goodsTotal,
|
total: goodsTotal,
|
||||||
current: goodsPage,
|
current: goodsPage,
|
||||||
pageSize: 10,
|
pageSize: goodsPageSize,
|
||||||
onChange: (page: number) => {
|
showSizeChanger: true,
|
||||||
goodsPage = page;
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
goodsPage.value = page;
|
||||||
|
goodsPageSize.value = pageSize;
|
||||||
loadGoods(currentSectionId);
|
loadGoods(currentSectionId);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -257,6 +259,7 @@
|
|||||||
const goodsList = ref<any[]>([]);
|
const goodsList = ref<any[]>([]);
|
||||||
const goodsTotal = ref(0);
|
const goodsTotal = ref(0);
|
||||||
const goodsPage = ref(1);
|
const goodsPage = ref(1);
|
||||||
|
const goodsPageSize = ref(10);
|
||||||
// 商品搜索/添加
|
// 商品搜索/添加
|
||||||
const goodsSearchText = ref('');
|
const goodsSearchText = ref('');
|
||||||
const goodsSearchLoading = ref(false);
|
const goodsSearchLoading = ref(false);
|
||||||
@@ -477,7 +480,7 @@
|
|||||||
|
|
||||||
const loadGoods = (sectionId: number) => {
|
const loadGoods = (sectionId: number) => {
|
||||||
goodsLoading.value = true;
|
goodsLoading.value = true;
|
||||||
listSectionGoods(sectionId, { page: goodsPage.value, limit: 10 })
|
listSectionGoods(sectionId, { page: goodsPage.value, limit: goodsPageSize.value })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
goodsList.value = res?.list || [];
|
goodsList.value = res?.list || [];
|
||||||
goodsTotal.value = res?.count || 0;
|
goodsTotal.value = res?.count || 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user