1
This commit is contained in:
59
doc/admin_development.md
Normal file
59
doc/admin_development.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 管理端开发文档
|
||||
|
||||
本文档用于同步当前管理端(admin/)前端与数据库的开发状态与下一步计划。
|
||||
|
||||
## 一、前端(admin/)
|
||||
- 技术栈:Vue3 + Vite + Element Plus
|
||||
- 主题:深色尊贵风格,数据展示面板/卡片/表格采用深蓝背景;Tag 颜色整体降亮。
|
||||
- 路由与页面:
|
||||
- `/vip-review`:VIP审核(默认页,卡片式,一卡一申请)
|
||||
- `/vip`:VIP管理(是否VIP、到期时间、启用/停用)
|
||||
- `/users`:用户管理(拉黑/恢复,状态文案“正常/黑名单”)
|
||||
- `/parts`:用户配件管理(拉黑/恢复)
|
||||
- `/consult`:咨询回复(回复/标记解决,标记后行内即时显示“已解决”)
|
||||
- Mock 数据:
|
||||
- 开启:`VITE_USE_MOCK=true`;使用 `public/mock/*.json`
|
||||
- 覆盖:`admin_vips.json`、`admin_vips_reviews.json`、`admin_users.json`、`admin_parts.json`、`admin_consults.json`
|
||||
- 状态:
|
||||
- 页面与交互:已完成
|
||||
- 主题色与降亮:已完成
|
||||
- Mock:已完成
|
||||
|
||||
## 二、数据库(DB)
|
||||
- 已有与复用
|
||||
- `users.status`:1=正常、0=黑名单(供用户管理拉黑/恢复)
|
||||
- `global_skus`:公共SKU库(供未来配件审核通过后发布使用)
|
||||
- 新增表
|
||||
- `vip_users`:管理VIP(is_vip/status/expire_at/reviewer_id/reviewed_at/remark)
|
||||
- `consults`、`consult_replies`:咨询与回复
|
||||
- 字段与索引改动
|
||||
- `products.is_blacklisted TINYINT(1) NOT NULL DEFAULT 0`(黑名单标记)
|
||||
- 索引:`idx_products_shop_blacklist (shop_id, is_blacklisted)`
|
||||
- 文档:`doc/database_documentation.md` 已同步
|
||||
|
||||
## 三、API 规范(OpenAPI)
|
||||
- 已补充
|
||||
- 管理端VIP:`/api/admin/vips` 列表/创建/更新/审核通过/驳回(标注“❌ Partially Implemented”)
|
||||
- 待补充
|
||||
- 管理端用户:列表(kw)、更新(status 拉黑/恢复)
|
||||
- 管理端用户配件:列表(kw/status)、黑名单/恢复(更新 `products.is_blacklisted`)
|
||||
- 管理端咨询:列表(status/kw)、回复、标记解决
|
||||
|
||||
## 四、落地与联调建议
|
||||
- 后端接口:按上述待补充接口实现,完成后将 OpenAPI 的相应条目更新为“✅ Fully Implemented”。
|
||||
- 前台/小程序:默认过滤 `products.is_blacklisted=0`,防止黑名单配件外显。
|
||||
- 安全收口:后端增加 JWT 校验,从 Token 注入 `shopId/userId`,避免完全信任请求头。
|
||||
|
||||
## 五、环境与运行
|
||||
- 开发:
|
||||
```powershell
|
||||
cd admin; npm i; $env:VITE_APP_API_BASE_URL="http://127.0.0.1:8080"; $env:VITE_APP_SHOP_ID="1"; npm run dev
|
||||
```
|
||||
- Mock:
|
||||
```powershell
|
||||
cd admin; npm i; $env:VITE_USE_MOCK="true"; npm run dev
|
||||
```
|
||||
- 构建/预览:
|
||||
```powershell
|
||||
npm run build; npm run preview
|
||||
```
|
||||
59
doc/admin_requirements.md
Normal file
59
doc/admin_requirements.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 管理端需求说明(配件管家)
|
||||
|
||||
本文件描述管理端前端与后端的需求要点,遵循“默认同意,管理端可拉黑/恢复”的运营策略;所有可配置值放置在配置或数据库,不允许硬编码。
|
||||
|
||||
## 1. 运营策略
|
||||
- 用户与用户配件(投稿/商品)默认允许展示与使用。
|
||||
- 管理端具备拉黑与恢复能力:
|
||||
- 拉黑后:对应对象在前台/小程序/客户侧不可见或被限制。
|
||||
- 恢复后:恢复正常可用状态。
|
||||
|
||||
## 2. 数据模型
|
||||
### 2.1 用户黑名单
|
||||
- 复用 `users.status` 字段:`1=正常`,`0=黑名单`。
|
||||
- 管理端动作:
|
||||
- 拉黑用户 → `PUT /api/admin/users/{id}` body `{ status: 0 }`
|
||||
- 恢复用户 → `PUT /api/admin/users/{id}` body `{ status: 1 }`
|
||||
|
||||
### 2.2 用户配件黑名单
|
||||
- 建议在承载表中增加状态位供管理端控制:
|
||||
- 若使用 `products` 承载:新增 `products.is_blacklisted TINYINT(1) NOT NULL DEFAULT 0`(黑名单标记)。
|
||||
- 若使用专表(如未来 `part_submissions`/`user_parts`):同样增加 `is_blacklisted`。
|
||||
- 索引建议:`KEY idx_products_shop_blacklist (shop_id, is_blacklisted)`,便于后台筛选。
|
||||
- 管理端动作:
|
||||
- 拉黑配件 → `PUT /api/admin/parts/{id}/blacklist`
|
||||
- 恢复配件 → `PUT /api/admin/parts/{id}/restore`
|
||||
|
||||
## 3. 前端(admin/)
|
||||
- 路由:
|
||||
- 用户管理:列表提供“拉黑/恢复”按钮,状态显示“正常/黑名单”。
|
||||
- 用户配件管理:列表提供“拉黑/恢复”按钮,状态显示“正常/黑名单”。
|
||||
- Mock:
|
||||
- 开启 `VITE_USE_MOCK=true` 使用 `public/mock/*.json`。
|
||||
|
||||
## 4. 后端接口(OpenAPI 标注)
|
||||
- 用户:
|
||||
- `GET /api/admin/users?kw=` → 列表(分页)
|
||||
- `PUT /api/admin/users/{id}` → 修改 `status`/`name`/`phone`/`role`
|
||||
- 用户配件:
|
||||
- `GET /api/admin/parts?kw=&status=` → 列表(分页,支持按 `is_blacklisted` 过滤)
|
||||
- `PUT /api/admin/parts/{id}/blacklist` → 标记黑名单
|
||||
- `PUT /api/admin/parts/{id}/restore` → 取消黑名单
|
||||
- 咨询服务:
|
||||
- `GET /api/admin/consults?status=&kw=` → 列表
|
||||
- `POST /api/admin/consults/{id}/reply` → 回复
|
||||
- `PUT /api/admin/consults/{id}/resolve` → 标记解决(前端需即时将该行状态改为 resolved)
|
||||
|
||||
说明:OpenAPI 需更新到 `doc/openapi.yaml` 并在 summary 中标注“❌ Partially Implemented”。
|
||||
|
||||
## 5. 前台/小程序侧行为
|
||||
- 用户被拉黑:登录受限或仅保留最低权限(由业务规则定义);其数据不可见或只读。
|
||||
- 配件被拉黑:前台与小程序配件列表/搜索/选择均需基于 `is_blacklisted=0` 过滤。
|
||||
|
||||
## 6. 审计与可观测性
|
||||
- 建议记录操作日志(操作者、对象、操作、时间),便于追责与回溯。
|
||||
- 建议在管理端提供简单的筛选/导出功能。
|
||||
|
||||
## 7. 迁移计划
|
||||
- 若使用 `products`:新增字段 `is_blacklisted` 并补齐索引;前台/小程序查询添加过滤条件。
|
||||
- 若采用专表方案:保持同名字段与相同行为,编写同步脚本将历史数据初始化为非黑名单。
|
||||
@@ -1,6 +1,6 @@
|
||||
## partsinquiry 数据库文档
|
||||
|
||||
更新日期:2025-09-16(已插入演示数据)
|
||||
更新日期:2025-09-23(已插入演示数据;新增演示店B/演示店C与其默认账户、两名用户)
|
||||
|
||||
说明:本文件根据远程库 mysql.tonaspace.com 中 `partsinquiry` 的实际结构生成,字段/索引/外键信息以线上为准。
|
||||
|
||||
@@ -32,14 +32,18 @@
|
||||
| password_hash | VARCHAR(255) | YES | | 密码哈希(若采用短信登录可为空) |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | 状态:1启用 0停用 |
|
||||
| is_owner | TINYINT(1) | NOT NULL | 0 | 是否店主 |
|
||||
| is_platform_admin | TINYINT(1) | NOT NULL | 0 | 是否平台管理员 |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_users_shop` (`shop_id`) - UNIQUE: `uk_users_phone` (`phone`) - UNIQUE: `ux_users_shop_phone` (`shop_id`,`phone`)
|
||||
|
||||
字段说明:
|
||||
- shop_id: 归属店铺
|
||||
- role: 角色标识字符串
|
||||
- is_owner: 是否店主标记
|
||||
- is_platform_admin: 是否平台管理员(平台级权限)
|
||||
- 其余同名含义
|
||||
|
||||
### user_identities
|
||||
@@ -57,10 +61,54 @@
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
说明:当前 `user_identities` 仅支持微信身份;短信登录采用 `users.phone` 作为全局唯一身份,不新增 identity 记录。
|
||||
|
||||
### sms_codes
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| phone | VARCHAR(32) | NOT NULL | | 手机号 |
|
||||
| scene | VARCHAR(32) | NOT NULL | login | 场景,默认为 login |
|
||||
| code_hash | CHAR(64) | NOT NULL | | 验证码哈希(SHA-256)|
|
||||
| salt | CHAR(32) | NOT NULL | | 加盐字符串 |
|
||||
| expire_at | DATETIME | NOT NULL | | 过期时间 |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 0 | 0=active,1=used,2=expired,3=blocked |
|
||||
| fail_count | TINYINT UNSIGNED | NOT NULL | 0 | 错误次数 |
|
||||
| ip | VARCHAR(45) | YES | | 发送IP |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_phone_created_at` (`phone`,`created_at`) - KEY: `idx_phone_scene_status` (`phone`,`scene`,`status`) - KEY: `idx_expire_at` (`expire_at`) - KEY: `idx_ip_created_at` (`ip`,`created_at`)
|
||||
|
||||
字段说明:
|
||||
- provider: wechat_mp(小程序)、wechat_app(APP)
|
||||
- openid/unionid: 微信身份标识
|
||||
|
||||
### vip_users
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | 店铺 |
|
||||
| user_id | BIGINT UNSIGNED | NOT NULL | | 用户 |
|
||||
| is_vip | TINYINT(1) | NOT NULL | 1 | 是否VIP(1是 0否) |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 0 | 启用状态:1启用 0停用(审核通过后启用) |
|
||||
| expire_at | DATETIME | YES | | 到期时间 |
|
||||
| remark | VARCHAR(255) | YES | | 备注/审核说明 |
|
||||
| reviewer_id | BIGINT UNSIGNED | YES | | 审核人 |
|
||||
| reviewed_at | DATETIME | YES | | 审核时间 |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_vu_shop_user` (`shop_id`,`user_id`) - KEY: `idx_vu_shop_status` (`shop_id`,`status`)
|
||||
**Foreign Keys**: - `fk_vu_shop`: `shop_id` → `shops(id)` - `fk_vu_user`: `user_id` → `users(id)` - `fk_vu_reviewer`: `reviewer_id` → `users(id)`
|
||||
|
||||
### vip_price
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| price | DECIMAL(10,2) | NOT NULL | | 全局价格(仅一条记录) |
|
||||
|
||||
说明:该表为全局配置表,仅包含一条记录用于表示当前 VIP 单月价格。
|
||||
|
||||
### wechat_sessions
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
@@ -88,7 +136,7 @@
|
||||
字段说明:
|
||||
- key/value: 键/值(JSON)
|
||||
|
||||
### product_units
|
||||
### product_units(含全局字典约定)
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
@@ -103,6 +151,11 @@
|
||||
- name: 单位名称,如 件/个/箱
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_units_shop` (`shop_id`) - UNIQUE: `ux_units_shop_name` (`shop_id`,`name`)
|
||||
|
||||
全局字典约定(方案A):
|
||||
- 以 `shop_id=0` 作为“全局字典库”的承载店铺(不对应真实租户)。
|
||||
- 单位接口 `/api/product-units` 始终返回 `shop_id=0` 的记录;新建/修改/删除仅写入 `shop_id=0`。
|
||||
- 兼容历史:不强制迁移既有数据,各店已有单位保留。
|
||||
**Foreign Keys**: - `fk_units_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_units_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### global_skus
|
||||
@@ -130,7 +183,7 @@
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_global_skus_brand_model` (`brand`,`model`) - UNIQUE: `ux_global_skus_barcode` (`barcode`)
|
||||
**Foreign Keys**: - `fk_globalsku_unit`: `unit_id` → `product_units(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### product_categories
|
||||
### product_categories(含全局字典约定)
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
@@ -148,6 +201,10 @@
|
||||
- sort_order: 排序
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_categories_shop` (`shop_id`) - KEY: `idx_categories_parent` (`parent_id`) - UNIQUE: `ux_categories_shop_name` (`shop_id`,`name`)
|
||||
|
||||
全局字典约定(方案A):
|
||||
- 类别接口 `/api/product-categories` 始终返回 `shop_id=0` 的记录;新建/修改/删除仅写入 `shop_id=0`。
|
||||
- 历史上各店铺已存在的基础项,后续逐步收敛至 `shop_id=0` 字典;为兼容引用,暂不强制删除。
|
||||
**Foreign Keys**: - `fk_categories_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_categories_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_categories_parent`: `parent_id` → `product_categories(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### products
|
||||
@@ -165,6 +222,7 @@
|
||||
| origin | VARCHAR(64) | YES | | |
|
||||
| barcode | VARCHAR(32) | YES | | |
|
||||
| alias | VARCHAR(120) | YES | | |
|
||||
| is_blacklisted | TINYINT(1) | NOT NULL | 0 | 黑名单标记(管理端可控) |
|
||||
| description | TEXT | YES | | |
|
||||
| global_sku_id | BIGINT UNSIGNED | YES | | |
|
||||
| safe_min | DECIMAL(18,3) | YES | | |
|
||||
@@ -179,7 +237,7 @@
|
||||
- safe_min/safe_max: 安全库存上下限
|
||||
- search_text: 聚合检索字段(触发器维护)
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_products_shop` (`shop_id`) - KEY: `idx_products_category` (`category_id`) - KEY: `idx_products_unit` (`unit_id`) - FULLTEXT: `ft_products_search` (`name`,`brand`,`model`,`spec`,`search_text`) - UNIQUE: `ux_products_shop_barcode` (`shop_id`,`barcode`)
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_products_shop` (`shop_id`) - KEY: `idx_products_category` (`category_id`) - KEY: `idx_products_unit` (`unit_id`) - KEY: `idx_products_shop_blacklist` (`shop_id`,`is_blacklisted`) - FULLTEXT: `ft_products_search` (`name`,`brand`,`model`,`spec`,`search_text`) - UNIQUE: `ux_products_shop_barcode` (`shop_id`,`barcode`)
|
||||
**Foreign Keys**: - `fk_products_shop`: `shop_id` → `shops(id)` - `fk_products_user`: `user_id` → `users(id)` - `fk_products_category`: `category_id` → `product_categories(id)` - `fk_products_unit`: `unit_id` → `product_units(id)` - `fk_products_globalsku`: `global_sku_id` → `global_skus(id)`
|
||||
|
||||
### product_aliases
|
||||
@@ -405,7 +463,7 @@
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_poi_order` (`order_id`) - KEY: `idx_poi_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_poi_order`: `order_id` → `purchase_orders(id)` ON DELETE CASCADE - `fk_poi_product`: `product_id` → `products(id)`
|
||||
|
||||
image.png
|
||||
### payments
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
@@ -451,161 +509,4 @@
|
||||
- type/category: 收入/支出与分类
|
||||
- counterparty_type/id: 往来单位(可空)
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_ot_shop_time` (`shop_id`,`tx_time`) - KEY: `idx_ot_account` (`account_id`)
|
||||
**Foreign Keys**: - `fk_ot_shop`: `shop_id` → `shops(id)` - `fk_ot_user`: `user_id` → `users(id)` - `fk_ot_account`: `account_id` → `accounts(id)`
|
||||
|
||||
### finance_categories
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| type | ENUM('income','expense') | NOT NULL | | 分类类型 |
|
||||
| key | VARCHAR(64) | NOT NULL | | 分类键(稳定标识) |
|
||||
| label | VARCHAR(64) | NOT NULL | | 分类名称(支持中文) |
|
||||
| sort_order | INT | NOT NULL | 0 | 排序 |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | 1启用 0停用 |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
字段说明(finance_categories):
|
||||
- key: 稳定标识(代码不随展示文案改变)
|
||||
- label: 展示名称
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - UNIQUE: `ux_finance_cat` (`shop_id`,`type`,`key`) - KEY: `idx_finance_cat_shop_type` (`shop_id`,`type`)
|
||||
**Foreign Keys**: - `fk_finance_cat_shop`: `shop_id` → `shops(id)`
|
||||
|
||||
### 触发器
|
||||
- `trg_products_bi`: BEFORE INSERT ON `products` → 设置 `products.search_text`
|
||||
- `trg_products_au`: BEFORE UPDATE ON `products` → 维护 `products.search_text`
|
||||
- `trg_palias_ai`: AFTER INSERT ON `product_aliases` → 重建 `products.search_text`
|
||||
- `trg_palias_au`: AFTER UPDATE ON `product_aliases` → 重建 `products.search_text`
|
||||
- `trg_palias_ad`: AFTER DELETE ON `product_aliases` → 重建 `products.search_text`
|
||||
|
||||
|
||||
### notices
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| title | VARCHAR(120) | NOT NULL | | |
|
||||
| content | VARCHAR(500) | NOT NULL | | |
|
||||
| tag | VARCHAR(32) | YES | | |
|
||||
| is_pinned | TINYINT(1) | NOT NULL | 0 | |
|
||||
| starts_at | DATETIME | YES | | |
|
||||
| ends_at | DATETIME | YES | | |
|
||||
| status | ENUM('draft','published','offline') | NOT NULL | published | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
字段说明(notices):
|
||||
- is_pinned: 是否置顶
|
||||
- starts_at/ends_at: 生效时间窗
|
||||
- status: 草稿/发布/下线
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_notices_time` (`starts_at`,`ends_at`)
|
||||
**Foreign Keys**: - 无
|
||||
|
||||
### inventory_movements
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| user_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| product_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| source_type | VARCHAR(32) | NOT NULL | | sale/purchase/sale_return/purchase_return/adjust |
|
||||
| source_id | BIGINT UNSIGNED | YES | | 关联单据ID |
|
||||
| qty_delta | DECIMAL(18,3) | NOT NULL | | 数量增减(正加负减) |
|
||||
| amount_delta | DECIMAL(18,2) | YES | | 金额变动(可空) |
|
||||
| reason | VARCHAR(64) | YES | | |
|
||||
| tx_time | DATETIME | NOT NULL | | |
|
||||
| remark | VARCHAR(255) | YES | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
字段说明(inventory_movements):
|
||||
- qty_delta: 数量变动(入库为正、出库为负)
|
||||
- amount_delta: 金额变动(可选)
|
||||
- source_type/source_id: 变动来源追溯
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_im_shop_time` (`shop_id`,`tx_time`) - KEY: `idx_im_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_im_shop`: `shop_id` → `shops(id)` - `fk_im_user`: `user_id` → `users(id)` - `fk_im_product`: `product_id` → `products(id)`
|
||||
|
||||
### 附:演示种子数据(非完整,仅用于联调验证)
|
||||
- 演示店铺:演示店A(用户 3,全部店长 owner)
|
||||
- 商品域:基础单位3条、类别2条、全局SKU2条、商品2条(含别名/价格/库存/图片)
|
||||
- 往来与账户:客户2、供应商2、账户3
|
||||
- 单据:销售单1(含明细2)与进货单1(含明细2)、收付款各1、其他收支2
|
||||
- 审核与公告:part_submissions 1、attachments 1、notices 2、新增 wechat 身份与会话各1
|
||||
|
||||
### sales_return_orders
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| user_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| customer_id | BIGINT UNSIGNED | YES | | |
|
||||
| order_no | VARCHAR(32) | NOT NULL | | |
|
||||
| order_time | DATETIME | NOT NULL | | |
|
||||
| status | ENUM('approved','void') | NOT NULL | approved | |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
|
||||
| paid_amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
|
||||
| remark | VARCHAR(255) | YES | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
字段说明(sales_return_orders):
|
||||
- 与销售单结构一致,用于退货业务;状态为 approved/void
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - UNIQUE: `ux_sr_order_no` (`shop_id`,`order_no`) - KEY: `idx_sr_shop_time` (`shop_id`,`order_time`)
|
||||
**Foreign Keys**: - `fk_sr_shop`: `shop_id` → `shops(id)` - `fk_sr_user`: `user_id` → `users(id)` - `fk_sr_customer`: `customer_id` → `customers(id)`
|
||||
|
||||
### sales_return_order_items
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| order_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| product_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| quantity | DECIMAL(18,3) | NOT NULL | | |
|
||||
| unit_price | DECIMAL(18,2) | NOT NULL | | |
|
||||
| discount_rate | DECIMAL(5,2) | NOT NULL | 0.00 | |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_sroi_order` (`order_id`) - KEY: `idx_sroi_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_sroi_order`: `order_id` → `sales_return_orders(id)` ON DELETE CASCADE - `fk_sroi_product`: `product_id` → `products(id)`
|
||||
|
||||
### purchase_return_orders
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| user_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| supplier_id | BIGINT UNSIGNED | YES | | |
|
||||
| order_no | VARCHAR(32) | NOT NULL | | |
|
||||
| order_time | DATETIME | NOT NULL | | |
|
||||
| status | ENUM('approved','void') | NOT NULL | approved | |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
|
||||
| paid_amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
|
||||
| remark | VARCHAR(255) | YES | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
字段说明(purchase_return_orders):
|
||||
- 与销售单结构一致,用于退货业务;状态为 approved/void
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - UNIQUE: `ux_pr_order_no` (`shop_id`,`order_no`) - KEY: `idx_pr_shop_time` (`shop_id`,`order_time`)
|
||||
**Foreign Keys**: - `fk_pr_shop`: `shop_id` → `shops(id)` - `fk_pr_user`: `user_id` → `users(id)` - `fk_pr_supplier`: `supplier_id` → `suppliers(id)`
|
||||
|
||||
### purchase_return_order_items
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| order_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| product_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| quantity | DECIMAL(18,3) | NOT NULL | | |
|
||||
| unit_price | DECIMAL(18,2) | NOT NULL | | |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_proi_order` (`order_id`) - KEY: `idx_proi_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_proi_order`: `order_id` → `purchase_return_orders(id)` ON DELETE CASCADE - `fk_proi_product`: `product_id` → `products(id)`
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_ot_shop_time`
|
||||
228
doc/openapi.yaml
228
doc/openapi.yaml
@@ -361,6 +361,7 @@ paths:
|
||||
/api/product-categories:
|
||||
get:
|
||||
summary: 类别列表(✅ Fully Implemented)
|
||||
description: 仅返回全局字典(shop_id=0)的类别列表。
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
@@ -412,6 +413,7 @@ paths:
|
||||
/api/product-units:
|
||||
get:
|
||||
summary: 单位列表(✅ Fully Implemented)
|
||||
description: 仅返回全局字典(shop_id=0)的单位列表。
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
@@ -944,8 +946,234 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
/api/auth/register:
|
||||
post:
|
||||
summary: 手机号注册创建店铺与店主(✅ Fully Implemented)
|
||||
description: |
|
||||
直接提交手机号进行注册:若手机号尚不存在,则创建店铺与店主用户(owner,`users.is_owner=1`),并初始化默认账户(现金/银行存款/微信)。
|
||||
若手机号已存在,则直接签发 JWT 并返回用户/店铺信息。
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
phone: { type: string }
|
||||
name: { type: string, nullable: true }
|
||||
password: { type: string, nullable: true, description: '预留字段,当前不校验' }
|
||||
required: [phone]
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token: { type: string }
|
||||
expiresIn: { type: integer }
|
||||
user:
|
||||
type: object
|
||||
properties:
|
||||
userId: { type: integer }
|
||||
shopId: { type: integer }
|
||||
phone: { type: string }
|
||||
/api/admin/vips:
|
||||
get:
|
||||
summary: 管理端-会员列表(❌ Partially Implemented)
|
||||
description: 返回店铺会员(或待启用)列表,基于 `vip_users` 表。
|
||||
parameters:
|
||||
- in: query
|
||||
name: phone
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: page
|
||||
schema: { type: integer, default: 1 }
|
||||
- in: query
|
||||
name: size
|
||||
schema: { type: integer, default: 20 }
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
list:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VipUser'
|
||||
total: { type: integer }
|
||||
post:
|
||||
summary: 管理端-新增/申请会员(❌ Partially Implemented)
|
||||
description: 向 `vip_users` 插入一条记录,支持作为“申请”进入待启用状态。
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateVipUserRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
/api/admin/vips/{id}:
|
||||
put:
|
||||
summary: 管理端-更新会员(❌ Partially Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateVipUserRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
/api/admin/vips/{id}/approve:
|
||||
post:
|
||||
summary: 管理端-审核通过VIP(❌ Partially Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
responses:
|
||||
'200': { description: 成功 }
|
||||
/api/admin/vips/{id}/reject:
|
||||
post:
|
||||
summary: 管理端-审核驳回VIP(❌ Partially Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
responses:
|
||||
'200': { description: 成功 }
|
||||
/api/admin/dicts/units:
|
||||
post:
|
||||
summary: 管理端-新增主单位(✅ Fully Implemented)
|
||||
description: 仅平台管理员可用;写入 `shop_id=0` 全局字典。
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
/api/admin/dicts/units/{id}:
|
||||
put:
|
||||
summary: 管理端-更新主单位(✅ Fully Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
responses: { '200': { description: 成功 } }
|
||||
delete:
|
||||
summary: 管理端-删除主单位(✅ Fully Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
responses: { '200': { description: 成功 } }
|
||||
/api/admin/dicts/categories:
|
||||
post:
|
||||
summary: 管理端-新增主类别(✅ Fully Implemented)
|
||||
description: 仅平台管理员可用;写入 `shop_id=0` 全局字典。
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
/api/admin/dicts/categories/{id}:
|
||||
put:
|
||||
summary: 管理端-更新主类别(✅ Fully Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
responses: { '200': { description: 成功 } }
|
||||
delete:
|
||||
summary: 管理端-删除主类别(✅ Fully Implemented)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer, format: int64 }
|
||||
responses: { '200': { description: 成功 } }
|
||||
components:
|
||||
schemas:
|
||||
VipUser:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
userId: { type: integer, format: int64 }
|
||||
isVip: { type: integer, enum: [0,1] }
|
||||
status: { type: integer, enum: [0,1] }
|
||||
expireAt: { type: string, format: date-time, nullable: true }
|
||||
remark: { type: string, nullable: true }
|
||||
reviewerId: { type: integer, format: int64, nullable: true }
|
||||
reviewedAt: { type: string, format: date-time, nullable: true }
|
||||
CreateVipUserRequest:
|
||||
type: object
|
||||
properties:
|
||||
userId: { type: integer, format: int64 }
|
||||
isVip: { type: integer, enum: [0,1], default: 1 }
|
||||
expireAt: { type: string, format: date-time, nullable: true }
|
||||
remark: { type: string, nullable: true }
|
||||
UpdateVipUserRequest:
|
||||
type: object
|
||||
properties:
|
||||
isVip: { type: integer, enum: [0,1] }
|
||||
status: { type: integer, enum: [0,1] }
|
||||
expireAt: { type: string, format: date-time, nullable: true }
|
||||
remark: { type: string, nullable: true }
|
||||
DashboardOverview:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -32,6 +32,23 @@ $env:DB_URL="jdbc:mysql://mysql.tonaspace.com:3306/partsinquiry?useSSL=false&all
|
||||
- 浏览器访问:`http://localhost:8080/api/dashboard/overview`
|
||||
- 返回概览数据即表示服务与数据库连接正常
|
||||
|
||||
### 按用户ID登录(用户端快速登录通道)
|
||||
> 仅在调试或特定场景启用。默认关闭。
|
||||
|
||||
1) 启用开关(临时):
|
||||
```powershell
|
||||
$env:AUTH_ID_LOGIN_ENABLED="true"; .\mvnw.cmd spring-boot:run -DskipTests
|
||||
```
|
||||
2) 请求示例:
|
||||
```http
|
||||
POST http://localhost:8080/api/auth/login-by-id
|
||||
Content-Type: application/json
|
||||
|
||||
{ "userId": 2 }
|
||||
```
|
||||
3) 成功返回:`{ token, expiresIn, user:{ userId, shopId, phone? } }`
|
||||
4) 之后在调用业务接口时携带:`Authorization: Bearer <token>`
|
||||
|
||||
### 常见问题
|
||||
- **端口被占用**:更换启动端口
|
||||
```powershell
|
||||
|
||||
Reference in New Issue
Block a user