docs: 补充支付设计文档、store 字段设计文档及实施计划
This commit is contained in:
@@ -0,0 +1,642 @@
|
||||
# 支付服务(Payment)设计文档
|
||||
|
||||
> **设计日期**: 2026-06-08
|
||||
> **版本**: v1.0
|
||||
> **状态**: 设计中
|
||||
> **适用前端**: admin-ui(管理后台)、管理 APP(接口一致)
|
||||
> **后端服务**: rui-payment-api(端口 9401,网关路径 `/payment/**`)
|
||||
> **后端状态**: ⏳ 预留,API 细节待补充
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
### 1.1 现状
|
||||
|
||||
收银系统(rui-cashier)已完成基础订单流程(开台、结账、退款),支付环节目前使用占位接口。需要建立独立的支付服务(rui-payment-api),作为聚合支付网关,统一对接多种第三方支付通道。
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
1. **聚合支付网关**:统一下单,按支付方式(微信、支付宝、银行卡等)自动路由到对应通道
|
||||
2. **多通道可配置**:支持同时配置多个第三方支付平台,按门店/场景灵活启用
|
||||
3. **商户进件**:商户在我们平台提交进件申请 → 平台审核 → 向第三方(微信、支付宝)提交进件 → 状态跟踪
|
||||
4. **多端一致**:管理后台(Web)和管理 APP 共享同一套后端接口,前端按各自框架实现
|
||||
|
||||
### 1.3 不在范围内
|
||||
|
||||
- 团购券/优惠券核销
|
||||
- 会员余额/储值卡支付
|
||||
- 对账、清算(后续迭代)
|
||||
- 顾客端支付流程(由 cashier-customer 负责,调用本服务下单接口)
|
||||
|
||||
---
|
||||
|
||||
## 二、整体架构
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────┐
|
||||
│ 前端层 │
|
||||
├─────────────────────┬─────────────────────────────────┤
|
||||
│ 管理后台 (admin-ui)│ 管理 APP (uni-app) │
|
||||
│ Vue3 + Element │ Vue3 语法 │
|
||||
├─────────────────────┴─────────────────────────────────┤
|
||||
│ 共享同一套后端 API │
|
||||
│ /payment/admin/** │
|
||||
└────────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ rui-gateway (:9300) │
|
||||
│ 路由:/payment/** → rui-payment-api │
|
||||
└────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ rui-payment-api (:9401) │
|
||||
├────────────┬──────────────┬──────────────┬─────────────┤
|
||||
│ 商户管理 │ 商户进件 │ 支付通道管理 │ 交易管理 │
|
||||
│ Merchant │ Onboarding │ Channel │ Transaction│
|
||||
├────────────┴──────────────┴──────────────┴─────────────┤
|
||||
│ 统一下单引擎 │
|
||||
│ 按 payType 路由到对应第三方支付通道 │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 微信支付通道 │ 支付宝通道 │ 银行卡通道(预留) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、菜单与权限
|
||||
|
||||
### 3.1 菜单结构
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "payment",
|
||||
"menus": [
|
||||
{
|
||||
"code": "payment",
|
||||
"name": "支付管理",
|
||||
"type": 1,
|
||||
"icon": "tabler:credit-card",
|
||||
"sortNo": 110,
|
||||
"children": [
|
||||
{
|
||||
"code": "payment-merchant",
|
||||
"name": "商户管理",
|
||||
"type": 2,
|
||||
"icon": "tabler:building-bank",
|
||||
"path": "/payment/merchant",
|
||||
"permission": "payment:merchant:list",
|
||||
"sortNo": 1,
|
||||
"buttons": [
|
||||
{ "code": "btn:add", "name": "新增", "permission": "payment:merchant:add" },
|
||||
{ "code": "btn:edit", "name": "编辑", "permission": "payment:merchant:edit" },
|
||||
{ "code": "btn:del", "name": "删除", "permission": "payment:merchant:delete" },
|
||||
{ "code": "btn:detail", "name": "详情", "permission": "payment:merchant:detail" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "payment-onboarding",
|
||||
"name": "商户进件",
|
||||
"type": 2,
|
||||
"icon": "tabler:clipboard-check",
|
||||
"path": "/payment/onboarding",
|
||||
"permission": "payment:onboarding:list",
|
||||
"sortNo": 2,
|
||||
"buttons": [
|
||||
{ "code": "btn:apply", "name": "提交进件", "permission": "payment:onboarding:apply" },
|
||||
{ "code": "btn:audit", "name": "审核", "permission": "payment:onboarding:audit" },
|
||||
{ "code": "btn:submit", "name": "提交第三方", "permission": "payment:onboarding:submit" },
|
||||
{ "code": "btn:detail", "name": "详情", "permission": "payment:onboarding:detail" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "payment-channel",
|
||||
"name": "支付通道",
|
||||
"type": 2,
|
||||
"icon": "tabler:route",
|
||||
"path": "/payment/channel",
|
||||
"permission": "payment:channel:list",
|
||||
"sortNo": 3,
|
||||
"buttons": [
|
||||
{ "code": "btn:add", "name": "新增", "permission": "payment:channel:add" },
|
||||
{ "code": "btn:edit", "name": "编辑", "permission": "payment:channel:edit" },
|
||||
{ "code": "btn:status", "name": "启停", "permission": "payment:channel:status" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "payment-transaction",
|
||||
"name": "交易记录",
|
||||
"type": 2,
|
||||
"icon": "tabler:receipt",
|
||||
"path": "/payment/transaction",
|
||||
"permission": "payment:transaction:list",
|
||||
"sortNo": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 权限标识汇总
|
||||
|
||||
| 模块 | 权限标识 | 说明 |
|
||||
|------|----------|------|
|
||||
| 商户 | `payment:merchant:list` | 查询商户列表 |
|
||||
| 商户 | `payment:merchant:add` | 新增商户 |
|
||||
| 商户 | `payment:merchant:edit` | 编辑商户 |
|
||||
| 商户 | `payment:merchant:delete` | 删除商户 |
|
||||
| 商户 | `payment:merchant:detail` | 查看商户详情 |
|
||||
| 进件 | `payment:onboarding:list` | 查询进件列表 |
|
||||
| 进件 | `payment:onboarding:apply` | 提交进件申请 |
|
||||
| 进件 | `payment:onboarding:audit` | 审核进件 |
|
||||
| 进件 | `payment:onboarding:submit` | 提交至第三方 |
|
||||
| 进件 | `payment:onboarding:detail` | 查看进件详情 |
|
||||
| 通道 | `payment:channel:list` | 查询支付通道 |
|
||||
| 通道 | `payment:channel:add` | 新增通道配置 |
|
||||
| 通道 | `payment:channel:edit` | 编辑通道配置 |
|
||||
| 通道 | `payment:channel:status` | 启用/禁用通道 |
|
||||
| 交易 | `payment:transaction:list` | 查询交易记录 |
|
||||
|
||||
---
|
||||
|
||||
## 四、商户管理
|
||||
|
||||
### 4.1 页面结构
|
||||
|
||||
**列表页** `/payment/merchant`
|
||||
|
||||
| 字段 | 类型 | 宽度 | 说明 |
|
||||
|------|------|------|------|
|
||||
| merchantNo | string | 150 | 商户编号(系统生成) |
|
||||
| merchantName | string | 150 | 商户名称 |
|
||||
| contactName | string | 120 | 联系人 |
|
||||
| contactPhone | string | 130 | 联系电话 |
|
||||
| channelCount | number | 100 | 已开通通道数 |
|
||||
| status | enum | 100 | 状态 |
|
||||
| createTime | datetime | 160 | 创建时间 |
|
||||
|
||||
**查询条件**:商户编号、商户名称、状态
|
||||
|
||||
**操作按钮**:新增、编辑、详情、删除
|
||||
|
||||
**详情页/弹窗**:展示商户基础信息 + 已开通通道列表 + 进件记录
|
||||
|
||||
### 4.2 字段定义
|
||||
|
||||
```typescript
|
||||
interface MerchantDTO {
|
||||
id: number
|
||||
/** 商户编号(系统生成,如 M202606080001) */
|
||||
merchantNo: string
|
||||
/** 商户名称 */
|
||||
merchantName: string
|
||||
/** 商户简称 */
|
||||
merchantShortName: string
|
||||
/** 联系人 */
|
||||
contactName: string
|
||||
/** 联系电话 */
|
||||
contactPhone: string
|
||||
/** 联系邮箱 */
|
||||
contactEmail?: string
|
||||
/** 商户地址 */
|
||||
address?: string
|
||||
/** 备注 */
|
||||
remark?: string
|
||||
/** 状态:0-禁用 1-启用 */
|
||||
status: number
|
||||
/** 已开通通道数(只读) */
|
||||
channelCount?: number
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 状态枚举
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | 禁用 | 商户被禁用,无法交易 |
|
||||
| 1 | 启用 | 正常状态 |
|
||||
|
||||
---
|
||||
|
||||
## 五、商户进件
|
||||
|
||||
### 5.1 业务流程
|
||||
|
||||
```
|
||||
商户提交进件申请
|
||||
│
|
||||
▼
|
||||
平台初审(内部审核)
|
||||
│
|
||||
┌───┴───┐
|
||||
│ │
|
||||
驳回 通过
|
||||
│ │
|
||||
▼ ▼
|
||||
退回 提交第三方
|
||||
修改 │
|
||||
┌───┴───┐
|
||||
│ │
|
||||
进件中 进件失败
|
||||
│ │
|
||||
▼ ▼
|
||||
进件成功 查看原因 → 修改后重新提交
|
||||
```
|
||||
|
||||
### 5.2 页面结构
|
||||
|
||||
**列表页** `/payment/onboarding`
|
||||
|
||||
| 字段 | 类型 | 宽度 | 说明 |
|
||||
|------|------|------|------|
|
||||
| merchantName | string | 150 | 商户名称 |
|
||||
| channelName | string | 120 | 目标通道 |
|
||||
| submitterName | string | 120 | 提交人 |
|
||||
| status | enum | 120 | 进件状态 |
|
||||
| auditStatus | enum | 120 | 内部审核状态 |
|
||||
| thirdPartyStatus | enum | 120 | 第三方状态 |
|
||||
| submitTime | datetime | 160 | 提交时间 |
|
||||
| updateTime | datetime | 160 | 更新时间 |
|
||||
|
||||
**查询条件**:商户名称、目标通道、进件状态、提交时间范围
|
||||
|
||||
**操作按钮**:提交进件、审核、提交第三方、查看详情
|
||||
|
||||
**进件申请弹窗/页面**:
|
||||
|
||||
分步表单,包含以下信息区域:
|
||||
|
||||
#### 5.2.1 基础信息
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| merchantId | select | ✅ | 关联商户(下拉选择) |
|
||||
| channelType | select | ✅ | 目标通道(微信/支付宝/银行卡) |
|
||||
| merchantType | select | ✅ | 商户类型(企业/个体工商户/小微商户) |
|
||||
| merchantName | input | ✅ | 商户名称(营业执照上的名称) |
|
||||
| merchantShortName | input | ✅ | 商户简称(对外展示) |
|
||||
| licenseNo | input | ✅ | 营业执照号 |
|
||||
| licenseExpireDate | date | ✅ | 营业执照有效期 |
|
||||
| licensePhoto | upload | ✅ | 营业执照照片 |
|
||||
|
||||
#### 5.2.2 法人信息
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| legalPersonName | input | ✅ | 法人姓名 |
|
||||
| legalPersonIdNo | input | ✅ | 法人身份证号 |
|
||||
| legalPersonIdFront | upload | ✅ | 身份证正面 |
|
||||
| legalPersonIdBack | upload | ✅ | 身份证反面 |
|
||||
| legalPersonIdExpire | date | ✅ | 身份证有效期 |
|
||||
|
||||
#### 5.2.3 结算信息
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| settleType | select | ✅ | 结算方式(对公/对私) |
|
||||
| bankName | input | ✅ | 开户银行 |
|
||||
| bankBranch | input | ✅ | 开户支行 |
|
||||
| bankAccountNo | input | ✅ | 银行账号 |
|
||||
| bankAccountName | input | ✅ | 户名 |
|
||||
| bankLicensePhoto | upload | 条件 | 开户许可证(对公必填) |
|
||||
|
||||
#### 5.2.4 其他材料
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| storePhotos | upload | ✅ | 门店照片(支持多张) |
|
||||
| extraMaterials | upload | ❌ | 补充材料(支持多张) |
|
||||
| remark | textarea | ❌ | 备注 |
|
||||
|
||||
### 5.3 字段定义
|
||||
|
||||
```typescript
|
||||
interface MerchantOnboardingDTO {
|
||||
id: number
|
||||
/** 关联商户 ID */
|
||||
merchantId: number
|
||||
/** 目标通道类型 */
|
||||
channelType: ChannelType
|
||||
/** 商户类型:1-企业 2-个体工商户 3-小微商户 */
|
||||
merchantType: number
|
||||
/** 商户名称(营业执照) */
|
||||
merchantName: string
|
||||
/** 商户简称 */
|
||||
merchantShortName: string
|
||||
/** 营业执照号 */
|
||||
licenseNo: string
|
||||
/** 营业执照有效期 */
|
||||
licenseExpireDate: string
|
||||
/** 营业执照照片(文件 ID) */
|
||||
licensePhoto: number
|
||||
/** 法人姓名 */
|
||||
legalPersonName: string
|
||||
/** 法人身份证号 */
|
||||
legalPersonIdNo: string
|
||||
/** 身份证正面(文件 ID) */
|
||||
legalPersonIdFront: number
|
||||
/** 身份证反面(文件 ID) */
|
||||
legalPersonIdBack: number
|
||||
/** 身份证有效期 */
|
||||
legalPersonIdExpire: string
|
||||
/** 结算方式:1-对公 2-对私 */
|
||||
settleType: number
|
||||
/** 开户银行 */
|
||||
bankName: string
|
||||
/** 开户支行 */
|
||||
bankBranch: string
|
||||
/** 银行账号 */
|
||||
bankAccountNo: string
|
||||
/** 户名 */
|
||||
bankAccountName: string
|
||||
/** 开户许可证(文件 ID,对公必填) */
|
||||
bankLicensePhoto?: number
|
||||
/** 门店照片(文件 ID 数组) */
|
||||
storePhotos: number[]
|
||||
/** 补充材料(文件 ID 数组) */
|
||||
extraMaterials?: number[]
|
||||
/** 内部审核状态 */
|
||||
auditStatus: AuditStatus
|
||||
/** 第三方进件状态 */
|
||||
thirdPartyStatus: ThirdPartyStatus
|
||||
/** 第三方返回的商户号 */
|
||||
thirdPartyMerchantNo?: string
|
||||
/** 第三方返回原因(失败时) */
|
||||
thirdPartyRejectReason?: string
|
||||
/** 审核人 */
|
||||
auditorName?: string
|
||||
/** 审核时间 */
|
||||
auditTime?: string
|
||||
/** 审核意见 */
|
||||
auditRemark?: string
|
||||
/** 提交人 */
|
||||
submitterName?: string
|
||||
/** 提交时间 */
|
||||
submitTime?: string
|
||||
remark?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 状态枚举
|
||||
|
||||
**内部审核状态** `AuditStatus`
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | 待提交 | 草稿,尚未提交审核 |
|
||||
| 1 | 待审核 | 已提交,等待平台审核 |
|
||||
| 2 | 审核通过 | 平台审核通过 |
|
||||
| 3 | 审核驳回 | 平台审核驳回,可修改后重新提交 |
|
||||
|
||||
**第三方进件状态** `ThirdPartyStatus`
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | 未提交 | 尚未提交至第三方 |
|
||||
| 1 | 审核中 | 第三方审核中 |
|
||||
| 2 | 进件成功 | 第三方审核通过,获得商户号 |
|
||||
| 3 | 进件失败 | 第三方审核驳回 |
|
||||
| 4 | 已冻结 | 第三方冻结商户 |
|
||||
|
||||
### 5.5 交互规则
|
||||
|
||||
1. **提交进件**:填写信息 → 提交内部审核(状态 → 待审核)
|
||||
2. **内部审核**:审核通过 → 状态变为审核通过;驳回 → 填写原因,状态变为审核驳回
|
||||
3. **提交第三方**:审核通过后,可提交至第三方(状态 → 审核中)
|
||||
4. **状态同步**:第三方回调更新状态(成功/失败)
|
||||
5. **重新提交**:审核驳回或第三方失败时,可修改后重新走流程
|
||||
|
||||
---
|
||||
|
||||
## 六、支付通道管理
|
||||
|
||||
### 6.1 页面结构
|
||||
|
||||
**列表页** `/payment/channel`
|
||||
|
||||
| 字段 | 类型 | 宽度 | 说明 |
|
||||
|------|------|------|------|
|
||||
| channelName | string | 150 | 通道名称 |
|
||||
| channelType | enum | 120 | 通道类型 |
|
||||
| channelProvider | string | 150 | 通道提供方 |
|
||||
| merchantCount | number | 100 | 关联商户数 |
|
||||
| status | enum | 100 | 状态 |
|
||||
| createTime | datetime | 160 | 创建时间 |
|
||||
|
||||
**查询条件**:通道名称、通道类型、状态
|
||||
|
||||
**操作按钮**:新增通道、编辑、启用/禁用
|
||||
|
||||
**通道配置弹窗**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| channelName | input | ✅ | 通道名称(如"微信支付-主通道") |
|
||||
| channelType | select | ✅ | 通道类型(见枚举) |
|
||||
| channelProvider | select | ✅ | 通道提供方(微信/支付宝/银联等) |
|
||||
| appId | input | ✅ | 应用 ID(AppId/MchId) |
|
||||
| merchantId | input | ✅ | 商户号 |
|
||||
| apiVersion | input | ❌ | API 版本 |
|
||||
| certFile | upload | 条件 | 证书文件(部分通道需要) |
|
||||
| notifyUrl | input | ✅ | 回调通知地址 |
|
||||
| remark | textarea | ❌ | 备注 |
|
||||
| status | radio | ✅ | 状态(启用/禁用) |
|
||||
|
||||
### 6.2 字段定义
|
||||
|
||||
```typescript
|
||||
interface PayChannelDTO {
|
||||
id: number
|
||||
/** 通道名称 */
|
||||
channelName: string
|
||||
/** 通道类型 */
|
||||
channelType: ChannelType
|
||||
/** 通道提供方 */
|
||||
channelProvider: string
|
||||
/** 应用 ID */
|
||||
appId: string
|
||||
/** 商户号 */
|
||||
merchantId: string
|
||||
/** API 版本 */
|
||||
apiVersion?: string
|
||||
/** 证书文件(文件 ID) */
|
||||
certFile?: number
|
||||
/** 回调通知地址 */
|
||||
notifyUrl: string
|
||||
/** 关联商户数(只读) */
|
||||
merchantCount?: number
|
||||
/** 状态:0-禁用 1-启用 */
|
||||
status: number
|
||||
remark?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 状态枚举
|
||||
|
||||
**通道类型** `ChannelType`
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| WECHAT | 微信支付 | 微信 JSAPI / H5 / Native / APP |
|
||||
| ALIPAY | 支付宝 | 支付宝网页/APP/扫码 |
|
||||
| BANK_CARD | 银行卡 | 银行卡支付(预留) |
|
||||
|
||||
**通道提供方** `ChannelProvider`
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| WECHAT_DIRECT | 微信直连 | 直接对接微信支付 |
|
||||
| ALIPAY_DIRECT | 支付宝直连 | 直接对接支付宝 |
|
||||
| LAKALA | 拉卡拉 | 聚合支付服务商 |
|
||||
| SHOULIANGBA | 收钱吧 | 聚合支付服务商 |
|
||||
| OTHER | 其他 | 其他聚合支付服务商 |
|
||||
|
||||
> 通道提供方后续可扩展,前端建议做成可配置的字典。
|
||||
|
||||
---
|
||||
|
||||
## 七、交易记录
|
||||
|
||||
### 7.1 页面结构
|
||||
|
||||
**列表页** `/payment/transaction`(只读)
|
||||
|
||||
| 字段 | 类型 | 宽度 | 说明 |
|
||||
|------|------|------|------|
|
||||
| transactionNo | string | 180 | 交易流水号 |
|
||||
| merchantName | string | 150 | 商户名称 |
|
||||
| channelName | string | 120 | 支付通道 |
|
||||
| payType | enum | 100 | 支付方式 |
|
||||
| amount | money | 120 | 交易金额 |
|
||||
| fee | money | 100 | 手续费 |
|
||||
| status | enum | 100 | 交易状态 |
|
||||
| payTime | datetime | 160 | 支付时间 |
|
||||
| createTime | datetime | 160 | 创建时间 |
|
||||
|
||||
**查询条件**:交易流水号、商户名称、支付方式、交易状态、时间范围
|
||||
|
||||
**操作**:查看详情
|
||||
|
||||
### 7.2 字段定义
|
||||
|
||||
```typescript
|
||||
interface TransactionDTO {
|
||||
id: number
|
||||
/** 交易流水号 */
|
||||
transactionNo: string
|
||||
/** 关联商户 ID */
|
||||
merchantId: number
|
||||
/** 商户名称(冗余) */
|
||||
merchantName: string
|
||||
/** 支付通道 ID */
|
||||
channelId: number
|
||||
/** 通道名称(冗余) */
|
||||
channelName: string
|
||||
/** 支付方式 */
|
||||
payType: ChannelType
|
||||
/** 交易金额(元) */
|
||||
amount: number
|
||||
/** 手续费(元) */
|
||||
fee: number
|
||||
/** 交易状态 */
|
||||
status: TransactionStatus
|
||||
/** 第三方交易号 */
|
||||
thirdPartyTransactionNo?: string
|
||||
/** 支付时间 */
|
||||
payTime?: string
|
||||
/** 关联的业务订单号 */
|
||||
bizOrderNo?: string
|
||||
/** 退款金额(元) */
|
||||
refundAmount?: number
|
||||
/** 备注 */
|
||||
remark?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 状态枚举
|
||||
|
||||
**交易状态** `TransactionStatus`
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | 待支付 | 已创建,等待支付 |
|
||||
| 1 | 支付中 | 正在处理 |
|
||||
| 2 | 支付成功 | 支付完成 |
|
||||
| 3 | 支付失败 | 支付失败 |
|
||||
| 4 | 已关闭 | 超时关闭或手动关闭 |
|
||||
| 5 | 已退款 | 全额退款 |
|
||||
| 6 | 部分退款 | 部分退款 |
|
||||
|
||||
---
|
||||
|
||||
## 八、统一下单流程(前端视角)
|
||||
|
||||
```
|
||||
顾客端(cashier-customer) 支付服务(rui-payment-api) 第三方
|
||||
│ │ │
|
||||
│ 1. 发起支付 │ │
|
||||
│ (payType, amount, │ │
|
||||
│ merchantId, bizOrderNo) │ │
|
||||
│ ──────────────────────────────► │ │
|
||||
│ │ 2. 按 payType 路由通道 │
|
||||
│ │ 3. 创建交易记录 │
|
||||
│ │ ──────────────────────────► │
|
||||
│ │ 4. 调用第三方下单 │
|
||||
│ 5. 返回支付参数 │ │
|
||||
│ (二维码/跳转URL/支付SDK参数) │ │
|
||||
│ ◄────────────────────────────── │ │
|
||||
│ │ │
|
||||
│ 6. 顾客完成支付 │ │
|
||||
│ │ ◄────────────────────────── │
|
||||
│ │ 7. 第三方回调通知 │
|
||||
│ │ 8. 更新交易状态 │
|
||||
│ 9. 查询支付结果 │ │
|
||||
│ ──────────────────────────────► │ │
|
||||
│ 10. 返回支付结果 │ │
|
||||
│ ◄────────────────────────────── │ │
|
||||
```
|
||||
|
||||
> 统一下单和支付回调的具体 API 定义待后端整理后补充。
|
||||
|
||||
---
|
||||
|
||||
## 九、前端页面清单
|
||||
|
||||
| 页面 | 路由 | 说明 |
|
||||
|------|------|------|
|
||||
| 商户列表 | `/payment/merchant` | 商户 CRUD |
|
||||
| 商户详情 | `/payment/merchant/:id` | 商户信息 + 通道 + 进件记录 |
|
||||
| 进件列表 | `/payment/onboarding` | 进件记录查询 |
|
||||
| 进件申请 | `/payment/onboarding/apply` | 分步表单(新开页面) |
|
||||
| 进件详情 | `/payment/onboarding/:id` | 进件详情 + 状态跟踪 |
|
||||
| 支付通道列表 | `/payment/channel` | 通道配置管理 |
|
||||
| 通道配置 | `/payment/channel/:id?` | 新增/编辑通道 |
|
||||
| 交易记录 | `/payment/transaction` | 交易查询(只读) |
|
||||
| 交易详情 | `/payment/transaction/:id` | 交易详情 |
|
||||
|
||||
---
|
||||
|
||||
## 十、待补充项
|
||||
|
||||
以下内容由后端整理后补充,前端文档标记为占位:
|
||||
|
||||
1. **API 接口定义**:各模块的请求路径、请求参数、响应结构
|
||||
2. **统一下单接口**:下单、查询、回调的完整 API 定义
|
||||
3. **通道配置参数**:不同通道的特定配置字段(如微信的 v3 密钥、支付宝的私钥等)
|
||||
4. **对账相关**:对账单查询、差异处理(后续迭代)
|
||||
5. **文件上传**:进件材料的 bizType 定义(参考 `API设计规范.md` 第 13 节存储服务)
|
||||
|
||||
---
|
||||
|
||||
> **下一步**: 后端整理 API 细节后,更新本文档的待补充项,然后前端可开始实现页面。
|
||||
@@ -0,0 +1,644 @@
|
||||
# 门店管理新增字段 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-subagent-driven-development (recommended) or superpowers-executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 在 admin-ui 门店管理模块中适配后端新增的 9 个字段,修改表单弹窗和列表页,所有变更在现有 `useApiForm` + `ApiFormDialog` + `RuiTable` 框架内完成。
|
||||
|
||||
**Architecture:** 修改 2 个现有文件。`StoreFormDialog.vue` 新增 7 个可编辑字段(useApiForm fields 数组)、2 个只读字段(custom-fields 插槽)、数据双向转换逻辑(amenities JSON 序列化、serviceFeeRate 百分比转换)。`Index.vue` 新增 3 列(门店类型 Tag、包间信息、设施标签)、1 个筛选条件(门店类型下拉)、`parseAmenities` 工具函数。
|
||||
|
||||
**Tech Stack:** Vue 3 (Composition API / `<script setup>`) + TypeScript + Element Plus + Vite + pnpm
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| # | 文件 | 操作 | 变更说明 |
|
||||
|---|------|------|---------|
|
||||
| 1 | `admin-ui/src/views/cashier/store/StoreFormDialog.vue` | 修改 | 扩展 `useApiForm` 的 `initial` 和 `fields`(+7 字段);修改 `onSubmit` 添加 amenities/serviceFeeRate 数据转换;修改 `watch` 添加编辑回填数据转换;模板添加 `width="720px"` 和 `#custom-fields` 插槽 |
|
||||
| 2 | `admin-ui/src/views/cashier/store/Index.vue` | 修改 | `queryParams` 增加 `storeType`;`handleReset` 补充 `storeType` 重置;新增 `parseAmenities` 工具函数;`columns` 增加 3 列;模板增加门店类型筛选和 3 个列 slot |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: StoreFormDialog — 扩展 initial 默认值
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/StoreFormDialog.vue` (lines 32–40)
|
||||
|
||||
- [ ] **Step 1: 在 `useApiForm` 的 `initial` 对象中追加新字段默认值**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
initial: {
|
||||
storeName: '',
|
||||
storeCode: '',
|
||||
address: '',
|
||||
contactPhone: '',
|
||||
contactName: '',
|
||||
businessHours: '',
|
||||
status: 1,
|
||||
},
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
initial: {
|
||||
storeName: '',
|
||||
storeCode: '',
|
||||
address: '',
|
||||
contactPhone: '',
|
||||
contactName: '',
|
||||
businessHours: '',
|
||||
status: 1,
|
||||
storeType: 'STANDARD',
|
||||
amenities: [],
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
serviceFeeRate: undefined,
|
||||
openingDate: '',
|
||||
legalPerson: '',
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/StoreFormDialog.vue
|
||||
git commit -m "feat(store): 扩展 useApiForm initial 默认值,新增 7 个字段默认值"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: StoreFormDialog — 在 fields 数组中追加 7 个新字段
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/StoreFormDialog.vue` (lines 76–86, status 字段之后、fields 数组结束之前)
|
||||
|
||||
- [ ] **Step 1: 在 status 字段配置之后、`],` 之前,插入 7 个新字段配置**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
{
|
||||
key: 'status',
|
||||
label: '状态',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 },
|
||||
],
|
||||
},
|
||||
],
|
||||
onSubmit: async (data) => {
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
{
|
||||
key: 'status',
|
||||
label: '状态',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'storeType',
|
||||
label: '门店类型',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '旗舰店', value: 'FLAGSHIP' },
|
||||
{ label: '标准店', value: 'STANDARD' },
|
||||
{ label: '社区店', value: 'COMMUNITY' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'amenities',
|
||||
label: '设施标签',
|
||||
type: 'checkbox',
|
||||
options: [
|
||||
{ label: '免费停车', value: '免费停车' },
|
||||
{ label: '免费WiFi', value: '免费WiFi' },
|
||||
{ label: '充电桩', value: '充电桩' },
|
||||
{ label: '24小时营业', value: '24小时营业' },
|
||||
{ label: '包厢', value: '包厢' },
|
||||
{ label: '吸烟区', value: '吸烟区' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'longitude',
|
||||
label: '经度',
|
||||
type: 'number',
|
||||
props: { min: -180, max: 180, precision: 6, step: 0.000001 },
|
||||
},
|
||||
{
|
||||
key: 'latitude',
|
||||
label: '纬度',
|
||||
type: 'number',
|
||||
props: { min: -90, max: 90, precision: 6, step: 0.000001 },
|
||||
},
|
||||
{
|
||||
key: 'serviceFeeRate',
|
||||
label: '平台服务费率(%)',
|
||||
type: 'number',
|
||||
props: { min: 0, max: 100, precision: 2, step: 0.01 },
|
||||
},
|
||||
{
|
||||
key: 'openingDate',
|
||||
label: '开业日期',
|
||||
type: 'date',
|
||||
props: { valueFormat: 'YYYY-MM-DD' },
|
||||
},
|
||||
{
|
||||
key: 'legalPerson',
|
||||
label: '法人姓名',
|
||||
type: 'input',
|
||||
},
|
||||
],
|
||||
onSubmit: async (data) => {
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/StoreFormDialog.vue
|
||||
git commit -m "feat(store): 在 useApiForm fields 中新增 7 个可编辑字段配置"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: StoreFormDialog — 修改 onSubmit 添加数据转换
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/StoreFormDialog.vue` (lines 86–100, onSubmit 回调)
|
||||
|
||||
- [ ] **Step 1: 替换 onSubmit 回调,添加 amenities JSON 序列化和 serviceFeeRate 百分比转小数**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
onSubmit: async (data) => {
|
||||
const isEdit = !!(data as any).id
|
||||
|
||||
if (isEdit) {
|
||||
await storeService.update(data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
else {
|
||||
await storeService.add(data)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
|
||||
emit('success')
|
||||
emit('update:visible', false)
|
||||
},
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
onSubmit: async (rawData) => {
|
||||
const data = { ...rawData } as any
|
||||
|
||||
// amenities: string[] → JSON 字符串
|
||||
if (Array.isArray(data.amenities)) {
|
||||
data.amenities = JSON.stringify(data.amenities)
|
||||
}
|
||||
|
||||
// serviceFeeRate: 百分比 → 小数
|
||||
if (data.serviceFeeRate != null && data.serviceFeeRate !== '') {
|
||||
data.serviceFeeRate = Number(data.serviceFeeRate) / 100
|
||||
}
|
||||
|
||||
const isEdit = !!data.id
|
||||
|
||||
if (isEdit) {
|
||||
await storeService.update(data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
else {
|
||||
await storeService.add(data)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
|
||||
emit('success')
|
||||
emit('update:visible', false)
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/StoreFormDialog.vue
|
||||
git commit -m "feat(store): onSubmit 中添加 amenities JSON 序列化和 serviceFeeRate 百分比转小数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: StoreFormDialog — 修改 watch 添加编辑回填数据转换
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/StoreFormDialog.vue` (lines 104–116, watch 回调)
|
||||
|
||||
- [ ] **Step 1: 替换 watch 回调,添加 amenities JSON 解析和 serviceFeeRate 小数转百分比**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
// 监听编辑数据
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
if (props.row) {
|
||||
// 编辑时设置表单数据
|
||||
form.value = {
|
||||
...props.row,
|
||||
}
|
||||
}
|
||||
else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
// 监听编辑数据
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
if (props.row) {
|
||||
const rowData = { ...props.row }
|
||||
|
||||
// amenities: JSON 字符串 → string[]
|
||||
if (typeof rowData.amenities === 'string' && rowData.amenities) {
|
||||
try {
|
||||
rowData.amenities = JSON.parse(rowData.amenities)
|
||||
}
|
||||
catch {
|
||||
rowData.amenities = []
|
||||
}
|
||||
}
|
||||
else if (!Array.isArray(rowData.amenities)) {
|
||||
rowData.amenities = []
|
||||
}
|
||||
|
||||
// serviceFeeRate: 小数 → 百分比
|
||||
if (rowData.serviceFeeRate != null) {
|
||||
rowData.serviceFeeRate = Number(rowData.serviceFeeRate) * 100
|
||||
}
|
||||
|
||||
// 编辑时设置表单数据
|
||||
form.value = rowData
|
||||
}
|
||||
else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/StoreFormDialog.vue
|
||||
git commit -m "feat(store): watch 中添加 amenities JSON 解析和 serviceFeeRate 小数转百分比回填"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: StoreFormDialog — 模板添加 width 属性和 custom-fields 插槽
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/StoreFormDialog.vue` (lines 120–128, template 中的 ApiFormDialog 标签)
|
||||
|
||||
- [ ] **Step 1: 替换 ApiFormDialog 自闭合标签为开闭标签,添加 width 和 custom-fields 插槽**
|
||||
|
||||
**Old string:**
|
||||
```vue
|
||||
<ApiFormDialog
|
||||
v-model:visible="localVisible"
|
||||
v-model:form="form"
|
||||
:title="(form as any).id ? '编辑门店' : '新增门店'"
|
||||
:fields="fields"
|
||||
:rules="rules"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```vue
|
||||
<ApiFormDialog
|
||||
v-model:visible="localVisible"
|
||||
v-model:form="form"
|
||||
:title="(form as any).id ? '编辑门店' : '新增门店'"
|
||||
:width="'720px'"
|
||||
:fields="fields"
|
||||
:rules="rules"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<template #custom-fields="{ form: formData }">
|
||||
<template v-if="formData.id">
|
||||
<el-form-item label="包间总数">
|
||||
<span>{{ formData.roomCount ?? '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="空闲包间数">
|
||||
<span>{{ formData.freeRoomCount ?? '-' }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</ApiFormDialog>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/StoreFormDialog.vue
|
||||
git commit -m "feat(store): ApiFormDialog 增加 width=720px 和 custom-fields 插槽展示只读包间字段"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Index.vue — 扩展 queryParams、handleReset、新增 parseAmenities 工具函数
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/Index.vue`
|
||||
|
||||
- [ ] **Step 1: 在 queryParams 中增加 storeType 字段**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
const queryParams = ref({
|
||||
storeName: '',
|
||||
status: undefined as number | undefined,
|
||||
})
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
const queryParams = ref({
|
||||
storeName: '',
|
||||
status: undefined as number | undefined,
|
||||
storeType: undefined as string | undefined,
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 handleReset 中补充 storeType 重置**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
function handleReset() {
|
||||
queryParams.value = {
|
||||
storeName: '',
|
||||
status: undefined,
|
||||
}
|
||||
tableRef.value?.reset()
|
||||
}
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
function handleReset() {
|
||||
queryParams.value = {
|
||||
storeName: '',
|
||||
status: undefined,
|
||||
storeType: undefined,
|
||||
}
|
||||
tableRef.value?.reset()
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在 `handleStatusChange` 函数之后(`</script>` 标签之前),添加 `parseAmenities` 工具函数**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
async function handleStatusChange(row: any, status: number) {
|
||||
if (!row?.id) return
|
||||
try {
|
||||
await storeService.changeStatus(row.id, status)
|
||||
ElMessage.success(status === 1 ? '启用成功' : '禁用成功')
|
||||
} catch {
|
||||
row.status = status === 1 ? 0 : 1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
async function handleStatusChange(row: any, status: number) {
|
||||
if (!row?.id) return
|
||||
try {
|
||||
await storeService.changeStatus(row.id, status)
|
||||
ElMessage.success(status === 1 ? '启用成功' : '禁用成功')
|
||||
} catch {
|
||||
row.status = status === 1 ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析设施标签(兼容 JSON 字符串和数组)
|
||||
*/
|
||||
function parseAmenities(val: any): string[] {
|
||||
if (Array.isArray(val)) return val
|
||||
if (typeof val === 'string' && val) {
|
||||
try { return JSON.parse(val) } catch { return [] }
|
||||
}
|
||||
return []
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/Index.vue
|
||||
git commit -m "feat(store): queryParams 增加 storeType 筛选、handleReset 补充重置、新增 parseAmenities 工具函数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Index.vue — 在 columns 数组中追加 3 列配置
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/Index.vue` (lines 12–28, columns 数组)
|
||||
|
||||
- [ ] **Step 1: 在 address 列之后追加 storeType、roomInfo、amenities 3 列**
|
||||
|
||||
**Old string:**
|
||||
```ts
|
||||
{ prop: 'address', label: '地址', minWidth: 200, tooltip: true },
|
||||
{ prop: 'businessHours', label: '营业时间', width: 120 },
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```ts
|
||||
{ prop: 'address', label: '地址', minWidth: 200, tooltip: true },
|
||||
{ prop: 'storeType', label: '门店类型', width: 100, align: 'center', slot: true },
|
||||
{ prop: 'roomInfo', label: '包间', width: 120, align: 'center', slot: true },
|
||||
{ prop: 'amenities', label: '设施标签', minWidth: 200, slot: true },
|
||||
{ prop: 'businessHours', label: '营业时间', width: 120 },
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/Index.vue
|
||||
git commit -m "feat(store): columns 增加 storeType/roomInfo/amenities 3 列配置"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Index.vue — 模板添加门店类型筛选和 3 个列 slot
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/cashier/store/Index.vue` (template 部分)
|
||||
|
||||
- [ ] **Step 1: 在状态筛选的 `</el-form-item>` 之后、查询按钮的 `<el-form-item>` 之前,插入门店类型筛选**
|
||||
|
||||
**Old string:**
|
||||
```vue
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```vue
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="门店类型">
|
||||
<el-select v-model="queryParams.storeType" placeholder="请选择门店类型" clearable>
|
||||
<el-option label="旗舰店" value="FLAGSHIP" />
|
||||
<el-option label="标准店" value="STANDARD" />
|
||||
<el-option label="社区店" value="COMMUNITY" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在状态列 slot(`#column-status`)的 `</template>` 之后、操作列 slot 之前,插入 3 个列 slot 模板**
|
||||
|
||||
**Old string:**
|
||||
```vue
|
||||
<!-- 状态列 -->
|
||||
<template #column-status="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="(val: any) => handleStatusChange(row, val as number)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
```
|
||||
|
||||
**New string:**
|
||||
```vue
|
||||
<!-- 状态列 -->
|
||||
<template #column-status="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="(val: any) => handleStatusChange(row, val as number)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 门店类型列 -->
|
||||
<template #column-storeType="{ row }">
|
||||
<el-tag
|
||||
:type="row.storeType === 'FLAGSHIP' ? 'danger' : row.storeType === 'STANDARD' ? '' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ { FLAGSHIP: '旗舰店', STANDARD: '标准店', COMMUNITY: '社区店' }[row.storeType] || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<!-- 包间信息列 -->
|
||||
<template #column-roomInfo="{ row }">
|
||||
<span>{{ row.freeRoomCount ?? '-' }}/{{ row.roomCount ?? '-' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 设施标签列 -->
|
||||
<template #column-amenities="{ row }">
|
||||
<template v-if="parseAmenities(row.amenities).length">
|
||||
<el-tag
|
||||
v-for="tag in parseAmenities(row.amenities)"
|
||||
:key="tag"
|
||||
size="small"
|
||||
type="info"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/cashier/store/Index.vue
|
||||
git commit -m "feat(store): 模板新增门店类型筛选条件和 storeType/roomInfo/amenities 列 slot"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: 构建验证 + 最终提交
|
||||
|
||||
- [ ] **Step 1: 运行 TypeScript 类型检查**
|
||||
|
||||
Run: `pnpm --filter admin-ui type-check`
|
||||
Expected: 0 errors, 命令退出码 0
|
||||
|
||||
- [ ] **Step 2: 运行 ESLint 检查**
|
||||
|
||||
Run: `pnpm --filter admin-ui lint`
|
||||
Expected: 0 errors, 0 warnings(或仅有与本次修改无关的已存在 warnings)
|
||||
|
||||
- [ ] **Step 3: 运行 Vite 构建**
|
||||
|
||||
Run: `pnpm --filter admin-ui build`
|
||||
Expected: 构建成功,无编译错误,输出 dist 目录
|
||||
|
||||
- [ ] **Step 4: 启动开发服务器进行手动验证**
|
||||
|
||||
Run: `pnpm --filter admin-ui dev`
|
||||
Expected: 开发服务器正常启动,浏览器打开后:
|
||||
1. 门店管理列表页正确展示新增 3 列
|
||||
2. 门店类型下拉筛选功能正常
|
||||
3. 点击新增门店,弹窗宽度 720px,7 个新字段正确渲染
|
||||
4. 新增模式下不显示包间总数/空闲包间数
|
||||
5. 点击编辑门店,所有新字段正确回填,包间字段只读展示
|
||||
6. 提交表单无报错
|
||||
|
||||
验证完毕后按 `Ctrl+C` 停止开发服务器。
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] `pnpm --filter admin-ui type-check` 通过
|
||||
- [ ] `pnpm --filter admin-ui lint` 通过
|
||||
- [ ] `pnpm --filter admin-ui build` 成功
|
||||
- [ ] 列表新增 3 列正确展示(门店类型 Tag、包间 X/Y、设施多 Tag)
|
||||
- [ ] 门店类型筛选功能正常(筛选 + 重置)
|
||||
- [ ] 新增门店:7 个新字段可正常填写和提交
|
||||
- [ ] 编辑门店:所有新字段正确回填,只读字段不可编辑
|
||||
- [ ] amenities 数据双向转换正确(JSON 字符串 ↔ 数组)
|
||||
- [ ] serviceFeeRate 数据双向转换正确(小数 ↔ 百分比)
|
||||
- [ ] 新增模式下 roomCount/freeRoomCount 区域不显示
|
||||
- [ ] 无数据时各列正确降级显示 `-`
|
||||
@@ -0,0 +1,393 @@
|
||||
# 门店管理新增字段设计规范
|
||||
|
||||
**工单**: rui/rui-frontend#6 — 门店管理适配后端新增字段
|
||||
**日期**: 2026-06-08
|
||||
**关联 Issue**: rui/rui-cashier#6(后端门店表新增字段)
|
||||
**优先级**: P2
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标(Goal)
|
||||
|
||||
在 admin-ui 门店管理模块的列表页和表单弹窗中,适配后端门店表新增的 9 个字段(storeType、amenities、longitude、latitude、roomCount、freeRoomCount、serviceFeeRate、openingDate、legalPerson)。表单弹窗新增 7 个可编辑字段和 2 个只读展示字段,列表页新增 3 列(门店类型标签、包间信息、设施标签)和 1 个筛选条件(门店类型下拉)。所有变更在现有 `useApiForm` + `ApiFormDialog` + `RuiTable` 框架内完成,不引入新依赖。
|
||||
|
||||
## 2. 非目标(Non-Goals)
|
||||
|
||||
明确不在本期范围内的事项:
|
||||
|
||||
- **不修改后端代码或 API 接口定义**:后端已提供字段,前端仅适配展示和交互。
|
||||
- **不做地图组件集成**:经纬度字段使用纯数字输入框,不嵌入高德/百度地图选点组件。
|
||||
- **不修改 storeService.ts**:现有 BaseService 的 CRUD 方法已覆盖需求,无需扩展。
|
||||
- **不新增独立详情页**:沿用当前「列表 + 表单弹窗」模式,只读字段在编辑弹窗中展示。
|
||||
- **不做包间数量编辑**:roomCount 和 freeRoomCount 由后端计算,前端仅只读展示。
|
||||
- **不引入新 npm 依赖**:所有 UI 组件使用现有 Element Plus + useApiForm 字段类型体系。
|
||||
|
||||
## 3. 背景与上下文(Context)
|
||||
|
||||
### 3.1 现有门店模块结构
|
||||
|
||||
- **列表页** `admin-ui/src/views/cashier/store/Index.vue`:使用 `RuiTable` 组件,现有 7 列(门店名称、门店编码、联系人、联系电话、地址、营业时间、状态),2 个筛选条件(门店名称、状态),支持增删改查和状态切换。
|
||||
- **表单弹窗** `admin-ui/src/views/cashier/store/StoreFormDialog.vue`:使用 `useApiForm` composable + `ApiFormDialog` 组件,现有 7 个字段(storeName、storeCode、address、contactName、contactPhone、businessHours、status),通过 `v-model:visible` / `row` prop 控制显示和数据回填。
|
||||
- **服务层** `admin-ui/src/service/cashier/storeService.ts`:继承 `BaseService('/cashier/admin/store')`,13 行代码,自动拥有 page/add/update/remove/changeStatus 能力。
|
||||
|
||||
### 3.2 useApiForm 字段类型支持
|
||||
|
||||
`useApiForm` 支持 9 种字段类型:`input`、`textarea`、`select`、`radio`、`checkbox`、`number`、`tree-select`、`date`、`datetime`。每个字段支持 `disabled` 属性(布尔值或返回布尔值的函数),可用于按编辑/新增模式动态禁用。
|
||||
|
||||
`ApiFormDialog` 在所有标准字段之后渲染 `<slot name="custom-fields" :form="formData" />`,用于放置无法用标准字段类型表达的 UI 内容。
|
||||
|
||||
### 3.3 后端新增字段一览
|
||||
|
||||
| 字段 | camelCase 键 | Java 类型 | 后端存储格式 |
|
||||
|------|-------------|----------|-------------|
|
||||
| 门店类型 | storeType | String | 纯字符串:`FLAGSHIP` / `STANDARD` / `COMMUNITY` |
|
||||
| 设施标签 | amenities | String | JSON 数组字符串:`"[\"免费停车\",\"免费WiFi\"]"` |
|
||||
| 经度 | longitude | BigDecimal | 小数 |
|
||||
| 纬度 | latitude | BigDecimal | 小数 |
|
||||
| 包间总数 | roomCount | Integer | 整数(后端计算) |
|
||||
| 空闲包间数 | freeRoomCount | Integer | 整数(后端计算) |
|
||||
| 平台服务费率 | serviceFeeRate | BigDecimal | 小数,如 `0.05` 表示 5% |
|
||||
| 开业日期 | openingDate | LocalDate | `yyyy-MM-dd` 字符串 |
|
||||
| 法人姓名 | legalPerson | String | 纯文本 |
|
||||
|
||||
## 4. 关键设计决策
|
||||
|
||||
| # | 决策项 | 选定方案 | 理由 |
|
||||
|---|--------|---------|------|
|
||||
| 1 | 整体方案 | 在现有 useApiForm + ApiFormDialog 框架内扩展 | 复用现有基础设施,保持与其他模块一致的开发模式 |
|
||||
| 2 | amenities 表现层 | 使用 `checkbox` 字段类型,options 固定 6 项 | useApiForm 原生支持 checkbox,无需自定义渲染 |
|
||||
| 3 | amenities 数据转换 | 提交时 `JSON.stringify`,编辑回填时 `JSON.parse` | 后端存 JSON 字符串,前端表单使用 `string[]` |
|
||||
| 4 | serviceFeeRate 展示 | 前端用百分比数值(5 表示 5%),提交时除以 100,编辑回填时乘以 100 | 用户体验直观,避免手动输入 0.05 这样的小数 |
|
||||
| 5 | roomCount / freeRoomCount | 使用 `#custom-fields` 插槽渲染只读文本 | 这两个字段仅编辑模式下只读展示,不需要 useApiForm 字段配置 |
|
||||
| 6 | 门店类型枚举 | 前端硬编码 3 个选项 | 后端接口稳定,选项固定,无需动态加载 |
|
||||
| 7 | 经纬度输入 | `number` 类型字段,精度限制 6 位小数 | 经纬度通常保留 6 位即可满足定位需求 |
|
||||
|
||||
## 5. 字段配置详情
|
||||
|
||||
### 5.1 useApiForm 字段新增(7 个可编辑字段)
|
||||
|
||||
以下字段追加到 `StoreFormDialog.vue` 中 `useApiForm` 的 `fields` 数组:
|
||||
|
||||
```ts
|
||||
{
|
||||
key: 'storeType',
|
||||
label: '门店类型',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '旗舰店', value: 'FLAGSHIP' },
|
||||
{ label: '标准店', value: 'STANDARD' },
|
||||
{ label: '社区店', value: 'COMMUNITY' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'amenities',
|
||||
label: '设施标签',
|
||||
type: 'checkbox',
|
||||
options: [
|
||||
{ label: '免费停车', value: '免费停车' },
|
||||
{ label: '免费WiFi', value: '免费WiFi' },
|
||||
{ label: '充电桩', value: '充电桩' },
|
||||
{ label: '24小时营业', value: '24小时营业' },
|
||||
{ label: '包厢', value: '包厢' },
|
||||
{ label: '吸烟区', value: '吸烟区' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'longitude',
|
||||
label: '经度',
|
||||
type: 'number',
|
||||
props: { min: -180, max: 180, precision: 6, step: 0.000001 },
|
||||
},
|
||||
{
|
||||
key: 'latitude',
|
||||
label: '纬度',
|
||||
type: 'number',
|
||||
props: { min: -90, max: 90, precision: 6, step: 0.000001 },
|
||||
},
|
||||
{
|
||||
key: 'serviceFeeRate',
|
||||
label: '平台服务费率(%)',
|
||||
type: 'number',
|
||||
props: { min: 0, max: 100, precision: 2, step: 0.01 },
|
||||
},
|
||||
{
|
||||
key: 'openingDate',
|
||||
label: '开业日期',
|
||||
type: 'date',
|
||||
props: { valueFormat: 'YYYY-MM-DD' },
|
||||
},
|
||||
{
|
||||
key: 'legalPerson',
|
||||
label: '法人姓名',
|
||||
type: 'input',
|
||||
},
|
||||
```
|
||||
|
||||
### 5.2 弹窗宽度调整
|
||||
|
||||
`StoreFormDialog` 中 `ApiFormDialog` 追加 `width` prop 为 `720px`(原默认 600px),因为新增字段较多,需要更宽的弹窗空间。
|
||||
|
||||
### 5.3 只读展示字段(2 个,通过 custom-fields 插槽)
|
||||
|
||||
`roomCount` 和 `freeRoomCount` 不加入 `fields` 数组,而是在 `ApiFormDialog` 的 `#custom-fields` 插槽中以 `el-form-item` + 纯文本方式渲染。仅在编辑模式(`form.id` 存在)时显示:
|
||||
|
||||
```vue
|
||||
<template #custom-fields="{ form }">
|
||||
<template v-if="form.id">
|
||||
<el-form-item label="包间总数">
|
||||
<span>{{ form.roomCount ?? '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="空闲包间数">
|
||||
<span>{{ form.freeRoomCount ?? '-' }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 5.4 initial 默认值扩展
|
||||
|
||||
在 `useApiForm` 的 `initial` 对象中追加新字段默认值:
|
||||
|
||||
```ts
|
||||
initial: {
|
||||
// ...现有字段
|
||||
storeType: 'STANDARD',
|
||||
amenities: [],
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
serviceFeeRate: undefined,
|
||||
openingDate: '',
|
||||
legalPerson: '',
|
||||
},
|
||||
```
|
||||
|
||||
### 5.5 数据转换逻辑
|
||||
|
||||
#### 提交时(`onSubmit` 回调内)
|
||||
|
||||
```ts
|
||||
onSubmit: async (rawData) => {
|
||||
const data = { ...rawData }
|
||||
|
||||
// amenities: string[] → JSON 字符串
|
||||
if (Array.isArray(data.amenities)) {
|
||||
data.amenities = JSON.stringify(data.amenities)
|
||||
}
|
||||
|
||||
// serviceFeeRate: 百分比 → 小数
|
||||
if (data.serviceFeeRate != null && data.serviceFeeRate !== '') {
|
||||
data.serviceFeeRate = Number(data.serviceFeeRate) / 100
|
||||
}
|
||||
|
||||
// ...现有 add/update 逻辑
|
||||
},
|
||||
```
|
||||
|
||||
#### 编辑回填时(`watch(visible)` 内)
|
||||
|
||||
```ts
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val && props.row) {
|
||||
const rowData = { ...props.row }
|
||||
|
||||
// amenities: JSON 字符串 → string[]
|
||||
if (typeof rowData.amenities === 'string' && rowData.amenities) {
|
||||
try {
|
||||
rowData.amenities = JSON.parse(rowData.amenities)
|
||||
} catch {
|
||||
rowData.amenities = []
|
||||
}
|
||||
} else if (!Array.isArray(rowData.amenities)) {
|
||||
rowData.amenities = []
|
||||
}
|
||||
|
||||
// serviceFeeRate: 小数 → 百分比
|
||||
if (rowData.serviceFeeRate != null) {
|
||||
rowData.serviceFeeRate = Number(rowData.serviceFeeRate) * 100
|
||||
}
|
||||
|
||||
form.value = rowData
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 6. 列表页变更(Index.vue)
|
||||
|
||||
### 6.1 新增列配置
|
||||
|
||||
在 `columns` 数组中,`address` 列之后追加以下 3 列:
|
||||
|
||||
```ts
|
||||
{
|
||||
prop: 'storeType',
|
||||
label: '门店类型',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
prop: 'roomInfo',
|
||||
label: '包间',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
prop: 'amenities',
|
||||
label: '设施标签',
|
||||
minWidth: 200,
|
||||
slot: true,
|
||||
},
|
||||
```
|
||||
|
||||
### 6.2 新增列 Slot 模板
|
||||
|
||||
#### 门店类型列(彩色 Tag)
|
||||
|
||||
```vue
|
||||
<template #column-storeType="{ row }">
|
||||
<el-tag
|
||||
:type="row.storeType === 'FLAGSHIP' ? 'danger' : row.storeType === 'STANDARD' ? '' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ { FLAGSHIP: '旗舰店', STANDARD: '标准店', COMMUNITY: '社区店' }[row.storeType] || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 包间信息列("空闲X/总数Y" 格式)
|
||||
|
||||
```vue
|
||||
<template #column-roomInfo="{ row }">
|
||||
<span>{{ row.freeRoomCount ?? '-' }}/{{ row.roomCount ?? '-' }}</span>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 设施标签列(多个小 Tag)
|
||||
|
||||
```vue
|
||||
<template #column-amenities="{ row }">
|
||||
<template v-if="parseAmenities(row.amenities).length">
|
||||
<el-tag
|
||||
v-for="tag in parseAmenities(row.amenities)"
|
||||
:key="tag"
|
||||
size="small"
|
||||
type="info"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
```
|
||||
|
||||
其中 `parseAmenities` 为组件内的工具函数:
|
||||
|
||||
```ts
|
||||
function parseAmenities(val: any): string[] {
|
||||
if (Array.isArray(val)) return val
|
||||
if (typeof val === 'string' && val) {
|
||||
try { return JSON.parse(val) } catch { return [] }
|
||||
}
|
||||
return []
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 新增筛选条件
|
||||
|
||||
在 `queryParams` 中增加 `storeType` 字段,在查询区增加门店类型下拉:
|
||||
|
||||
```ts
|
||||
const queryParams = ref({
|
||||
storeName: '',
|
||||
status: undefined as number | undefined,
|
||||
storeType: undefined as string | undefined,
|
||||
})
|
||||
```
|
||||
|
||||
查询区模板中,状态筛选后追加:
|
||||
|
||||
```vue
|
||||
<el-form-item label="门店类型">
|
||||
<el-select v-model="queryParams.storeType" placeholder="请选择门店类型" clearable>
|
||||
<el-option label="旗舰店" value="FLAGSHIP" />
|
||||
<el-option label="标准店" value="STANDARD" />
|
||||
<el-option label="社区店" value="COMMUNITY" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
```
|
||||
|
||||
`handleReset` 方法中补充 `storeType: undefined` 重置。
|
||||
|
||||
## 7. 涉及文件清单(Files To Change)
|
||||
|
||||
| # | 文件 | 操作 | 变更说明 |
|
||||
|---|------|------|---------|
|
||||
| 1 | `admin-ui/src/views/cashier/store/StoreFormDialog.vue` | 修改 | 新增 7 个 useApiForm 字段、custom-fields 插槽渲染 roomCount/freeRoomCount、数据转换逻辑、弹窗宽度调整为 720px |
|
||||
| 2 | `admin-ui/src/views/cashier/store/Index.vue` | 修改 | 新增 3 列配置(storeType/roomInfo/amenities)及对应 slot 模板、新增 storeType 筛选条件、queryParams 扩展、parseAmenities 工具函数、handleReset 补充 |
|
||||
|
||||
**变更统计**:修改 2 个文件。**不新建文件,不修改服务层和路由**。
|
||||
|
||||
## 8. 测试策略
|
||||
|
||||
### 8.1 静态检查
|
||||
|
||||
```bash
|
||||
pnpm --filter admin-ui type-check # 0 errors
|
||||
pnpm --filter admin-ui lint # 0 errors
|
||||
```
|
||||
|
||||
### 8.2 功能验证(手动)
|
||||
|
||||
| # | 验证场景 | 预期结果 |
|
||||
|---|---------|---------|
|
||||
| 1 | 列表加载 | 新增 3 列正确展示:门店类型(Tag)、包间(X/Y 格式)、设施标签(多个小 Tag) |
|
||||
| 2 | 门店类型筛选 | 下拉选择后点击查询,列表按类型过滤;重置后筛选条件清空 |
|
||||
| 3 | 无数据的门店 | storeType 为空显示 `-`,amenities 为空显示 `-`,包间无数据显示 `-/--` |
|
||||
| 4 | 新增门店 | 弹窗宽度 720px,7 个新字段正确渲染,roomCount/freeRoomCount 不显示(新增模式) |
|
||||
| 5 | 新增提交 | amenities 提交为 JSON 字符串,serviceFeeRate 提交为小数(5→0.05) |
|
||||
| 6 | 编辑门店 | 弹窗回填所有字段:amenities 从 JSON 字符串解析为勾选状态,serviceFeeRate 从小数转为百分比(0.05→5) |
|
||||
| 7 | 编辑模式只读字段 | roomCount 和 freeRoomCount 以纯文本显示,不可编辑 |
|
||||
| 8 | 新增模式无只读字段 | 新增门店时 roomCount 和 freeRoomCount 区域不显示 |
|
||||
| 9 | 门店类型必填校验 | storeType 为空时提交,表单校验不通过 |
|
||||
|
||||
### 8.3 验证清单(提交前必过)
|
||||
|
||||
- [ ] `pnpm --filter admin-ui type-check` 通过
|
||||
- [ ] `pnpm --filter admin-ui lint` 通过
|
||||
- [ ] 列表新增 3 列正确展示
|
||||
- [ ] 门店类型筛选功能正常
|
||||
- [ ] 新增门店:7 个新字段可正常填写和提交
|
||||
- [ ] 编辑门店:所有新字段正确回填,只读字段不可编辑
|
||||
- [ ] amenities 数据双向转换正确(JSON 字符串 ↔ 数组)
|
||||
- [ ] serviceFeeRate 数据双向转换正确(小数 ↔ 百分比)
|
||||
|
||||
## 9. 风险与缓解(Risks And Mitigations)
|
||||
|
||||
| # | 风险 | 缓解措施 |
|
||||
|---|------|---------|
|
||||
| 1 | **amenities JSON 解析失败**:后端返回格式异常或 null 时,前端 JSON.parse 抛错导致表单崩溃 | 在编辑回填时用 try-catch 包裹 JSON.parse,解析失败降级为空数组 `[]`。在列表 parseAmenities 工具函数中同样做防御性解析。 |
|
||||
| 2 | **serviceFeeRate 精度问题**:BigDecimal 除以 100 或乘以 100 可能产生浮点精度误差 | 使用 `Number()` 转换后通过 `el-input-number` 的 `precision: 2` 限制小数位数,避免显示多余精度。后端使用 BigDecimal 不会丢失精度。 |
|
||||
| 3 | **后端字段尚未部署**:前端先于后端部署时,新增列显示空值 | 列表和表单均对 undefined/null 做降级处理(显示 `-`),不影响现有功能。前端部署无阻断风险。 |
|
||||
| 4 | **弹窗字段过多导致布局拥挤**:原有 7 个字段 + 新增 9 个字段共 16 个表单项 | 弹窗宽度从 600px 增至 720px;新增模式不显示 roomCount/freeRoomCount,实际展示 14 项。后续如仍拥挤可考虑分组或 tabs 布局。 |
|
||||
|
||||
## 10. 决策摘要(Decision Summary)
|
||||
|
||||
- **架构**:在现有 `useApiForm` + `ApiFormDialog` + `RuiTable` 框架内扩展,不新建文件、不引入新依赖。
|
||||
- **amenities**:使用 `checkbox` 字段类型,options 固定 6 项。提交时 `JSON.stringify`,编辑回填时 `JSON.parse`,均做防御性处理。
|
||||
- **serviceFeeRate**:前端以百分比输入/显示,提交时 `÷100`,回填时 `×100`。
|
||||
- **roomCount / freeRoomCount**:通过 `#custom-fields` 插槽只读展示,仅在编辑模式显示。
|
||||
- **storeType**:`select` 字段,3 个固定选项(FLAGSHIP/STANDARD/COMMUNITY),列表用彩色 Tag 展示,增加筛选条件。
|
||||
- **longitude / latitude**:`number` 字段,精度 6 位小数,范围限制经度 [-180, 180]、纬度 [-90, 90]。
|
||||
- **openingDate**:`date` 字段,valueFormat 为 `YYYY-MM-DD`。
|
||||
- **legalPerson**:`input` 字段,无特殊处理。
|
||||
- **列表新增**:3 列(门店类型 Tag、包间 X/Y、设施多 Tag)+ 1 个筛选条件。
|
||||
- **弹窗宽度**:从默认 600px 增至 720px。
|
||||
- **文件变更**:仅修改 2 个文件(StoreFormDialog.vue、Index.vue)。
|
||||
|
||||
---
|
||||
|
||||
**设计评审状态**: 待评审
|
||||
**下一步**: 用户评审通过后,编写实施计划(Plan)
|
||||
Reference in New Issue
Block a user