# 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。 ## 后端改动(/Users/gxwebsoft/JAVA/guilixu-java,Spring Boot + MyBatis-Plus) - `entity/ShopGoods.java`:新增 `private Integer isTop;`(映射列 is_top,沿用 camelCase→下划线策略;mapper 用 SELECT a.*,无需改 XML)。 - `service/impl/ShopGoodsServiceImpl.java`: - `pageRel` 与 `listRel` 默认排序由 `sort_number asc, create_time desc` 改为 `is_top desc, sort_number asc, create_time desc`(置顶优先,置顶内按排序号,其余按创建时间倒序)。 - `collectFromUrl` 采集默认值补 `goods.setIsTop(0)`(与 recommend 一致)。 - 排序字段无白名单:`PageParam` 直接按列名拼 ORDER BY,前端点「置顶」「排序号」列排序无需额外放开。 - DB 仍需执行 `add-is-top-shop-goods.sql`(在 admin 仓库根目录):`ALTER TABLE shop_goods ADD COLUMN is_top TINYINT(1) NOT NULL DEFAULT 0`。 - 副作用提醒:默认排序改为置顶优先会影响所有调用 pageRel/listRel 的端(含小程序商品列表),置顶商品会浮到最前——通常正是期望行为。 ## shopGoods 列表:商品编号移到名称下方 - `src/views/shop/shopGoods/index.vue`:删除独立的「编号」列(原 `code` 列),改为在「商品」列的名称下方显示「编号:{{ record.code }}」(灰色小字),节省一列横向空间。无 code 时不渲染该行。 ## shopGoods 列表:三个价格合并为一列 + 与编辑弹窗对齐 - 原「市场价/价格/会员价」三列(字段 salePrice/price/dealerPrice)合并为单个「价格」列(`key: 'priceGroup'`,width 150)。 - 单元格内堆叠显示,顺序与命名对齐编辑弹窗(`shopGoodsEdit.vue` 基本信息 Tab):价格(price,加粗头条)→ 市场价(salePrice)→ 会员价(dealerPrice);空值显示 `-`。 - 列表展示用 priceGroup 自定义 `#bodyCell` 渲染;导出 Excel(`handleExport`)仍保留三列独立价格,未改动。 ## 商品分类(ShopGoodsCategory)多级分类调研 - 分类管理页 `/shop/shopGoodsCategory` 本身**支持**多级:模型有 parentId/children,编辑弹窗用 a-tree-select(toTreeData) 不限层级,列表 computeDepth 递归缩进。 - 但商品端选分类的级联组件 `src/components/SelectGoodsCategory/index.vue` 的 formatData/filterData 只映射 2~3 级;shopGoodsEdit 的 `chooseGoodsCategory` 写死 `value[1].value`/`value[0].label`(仅 2 级),3 级及以上会选错/选不中。 - 后端 `/shop/shop-goods-category` 是否限层级需查后端仓库确认(本仓库看不到)。 ## 新增:忘记密码(找回密码)功能 - 参考项目:`/Users/gxwebsoft/VUE/website-admin/app/pages/login.vue`(已验证可用)。 - 新增页面 `src/views/passport/forget/index.vue`:复用登录页视觉风格,支持「邮箱找回 / 手机找回」两种 Tab,含验证码 60s 倒计时、密码强度校验(≥8 位且含字母+数字)、两次密码一致性校验。 - 路由:在 `src/router/routes.ts` 启用 `/forget`(登录页「忘记密码」链接 `push('/forget')` 原本就指向它,路由此前被注释)。 - 新增 API: - `src/api/passport/login/index.ts`:`sendEmailCaptcha(data,{skipTenant})` → POST /sendEmailCaptcha;`sendSmsCaptchaByAdmin(data,{skipTenant})` → POST /sendSmsCaptchaByAdmin。 - `src/api/layout/index.ts`:`resetPassword(data)` → POST /resetPassword(带 X-Tenant-Skip:1);`src/api/layout/model/index.ts` 新增 `ResetPasswordParam`。 - 关键改动:`src/utils/request.ts` 请求拦截器在请求头含 `X-Tenant-Skip` 时跳过注入 TenantId/CompanyId/MerchantId/Domain(登录前找回密码场景,避免串租户)。`vite build --outDir dist-verify` 验证通过(真实 `vite build` 因沙箱 safe-delete 守卫删 dist 拦截,非代码问题)。 - 后端同址 server.websoft.top/api,与 website-admin 共用一套找回密码接口。 ## 优化:忘记密码页发送验证码前 3 次免图形验证 - 修改 `src/views/passport/forget/index.vue`:引入 `getCaptcha`,新增图形验证码弹窗(与 `register2/index.vue` 风格一致)。 - 用 `localStorage` 按 `forget_send_count_${activeTab}_${account}` 记录每个邮箱/手机号的发送次数。 - 同一个账号前 3 次点击「获取验证码」直接调用 `sendEmailCaptcha`/`sendSmsCaptchaByAdmin`;第 4 次起弹出图形验证码弹窗,输入正确并点击「立即发送」后才真正发送。 - 失败时自动刷新图形验证码;弹窗关闭时重置 `codeLoading`。 - 新增 i18n 键 `login.forgetPage.captchaTitle`、`captchaPlaceholder`、`sendNow`(zh_CN/en)。 - `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-captcha` 编译通过。