diff --git a/.trae/specs/adjust-customer-management-ui/checklist.md b/.trae/specs/adjust-customer-management-ui/checklist.md new file mode 100644 index 0000000..f15c912 --- /dev/null +++ b/.trae/specs/adjust-customer-management-ui/checklist.md @@ -0,0 +1,12 @@ +# Checklist + +- [x] `database/db_seed.sql` 中客户管理子菜单:12 改名「客户档案管理」、14 改名「客户小程序授权管理」、删除 25/26、新增 27「空间拓扑与用电架构」(实际 id=32),id/父级/权限正确 +- [x] `client/src/router/index.ts` 同步菜单标题,移除 building/room 路由,新增 topology 路由,无死链 +- [x] 后端新增 `GET /customers/stats`,返回 total/statusCounts/levelCounts,并按登录人 salesman_id 过滤 +- [x] `client/src/api/customer.ts` 提供 `getStats()` 且类型正确 +- [x] 客户档案管理页重构符合 UI 风格:KPI 卡 + 搜索 + 表格,列/表单仅含数据库 `customer` 字段,无数据库不支持字段 +- [x] 客户小程序授权管理页重构:标题为「客户小程序授权管理/定向邀请准入制」,列含微信绑定状态,操作含重置密码/解绑微信,无邀请类能力 +- [x] 空间拓扑页:客户→建筑→房间 三层树渲染正确,节点新增/编辑/删除走现有 building/room API +- [x] 废弃的 building/room 视图已处理,不再出现在菜单与路由 +- [x] 前端类型检查通过(`npm run check` 0 错误),后端编译通过(`npm run build` 成功) +- [ ] dev server 联调:菜单渲染、客户档案管理、小程序授权管理、空间拓扑增删改,salesman 数据权限一致(未在本会话启动 dev server 验证,如需可另行联调) \ No newline at end of file diff --git a/.trae/specs/adjust-customer-management-ui/spec.md b/.trae/specs/adjust-customer-management-ui/spec.md new file mode 100644 index 0000000..794c24d --- /dev/null +++ b/.trae/specs/adjust-customer-management-ui/spec.md @@ -0,0 +1,77 @@ +# 客户管理 UI 调整 Spec + +## Why +现有客户管理的功能菜单与界面与最新 UI 设计(`系统设计/UI/客户档案管理`、`客户档案编辑`、`客户档案详情`、`客户小程序授权管理`、`空间拓扑与用电架构`)不一致。需要按 UI 设计调整菜单结构与页面形态,同时以架构设计(`系统设计/xintong-architecture-full.html`)和数据库设计(`系统设计/db_design.html`)为功能边界——UI 中存在但数据库/架构中不存在的功能与字段一律舍弃,以数据库为准。 + +## What Changes +- **菜单结构调整**(`database/db_seed.sql` + `client/src/router/index.ts`): + - `客户信息` → 重命名 `客户档案管理`(12) + - `客户账号` → 重命名 `客户小程序授权管理`(14) + - 新增 `空间拓扑与用电架构` 菜单(新 id 27,`/customer/topology`) + - 移除 `客户建筑`(25)、`客户房间`(26)两个菜单及对应路由 + - 保留 `客户部门`(13)、`客户拜访`(24)菜单与路由不变 +- **客户档案管理页**(`client/src/views/customer/info/index.vue` 重构): + - 按 UI 风格重排:顶部 KPI 统计卡 + 搜索栏 + 客户档案表格 + - 仅使用数据库 `customer` 字段(客户编号/名称、省市区、地址、联系人/电话、邮编、传真、客户级别、状态、负责业务员) + - **舍弃** UI 中数据库不存在的字段/能力:统一社会信用代码、法定代表人、行业属性、托管服务状态、ICC 设备状态、大华 ICC 同步、营业执照智能识别、导入意向客户、托管周期与费用等 + - 保留 KPI 卡(基于数据库可聚合统计:客户总数、按状态/级别计数),需新增轻量后端 `/customers/stats` +- **客户小程序授权管理页**(`client/src/views/customer/account/index.vue` 重构): + - 重命名为「客户小程序授权管理」,UI 标题/说明改为「定向邀请准入制」风格 + - 仅使用数据库 `customer_account` 字段:所属客户、登录手机号、微信绑定状态(wx_openid 是否存在)、状态、最近登录时间、创建时间 + - **舍弃** UI 中无数据库支撑的邀请凭证/二维码/批量短信/发起邀请等能力 + - 保留新增/编辑(含重置默认密码)、删除、微信解绑等基于现有 API 的操作 +- **空间拓扑与用电架构页**(新增 `client/src/views/customer/topology/index.vue`): + - 替换原「客户建筑」「客户房间」两个列表菜单,合并为单一空间拓扑页 + - 树形拓扑:客户 → 建筑 → 房间 三层渲染(基于 `customer`、`customer_building`、`customer_room`) + - 通过「新增/编辑空间节点」弹窗维护建筑与房间数据(复用现有 building/room CRUD API) + - **舍弃** UI 中不存在的节点类型(变电所/配电室、监测回路、ICC 边缘网关)与相关指标 +- 后端 API 基本不变,仅新增一个客户统计端点 `/customers/stats` + +## Impact +- 受影响对称菜单/权限:`sys_menu` 中客户管理子菜单(build/seed 变更) +- 受影响前端:`client/src/router/index.ts`、`client/src/views/customer/info/index.vue`、`client/src/views/customer/account/index.vue`、新增 `client/src/views/customer/topology/index.vue`、`client/src/api/customer.ts`(新增 stats)、新增 `client/src/api/customerTopology.ts` +- 受影响后端:`server/src/` 客户 controller 新增 `/customers/stats` +- 不受影响:`客户部门`(customer/department)、`客户拜访`(customer/visit)页面与后端 + +## ADDED Requirements +### Requirement: 客户统计端点 +系统 SHALL 新增 `GET /customers/stats`,返回客户总数及按状态、按级别的计数,用于客户档案管理页顶部 KPI 卡。 + +#### Scenario: 成功获取统计 +- **WHEN** 前端加载客户档案管理页 +- **THEN** 后端返回 `{ total, statusCounts: {1/2/3/4: n}, levelCounts: {...} }`,仅统计当前登录人可见的客户(销售员按 salesman_id 过滤) + +### Requirement: 空间拓扑页 +系统 SHALL 提供客户→建筑→房间三层树形拓扑视图,并支持通过节点弹窗对建筑、房间进行新增/编辑/删除。 + +#### Scenario: 渲染拓扑 +- **WHEN** 用户在客户管理选择一个客户 +- **THEN** 页面展示该客户下建筑节点(含 building_name、purpose、floor_count),展开建筑展示其房间节点(含 room_code、room_name、floor_no、room_purpose) + +#### Scenario: 维护空间节点 +- **WHEN** 用户点击「新增空间节点/新增子节点」或在节点右键操作 +- **THEN** 弹出创建/编辑弹窗,提交后调用现有 building/room 创建、编辑、删除 API,并刷新拓扑 + +## MODIFIED Requirements +### Requirement: 客户档案管理菜单与页面 +菜单名由「客户信息」改为「客户档案管理」;页面按 UI 排版重构(KPI 卡 + 搜索 + 表格),字段仅保留数据库 `customer` 表已有字段。 + +#### Scenario: 列表展示 +- **WHEN** 用户进入客户档案管理页 +- **THEN** 展示基于数据库字段的 KPI 统计卡与客户档案表格,表格列包含:客户编号、客户名称、省市区、地址、联系人、联系电话、客户级别、状态、负责业务员、操作 + +### Requirement: 小程序授权管理菜单与页面 +菜单名由「客户账号」改为「客户小程序授权管理」;页面按 UI 标题风格调整,字段仅使用数据库 `customer_account` 字段,去掉无数据库支撑的邀请类能力。 + +#### Scenario: 列表与账号维护 +- **WHEN** 用户进入客户小程序授权管理页 +- **THEN** 展示账号列表(手机号、所属客户、微信绑定状态、状态、最近登录时间)并可新增/编辑/删除/重置密码/解绑微信 + +## REMOVED Requirements +### Requirement: 客户建筑、客户房间独立菜单页 +**Reason**: UI 设计将建筑/房间合并进「空间拓扑与用电架构」单一页面;数据库支持该合并(customer_building、customer_room)。 +**Migration**: 移除 `sys_menu` 中 25、26 菜单与前端对应路由;建筑/房间的增删改改由空间拓扑页节点弹窗承担,复用现有 building/room API。 + +### Requirement: UI 中存在但数据库/架构不支持的功能与字段 +**Reason**: 按用户规则「若 UI 中存在架构和数据库中不存在的功能和字段,以架构和数据库设计为准」。 +**Migration**: 舍弃统一社会信用代码、法定代表人、行业属性、托管服务状态、ICC 设备、用电参数、拓扑率、大华 ICC 同步、营业执照导入、邀请凭证/二维码/短信等字段与能力;KPI 改用数据库可聚合统计。 \ No newline at end of file diff --git a/.trae/specs/adjust-customer-management-ui/tasks.md b/.trae/specs/adjust-customer-management-ui/tasks.md new file mode 100644 index 0000000..0d7de2c --- /dev/null +++ b/.trae/specs/adjust-customer-management-ui/tasks.md @@ -0,0 +1,51 @@ +# Tasks + +- [x] Task 1: 调整客户管理菜单权限种子(`database/db_seed.sql`) + - [x] 1.1 将 id=12 菜单名「客户信息」改为「客户档案管理」 + - [x] 1.2 将 id=14 菜单名「客户账号」改为「客户小程序授权管理」,权限/图标可沿用 + - [x] 1.3 删除 id=25「客户建筑」、id=26「客户房间」两条菜单 + - [x] 1.4 新增 id=32「空间拓扑与用电架构」,path `/customer/topology`,component `customer/topology/index`,permission `customer:topology` + +- [x] Task 2: 调整前端路由与侧边菜单(`client/src/router/index.ts`) + - [x] 2.1 同步各子菜单 `meta.title`(信息→客户档案管理、账号→客户小程序授权管理) + - [x] 2.2 移除 `customer/building`、`customer/room` 路由 + - [x] 2.3 新增 `customer/topology` 路由(title「空间拓扑与用电架构」、permission `customer:topology`) + +- [x] Task 3: 后端客户统计端点(`server/src` 客户 controller/service) + - [x] 3.1 新增 `GET /customers/stats`,返回 `{ total, statusCounts, levelCounts }`,按登录人 salesman_id 过滤 + - [x] 3.2 前端 `client/src/api/customer.ts` 新增 `getStats()` 并扩展类型 + +- [x] Task 4: 重构客户档案管理页(`client/src/views/customer/info/index.vue`) + - [x] 4.1 顶部 KPI 统计卡(总数、按状态、按级别),数据来自 stats 端点 + - [x] 4.2 搜索栏:关键词、行政区划(省市县级联)、客户级别、状态、负责业务员 + - [x] 4.3 表格列:客户编号、客户名称、省市区、详细地址、联系人/电话、客户级别、状态、负责业务员、操作(编辑/删除/详情) + - [x] 4.4 新增/编辑弹窗仅含数据库 `customer` 字段 + - [x] 4.5 仅使用数据库已有字段,去除 UI 中无支撑字段 + +- [x] Task 5: 重构客户小程序授权管理页(`client/src/views/customer/account/index.vue`) + - [x] 5.1 页面标题/说明改为「客户小程序授权管理 / 定向邀请准入制」风格 + - [x] 5.2 表格列:登录手机号、所属客户、微信绑定状态(wx_openid 是否存在)、状态、最近登录时间、操作 + - [x] 5.3 新增/编辑弹窗保留所属客户、登录电话、状态、重置默认密码;删除、解绑微信操作 + - [x] 5.4 去除邀请凭证/二维码/短信等无数据库支撑能力 + +- [x] Task 6: 新增空间拓扑与用电架构页(`client/src/views/customer/topology/index.vue`) + - [x] 6.1 客户选择(复用客户 options 接口) + - [x] 6.2 树形拓扑:客户 → 建筑(building_name、purpose、floor_count)→ 房间(room_code、room_name、floor_no、room_purpose) + - [x] 6.3 节点新增/编辑/删除:复用现有 `customer-building`、`customer-room` CRUD API,通过弹窗维护 + - [x] 6.4 前端组合拓扑数据(客户 list → building list → room list) + - [x] 6.5 按 UI 设计风格(KPI/指标卡、搜索、卡片化)排版,仅用数据库字段 + +- [x] Task 7: 清理废弃建筑/房间页面 + - [x] 7.1 删除或停用 `client/src/views/customer/building`、`client/src/views/customer/room` 视图(其 CRUD 逻辑已迁移至空间拓扑页引用) + +- [x] Task 8: 静态校验与联调 + - [x] 8.1 前端 `vue-tsc` / 类型检查通过(`client` `npm run check` 0 错误) + - [x] 8.2 后端 TypeScript 编译通过(`server` `npm run build` 成功) + - [ ] 8.3 起 dev server 验证:菜单渲染、客户档案管理、小程序授权管理、空间拓扑增删改,salesman 数据权限正常(未在本会话执行,如需可另行启动联调) + +# Task Dependencies +- Task 4 依赖 Task 3(stats 端点)与 Task 1/2(菜单、路由) +- Task 5 依赖 Task 1/2 +- Task 6 依赖 Task 1/2 +- Task 7 依赖 Task 6 +- Task 8 依赖 Task 3/4/5/6 \ No newline at end of file diff --git a/client/src/api/customer.ts b/client/src/api/customer.ts index 6af002f..f334dd8 100644 --- a/client/src/api/customer.ts +++ b/client/src/api/customer.ts @@ -68,6 +68,12 @@ export interface CustomerStatusParams { status: number; } +export interface CustomerStats { + total: number; + statusCounts: Record; + levelCounts: Record; +} + export const customerApi = { getCustomerList: (params: CustomerQuery) => request.get('/customers', { params }), @@ -78,6 +84,8 @@ export const customerApi = { getCustomerOptions: () => request.get('/customers/options/all'), + getCustomerStats: () => request.get('/customers/stats'), + createCustomer: (data: CustomerCreateParams) => request.post('/customers', data), diff --git a/client/src/api/customerAccount.ts b/client/src/api/customerAccount.ts index 7bf4f66..afb9c63 100644 --- a/client/src/api/customerAccount.ts +++ b/client/src/api/customerAccount.ts @@ -4,6 +4,7 @@ export interface CustomerAccountInfo { id: number; customerId: number; phone: string; + wxOpenid?: string | null; status: number; lastLoginAt?: string | null; createdAt: string; diff --git a/client/src/api/dashboard.ts b/client/src/api/dashboard.ts index 43f3ca3..4215346 100644 --- a/client/src/api/dashboard.ts +++ b/client/src/api/dashboard.ts @@ -1,31 +1,64 @@ import request from '@/utils/request'; +export interface ModuleCount { + name: string; + value: number; +} + +export interface TrendPoint { + date: string; + customers: number; + contracts: number; +} + +export interface StatusSlice { + name: string; + value: number; +} + +export interface SurveyOrderItem { + id: number; + orderNo: string; + customerName: string; + locale: string; + owner: string; + status: number; + time: string; +} + +export interface ProjectKanban { + id: number; + customerName: string; + amount: number; + stage: number; + stageLabel: string; + progress: number; + owner: string; + desc: string; + orderNo: string; +} + export interface DashboardStats { - deviceTotal: number; - deviceOnline: number; - alertToday: number; - activeUsers: number; - deviceTrend: Array<{ date: string; online: number; offline: number }>; - deviceTypeDistribution: Array<{ name: string; value: number }>; - recentAlerts: Array<{ - id: number; - type: string; - device: string; - level: string; - time: string; - status: string; - }>; - recentActivities: Array<{ - id: number; - user: string; - action: string; - module: string; - ip: string; - time: string; - }>; + customerTotal: number; + customerMonthlyNew: number; + contractTotal: number; + contractYearAmount: number; + surveyPending: number; + surveyTotal: number; + visitMonthly: number; + visitTotal: number; + planTotal: number; + acceptTotal: number; + surveyByStatus: StatusSlice[]; + modules: ModuleCount[]; + trend: TrendPoint[]; + customerStatus: StatusSlice[]; + customerSignRate: number; + surveyList: SurveyOrderItem[]; + projects: ProjectKanban[]; } export const dashboardApi = { getStats: () => request.get('/dashboard/stats'), -}; +}; \ No newline at end of file diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 3c948aa..33d5a61 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -32,11 +32,11 @@ const routes: RouteRecordRaw[] = [ }, // 业务模块 { - path: "customer/info", - name: "CustomerInfo", - component: () => import("@/views/customer/info/index.vue"), - meta: { requiresAuth: true, title: "客户信息", permission: "customer:list" } - }, + path: "customer/info", + name: "CustomerInfo", + component: () => import("@/views/customer/info/index.vue"), + meta: { requiresAuth: true, title: "客户档案管理", permission: "customer:list" } + }, { path: "customer/department", name: "CustomerDept", @@ -47,7 +47,7 @@ const routes: RouteRecordRaw[] = [ path: "customer/account", name: "CustomerAccount", component: () => import("@/views/customer/account/index.vue"), - meta: { requiresAuth: true, title: "客户账号", permission: "customer:account" } + meta: { requiresAuth: true, title: "客户小程序授权管理", permission: "customer:account" } }, { path: "customer/visit", @@ -56,16 +56,10 @@ const routes: RouteRecordRaw[] = [ meta: { requiresAuth: true, title: "客户拜访", permission: "customer:visit" } }, { - path: "customer/building", - name: "CustomerBuilding", - component: () => import("@/views/customer/building/index.vue"), - meta: { requiresAuth: true, title: "客户建筑", permission: "customer:building" } - }, - { - path: "customer/room", - name: "CustomerRoom", - component: () => import("@/views/customer/room/index.vue"), - meta: { requiresAuth: true, title: "客户房间", permission: "customer:room" } + path: "customer/topology", + name: "CustomerTopology", + component: () => import("@/views/customer/topology/index.vue"), + meta: { requiresAuth: true, title: "空间拓扑与用电架构", permission: "customer:topology" } }, { path: "project", diff --git a/client/src/views/customer/account/index.vue b/client/src/views/customer/account/index.vue index 19dd1e2..b59fee2 100644 --- a/client/src/views/customer/account/index.vue +++ b/client/src/views/customer/account/index.vue @@ -1,114 +1,163 @@