From 944ec6b605236c5fe1257e9345e9baf40f6c3dd3 Mon Sep 17 00:00:00 2001 From: dzq Date: Thu, 18 Dec 2025 11:52:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=9A=82=E5=AD=98=E6=A8=A1=E5=BC=8F):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=99=BA=E8=83=BD=E6=9F=9C=E6=9A=82=E5=AD=98?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加暂存模式(模式5)相关功能实现,包括: 1. 在shop类型中扩展暂存模式定义 2. 新增storageCabinet pinia store管理暂存状态 3. 实现暂存柜格口选择组件 4. 更新支付方式映射关系 5. 添加运行模式文档说明 --- .claude/settings.local.json | 30 -- .gitignore | 1 + doc/Shop运行模式文档.md | 240 +++++++++ src/api/cabinet/index.ts | 4 +- src/api/cabinet/types.ts | 6 +- src/api/shop/types.ts | 6 +- .../components/storage-cabinet-container.vue | 500 ++++++++++++++++++ src/pages/index/index.vue | 117 +++- src/pinia/stores/storageCabinet.ts | 92 ++++ src/utils/maps/payment.ts | 1 + 10 files changed, 940 insertions(+), 57 deletions(-) delete mode 100644 .claude/settings.local.json create mode 100644 doc/Shop运行模式文档.md create mode 100644 src/pages/index/components/storage-cabinet-container.vue create mode 100644 src/pinia/stores/storageCabinet.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 2467b05..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(tree -L 3 -I 'node_modules' src/)", - "Bash(mkdir -p 'E:\\code\\智柜宝\\wx\\src\\api\\shop')", - "Bash(tree 'E:\\code\\智柜宝\\wx\\src\\api' -I 'foo|goods|layout|login|me|order|system|types' -L 2)", - "Bash(mkdir -p 'E:\\code\\智柜宝\\wx\\src\\pages\\home')", - "Bash(mkdir -p 'E:\\code\\智柜宝\\wx\\src\\pages\\home\\components')", - "Bash(cp 'E:\\code\\智柜宝\\wx\\doc\\thirdParty\\src\\pages\\product\\components\\checkout.vue' 'E:\\code\\智柜宝\\wx\\src\\pages\\index\\components\\checkout.vue')", - "Bash(cp 'E:\\code\\智柜宝\\wx\\doc\\thirdParty\\src\\pages\\product\\components\\RentingCabinetContainer.vue' 'E:\\code\\智柜宝\\wx\\src\\pages\\index\\components\\renting-cabinet-container.vue')", - "Bash(test -f 'E:\\code\\智柜宝\\wx\\src\\pages\\index\\components\\product-container.vue')", - "Bash(ls -la 'E:\\\\code\\\\智柜宝\\\\wx\\\\src\\\\pages\\\\me')", - "Bash(mkdir -p 'E:\\\\code\\\\智柜宝\\\\wx\\\\src\\\\pages\\\\me')", - "Bash(ls -la 'E:\\\\code\\\\智柜宝\\\\wx\\\\static')", - "Bash(ls -la 'E:\\\\code\\\\智柜宝\\\\wx\\\\src\\\\static')", - "Bash(tree -L 3 -I 'node_modules|dist' /E/code/智柜宝/wx)", - "Bash(tree 'E:\\code\\智柜宝\\wx\\src\\pages\\order' -L 3)", - "Bash(pnpm type-check)", - "Bash(ls -la 'E:\\\\code\\\\智柜宝\\\\wx\\\\src\\\\pages\\\\index\\\\components')", - "Bash(ls -la 'E:\\\\code\\\\智柜宝\\\\wx\\\\doc\\\\thirdParty\\\\src\\\\pages\\\\product\\\\components')", - "Bash(mkdir -p \"E:\\code\\智柜宝\\wx\\src\\pages\\rental\")", - "Bash(cat /E/code/智柜宝/wx/README.md)", - "Bash(cat /E/code/智柜宝/wx/package.json)", - "Bash(cat /E/code/智柜宝/wx/manifest.config.ts)", - "Bash(tree:*)" - ], - "deny": [], - "ask": [] - } -} diff --git a/.gitignore b/.gitignore index 480e91f..f7e6ff4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ src/types # 更新 uni-app 官方版本 # npx @dcloudio/uvm@latest +/.claude diff --git a/doc/Shop运行模式文档.md b/doc/Shop运行模式文档.md new file mode 100644 index 0000000..89d4799 --- /dev/null +++ b/doc/Shop运行模式文档.md @@ -0,0 +1,240 @@ +# Shop运行模式文档 + +本文档详细说明项目中Shop的mode运行模式相关处理逻辑。运行模式决定了商店的业务流程、支付方式以及用户交互方式。 + +## 运行模式定义 + +在 `src/api/shop/types.ts` 中定义了Shop实体和运行模式: + +```typescript +export interface ShopEntity { + /** 主键ID */ + shopId: number; + /** 商店名称 */ + shopName: string; + /** 企业微信id */ + corpid: string; + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + mode?: number; + /** 借呗支付(1-正常使用 0-禁止使用) */ + balanceEnable?: number; + /** 封面图URL */ + coverImg?: string; +} +``` + +运行模式的具体含义: + +| 模式值 | 模式名称 | 说明 | +|--------|----------|------| +| 0 | 支付模式 | 用户直接支付购买商品 | +| 1 | 审批模式 | 需要审批流程后才能购买 | +| 2 | 借还模式 | 借还物品模式 | +| 3 | 会员模式 | 会员租用模式 | +| 4 | 耗材模式 | 耗材领用模式 | + +## 支付方式映射 + +不同运行模式支持不同的支付方式,映射关系定义在 `src/utils/maps/payment.ts`: + +```typescript +export const modeToPaymentMethodMap: Record = { + 0: [0], // 支付模式:微信支付 + 1: [0, 1], // 审批模式:微信支付、借呗支付 + 2: [0, 1], // 借还模式:微信支付、借呗支付 + 3: [0], // 会员模式:微信支付 + 4: [2], // 耗材模式:要呗支付 +}; +``` + +支付方式的枚举值: +- `0`: 微信支付 +- `1`: 借呗支付 +- `2`: 要呗支付 + +## 业务逻辑处理 + +### 1. 首页模式切换 (`pages/index/index.vue`) + +首页根据选中的商店模式决定显示内容: + +```typescript +// 当选择商店时触发 +if (selectedShop.mode == 3) { + // 会员模式:进入租用模式 + rentingCabinetStore.fetchRentingCabinetDetail(selectedShopId); +} else { + // 其他模式:获取普通商品列表 + productStore.getGoods(selectedShopId); +} +``` + +### 2. 结账页面处理 (`pages/index/checkout.vue`) + +结账页面使用 `isRentingMode` 计算属性区分模式: + +```typescript +// 是否为租用模式(会员模式) +const isRentingMode = computed(() => { + return selectedShop.value?.mode === 3; +}); +``` + +根据模式渲染不同的商品列表: +- 普通模式:显示普通购物车商品 +- 租用模式:显示租用购物车商品 + +支付方式动态确定: +```typescript +// 根据模式获取支持的支付方式 +const paymentMethodList = computed(() => { + const mode = selectedShop.value?.mode || 0; + const methods = modeToPaymentMethodMap[mode] || [0]; + return methods; +}); +``` + +订单提交时传递模式参数: +```typescript +// 在 SubmitOrderRequestData 中包含 mode 字段 +const submitData: SubmitOrderRequestData = { + // ... 其他字段 + mode: selectedShop.value?.mode || 0, + goodsList: [ + { + // ... 商品信息 + mode: selectedShop.value?.mode || 0, // 每个商品项也包含 mode + } + ] +}; +``` + +### 3. 购物车管理 (`pages/index/components/cart.vue`) + +购物车组件支持两种模式: +- 普通购物车:用于模式 0,1,2,4 +- 租用购物车:用于模式 3 + +提供统一的购物车操作方法,内部根据模式区分处理。 + +### 4. 租用模式专门处理 (`pages/index/components/renting-cabinet-container.vue`) + +专门为会员模式(租用模式)设计的组件,提供: +- 租用格口的选择界面 +- 不同尺寸格口的展示(使用SVG图标) +- 加入租用购物车功能 +- 租用时长和费用计算 + +## 相关API端点 + +### 获取店铺列表 +``` +GET /shop/list +``` +支持根据mode参数过滤店铺。 + +### 提交订单 +``` +POST /order/submit +``` +请求体需要包含 `mode` 字段,表示订单的运行模式。 + +### 订单数据模型 +在 `SubmitOrderRequestData` 中: +```typescript +export interface SubmitOrderRequestData { + // ... 其他字段 + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + mode: number; + /** 订单商品明细列表 */ + goodsList: Array<{ + // ... 商品信息 + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + mode: number; + }>; +} +``` + +## 组件和页面 + +### 主要页面 +1. **首页 (`pages/index/index.vue`)** + - 模式切换和渲染 + - 根据模式调用不同的数据获取方法 + +2. **结账页面 (`pages/index/checkout.vue`)** + - 模式支付处理 + - 订单提交逻辑 + +### 主要组件 +1. **购物车组件 (`pages/index/components/cart.vue`)** + - 模式购物车管理 + - 统一的操作接口 + +2. **租用柜格容器组件 (`pages/index/components/renting-cabinet-container.vue`)** + - 租用模式专门UI + - 格口选择和租用逻辑 + +3. **商品容器组件 (`pages/index/components/product-container.vue`)** + - 普通商品展示 + - 适用于非租用模式 + +## 模式处理流程图 + +```mermaid +graph TD + A[用户访问首页] --> B{选择商店} + B --> C[获取商店信息] + C --> D{检查商店模式} + D -->|模式=3| E[进入租用模式] + D -->|模式≠3| F[进入普通模式] + + E --> E1[显示租用柜格容器] + E --> E2[使用租用购物车] + E --> E3[租用结算流程] + + F --> F1[显示商品容器] + F --> F2[使用普通购物车] + F --> F3[普通结算流程] + + E3 --> G[提交订单] + F3 --> G + + G --> H{根据模式确定支付方式} + H --> I[完成订单] +``` + +## 开发注意事项 + +1. **模式判断一致性** + - 始终使用 `selectedShop.value?.mode` 获取当前模式 + - 模式判断应集中在业务逻辑入口处 + +2. **支付方式配置** + - 修改支付方式映射时更新 `payment.ts` 文件 + - 确保前端支付方式与后端一致 + +3. **租用模式特殊处理** + - 模式3有专门的组件和流程 + - 租用购物车与普通购物车数据分离 + +4. **扩展新模式** + - 在 `types.ts` 中更新模式注释 + - 更新 `payment.ts` 中的支付方式映射 + - 根据需要添加新的业务逻辑分支 + +## 常见问题 + +**Q: 如何添加新的运行模式?** +A: 1. 在 `types.ts` 中更新模式定义注释;2. 在 `payment.ts` 中添加支付方式映射;3. 在业务逻辑中添加新的处理分支。 + +**Q: 模式如何影响支付方式?** +A: 通过 `modeToPaymentMethodMap` 映射关系确定,前端根据当前模式过滤可用的支付方式。 + +**Q: 租用模式与其他模式的主要区别?** +A: 租用模式使用独立的购物车系统、专门的UI组件和不同的结算逻辑。 + +--- + +*文档最后更新:2025-12-15* +*维护者:Claude Code* \ No newline at end of file diff --git a/src/api/cabinet/index.ts b/src/api/cabinet/index.ts index 4054ede..b85828e 100644 --- a/src/api/cabinet/index.ts +++ b/src/api/cabinet/index.ts @@ -1,10 +1,10 @@ import { http } from "@/http/http"; -import type { CabinetDetailResponse, RentingCabinetDetailDTO } from './types'; +import type { CabinetDetailDTO, RentingCabinetDetailDTO } from './types'; import type { OpenCabinetApiData } from '@/api/shop/types'; /** 获取智能柜详情接口 */ export async function getCabinetDetailApi(shopId: number) { - return await http.get("cabinet/detail", { shopId }); + return await http.get("cabinet/detail", { shopId }); } /** 获取出租中的智能柜详情接口 */ diff --git a/src/api/cabinet/types.ts b/src/api/cabinet/types.ts index 82968e2..9d76e67 100644 --- a/src/api/cabinet/types.ts +++ b/src/api/cabinet/types.ts @@ -48,6 +48,8 @@ export interface CabinetCellEntity { availableStatus: number /** 商品ID */ goodsId?: number + /** 密码(暂存模式使用) */ + password?: string } export interface CellInfoDTO { @@ -63,8 +65,4 @@ export interface ProductInfoDTO { goodsName: string price: number coverImg: string -} - -export type CabinetDetailResponse = { - data: CabinetDetailDTO[] } \ No newline at end of file diff --git a/src/api/shop/types.ts b/src/api/shop/types.ts index 522c6f8..71cec21 100644 --- a/src/api/shop/types.ts +++ b/src/api/shop/types.ts @@ -71,14 +71,14 @@ export interface SubmitOrderRequestData { /** 是否内部订单 0否 1汇邦云用户 2企业微信用户 */ isInternal: number; applyRemark: string; - /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式) */ mode: number; /** 订单商品明细列表 */ goodsList: Array<{ goodsId?: number quantity: number cellId: number - /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式) */ mode: number; }>; /** 是否微信小程序订单 0否 1是 */ @@ -230,7 +230,7 @@ export interface ShopEntity { shopName: string; /** 企业微信id */ corpid: string; - /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */ + /** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式) */ mode?: number; /** 借呗支付(1-正常使用 0-禁止使用) */ balanceEnable?: number; diff --git a/src/pages/index/components/storage-cabinet-container.vue b/src/pages/index/components/storage-cabinet-container.vue new file mode 100644 index 0000000..6846e77 --- /dev/null +++ b/src/pages/index/components/storage-cabinet-container.vue @@ -0,0 +1,500 @@ + + + + + \ No newline at end of file diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 56145d2..5ab58df 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -8,11 +8,13 @@ import { getShopListApi } from '@/api/shop' import type { ShopEntity } from '@/api/shop/types' import ProductContainer from './components/product-container.vue'; import RentingCabinetContainer from './components/renting-cabinet-container.vue'; +import StorageCabinetContainer from './components/storage-cabinet-container.vue'; import { generateDynamicCode, getWxUserByOpenid, mpCodeToOpenId } from '@/api/users' import { toHttpsUrl, uniLogin } from '@/utils' import { useAb98UserStore } from '@/pinia/stores/ab98-user' import { storeToRefs } from 'pinia' import { useWxParamsStore } from '@/pinia/stores/wx-params' +import { useStorageCabinetStore } from '@/pinia/stores/storageCabinet' definePage({ style: { @@ -25,6 +27,7 @@ const wxStore = useWxStore() const productStore = useProductStore() const cartStore = useCartStore() const rentingCabinetStore = useRentingCabinetStore() +const storageCabinetStore = useStorageCabinetStore() const ab98UserStore = useAb98UserStore() // 显示选择商店列表 @@ -32,6 +35,26 @@ const showShopList = ref(true) const shopList = ref([]) const shopId = ref(0) +// tabs状态 +const activeTab = ref(0) +const tabs = [ + { title: '借还柜', value: 0 }, + { title: '暂存柜', value: 1 } +] + +// 根据activeTab过滤店铺列表 +const filteredShopList = computed(() => { + if (!shopList.value.length) return [] + + if (activeTab.value === 0) { + // 借还柜:显示mode不为5的shop + return shopList.value.filter(shop => shop.mode !== 5) + } else { + // 暂存柜:显示mode为5的shop + return shopList.value.filter(shop => shop.mode === 5) + } +}) + // 计算当前选中的店铺模式 const selectedShopMode = computed(() => { if (!shopId.value) return 0 @@ -41,6 +64,8 @@ const selectedShopMode = computed(() => { // 是否为租用模式 const isRentingMode = computed(() => selectedShopMode.value === 3) +// 是否为暂存模式 +const isStorageMode = computed(() => selectedShopMode.value === 5) @@ -58,11 +83,15 @@ function handleShopSelect(selectedShopId: number) { if (selectedShop.mode == 3) { // 租用模式 rentingCabinetStore.fetchRentingCabinetDetail(selectedShopId) + } else if (selectedShop.mode == 5) { + // 暂存模式 + storageCabinetStore.fetchStorageCabinetDetail(selectedShopId) } else { productStore.getGoods(selectedShopId) } cartStore.clearCart() rentingCabinetStore.clearRentingCart() + storageCabinetStore.clearStorageCart() } } @@ -73,6 +102,7 @@ function backToShopList() { productStore.categories = [] cartStore.clearCart() rentingCabinetStore.clearRentingCart() + storageCabinetStore.clearStorageCart() } // 结算方法 @@ -89,6 +119,13 @@ function handleCheckoutRenting() { }) } +// 暂存结算方法 +function handleCheckoutStorage() { + uni.navigateTo({ + url: '/pages/index/checkout' + }) +} + onLoad(async (query) => { const wxParamsStore = useWxParamsStore(); const { wxUserDTO } = storeToRefs(wxStore); @@ -169,27 +206,40 @@ onShow(async () => { - + + + + + + - - - - - - - {{ shop.shopName }} + + @@ -200,12 +250,21 @@ onShow(async () => { @backToShopList="backToShopList" @checkoutRenting="handleCheckoutRenting" /> + + + + @@ -230,6 +289,13 @@ onShow(async () => { height: calc(100vh - 150px); } + .shop-tabs { + margin: 8px; + background: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + } + .shop-prompt { margin: 8px; padding: 12px 16px; @@ -247,12 +313,27 @@ onShow(async () => { .shop-row { overflow-y: auto; overflow-x: hidden; - padding: 0 8px; + padding: 10px 8px 0 8px; display: flex; flex-wrap: wrap; gap: 8px; flex: 1; - height: calc(100vh - 150px - 60px); + height: calc(100vh - 150px - 52px); + + .empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 200px; + + .empty-text { + margin-top: 16px; + font-size: 14px; + color: #999; + } + } } .shop-col { diff --git a/src/pinia/stores/storageCabinet.ts b/src/pinia/stores/storageCabinet.ts new file mode 100644 index 0000000..7f96da4 --- /dev/null +++ b/src/pinia/stores/storageCabinet.ts @@ -0,0 +1,92 @@ +import { defineStore } from 'pinia' +import { computed } from 'vue' +import { ref } from 'vue' +import { getCabinetDetailApi } from '@/api/cabinet' +import type { CabinetCellEntity, CabinetDetailDTO } from '@/api/cabinet/types' + +export const useStorageCabinetStore = defineStore('storageCabinet', () => { + const storageCabinets = ref([]) + + // 暂存购物车列表 + const storageCartItems = ref>([]) + + // 存储从后端接收的密码 + const storagePassword = ref('') + + const fetchStorageCabinetDetail = async (shopId: number) => { + const res = await getCabinetDetailApi(shopId) + console.log(res) + if (res.code === 0 && res.data) { + storageCabinets.value = res.data + } + } + + // 添加到暂存购物车 + const addToStorageCart = (cabinetCell: CabinetCellEntity, quantity: number = 1): boolean => { + if (quantity <= 0) return false + const existingItem = storageCartItems.value.find(item => item.cabinetCell.cellId === cabinetCell.cellId) + if (existingItem) { + existingItem.quantity += quantity + } else { + storageCartItems.value.push({ cabinetCell, quantity }) + } + return true + } + + // 从购物车移除 + const removeFromStorageCart = (cellId: number, quantity: number = 1) => { + const index = storageCartItems.value.findIndex(item => item.cabinetCell.cellId === cellId) + if (index !== -1) { + if (storageCartItems.value[index].quantity <= quantity) { + storageCartItems.value.splice(index, 1) + } else { + storageCartItems.value[index].quantity -= quantity + } + } + } + + // 计算暂存总数 + const storageCartTotalQuantity = computed(() => { + return storageCartItems.value.reduce((total, item) => total + item.quantity, 0) + }) + + // 暂存模式价格为0 + const storageCartTotalPrice = computed(() => { + return 0 + }) + + // 设置密码 + const setStoragePassword = (password: string) => { + storagePassword.value = password + } + + // 清空暂存购物车 + const clearStorageCart = () => { + storageCartItems.value = [] + } + + // 重置store(用于退出暂存模式时) + const resetStore = () => { + storageCabinets.value = [] + storageCartItems.value = [] + storagePassword.value = '' + } + + return { + storageCabinets, + storageCartItems, + storagePassword, + addToStorageCart, + removeFromStorageCart, + storageCartTotalQuantity, + storageCartTotalPrice, + setStoragePassword, + clearStorageCart, + resetStore, + fetchStorageCabinetDetail + } +}) + +export function useStorageCabinetStoreOutside() { + return useStorageCabinetStore() +} \ No newline at end of file diff --git a/src/utils/maps/payment.ts b/src/utils/maps/payment.ts index ff83145..057fc31 100644 --- a/src/utils/maps/payment.ts +++ b/src/utils/maps/payment.ts @@ -11,4 +11,5 @@ export const modeToPaymentMethodMap: Record = { 2: [0, 1], 3: [0], 4: [2], + 5: [], }; \ No newline at end of file