From 516f077568efba90f324dceeb3af3279542b9ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B9=B4=E7=B3=95=E5=B4=BD=E5=B4=BD?= Date: Fri, 14 Aug 2026 23:17:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20=E7=BC=BA?= =?UTF-8?q?=E9=99=B7=EF=BC=9A=E4=B8=BB=E9=A2=98=E9=85=8D=E8=89=B2=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E4=BC=98=E5=8C=96=E6=95=B4=E4=BD=93=E7=9A=84=E5=90=8C?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/lgp-pitfalls.mdc | 40 ++ .cursor/rules/lgp-project-map.mdc | 46 ++ App.vue | 6 + api/env.js | 92 +++ api/interceptor.js | 16 +- api/page/cart.js | 28 +- api/page/image.js | 15 - api/page/order.js | 96 +++ api/page/theme.js | 19 + common/classify.data.js | 1087 ----------------------------- mixins/productWaterfall.js | 100 +++ mixins/theme.js | 50 ++ pages.json | 31 +- pages/cart/cart.vue | 74 +- pages/cart/detail.vue | 214 +++++- pages/goods/index.vue | 30 +- pages/login/index.vue | 1 - pages/mine/mine.vue | 1 - pages/my/index.vue | 14 +- pages/order/detail.vue | 801 +++++++++++++++++++++ pages/order/list.vue | 350 ++++++++++ pages/product/detail.vue | 185 ++++- pages/product/product.vue | 119 +--- pages/search/search.vue | 117 +--- pnpm-lock.yaml | 31 +- utils/theme.js | 87 +++ 26 files changed, 2280 insertions(+), 1370 deletions(-) create mode 100644 .cursor/rules/lgp-pitfalls.mdc create mode 100644 .cursor/rules/lgp-project-map.mdc create mode 100644 api/env.js delete mode 100644 api/page/image.js create mode 100644 api/page/order.js create mode 100644 api/page/theme.js delete mode 100644 common/classify.data.js create mode 100644 mixins/productWaterfall.js create mode 100644 mixins/theme.js create mode 100644 pages/order/detail.vue create mode 100644 pages/order/list.vue create mode 100644 utils/theme.js diff --git a/.cursor/rules/lgp-pitfalls.mdc b/.cursor/rules/lgp-pitfalls.mdc new file mode 100644 index 0000000..1530a70 --- /dev/null +++ b/.cursor/rules/lgp-pitfalls.mdc @@ -0,0 +1,40 @@ +--- +description: LGP 新后端开发约定与全项目群已知陷阱 +alwaysApply: true +--- + +# 开发约定与已知陷阱 + +## 新后端 lgp-admin-plus-api + +- 路由由 `UtilsService::autoRouteRegister()` **反射注册**,继承自 `BaseController` 的方法也会被注册成路由。不需要暴露的继承方法必须用 `@Method NO` 覆盖,否则每个控制器白送一套 `create/update/delete` +- 方法名 camelCase 会转成 kebab-case 路径:`myInfo` → `my-info` +- 缺 `@Method` 注解会注册成 `ANY`,务必显式写 `@Method GET` 或 `@Method POST` +- 响应统一走 `jok` / `jerr`,结构固定 `{code, message, result}`,成功 `code=0`。前端拦截器按 `result` 取数据,**不要用 `data`** +- 业务表 Model 覆盖 `$connection = 'business'`;系统表沿用默认 `mysql` +- 免鉴权 Service 继承 `BaseNotAuthService`,不要像老项目那样硬编码路径白名单 +- 鉴权真正发生在 `BaseService::__construct`,绕过 BaseService 的方法就没有鉴权。新代码一律走 BaseService +- `nl_menu` 的 `keep_alive` 与 `affix_tab` 输出到 meta 时是**取反**的,插数据别弄反 +- 第三方凭据用 `FieldEncryptService` 加密入库,不回显明文 +- **库表变更禁止新建 PHP Migration**:写 `database/sql//01_名字.sql`(当天序号递增、尽量幂等);系统表同步 `public/nl_admin.sql`,业务表 `cc_*` 只放 sql 目录 + +## 老项目只读 + +`lgp-api` / `lgp-wx-api` / `lgp-vben` / `lgp-vben-new` 处于迁移期,除安全修复外不要改动。 +查业务逻辑可以读,新功能一律写在 `lgp-admin-plus-api` + `lgp-admin-plus`。 + +## 已知陷阱 + +- 老 `lgp-wx-api` 把微信 `session_key` 当 token 直接返回客户端,且 `AuthMiddleware` 先执行控制器再检查 token、只判空不验签。归并时必须改成服务端自签 token +- 老 `lgp-api` 密码是**无盐 sha1**,新后端是 bcrypt,靠 `nl_admin.legacy_password` 惰性升级,遗留列有过期时间 +- 老 `cc_role.id=1` 不是超管,而新 `nl_role.id=1` 是超管且代码硬判断全量放行。角色迁移必须做 ID 偏移 +- 老前端 10 个模块的批量删除在静默 404(后端只有 `template` 注册了 `delete-batch`) +- `cc_price_sheet` 的 6 个材质列是死 schema,`create()` 从不赋值、前端已注释,实际只有 `routine` 在用 +- 图片水印不是后端接口,是前端拼 OSS URL 参数 `?watermark/2/text/...` +- 小程序 `baseURL` 硬编码在 `api/interceptor.js` 且靠注释切换,极易把测试地址发上线 +- 小程序无分包配置,uview-plus 全量进主包 + +## 安全红线 + +不要把任何密钥写进源码。老项目里七牛 AK/SK(`UploadService.php`)、微信 AppSecret(`WeChatService.php`)明文硬编码, +`.env.example` 里还提交了真实数据库密码——这些是待清理的历史欠账,不要照抄这个做法。 diff --git a/.cursor/rules/lgp-project-map.mdc b/.cursor/rules/lgp-project-map.mdc new file mode 100644 index 0000000..bd54f17 --- /dev/null +++ b/.cursor/rules/lgp-project-map.mdc @@ -0,0 +1,46 @@ +--- +description: LGP 项目群架构、各仓库角色与双品牌对应关系 +alwaysApply: true +--- + +# LGP 项目地图 + +两个家具品牌共用一套代码,正从老架构迁往新架构。 + +## 新架构(开发主战场) + +- `lgp-admin-plus` — 后台前端,vben **5.7.0** monorepo,主应用 `apps/web-antd`,上游是自研 `nl-admin-view` +- `lgp-admin-plus-api` — 后端,**Laravel 13**,位于 `sites/lgp-api/index/lgp-admin-plus-api` + +## 老架构(迁移期只读,迁完归档) + +- `lgp-vben` — 老后台前端,vben 2.11.5 +- `lgp-api` — 老后台后端,Laravel 10 +- `lgp-wx-api` — 老小程序后端,Laravel 12,本期归并进新后端的 `wx` 路由组 +- `lgp-vben-new` — **已弃用**,与 `lgp-vben` 同源克隆,不要在里面做任何改动 + +## 小程序(uni-app + Vue3 + uview-plus) + +- `lgp-wx` — 佛山铂尔曼,AppID `wx679d36842570cea7`,API `brm.wx.api.borman.top` +- `lgp-wx-new` — 维伦家具,AppID `wx57b54060a579c31a`,API `wx.api.borman.top` + +两者页面与业务代码基本一致,差异只有 AppID、`baseURL` 与首页视觉。改一边通常要同步另一边。 + +## 双品牌 + +同一套后台代码部署两份,靠构建模式区分:`build:brm`(佛山铂尔曼)/ `build:wl`(维伦家具)。 +两个品牌共用一套代码与数据库,任何业务改动会同时影响两家客户,上线前按品牌分别回归。 + +## 数据库 + +同一个库、两个连接(`config/database.php`): + +- `mysql`,前缀 `nl_` — 脚手架系统表(admin / role / menu / oss_config / file / api_endpoint 等) +- `business`,前缀 `cc_` — 业务表(catalogue / category / image / price_sheet / list / wx_user / enterprise 等) + +业务数据零迁移,只迁账号与角色。业务 Model 必须覆盖 `protected $connection = 'business'`。 + +## 迁移范围 + +老后端注册 171 条路由但前端只调用 **79** 条;老小程序后端注册 35 条但小程序只调用 **14** 条。 +迁移目标是这 93 条,其余是反射自动注册出来的死路由,不要跟着迁。 diff --git a/App.vue b/App.vue index d7b19c1..d073032 100644 --- a/App.vue +++ b/App.vue @@ -1,11 +1,17 @@ + + diff --git a/pages/order/list.vue b/pages/order/list.vue new file mode 100644 index 0000000..ba46815 --- /dev/null +++ b/pages/order/list.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/pages/product/detail.vue b/pages/product/detail.vue index 5569516..5327f2a 100644 --- a/pages/product/detail.vue +++ b/pages/product/detail.vue @@ -78,13 +78,14 @@ bgColor="#F7F8FA" > - + + {{ item.name }} @@ -96,6 +97,42 @@ + + + + 选择规格 + 返回清单 + + + + + {{ spec.specification }} + {{ spec.dimension }} + + + ¥{{ spec.routine[0] }} + + + + 该商品暂无规格,将按默认规格加入 + + + + 数量 + + - + {{ specPicker.quantity }} + + + + + + @@ -131,6 +168,14 @@ export default { p_user_id: 0, listModal: false, listOption: [], + // 规格/数量选择:加入清单时必须带上 price_sheet_id,否则转订单会被 OrderCoreService 拒 + specPicker: { + visible: false, + listId: 0, + sheets: [], + priceSheetId: 0, + quantity: 1, + }, product: { title: '', cover: '', @@ -158,7 +203,6 @@ export default { previewImages(url) { let w = this.product.title; w = urlSafeBase64Encode(w); - console.log(`${url}?watermark/2/text/${w}/fontsize/800`); uni.previewImage({ current: 0, urls: [`${url}?watermark/2/text/${w}/fontsize/800`], @@ -169,10 +213,35 @@ export default { }, showModal() { this.getListOption(); + this.specPicker.visible = false; this.listModal = true; }, - addToCart(id) { - toCartApi(id, this.id).then(() => { + // 选定某个清单后进入规格选择步骤;该商品的 price_sheet 作为可选规格 + onPickList(listId) { + const sheets = (this.product.price_sheet || []).map((s) => ({ + id: s.id, + specification: s.specification, + dimension: s.dimension, + routine: s.routine || [], + })); + this.specPicker.listId = listId; + this.specPicker.sheets = sheets; + this.specPicker.priceSheetId = sheets.length > 0 ? sheets[0].id : 0; + this.specPicker.quantity = 1; + this.specPicker.visible = true; + }, + changeQty(delta) { + const next = this.specPicker.quantity + delta; + if (next < 1) return; + this.specPicker.quantity = next; + }, + // 确定加入:带 price_sheet_id 与 quantity 提交,后端按规格去重累加 + confirmAddToCart() { + const { listId, priceSheetId, quantity } = this.specPicker; + toCartApi(listId, this.id, { + price_sheet_id: priceSheetId, + quantity, + }).then(() => { this.listModal = false; uni.showToast({ title: '已添加到清单', @@ -468,4 +537,110 @@ export default { align-items: center; justify-content: center; } + +/* 规格选择步骤 */ +.spec-picker { + padding: 10rpx 0 20rpx; +} +.spec-picker-head { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 6rpx 20rpx; +} +.spec-picker-title { + font-size: 30rpx; + font-weight: 600; + color: #222; +} +.spec-picker-back { + font-size: 24rpx; + color: #B4854D; +} +.spec-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24rpx 20rpx; + background: #fff; + border: 1rpx solid #EEE; + border-radius: 10rpx; + margin-bottom: 16rpx; +} +.spec-row.active { + border-color: #B4854D; + background: rgba(180, 133, 77, 0.06); +} +.spec-row-info { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; +} +.spec-row-name { + font-size: 28rpx; + color: #222; + font-weight: 500; +} +.spec-row-dim { + font-size: 22rpx; + color: #999; + margin-top: 6rpx; +} +.spec-row-price { + flex-shrink: 0; + margin-left: 20rpx; +} +.spec-row-curr { + font-size: 28rpx; + color: #B4854D; + font-weight: 600; +} +.spec-empty { + text-align: center; + color: #999; + font-size: 24rpx; + padding: 40rpx 0; +} +.qty-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20rpx 6rpx; + margin-top: 10rpx; +} +.qty-label { + font-size: 28rpx; + color: #333; +} +.qty-ctrl { + display: flex; + align-items: center; + gap: 30rpx; +} +.qty-btn { + width: 56rpx; + height: 56rpx; + line-height: 52rpx; + text-align: center; + border-radius: 50%; + background: #F2F2F2; + color: #333; + font-size: 36rpx; +} +.qty-num { + font-size: 30rpx; + color: #222; + min-width: 40rpx; + text-align: center; +} +.spec-confirm-btn { + margin-top: 24rpx; + background: #B4854D; + color: #fff; + font-size: 30rpx; + border-radius: 48rpx; + height: 88rpx; + line-height: 88rpx; +} \ No newline at end of file diff --git a/pages/product/product.vue b/pages/product/product.vue index 96d8014..2fc5a31 100644 --- a/pages/product/product.vue +++ b/pages/product/product.vue @@ -135,33 +135,19 @@