From 3cb60139d58ff69a889e2a99b9be177eec68937b Mon Sep 17 00:00:00 2001 From: "pikachu1995@126.com" Date: Tue, 20 Oct 2026 13:51:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor(seller):=20=E5=88=97=E8=A1=A8=E9=A1=B5?= =?UTF-8?q?=E6=8A=BD=E7=A6=BB=20useTable=20=E9=87=8D=E6=9E=84=E4=B8=8E?= =?UTF-8?q?=E9=80=9A=E7=94=A8=20UI=20=E8=A7=84=E8=8C=83=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽离通用 useTable composable,并迁移 13 个典型列表页(采购/报损/订单/售后/统计/营销等) - 增加表格与树组件全局空状态 (el-empty) 样式规范 - 补齐视图图片语义化 alt,并添加全局图片加载异常 SVG 占位兜底 - 替换遗留的 $Message 兼容语法为 ElMessage - 将 WAP 店铺装修模板的 Options API 统一为 defineOptions - 优化数据报表卡片视觉样式,并移除失效的品牌筛选选项 - 清理冗余依赖 vuex,将 sass-loader 调整至 devDependencies --- seller/package.json | 3 +- seller/src/main.js | 2 + seller/src/styles/element.scss | 27 +++ seller/src/utils/imgFallback.js | 31 +++ seller/src/utils/useTable.js | 119 ++++++++++++ seller/src/views/ForgetPassword.vue | 18 +- seller/src/views/Main.vue | 2 +- seller/src/views/goods/goods-seller/goods.vue | 2 +- .../goods/goods-seller/goodsOperationSec.vue | 10 +- seller/src/views/login.vue | 8 +- seller/src/views/main-components/header.vue | 2 +- .../order/after-order/orderComplaint.vue | 30 +-- .../order/after-order/returnGoodsOrder.vue | 44 ++--- .../after-order/reurnGoodsOrderDetail.vue | 4 +- seller/src/views/order/order/orderDetail.vue | 2 +- seller/src/views/order/order/orderList.vue | 48 ++--- .../views/order/order/virtualOrderList.vue | 38 ++-- .../views/procurement/damage-report/list.vue | 50 ++--- seller/src/views/procurement/inbound/list.vue | 51 ++--- .../procurement/inventory-count/list.vue | 29 +-- .../views/procurement/purchase-order/list.vue | 64 +++---- .../flash-discount/flash-discount.vue | 33 +--- seller/src/views/promotion/live/liveGoods.vue | 10 +- .../nth-item-discount/nth-item-discount.vue | 33 +--- seller/src/views/shop/floorList.vue | 3 +- .../views/shop/hotzone/components/Hotzone.vue | 2 +- .../views/shop/hotzone/components/Zone.vue | 4 +- seller/src/views/shop/modelList/carousel.vue | 2 +- seller/src/views/shop/modelList/carousel1.vue | 2 +- seller/src/views/shop/modelList/carousel2.vue | 8 +- seller/src/views/shop/modelList/forYour.vue | 4 +- .../src/views/shop/modelList/goodsAndType.vue | 4 +- seller/src/views/shop/modelList/mixModel.vue | 2 +- .../shop/modelList/oneRowThreeColumns.vue | 4 +- .../views/shop/modelList/onlyGoodsModel.vue | 4 +- .../views/shop/wap/template/tpl_banner.vue | 2 +- .../views/shop/wap/template/tpl_flex_five.vue | 7 +- .../views/shop/wap/template/tpl_flex_two.vue | 7 +- .../wap/template/tpl_left_one_right_two.vue | 2 +- .../wap/template/tpl_left_two_right_one.vue | 2 +- .../shop/wap/template/tpl_text_picture.vue | 7 +- .../wap/template/tpl_top_one_bottom_two.vue | 2 +- .../wap/template/tpl_top_two_bottom_one.vue | 2 +- seller/src/views/shop/wap/wapList.vue | 5 +- seller/src/views/statistics/data-report.vue | 178 +++++++++++++++--- .../src/views/statistics/goods-comparison.vue | 14 +- .../views/statistics/goods-sales-summary.vue | 31 +-- .../views/statistics/sales-order-detail.vue | 35 +--- .../views/statistics/store-performance.vue | 29 +-- 49 files changed, 543 insertions(+), 479 deletions(-) create mode 100644 seller/src/utils/imgFallback.js create mode 100644 seller/src/utils/useTable.js diff --git a/seller/package.json b/seller/package.json index 7da9facb9..7ac3a47e8 100644 --- a/seller/package.json +++ b/seller/package.json @@ -26,7 +26,6 @@ "nprogress": "^0.2.0", "pinia": "^4.0.3", "sass": "^1.63.6", - "sass-loader": "^10.4.1", "swiper": "^6.3.5", "uuid": "^8.3.2", "vue": "^3.5.35", @@ -39,7 +38,6 @@ "@eslint/js": "^10.0.1", "@vue/cli-plugin-babel": "^5.0.9", "@vue/cli-plugin-router": "^5.0.9", - "@vue/cli-plugin-vuex": "^5.0.9", "@vue/cli-service": "^5.0.9", "@vue/compiler-sfc": "^3.5.35", "compression-webpack-plugin": "^10.0.0", @@ -49,6 +47,7 @@ "globals": "^17.13.0", "less": "^4.2.0", "less-loader": "^11.1.4", + "sass-loader": "^10.4.1", "style-loader": "^3.3.4", "style-resources-loader": "^1.5.0", "unplugin-vue-components": "0.27.5", diff --git a/seller/src/main.js b/seller/src/main.js index d10a4bcf0..9d06f0657 100644 --- a/seller/src/main.js +++ b/seller/src/main.js @@ -5,6 +5,7 @@ import App from "./App.vue"; import { router } from "./router/index"; import { setupElementPlus } from "@/plugins/element"; import { setupLegacyMessage } from "@/utils/message"; +import { setupImgFallback } from "@/utils/imgFallback"; import liliDialog from "@/views/lili-dialog"; import PriceColorScheme from "@/components/price-color-scheme.vue"; import { install as installVueQr } from "vue-qr"; @@ -39,6 +40,7 @@ const app = createApp(App); setupElementPlus(app); setupLegacyMessage(app); +setupImgFallback(); installVueQr(app); app.use(router); diff --git a/seller/src/styles/element.scss b/seller/src/styles/element.scss index 747acefec..46e57cc61 100644 --- a/seller/src/styles/element.scss +++ b/seller/src/styles/element.scss @@ -112,3 +112,30 @@ .el-overlay.is-message-box { z-index: 4000 !important; } + +/* 统一空状态:表格 / 树 / 列表无数据时的展示(未自定义 #empty 插槽的页面均生效) */ +.el-table__empty-block { + min-height: 160px; +} + +.el-table__empty-text, +.el-tree__empty-text { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + line-height: 1.5; + color: #909399; + font-size: 14px; + + &::before { + content: ""; + display: block; + width: 64px; + height: 64px; + margin-bottom: 8px; + opacity: 0.7; + background: no-repeat center / contain + url("data:image/svg+xml;utf8,"); + } +} diff --git a/seller/src/utils/imgFallback.js b/seller/src/utils/imgFallback.js new file mode 100644 index 000000000..496e759b5 --- /dev/null +++ b/seller/src/utils/imgFallback.js @@ -0,0 +1,31 @@ +/** + * 全局图片加载失败占位: + * 在捕获阶段监听 的 error 事件(error 不冒泡),失败时替换为内联 SVG 占位图。 + * - 每张图片只替换一次,占位图自身加载失败不会再次触发,避免死循环 + * - 空 src 不处理 + * - 需要自行处理 error 的图片可添加 data-no-fallback 属性跳过 + */ +const PLACEHOLDER = + "data:image/svg+xml;utf8," + + encodeURIComponent( + "" + + "" + + "" + + "" + + "" + ); + +export function setupImgFallback() { + document.addEventListener( + "error", + (event) => { + const el = event.target; + if (!el || el.tagName !== "IMG") return; + if (el.dataset.noFallback !== undefined || el.dataset.fallbackApplied) return; + if (!el.getAttribute("src")) return; + el.dataset.fallbackApplied = "1"; + el.src = PLACEHOLDER; + }, + true + ); +} diff --git a/seller/src/utils/useTable.js b/seller/src/utils/useTable.js new file mode 100644 index 000000000..69af9ba76 --- /dev/null +++ b/seller/src/utils/useTable.js @@ -0,0 +1,119 @@ +import { onMounted, reactive, ref } from "vue"; + +/** + * 分页列表通用逻辑:loading / data / total / searchForm / 查询 / 重置 / 刷新。 + * + * 用法: + * const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = + * useTable(getXxxPage, { form: { name: "", status: "" } }); + * + * + * + * + * @param {(params: object) => Promise} fetcher 请求函数,入参为 searchForm + * @param {object} [options] + * @param {object} [options.form] 查询条件默认值(会与分页/排序默认值合并) + * @param {number} [options.pageSize=10] 默认每页条数 + * @param {string} [options.sort] 默认排序字段 + * @param {string} [options.order] 默认排序方向 + * @param {boolean} [options.immediate=true] 挂载后是否自动查询 + * @param {(res: any) => ({records: any[], total: number}|null|undefined)} [options.resolve] + * 从响应中取出 { records, total };返回空表示请求失败(保持原数据)。 + * 默认:res.success 为真时取 res.result.records / res.result.total + * @param {(params: object) => object} [options.beforeRequest] 请求前加工参数(返回新对象,不影响 searchForm) + * @param {(res: any, parsed: {records: any[], total: number}) => void} [options.onSuccess] + * 查询成功且数据已更新后回调(仅最后一次请求触发),可用于刷新状态计数等 + */ +export function useTable(fetcher, options = {}) { + const { + form = {}, + pageSize = 10, + sort, + order, + immediate = true, + resolve = defaultResolve, + beforeRequest, + onSuccess, + } = options; + + const loading = ref(false); + const data = ref([]); + const total = ref(0); + + const initialForm = { ...form }; + const searchForm = reactive({ + pageNumber: 1, + pageSize, + ...(sort ? { sort } : {}), + ...(order ? { order } : {}), + ...form, + }); + + // 只采纳最后一次请求的结果,避免快速切换页码/条件时旧响应覆盖新数据 + let requestId = 0; + + function getDataList() { + const current = ++requestId; + loading.value = true; + const params = beforeRequest ? beforeRequest({ ...searchForm }) : searchForm; + return Promise.resolve(fetcher(params)) + .then((res) => { + if (current !== requestId) return res; + const parsed = resolve(res); + if (parsed) { + data.value = parsed.records || []; + total.value = parsed.total || 0; + if (onSuccess) onSuccess(res, parsed); + } + return res; + }) + .finally(() => { + if (current === requestId) loading.value = false; + }); + } + + /** 以当前条件从第 1 页重新查询 */ + function handleSearch() { + searchForm.pageNumber = 1; + return getDataList(); + } + + /** 恢复查询条件默认值(保留当前每页条数)并从第 1 页查询 */ + function resetSearch() { + Object.assign(searchForm, initialForm); + return handleSearch(); + } + + /** 保持当前页刷新(如删除/修改后) */ + function refresh() { + return getDataList(); + } + + if (immediate) { + onMounted(getDataList); + } + + return { + loading, + data, + total, + searchForm, + getDataList, + handleSearch, + resetSearch, + refresh, + }; +} + +function defaultResolve(res) { + if (res && res.success && res.result) { + return { records: res.result.records, total: res.result.total }; + } + return null; +} diff --git a/seller/src/views/ForgetPassword.vue b/seller/src/views/ForgetPassword.vue index 65573dd82..4b94906ed 100644 --- a/seller/src/views/ForgetPassword.vue +++ b/seller/src/views/ForgetPassword.vue @@ -2,7 +2,7 @@
- + 店铺 Logo
修改密码