# 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` 编译通过。 ## 优化:登录页手机号验证码前 3 次免图形验证 - 修改 `src/views/passport/login/index.vue`:手机号登录 Tab 下,「发送验证码」按钮由直接弹窗改为先计数判断。 - 用 `localStorage` 按 `login_sms_send_count_${phone}` 记录每个手机号的发送次数。 - 同一个手机号前 3 次直接调用 `sendSmsCaptcha`;第 4 次起弹出原有图形验证码弹窗,输入正确并点击「立即发送」后才真正发送。 - 失败时自动刷新图形验证码;补充了 `.catch`/`.finally`,避免发送异常时按钮一直 loading。 - `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-login` 编译通过。 ## 修复:邮箱登录图形验证码未校验 - **问题**:`src/views/passport/login/index.vue` 第 651-555 行的验证码比对逻辑被注释掉,导致用户输入任意非空字符串即可通过前端校验。 - **修复**:取消注释 `form.code?.toLowerCase() !== text.value` 的校验块,恢复「输入错误则提示"验证码错误"并刷新验证码」的逻辑。 - **说明**:当前后端 `/captcha` 接口返回明文答案 `data.text`,前端直接比对即可。后续若后端改为返回 key 模式,需同步改为传 key 给后端校验。 ## 新增:手机号登录多租户选择 - 参考项目:`/Users/gxwebsoft/VUE/website-admin/app/pages/login.vue`。 - `src/api/passport/login/model/index.ts`: - `LoginResult` 新增 `tenants?: TenantOption[]`; - 新增 `TenantOption` 类型与 `LoginOutcome` 联合类型(`ok` / `selectTenant`)。 - `src/api/passport/login/index.ts`: - 新增 `saveLoginState` 统一保存 token、TenantId、UserId; - `loginBySuperAdminSms` 改为返回 `LoginOutcome`,检测 `data.tenants` 时返回 `selectTenant`; - 新增 `loginBySelectTenant({ phone, code, tenantId, remember })` 二次登录接口。 - `src/views/passport/login/index.vue`: - `onLoginBySms` 根据 `outcome.status` 处理:单租户直接登录成功;多租户弹出「请选择要登录的租户」弹窗。 - 新增弹窗与 `onSelectTenant`:选择租户后调用 `loginBySelectTenant` 完成二次登录并跳转。 - `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-tenant` 编译通过。