diff --git a/.workbuddy/memory/2026-07-24.md b/.workbuddy/memory/2026-07-24.md new file mode 100644 index 0000000..8cf1821 --- /dev/null +++ b/.workbuddy/memory/2026-07-24.md @@ -0,0 +1,15 @@ +# 2026-07-24 工作日志 + +## shopGoods 商品列表排序机制分析 +- 现状:列表「排序号」列(`src/views/shop/shopGoods/index.vue` 约 273-279 行)只做展示,**未启用 `sorter`**,列表默认也不按 `sortNumber` 排序(默认排序由后端决定,前端 `orders` 仅来自「创建时间」列的排序)。 +- 编辑表单里「排序号」是 `a-input-number`(0-9999,默认 100),且在 `rules` 里是 **必填**。 +- 已存在类似的布尔开关 `recommend`(列表内联切换,index.vue 约 47-58 行),「置顶」可复用同一 UI 模式。 +- 结论倾向:纯排序号在商品多时难用,建议「置顶开关 + 少量置顶项内排序 + 其余按默认(创建时间/销量)」的混合方案,而非完全替换。 + +## 已实现:方案 B(商品置顶功能) +- 数据模型 `src/api/shop/shopGoods/model/index.ts`:新增 `isTop?: number`(置顶 1/不置顶 0)。 +- 列表 `src/views/shop/shopGoods/index.vue`: + - 新增「置顶」列,列表内联一键切换(复用 recommend 的 CheckOutlined/CloseOutlined 模式),新增 `onTop` 处理函数。 + - 「排序号」列开启 `sorter: true`(之前只展示、不可排序)。 +- 后端迁移脚本 `add-is-top-shop-goods.sql`:新增 `is_top` TINYINT 字段;分页默认排序需改为 `ORDER BY is_top DESC, sort_number ASC, create_time DESC`(后端不在本仓库,需手动改 page 接口 ORDER BY / 放开 orders 白名单)。 +- 注意:置顶真正"排到最前"依赖后端默认排序改动;点击列排序也需后端放开 is_top / sort_number。 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 601be4b..4bc18a3 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -21,3 +21,12 @@ - `2` = 商家送货 来源:管理后台 `src/views/shop/shopOrder/components/deliveryModal.vue` + +### shopGoods status 字段(上架/下架) +- `0` = 已上架 / 上架 +- `1` = 待上架 / 下架 +- `2` = 待审核,`3` = 审核不通过 +- 编辑表单「状态」radio 绑定 `form.status`:0=上架、1=下架。 +- 一键上下架、批量上下架均通过 `updateShopGoods({ ...record, status })` 直接改 status 实现(不额外用 isShow 字段)。 +- 列表 tag 仍显示:0=已上架、1=待上架、2=待审核、3=审核不通过。 + diff --git a/add-is-top-shop-goods.sql b/add-is-top-shop-goods.sql new file mode 100644 index 0000000..9e5c56a --- /dev/null +++ b/add-is-top-shop-goods.sql @@ -0,0 +1,35 @@ +-- ============================================================ +-- 商品表新增「置顶」字段 is_top,并调整默认排序(方案 B) +-- ------------------------------------------------------------ +-- 约定说明(依据本项目代码 /Users/gxwebsoft/VUE/guilixu-admin): +-- 1. is_top = 1 表示置顶,0(默认)表示不置顶 +-- 2. 表名/字段名 snake_case 约定(Node + TypeORM 常见); +-- 若你的库列名是驼峰(goodsId / isTop),请自行替换 +-- 3. 与 recommend(推荐)同为「列表内联一键切换」的布尔开关 +-- 4. 混合排序思路:置顶的永远在最前;置顶内部按排序号升序; +-- 其余商品按创建时间倒序(绝大多数商品无需手动排) +-- ============================================================ + +-- 第一步:新增字段(已存在则跳过;MySQL 5.7 / 8 通用) +ALTER TABLE shop_goods + ADD COLUMN is_top TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶:1置顶 0不置顶'; + +-- (可选)联合索引,置顶 + 排序号分页查询更稳 +-- ALTER TABLE shop_goods +-- ADD INDEX idx_is_top_sort (is_top, sort_number); + +-- 第二步:调整分页查询(page 接口)默认排序 +-- 原(示例):ORDER BY create_time DESC +-- 改: ORDER BY is_top DESC, sort_number ASC, create_time DESC +-- 说明:若后端排序字段来自前端 orders 参数,需放开 is_top / sort_number 参与排序 +-- (前端「置顶」「排序号」列已开启 sorter,会向后端传 orders) + +-- 第三步(验证):确认字段已加、数据默认 0,且排序符合预期 +SELECT goods_id, + name, + is_top, + sort_number, + create_time +FROM shop_goods +ORDER BY is_top DESC, sort_number ASC, create_time DESC +LIMIT 20; diff --git a/offline-noimage-goods-10332.sql b/offline-noimage-goods-10332.sql new file mode 100644 index 0000000..50fc168 --- /dev/null +++ b/offline-noimage-goods-10332.sql @@ -0,0 +1,37 @@ +-- ============================================================ +-- 下架「分类ID = 10332 且 没有图片」的商品 +-- ------------------------------------------------------------ +-- 约定说明(依据本项目代码 /Users/gxwebsoft/VUE/guilixu-admin): +-- 1. 下架 = status = 1(项目约定:0 上架 / 1 下架,见 shopGoodsEdit.vue 状态 radio) +-- 2. 「没有图片」以封面图 image 是否为空判断(封面图为必填项) +-- 3. 已删除商品(deleted = 1)不参与,避免误改 +-- 4. 表名/字段名按 snake_case 约定(Node + TypeORM 常见); +-- 若你的库列名是驼峰(goodsId / categoryId),请自行替换 +-- 5. 分类 10332 若是父分类、需连同其子分类商品一起处理, +-- 请把 category_id = 10332 改为 category_id IN (10332, 子分类ID...) +-- ============================================================ + +-- 第一步:先预览,确认影响范围(务必先执行核对,再跑 UPDATE) +SELECT goods_id, + name, + image, + status +FROM shop_goods +WHERE category_id = 10332 + AND (image IS NULL OR image = '') + AND (deleted = 0 OR deleted IS NULL); + +-- 第二步:确认无误后执行下架 +UPDATE shop_goods +SET status = 1, + update_time = NOW() +WHERE category_id = 10332 + AND (image IS NULL OR image = '') + AND (deleted = 0 OR deleted IS NULL); + +-- ------------------------------------------------------------ +-- (可选)若你认为「没有图片」应同时要求轮播图也为空, +-- 即封面图和轮播图都没有才算没图,使用下面更严格的条件: +-- AND (image IS NULL OR image = '') +-- AND (files IS NULL OR files = '' OR files = '[]') +-- ------------------------------------------------------------ diff --git a/src/api/shop/shopGoods/model/index.ts b/src/api/shop/shopGoods/model/index.ts index 3756be8..c9fd09d 100644 --- a/src/api/shop/shopGoods/model/index.ts +++ b/src/api/shop/shopGoods/model/index.ts @@ -87,6 +87,8 @@ export interface ShopGoods { gainIntegral?: number; // 推荐 recommend?: number; + // 置顶(1置顶 0不置顶) + isTop?: number; // 商户ID merchantId?: number; // 商户名称 diff --git a/src/utils/image.ts b/src/utils/image.ts index 2872574..70f890f 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -41,6 +41,26 @@ export function ensureFullUrl(url: string): string { return `${OSS_BASE_URL}${url.startsWith('/') ? '' : '/'}${url}`; } +/** + * 移除 URL 中已有的 x-oss-process 参数(无论其在 query 中的位置) + * + * 阿里云 OSS 的图片处理参数通常以 `x-oss-process=image/...` 形式附加在 query 中, + * 可能位于首个或后续参数位。本函数精确剔除该参数,保留其余 query 参数。 + * + * @param url 完整图片 URL + * @returns 已剥离 x-oss-process 的 URL + */ +function removeOssProcess(url: string): string { + const qIndex = url.indexOf('?'); + if (qIndex === -1) return url; + const base = url.slice(0, qIndex); + const params = url + .slice(qIndex + 1) + .split('&') + .filter((p) => !p.startsWith('x-oss-process=')); + return params.length ? `${base}?${params.join('&')}` : base; +} + /** * 获取 OSS 压缩后的图片 URL * @@ -49,8 +69,11 @@ export function ensureFullUrl(url: string): string { * * 注意:预览原图时不需要调用此函数,直接用原始 URL 即可。 * + * 优先级:以调用方指定的 width/quality 为准。若 URL 已自带 x-oss-process + * (接口已返回压缩参数),会先剥离再按本函数参数重新拼接,确保调用方参数生效。 + * * @param url 原始图片 URL - * @param options 压缩选项 + * @param options 压缩选项(width/quality 不传则使用默认值) * @returns 处理后的图片 URL */ export function getCompressedImageUrl( @@ -66,11 +89,12 @@ export function getCompressedImageUrl( // 未启用压缩,原样返回 if (!enabled) return fullUrl; - // 已包含 OSS 处理参数,避免重复拼接 - if (fullUrl.includes('x-oss-process')) return fullUrl; + // 优先使用调用方指定的 width/quality: + // 先剥离 URL 自带的 x-oss-process,再按本函数参数重新拼接,确保以调用方为准。 + const baseUrl = removeOssProcess(fullUrl); // 已有其他 query 参数用 & 拼接,否则用 ? - const separator = fullUrl.includes('?') ? '&' : '?'; + const separator = baseUrl.includes('?') ? '&' : '?'; - return `${fullUrl}${separator}x-oss-process=image/resize,w_${width}/quality,Q_${quality}`; + return `${baseUrl}${separator}x-oss-process=image/resize,w_${width}/quality,Q_${quality}`; } diff --git a/src/views/shop/shopGoods/components/search.vue b/src/views/shop/shopGoods/components/search.vue index 42459a9..0b338b6 100644 --- a/src/views/shop/shopGoods/components/search.vue +++ b/src/views/shop/shopGoods/components/search.vue @@ -1,89 +1,112 @@ + + diff --git a/src/views/shop/shopGoods/components/shop-goods-detail-import.vue b/src/views/shop/shopGoods/components/shop-goods-detail-import.vue index b412b55..3a16fb5 100644 --- a/src/views/shop/shopGoods/components/shop-goods-detail-import.vue +++ b/src/views/shop/shopGoods/components/shop-goods-detail-import.vue @@ -20,16 +20,53 @@
- 只能上传 xls、xlsx 文件 + 只能上传 xls、xlsx 文件, + 下载导入模板
+ + + + + + +