feat: 新增甲方客户管理模块及配套功能

1. 新增甲方客户CRUD接口、前端页面与权限控制
2. 新增发货单管理模块,包含订单状态流转
3. 修复系统菜单名称乱码问题
4. 新增项目启动脚本与数据库初始化脚本
5. 新增相关实体类、Mapper、Service实现
6. 补充项目设计文档与忽略配置
This commit is contained in:
2026-06-09 21:44:31 +08:00
parent ef9584cdb9
commit bbddcb494d
26 changed files with 2084 additions and 0 deletions

6
.gitignore vendored
View File

@@ -23,6 +23,12 @@ logs/
# Spring Boot
*.pid
# Demo / sample projects
shipping_system/
# Design documents (internal)
doc/order_module_design/
# Docker local build artifacts
deploy/dist-ui/
deploy/ruoyi-admin.jar

View File

@@ -0,0 +1,8 @@
#!/bin/bash
cd D:/DeXun_workspace/projects/erp-next
mvn spring-boot:run -pl ruoyi-admin -q &
PID=$!
echo "App PID: $PID"
sleep 40
curl -s http://localhost:8080/v3/api-docs > D:/DeXun_workspace/projects/erp-next/doc/zz_api_result.json 2>&1
echo "Done, check zz_api_result.json"

View File

@@ -0,0 +1,797 @@
# 甲方客户界面及订单相关页面设计流程文档
## 一、Shipping_System Demo 全面分析
### 1.1 交互模式分析
#### 1.1.1 页面导航结构
```
顶部导航栏(固定)
├── Logo区域品牌标识
├── 主导航菜单(水平排列)
│ ├── 总览Dashboard
│ ├── 物料管理
│ ├── 客户管理
│ ├── 待发订单
│ ├── 在途订单
│ ├── 历史订单
│ ├── 结单时间
│ ├── 数据报表
│ └── 操作记录
└── 系统状态指示器
内容区域(单页应用模式)
├── 页面标题区Section Header
├── 工具栏(搜索/筛选/操作按钮)
├── 数据表格/卡片区域
└── 分页/统计信息
```
#### 1.1.2 核心交互模式
| 交互类型 | 实现方式 | 用户体验特点 |
|---------|---------|-------------|
| 页面切换 | 单页应用(SPA),无刷新切换 | 快速、流畅 |
| 数据加载 | 异步fetch APIloading状态 | 非阻塞、有反馈 |
| 表单提交 | Modal弹窗实时验证 | 沉浸式、不打断 |
| 搜索筛选 | 防抖输入(300ms),即时响应 | 高效、省资源 |
| 状态流转 | 按钮操作+状态标签变色 | 直观、可追踪 |
| 数据展示 | 表格+卡片+图表混合 | 信息层次清晰 |
#### 1.1.3 关键交互细节
**订单状态流转:**
```
待发(PENDING) → 在途(TRANSIT) → 历史(HISTORY)
↓ ↓
编辑/删除 完成/撤回
```
**预警提示机制:**
- 交货期预警:红色边框+逾期天数标签
- 状态标识彩色Badge标签
- 操作反馈Toast消息提示
### 1.2 布局结构分析
#### 1.2.1 整体布局框架
```
┌─────────────────────────────────────────────────────────────┐
│ [Logo] 总览 物料管理 客户管理 待发...在途...历史... │
├─────────────────────────────────────────────────────────────┤
│ │
│ 页面标题 [状态标签] │
│ ───────────────────────────────────────────────────────── │
│ │
│ [搜索框] [筛选器] [新增按钮] │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 数据表格 │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ 订单号 客户 金额 交货期 状态 操作 │ │
│ │ ORD-001 XXX ¥100 2026-06 PENDING [详][编][发] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
#### 1.2.2 响应式断点设计
| 断点 | 宽度 | 布局调整 |
|-----|------|---------|
| Desktop | ≥1200px | 完整布局,多列展示 |
| Tablet | 768-1199px | 简化表格,隐藏次要列 |
| Mobile | <768px | 卡片式布局垂直堆叠 |
#### 1.2.3 页面类型模板
**列表页模板:**
- 顶部标题 + 状态说明
- 工具栏搜索 + 筛选 + 主要操作
- 内容区数据表格
- 底部分页器
**详情/编辑页模板:**
- Modal弹窗形式
- 分区块表单2列布局
- 底部操作按钮组
**报表页模板:**
- KPI指标卡片行
- 图表网格2列
- 详细数据表格
### 1.3 功能模块划分
#### 1.3.1 模块结构图
```
shipping_system
├── 核心数据模块
│ ├── 物料管理SKU级
│ ├── 客户管理(企业/个人)
│ └── 订单管理(生命周期)
├── 业务流程模块
│ ├── 订单状态流转
│ ├── 结单时间管理
│ └── 发货记录追踪
├── 分析报表模块
│ ├── 统计概览
│ ├── 趋势分析
│ └── 排名报表
└── 系统管理模块
├── 操作日志
└── 数据审计
```
#### 1.3.2 功能对照表
| 功能模块 | 当前项目对应 | 复用建议 |
|---------|-------------|---------|
| 客户管理 | 甲方客户管理 | 高度复用界面结构 |
| 订单管理 | 销售订单/发货单 | 复用状态流转逻辑 |
| 物料管理 | 产品/物料库 | 复用表格布局 |
| 数据报表 | 统计分析 | 复用图表组件 |
| 操作记录 | 系统日志 | 复用表格+筛选 |
---
## 二、视觉设计规范提取
### 2.1 色彩系统
#### 2.1.1 主色调(工业暗黑风)
```css
:root {
/* 背景层级 */
--bg: #1a1c1e; /* 主背景 - 深灰黑 */
--bg2: #222426; /* 卡片背景 */
--bg3: #2a2d30; /* 表头/输入框背景 */
--bg4: #333639; /* 悬停背景 */
/* 边框层级 */
--border: #3a3d41; /* 普通边框 */
--border2: #4a4d52; /* 高亮边框 */
/* 文字层级 */
--text: #c8ccd0; /* 主文字 - 浅灰 */
--text2: #8a8f96; /* 次要文字 */
--text3: #5a5f66; /* 禁用/提示文字 */
/* 强调色 */
--yellow: #e8b400; /* 主强调色 - 琥珀金 */
--yellow2: #ffcc00; /* 高亮 */
/* 状态色 */
--red: #c0392b; /* 危险/逾期 */
--red2: #e74c3c; /* 警告高亮 */
--green: #27ae60; /* 成功/完成 */
--green2: #2ecc71; /* 成功高亮 */
--blue: #2980b9; /* 信息/在途 */
--blue2: #3498db; /* 信息高亮 */
--orange: #d35400; /* 警告/待发 */
--orange2: #e67e22; /* 警告高亮 */
}
```
#### 2.1.2 色彩应用场景
| 场景 | 颜色 | 用途 |
|-----|------|------|
| 品牌标识 | #e8b400 | Logo主按钮强调线 |
| 成功状态 | #27ae60 | 完成通过正常 |
| 警告状态 | #e67e22 | 待发待处理 |
| 危险状态 | #e74c3c | 逾期删除异常 |
| 信息状态 | #3498db | 在途进行中 |
| 中性状态 | #8a8f96 | 草稿默认 |
### 2.2 字体系统
```css
:root {
--font: 'JetBrains Mono', 'Consolas', 'Courier New', monospace;
}
```
| 元素 | 字号 | 字重 | 样式 |
|-----|------|------|------|
| Logo | 14px | 700 | 大写字间距2px |
| 导航 | 12px | 700 | 大写字间距0.5px |
| 页面标题 | 11px | 700 | 大写字间距1.5px |
| 表格表头 | 11px | 700 | 大写字间距0.5px |
| 表格内容 | 13px | 400 | 正常 |
| 按钮 | 12px | 400 | 字间距0.5px |
| 统计数字 | 28px | 700 | 字间距-1px |
### 2.3 间距系统
| 元素 | 间距值 |
|-----|-------|
| 页面内边距 | 20px |
| 卡片内边距 | 16px 20px |
| 表格单元格 | 8px 12px |
| 按钮内边距 | 5px 14px |
| 表单行间距 | 12px |
| 组件间隙 | 12-16px |
### 2.4 组件样式
#### 2.4.1 按钮规范
```css
/* 基础按钮 */
button {
padding: 5px 14px;
font-size: 12px;
border: 1px solid var(--border2);
background: var(--bg3);
transition: all .15s;
}
/* 主要按钮 */
.btn-primary {
background: var(--yellow);
color: #000;
border-color: var(--yellow);
font-weight: 700;
}
/* 危险按钮 */
.btn-danger {
border-color: var(--red);
color: var(--red);
}
/* 成功按钮 */
.btn-success {
border-color: var(--green);
color: var(--green);
}
```
#### 2.4.2 表格规范
```css
th {
background: var(--bg3);
color: var(--text2);
font-weight: 700;
text-transform: uppercase;
border-bottom: 2px solid var(--yellow);
padding: 8px 12px;
}
td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
}
tr:hover td {
background: var(--bg3);
}
```
#### 2.4.3 状态标签(Badge)
```css
.badge-pending { /* 待发 */
color: #e67e22;
border-color: #e67e22;
background: rgba(230,126,34,.1);
}
.badge-transit { /* 在途 */
color: #3498db;
border-color: #3498db;
background: rgba(52,152,219,.1);
}
.badge-history { /* 历史 */
color: #27ae60;
border-color: #27ae60;
background: rgba(39,174,96,.1);
}
.badge-warn { /* 警告/逾期 */
color: var(--red2);
border-color: var(--red2);
background: rgba(231,76,60,.1);
}
```
---
## 三、设计流程规划
### 3.1 阶段一需求分析与确认3-5天
#### 3.1.1 工作内容
1. **业务需求梳理**
- 甲方客户管理流程梳理
- 订单生命周期定义
- 状态流转规则确认
- 权限角色划分
2. **现有系统分析**
- 当前项目架构调研
- 数据模型分析
- API接口梳理
- 复用组件盘点
3. **竞品/参考分析**
- shipping_system demo深度体验
- 行业最佳实践研究
- 用户痛点收集
#### 3.1.2 交付物
| 交付物 | 标准 | 负责人 |
|-------|------|-------|
| 需求规格说明书 | 功能点清单业务流程图 | 产品经理 |
| 数据模型设计 | ER图字段定义 | 后端开发 |
| 接口文档初稿 | API列表字段定义 | 后端开发 |
| 竞品分析报告 | 优缺点对比借鉴点 | 设计师 |
#### 3.1.3 质量验收标准
- [ ] 所有功能点已明确优先级P0/P1/P2
- [ ] 业务流程图通过评审
- [ ] 数据模型覆盖所有业务场景
- [ ] 技术可行性已确认
---
### 3.2 阶段二信息架构设计2-3天
#### 3.2.1 工作内容
1. **站点地图设计**
```
甲方客户管理
├── 客户列表
│ ├── 搜索/筛选
│ ├── 新增客户
│ ├── 编辑客户
│ └── 查看详情
├── 客户详情
│ ├── 基本信息
│ ├── 历史订单
│ └── 联系记录
└── 客户统计
订单管理
├── 待发订单
├── 在途订单
├── 历史订单
└── 订单详情
├── 基本信息
├── 物料明细
├── 状态流转
└── 操作日志
```
2. **导航结构设计**
- 主导航菜单规划
- 面包屑导航规则
- 快捷入口设计
3. **页面层级规划**
- 一级页面:列表页
- 二级页面:详情页/编辑页
- 三级页面:关联信息
#### 3.2.2 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 站点地图 | 完整页面层级 | XMind/ProcessOn |
| 用户流程图 | 关键任务流程 | Figma/Draw.io |
| 信息架构图 | 模块关系图 | 架构图工具 |
#### 3.2.3 质量验收标准
- [ ] 所有页面层级不超过3级
- [ ] 核心任务完成路径≤3步
- [ ] 导航结构符合用户心智模型
- [ ] 与现有系统导航风格一致
---
### 3.3 阶段三线框图绘制3-5天
#### 3.3.1 工作内容
1. **页面布局线框**
- 列表页布局参考shipping_system
- 详情页布局
- 表单页布局
- Modal弹窗布局
2. **组件布局规划**
- 表格列宽分配
- 表单字段排列
- 按钮位置规划
- 响应式断点设计
3. **交互流程标注**
- 点击热区标注
- 状态变化说明
- 转场动画示意
#### 3.3.2 页面线框清单
| 页面 | 优先级 | 复杂度 |
|-----|-------|-------|
| 甲方客户列表页 | P0 | 中 |
| 甲方客户详情页 | P0 | 中 |
| 客户编辑/新增页 | P0 | 低 |
| 待发订单列表页 | P0 | 中 |
| 在途订单列表页 | P0 | 中 |
| 历史订单列表页 | P0 | 中 |
| 订单详情页 | P0 | 高 |
| 订单编辑/新增页 | P0 | 高 |
| 订单统计报表 | P1 | 中 |
| 客户统计报表 | P1 | 中 |
#### 3.3.3 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 低保真线框图 | 灰度,无色彩 | Figma/Axure |
| 交互说明文档 | 标注交互细节 | 文档工具 |
| 页面流程图 | 页面跳转关系 | 流程图工具 |
#### 3.3.4 质量验收标准
- [ ] 所有P0页面线框已完成
- [ ] 布局符合shipping_system风格
- [ ] 核心交互路径已标注
- [ ] 开发团队已评审通过
---
### 3.4 阶段四视觉设计规范制定2-3天
#### 3.4.1 工作内容
1. **色彩系统定义**
- 提取shipping_system配色
- 适配当前项目品牌色
- 定义状态色规范
- 输出CSS变量
2. **字体系统定义**
- 中文字体选择(建议:思源黑体/微软雅黑)
- 字号层级定义
- 行高/字间距规范
3. **组件视觉规范**
- 按钮样式(主/次/危险/成功)
- 输入框样式
- 表格样式
- 卡片/面板样式
- 标签/Badge样式
- Modal弹窗样式
4. **图标系统**
- 图标风格统一(线性/面性)
- 图标尺寸规范
- 常用图标清单
#### 3.4.2 设计规范输出
```
design-system/
├── colors/
│ ├── palette.scss # 色板定义
│ ├── semantic.scss # 语义化颜色
│ └── dark-theme.scss # 暗黑主题
├── typography/
│ ├── fonts.scss # 字体定义
│ ├── scale.scss # 字号层级
│ └── styles.scss # 文字样式
├── components/
│ ├── buttons.scss # 按钮规范
│ ├── forms.scss # 表单规范
│ ├── tables.scss # 表格规范
│ ├── cards.scss # 卡片规范
│ └── modals.scss # 弹窗规范
└── spacing/
├── grid.scss # 栅格系统
└── spacing.scss # 间距系统
```
#### 3.4.3 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 视觉设计规范文档 | 完整Design Token | Figma/文档 |
| 组件样式库 | SCSS/CSS代码 | 代码仓库 |
| 图标资源包 | SVG格式 | 设计工具 |
#### 3.4.4 质量验收标准
- [ ] 色彩对比度符合WCAG 2.1 AA标准
- [ ] 所有组件有对应的代码实现
- [ ] 与shipping_system视觉风格一致
- [ ] 开发团队可无障碍使用
---
### 3.5 阶段五高保真原型制作5-7天
#### 3.5.1 工作内容
1. **高保真页面设计**
- 基于线框图添加视觉细节
- 应用设计规范
- 填充真实数据示例
2. **交互原型制作**
- 页面跳转链接
- 交互动效定义
- 状态变化演示
3. **响应式设计**
- Desktop端≥1200px
- Tablet端768-1199px
- Mobile端<768px
#### 3.5.2 关键页面设计要点
**甲方客户列表页:**
- 顶部:标题 + 客户统计
- 工具栏:搜索框 + 客户类型筛选 + 新增按钮
- 表格:编号/名称/联系人/电话/城市/订单数/状态/操作
- 操作列:详情/编辑/删除
**订单详情页:**
- 左侧:订单基本信息卡片
- 右侧:状态流转时间线
- 下方:物料明细表格
- 底部:操作按钮组(发货/完成/撤回)
**订单编辑页:**
- 分步表单或标签页
- 基本信息区
- 物料明细区(可增删改)
- 实时金额计算
#### 3.5.3 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 高保真设计稿 | 所有P0页面 | Figma |
| 交互原型 | 可点击演示 | Figma/Axure |
| 设计标注 | 尺寸/颜色/字体 | Figma Dev Mode |
| 切图资源 | 1x/2x/3x | Figma Export |
#### 3.5.4 质量验收标准
- [ ] 所有P0页面高保真设计完成
- [ ] 交互原型可完整演示核心流程
- [ ] 视觉还原度与shipping_system≥90%
- [ ] 设计稿已通过产品/开发评审
---
### 3.6 阶段六交互细节设计2-3天
#### 3.6.1 工作内容
1. **微交互设计**
- 按钮悬停效果(边框变色+文字变色)
- 表格行悬停效果(背景色变化)
- 加载状态设计
- 空状态设计
2. **动效规范**
- 页面切换过渡(建议:淡入淡出 200ms
- Modal弹窗动画建议缩放+淡入 150ms
- 数据刷新动画
- 状态变化动画
3. **反馈机制**
- 成功提示绿色Toast
- 错误提示红色Toast
- 警告提示橙色Alert
- 加载提示Loading Spinner
4. **键盘快捷键**
- Ctrl+S 保存
- ESC 关闭弹窗
- Enter 确认
- / 聚焦搜索框
#### 3.6.2 交互规范文档
```
interactions/
├── hover-states.md # 悬停状态规范
├── transitions.md # 转场动画规范
├── feedback.md # 反馈机制规范
├── keyboard-shortcuts.md # 快捷键规范
└── accessibility.md # 无障碍规范
```
#### 3.6.3 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 交互规范文档 | 详细交互说明 | 文档工具 |
| 动效参数表 | 时长/缓动函数 | 表格 |
| 交互Demo | 关键交互演示 | Figma/视频 |
#### 3.6.4 质量验收标准
- [ ] 所有交互有明确的触发条件和反馈
- [ ] 动效时长符合人体工程学150-300ms
- [ ] 无障碍访问支持(键盘/屏幕阅读器)
- [ ] 性能影响已评估(避免重绘/重排)
---
### 3.7 阶段七用户测试与反馈优化3-5天
#### 3.7.1 工作内容
1. **可用性测试**
- 测试对象目标用户5-8人
- 测试任务:
- 创建新客户
- 创建新订单
- 查询客户历史订单
- 变更订单状态
- 测试指标:
- 任务完成率
- 完成时间
- 错误率
- 满意度评分
2. **A/B测试可选**
- 表格视图 vs 卡片视图
- 不同按钮位置
- 不同颜色方案
3. **反馈收集与优化**
- 整理测试问题
- 优先级排序
- 设计调整
- 二次验证
#### 3.7.2 测试检查清单
| 检查项 | 测试方法 | 通过标准 |
|-------|---------|---------|
| 核心任务完成率 | 用户测试 | ≥90% |
| 首次使用无帮助完成 | 用户测试 | ≥70% |
| 页面加载时间 | 性能测试 | ≤2s |
| 交互响应时间 | 性能测试 | ≤100ms |
| 视觉一致性 | 设计走查 | 无偏差 |
| 代码实现一致性 | 开发走查 | 还原度≥95% |
#### 3.7.3 交付物
| 交付物 | 标准 | 工具 |
|-------|------|------|
| 测试报告 | 问题清单+改进建议 | 文档工具 |
| 优化后的设计稿 | 修改后的高保真 | Figma |
| 设计走查记录 | 开发还原度检查 | 表格 |
#### 3.7.4 质量验收标准
- [ ] 核心任务完成率≥90%
- [ ] 严重问题已修复
- [ ] 开发还原度≥95%
- [ ] 产品/业务方验收通过
---
## 四、实施建议
### 4.1 技术实现建议
#### 4.1.1 前端技术栈
| 技术 | 用途 | 说明 |
|-----|------|------|
| Vue 2 | 框架 | 与现有项目保持一致 |
| Element UI | 组件库 | 基于shipping_system风格定制主题 |
| SCSS | 样式 | 使用CSS变量实现主题 |
| ECharts | 图表 | 统一图表风格 |
#### 4.1.2 样式实现策略
```scss
// 主题变量文件styles/theme.scss
:root {
// 提取shipping_system配色
--smg-bg: #1a1c1e;
--smg-bg-card: #222426;
--smg-bg-input: #2a2d30;
--smg-border: #3a3d41;
--smg-text: #c8ccd0;
--smg-text-secondary: #8a8f96;
--smg-primary: #e8b400;
--smg-success: #27ae60;
--smg-warning: #e67e22;
--smg-danger: #e74c3c;
--smg-info: #3498db;
}
// Element UI主题覆盖
.el-button--primary {
background-color: var(--smg-primary);
border-color: var(--smg-primary);
color: #000;
font-weight: 700;
}
.el-table {
background-color: var(--smg-bg-card);
th {
background-color: var(--smg-bg-input);
border-bottom: 2px solid var(--smg-primary);
text-transform: uppercase;
font-size: 11px;
letter-spacing: 0.5px;
}
}
```
### 4.2 组件复用建议
| shipping_system组件 | 当前项目对应 | 复用方式 |
|-------------------|-------------|---------|
| 顶部导航栏 | 现有侧边栏+顶部 | 提取配色方案 |
| 数据表格 | Element Table | 定制主题样式 |
| Modal弹窗 | Element Dialog | 定制暗黑主题 |
| 状态Badge | Element Tag | 定制颜色方案 |
| 统计卡片 | 自定义组件 | 复用布局结构 |
| 搜索框 | Element Input | 添加搜索图标 |
| 表单布局 | Element Form | 复用2列布局 |
### 4.3 开发排期建议
| 阶段 | 工期 | 并行任务 |
|-----|------|---------|
| 需求分析 | 3-5天 | 技术预研 |
| 信息架构 | 2-3天 | - |
| 线框图 | 3-5天 | 接口开发 |
| 视觉规范 | 2-3天 | 组件开发 |
| 高保真原型 | 5-7天 | 后端开发 |
| 交互细节 | 2-3天 | 前端开发 |
| 测试优化 | 3-5天 | 联调测试 |
**总计20-30天**
---
## 五、附录
### 5.1 参考资源
- shipping_system demo路径`/shipping_system/static/index.html`
- 当前项目前端路径:`/ruoyi-ui/src/views/bid/`
- 设计规范文档:`/doc/design-system/`
### 5.2 术语表
| 术语 | 说明 |
|-----|------|
| SMG | Shipping Management System 发货管理系统 |
| RFQ | Request for Quotation 询价单 |
| SKU | Stock Keeping Unit 库存单位 |
| P0/P1/P2 | 优先级// |
### 5.3 版本记录
| 版本 | 日期 | 修改内容 | 作者 |
|-----|------|---------|------|
| v1.0 | 2026-06-03 | 初始版本 | AI Assistant |
---
**文档结束**

View File

@@ -0,0 +1,67 @@
package com.ruoyi.web.controller.bid;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bid.BizClient;
import com.ruoyi.system.service.bid.IBizClientService;
@RestController
@RequestMapping("/bid/client")
public class BizClientController extends BaseController {
@Autowired
private IBizClientService service;
@PreAuthorize("@ss.hasPermi('bid:client:list')")
@GetMapping("/list")
public TableDataInfo list(BizClient query) {
startPage();
List<BizClient> list = service.selectBizClientList(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('bid:client:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(service.selectBizClientById(id));
}
@PreAuthorize("@ss.hasPermi('bid:client:add')")
@Log(title = "甲方客户", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BizClient record) {
record.setCreateBy(getUsername());
return toAjax(service.insertBizClient(record));
}
@PreAuthorize("@ss.hasPermi('bid:client:edit')")
@Log(title = "甲方客户", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody BizClient record) {
record.setUpdateBy(getUsername());
return toAjax(service.updateBizClient(record));
}
@PreAuthorize("@ss.hasPermi('bid:client:remove')")
@Log(title = "甲方客户", businessType = BusinessType.DELETE)
@DeleteMapping("/{clientIds}")
public AjaxResult remove(@PathVariable Long[] clientIds) {
return toAjax(service.deleteBizClientByIds(clientIds));
}
/**
* 查询客户的关联历史发货单
* 链路: client → client_quote → rfq → delivery_order
*/
@PreAuthorize("@ss.hasPermi('bid:client:query')")
@GetMapping("/{clientId}/orders")
public AjaxResult clientOrders(@PathVariable Long clientId) {
return success(service.selectClientDeliveryOrders(clientId));
}
}

View File

@@ -0,0 +1,57 @@
package com.ruoyi.web.controller.bid;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bid.BizDeliveryOrder;
import com.ruoyi.system.service.bid.IBizDeliveryOrderService;
@RestController
@RequestMapping("/bid/delivery")
public class BizDeliveryOrderController extends BaseController {
@Autowired
private IBizDeliveryOrderService service;
@PreAuthorize("@ss.hasPermi('bid:order:pending') or @ss.hasPermi('bid:order:transit') or @ss.hasPermi('bid:order:history')")
@GetMapping("/list")
public TableDataInfo list(BizDeliveryOrder query) {
startPage();
List<BizDeliveryOrder> list = service.selectBizDeliveryOrderList(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('bid:order:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(service.selectBizDeliveryOrderById(id));
}
@PreAuthorize("@ss.hasPermi('bid:order:status')")
@Log(title = "发货管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BizDeliveryOrder record) {
record.setCreateBy(getUsername());
return toAjax(service.insertBizDeliveryOrder(record));
}
@PreAuthorize("@ss.hasPermi('bid:order:status')")
@Log(title = "发货管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody BizDeliveryOrder record) {
record.setUpdateBy(getUsername());
return toAjax(service.updateBizDeliveryOrder(record));
}
@PreAuthorize("@ss.hasPermi('bid:order:pending')")
@Log(title = "发货管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{doIds}")
public AjaxResult remove(@PathVariable Long[] doIds) {
return toAjax(service.deleteBizDeliveryOrderByIds(doIds));
}
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.system.domain.bid;
import com.ruoyi.common.core.domain.BaseEntity;
public class BizClient extends BaseEntity {
private Long clientId;
private Long tenantId;
private String clientNo;
private String clientName;
private String contact;
private String phone;
private String email;
private String city;
private String address;
private String grade;
private String source;
private String status;
public Long getClientId() { return clientId; }
public void setClientId(Long clientId) { this.clientId = clientId; }
public Long getTenantId() { return tenantId; }
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
public String getClientNo() { return clientNo; }
public void setClientNo(String clientNo) { this.clientNo = clientNo; }
public String getClientName() { return clientName; }
public void setClientName(String clientName) { this.clientName = clientName; }
public String getContact() { return contact; }
public void setContact(String contact) { this.contact = contact; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getGrade() { return grade; }
public void setGrade(String grade) { this.grade = grade; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.system.domain.bid;
import com.ruoyi.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
public class BizDeliveryOrder extends BaseEntity {
private Long doId;
private Long tenantId;
private String doNo;
private Long rfqId;
private Long quotationId;
private Long supplierId;
private BigDecimal totalAmount;
private String currency;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date deliveryDate;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date delayDate;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualCloseDate;
private String closeDateSetBy;
private String deliveryStatus;
private List<BizDeliveryOrderItem> items;
private String supplierName;
public Long getDoId() { return doId; }
public void setDoId(Long doId) { this.doId = doId; }
public Long getTenantId() { return tenantId; }
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
public String getDoNo() { return doNo; }
public void setDoNo(String doNo) { this.doNo = doNo; }
public Long getRfqId() { return rfqId; }
public void setRfqId(Long rfqId) { this.rfqId = rfqId; }
public Long getQuotationId() { return quotationId; }
public void setQuotationId(Long quotationId) { this.quotationId = quotationId; }
public Long getSupplierId() { return supplierId; }
public void setSupplierId(Long supplierId) { this.supplierId = supplierId; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Date getDeliveryDate() { return deliveryDate; }
public void setDeliveryDate(Date deliveryDate) { this.deliveryDate = deliveryDate; }
public Date getDelayDate() { return delayDate; }
public void setDelayDate(Date delayDate) { this.delayDate = delayDate; }
public Date getActualCloseDate() { return actualCloseDate; }
public void setActualCloseDate(Date actualCloseDate) { this.actualCloseDate = actualCloseDate; }
public String getCloseDateSetBy() { return closeDateSetBy; }
public void setCloseDateSetBy(String closeDateSetBy) { this.closeDateSetBy = closeDateSetBy; }
public String getDeliveryStatus() { return deliveryStatus; }
public void setDeliveryStatus(String deliveryStatus) { this.deliveryStatus = deliveryStatus; }
public List<BizDeliveryOrderItem> getItems() { return items; }
public void setItems(List<BizDeliveryOrderItem> items) { this.items = items; }
public String getSupplierName() { return supplierName; }
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.system.domain.bid;
import java.math.BigDecimal;
public class BizDeliveryOrderItem {
private Long itemId;
private Long doId;
private Long materialId;
private String materialName;
private String spec;
private String unit;
private BigDecimal quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private String remark;
public Long getItemId() { return itemId; }
public void setItemId(Long itemId) { this.itemId = itemId; }
public Long getDoId() { return doId; }
public void setDoId(Long doId) { this.doId = doId; }
public Long getMaterialId() { return materialId; }
public void setMaterialId(Long materialId) { this.materialId = materialId; }
public String getMaterialName() { return materialName; }
public void setMaterialName(String materialName) { this.materialName = materialName; }
public String getSpec() { return spec; }
public void setSpec(String spec) { this.spec = spec; }
public String getUnit() { return unit; }
public void setUnit(String unit) { this.unit = unit; }
public BigDecimal getQuantity() { return quantity; }
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
public BigDecimal getUnitPrice() { return unitPrice; }
public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
public BigDecimal getTotalPrice() { return totalPrice; }
public void setTotalPrice(BigDecimal totalPrice) { this.totalPrice = totalPrice; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizClient;
import java.util.List;
import java.util.Map;
public interface BizClientMapper {
List<BizClient> selectBizClientList(BizClient query);
BizClient selectBizClientById(Long id);
int insertBizClient(BizClient record);
int updateBizClient(BizClient record);
int deleteBizClientById(Long id);
int deleteBizClientByIds(Long[] ids);
List<Map<String, Object>> selectClientDeliveryOrders(Long clientId);
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizDeliveryOrderItem;
import java.util.List;
public interface BizDeliveryOrderItemMapper {
List<BizDeliveryOrderItem> selectItemsByDoId(Long doId);
int insertBizDeliveryOrderItem(BizDeliveryOrderItem item);
int deleteByDoId(Long doId);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizDeliveryOrder;
import java.util.List;
public interface BizDeliveryOrderMapper {
List<BizDeliveryOrder> selectBizDeliveryOrderList(BizDeliveryOrder query);
BizDeliveryOrder selectBizDeliveryOrderById(Long id);
int insertBizDeliveryOrder(BizDeliveryOrder record);
int updateBizDeliveryOrder(BizDeliveryOrder record);
int deleteBizDeliveryOrderById(Long id);
int deleteBizDeliveryOrderByIds(Long[] ids);
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.system.service.bid;
import com.ruoyi.system.domain.bid.BizClient;
import java.util.List;
import java.util.Map;
public interface IBizClientService {
List<BizClient> selectBizClientList(BizClient query);
BizClient selectBizClientById(Long id);
int insertBizClient(BizClient record);
int updateBizClient(BizClient record);
int deleteBizClientById(Long id);
int deleteBizClientByIds(Long[] ids);
List<Map<String, Object>> selectClientDeliveryOrders(Long clientId);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.system.service.bid;
import com.ruoyi.system.domain.bid.BizDeliveryOrder;
import java.util.List;
public interface IBizDeliveryOrderService {
List<BizDeliveryOrder> selectBizDeliveryOrderList(BizDeliveryOrder query);
BizDeliveryOrder selectBizDeliveryOrderById(Long id);
int insertBizDeliveryOrder(BizDeliveryOrder record);
int updateBizDeliveryOrder(BizDeliveryOrder record);
int deleteBizDeliveryOrderById(Long id);
int deleteBizDeliveryOrderByIds(Long[] ids);
}

View File

@@ -0,0 +1,50 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.system.domain.bid.BizClient;
import com.ruoyi.system.mapper.bid.BizClientMapper;
import com.ruoyi.system.service.bid.IBizClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class BizClientServiceImpl implements IBizClientService {
@Autowired
private BizClientMapper mapper;
@Override
public List<BizClient> selectBizClientList(BizClient query) {
return mapper.selectBizClientList(query);
}
@Override
public BizClient selectBizClientById(Long id) {
return mapper.selectBizClientById(id);
}
@Override
public int insertBizClient(BizClient record) {
return mapper.insertBizClient(record);
}
@Override
public int updateBizClient(BizClient record) {
return mapper.updateBizClient(record);
}
@Override
public int deleteBizClientById(Long id) {
return mapper.deleteBizClientById(id);
}
@Override
public int deleteBizClientByIds(Long[] ids) {
return mapper.deleteBizClientByIds(ids);
}
@Override
public List<Map<String, Object>> selectClientDeliveryOrders(Long clientId) {
return mapper.selectClientDeliveryOrders(clientId);
}
}

View File

@@ -0,0 +1,89 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.system.domain.bid.BizDeliveryOrder;
import com.ruoyi.system.domain.bid.BizDeliveryOrderItem;
import com.ruoyi.system.mapper.bid.BizDeliveryOrderMapper;
import com.ruoyi.system.mapper.bid.BizDeliveryOrderItemMapper;
import com.ruoyi.system.service.bid.IBizDeliveryOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Service
public class BizDeliveryOrderServiceImpl implements IBizDeliveryOrderService {
@Autowired
private BizDeliveryOrderMapper mapper;
@Autowired
private BizDeliveryOrderItemMapper itemMapper;
@Override
public List<BizDeliveryOrder> selectBizDeliveryOrderList(BizDeliveryOrder query) {
return mapper.selectBizDeliveryOrderList(query);
}
@Override
public BizDeliveryOrder selectBizDeliveryOrderById(Long id) {
BizDeliveryOrder d = mapper.selectBizDeliveryOrderById(id);
if (d != null) d.setItems(itemMapper.selectItemsByDoId(id));
return d;
}
@Override
@Transactional
public int insertBizDeliveryOrder(BizDeliveryOrder record) {
if (record.getDoNo() == null || record.getDoNo().isEmpty()) {
record.setDoNo("DO" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
}
if (record.getDeliveryStatus() == null || record.getDeliveryStatus().isEmpty()) {
record.setDeliveryStatus("pending");
}
int rows = mapper.insertBizDeliveryOrder(record);
if (record.getItems() != null) {
BigDecimal total = BigDecimal.ZERO;
for (BizDeliveryOrderItem item : record.getItems()) {
item.setDoId(record.getDoId());
if (item.getUnitPrice() != null && item.getQuantity() != null) {
item.setTotalPrice(item.getUnitPrice().multiply(item.getQuantity()));
total = total.add(item.getTotalPrice());
}
itemMapper.insertBizDeliveryOrderItem(item);
}
record.setTotalAmount(total);
mapper.updateBizDeliveryOrder(record);
}
return rows;
}
@Override
@Transactional
public int updateBizDeliveryOrder(BizDeliveryOrder record) {
itemMapper.deleteByDoId(record.getDoId());
if (record.getItems() != null) {
BigDecimal total = BigDecimal.ZERO;
for (BizDeliveryOrderItem item : record.getItems()) {
item.setDoId(record.getDoId());
if (item.getUnitPrice() != null && item.getQuantity() != null) {
item.setTotalPrice(item.getUnitPrice().multiply(item.getQuantity()));
total = total.add(item.getTotalPrice());
}
itemMapper.insertBizDeliveryOrderItem(item);
}
record.setTotalAmount(total);
}
return mapper.updateBizDeliveryOrder(record);
}
@Override
public int deleteBizDeliveryOrderById(Long id) {
return mapper.deleteBizDeliveryOrderById(id);
}
@Override
public int deleteBizDeliveryOrderByIds(Long[] ids) {
return mapper.deleteBizDeliveryOrderByIds(ids);
}
}

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizClientMapper">
<resultMap id="BaseRM" type="com.ruoyi.system.domain.bid.BizClient">
<id property="clientId" column="client_id"/>
<result property="tenantId" column="tenant_id"/>
<result property="clientNo" column="client_no"/>
<result property="clientName" column="client_name"/>
<result property="contact" column="contact"/>
<result property="phone" column="phone"/>
<result property="email" column="email"/>
<result property="city" column="city"/>
<result property="address" column="address"/>
<result property="grade" column="grade"/>
<result property="source" column="source"/>
<result property="status" column="status"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<select id="selectBizClientList" resultMap="BaseRM">
SELECT * FROM biz_client
<where>
<if test="tenantId != null"> AND tenant_id=#{tenantId}</if>
<if test="clientName != null and clientName != ''">
AND (client_name LIKE CONCAT('%',#{clientName},'%')
OR client_no LIKE CONCAT('%',#{clientName},'%')
OR contact LIKE CONCAT('%',#{clientName},'%'))
</if>
<if test="status != null and status != ''"> AND status=#{status}</if>
</where>
ORDER BY client_id DESC
</select>
<select id="selectBizClientById" resultMap="BaseRM">
SELECT * FROM biz_client WHERE client_id=#{id}
</select>
<insert id="insertBizClient" useGeneratedKeys="true" keyProperty="clientId">
INSERT INTO biz_client(tenant_id,client_no,client_name,contact,phone,email,city,address,grade,source,status,create_by,create_time)
VALUES(#{tenantId},#{clientNo},#{clientName},#{contact},#{phone},#{email},#{city},#{address},#{grade},#{source},#{status},#{createBy},NOW())
</insert>
<update id="updateBizClient">
UPDATE biz_client
<set>
<if test="clientNo != null">client_no=#{clientNo},</if>
<if test="clientName != null">client_name=#{clientName},</if>
<if test="contact != null">contact=#{contact},</if>
<if test="phone != null">phone=#{phone},</if>
<if test="email != null">email=#{email},</if>
<if test="city != null">city=#{city},</if>
<if test="address != null">address=#{address},</if>
<if test="grade != null">grade=#{grade},</if>
<if test="source != null">source=#{source},</if>
<if test="status != null">status=#{status},</if>
update_by=#{updateBy}, update_time=NOW()
</set>
WHERE client_id=#{clientId}
</update>
<delete id="deleteBizClientById">DELETE FROM biz_client WHERE client_id=#{id}</delete>
<delete id="deleteBizClientByIds">
DELETE FROM biz_client WHERE client_id IN
<foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<select id="selectClientDeliveryOrders" resultType="java.util.Map">
SELECT d.do_no,
d.delivery_date,
d.delay_date,
d.actual_close_date,
d.delivery_status,
d.total_amount,
s.supplier_name,
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS item_count
FROM biz_client_quote cq
JOIN biz_rfq r ON r.client_quote_id = cq.quote_id
JOIN biz_delivery_order d ON d.rfq_id = r.rfq_id
LEFT JOIN biz_supplier s ON d.supplier_id = s.supplier_id
WHERE cq.client_id = #{clientId}
ORDER BY d.create_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizDeliveryOrderItemMapper">
<resultMap id="BaseRM" type="com.ruoyi.system.domain.bid.BizDeliveryOrderItem">
<id property="itemId" column="item_id"/>
<result property="doId" column="do_id"/>
<result property="materialId" column="material_id"/>
<result property="materialName" column="material_name"/>
<result property="spec" column="spec"/>
<result property="unit" column="unit"/>
<result property="quantity" column="quantity"/>
<result property="unitPrice" column="unit_price"/>
<result property="totalPrice" column="total_price"/>
<result property="remark" column="remark"/>
</resultMap>
<select id="selectItemsByDoId" resultMap="BaseRM">
SELECT * FROM biz_delivery_order_item WHERE do_id=#{doId}
</select>
<insert id="insertBizDeliveryOrderItem" useGeneratedKeys="true" keyProperty="itemId">
INSERT INTO biz_delivery_order_item(do_id,material_id,material_name,spec,unit,quantity,unit_price,total_price,remark)
VALUES(#{doId},#{materialId},#{materialName},#{spec},#{unit},#{quantity},#{unitPrice},#{totalPrice},#{remark})
</insert>
<delete id="deleteByDoId">DELETE FROM biz_delivery_order_item WHERE do_id=#{doId}</delete>
</mapper>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizDeliveryOrderMapper">
<resultMap id="BaseRM" type="com.ruoyi.system.domain.bid.BizDeliveryOrder">
<id property="doId" column="do_id"/>
<result property="tenantId" column="tenant_id"/>
<result property="doNo" column="do_no"/>
<result property="rfqId" column="rfq_id"/>
<result property="quotationId" column="quotation_id"/>
<result property="supplierId" column="supplier_id"/>
<result property="totalAmount" column="total_amount"/>
<result property="currency" column="currency"/>
<result property="deliveryDate" column="delivery_date"/>
<result property="delayDate" column="delay_date"/>
<result property="actualCloseDate" column="actual_close_date"/>
<result property="closeDateSetBy" column="close_date_set_by"/>
<result property="deliveryStatus" column="delivery_status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="supplierName" column="supplier_name"/>
</resultMap>
<select id="selectBizDeliveryOrderList" resultMap="BaseRM">
SELECT d.*, s.supplier_name
FROM biz_delivery_order d
LEFT JOIN biz_supplier s ON d.supplier_id=s.supplier_id
<where>
<if test="tenantId != null"> AND d.tenant_id=#{tenantId}</if>
<if test="doNo != null and doNo != ''"> AND d.do_no LIKE CONCAT('%',#{doNo},'%')</if>
<if test="supplierId != null"> AND d.supplier_id=#{supplierId}</if>
<if test="deliveryStatus != null and deliveryStatus != ''"> AND d.delivery_status=#{deliveryStatus}</if>
<if test="supplierName != null and supplierName != ''"> AND s.supplier_name LIKE CONCAT('%',#{supplierName},'%')</if>
</where>
ORDER BY d.create_time DESC
</select>
<select id="selectBizDeliveryOrderById" resultMap="BaseRM">
SELECT d.*, s.supplier_name
FROM biz_delivery_order d
LEFT JOIN biz_supplier s ON d.supplier_id=s.supplier_id
WHERE d.do_id=#{id}
</select>
<insert id="insertBizDeliveryOrder" useGeneratedKeys="true" keyProperty="doId">
INSERT INTO biz_delivery_order(tenant_id,do_no,rfq_id,quotation_id,supplier_id,total_amount,currency,delivery_date,delay_date,actual_close_date,close_date_set_by,delivery_status,remark,create_by,create_time)
VALUES(#{tenantId},#{doNo},#{rfqId},#{quotationId},#{supplierId},#{totalAmount},#{currency},#{deliveryDate},#{delayDate},#{actualCloseDate},#{closeDateSetBy},#{deliveryStatus},#{remark},#{createBy},NOW())
</insert>
<update id="updateBizDeliveryOrder">
UPDATE biz_delivery_order
<set>
<if test="doNo != null">do_no=#{doNo},</if>
<if test="supplierId != null">supplier_id=#{supplierId},</if>
<if test="totalAmount != null">total_amount=#{totalAmount},</if>
<if test="deliveryDate != null">delivery_date=#{deliveryDate},</if>
<if test="delayDate != null">delay_date=#{delayDate},</if>
<if test="actualCloseDate != null">actual_close_date=#{actualCloseDate},</if>
<if test="closeDateSetBy != null">close_date_set_by=#{closeDateSetBy},</if>
<if test="deliveryStatus != null">delivery_status=#{deliveryStatus},</if>
<if test="remark != null">remark=#{remark},</if>
update_by=#{updateBy}, update_time=NOW()
</set>
WHERE do_id=#{doId}
</update>
<delete id="deleteBizDeliveryOrderById">DELETE FROM biz_delivery_order WHERE do_id=#{id}</delete>
<delete id="deleteBizDeliveryOrderByIds">
DELETE FROM biz_delivery_order WHERE do_id IN
<foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,8 @@
import request from '@/utils/request'
const baseUrl = '/bid/client'
export const listClient = (params) => request({ url: baseUrl + '/list', method: 'get', params })
export const getClient = (id) => request({ url: baseUrl + '/' + id, method: 'get' })
export const addClient = (data) => request({ url: baseUrl, method: 'post', data })
export const updateClient = (data) => request({ url: baseUrl, method: 'put', data })
export const delClient = (ids) => request({ url: baseUrl + '/' + ids, method: 'delete' })
export const getClientOrders = (id) => request({ url: baseUrl + '/' + id + '/orders', method: 'get' })

View File

@@ -0,0 +1,11 @@
import request from '@/utils/request'
const baseUrl = '/bid/delivery'
export const listDelivery = (params) => request({ url: baseUrl + '/list', method: 'get', params })
export const getDelivery = (id) => request({ url: baseUrl + '/' + id, method: 'get' })
export const addDelivery = (data) => request({ url: baseUrl, method: 'post', data })
export const updateDelivery = (data) => request({ url: baseUrl, method: 'put', data })
export const delDelivery = (ids) => request({ url: baseUrl + '/' + ids, method: 'delete' })
export const shipDelivery = (id) => request({ url: baseUrl + '/' + id + '/ship', method: 'put' })
export const completeDelivery = (id) => request({ url: baseUrl + '/' + id + '/complete', method: 'put' })
export const recallDelivery = (id) => request({ url: baseUrl + '/' + id + '/recall', method: 'put' })
export const setCloseDate = (id, closeDate) => request({ url: baseUrl + '/' + id + '/closeDate', method: 'put', params: { closeDate } })

View File

@@ -176,6 +176,19 @@ export const dynamicRoutes = [
meta: { title: '甲方报价单详情', activeMenu: '/clientquote' }
}]
},
// ── 甲方客户 ──
{
path: '/bid/client',
component: Layout,
permissions: ['bid:client:list'],
children: [{
path: '',
component: () => import('@/views/bid/client/index'),
name: 'Client',
meta: { title: '甲方客户', activeMenu: '/bid/client' }
}]
},
{
path: '/bid/comparison/detail',
component: Layout,

View File

@@ -0,0 +1,350 @@
<template>
<div class="app-container">
<el-tabs v-model="activeTab">
<!-- Tab 1: 客户列表 -->
<el-tab-pane label="客户列表" name="list">
<div class="toolbar">
<el-input
v-model="queryParams.clientName"
placeholder="搜索名称/编号/联系人"
size="small"
clearable
style="width:320px"
prefix-icon="el-icon-search"
@keyup.enter.native="handleSearch"
/>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增客户</el-button>
</div>
<el-table v-loading="loading" :data="clientList" border size="small" style="width:100%">
<el-table-column label="编号" prop="clientNo" width="100" />
<el-table-column label="名称" prop="clientName" min-width="160" />
<el-table-column label="联系人" prop="contact" width="100" />
<el-table-column label="电话" prop="phone" width="130" />
<el-table-column label="城市" prop="city" width="110" />
<el-table-column label="订单数" prop="orderCount" width="80" align="center" />
<el-table-column label="备注" prop="remark" min-width="120" :show-overflow-tooltip="true" />
<el-table-column label="操作" width="140" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</el-tab-pane>
<!-- Tab 2: 历史发货单 -->
<el-tab-pane label="历史发货单" name="orders">
<div class="toolbar">
<el-select v-model="orderClientId" filterable placeholder="选择甲方客户" style="width:400px" @change="loadClientOrders" clearable>
<el-option v-for="c in clientOptions" :key="c.clientId" :label="c.clientNo + ' | ' + c.clientName" :value="c.clientId" />
</el-select>
<span v-if="orderClientName" style="margin-left:12px;color:#909399;font-size:12px"> {{ orderList.length }} 条记录</span>
</div>
<el-table v-loading="orderLoading" :data="orderList" border size="small" style="width:100%">
<el-table-column label="发货单号" prop="doNo" width="150" />
<el-table-column label="供应商" prop="supplierName" min-width="140" />
<el-table-column label="金额" width="130" align="right">
<template slot-scope="scope">¥{{ scope.row.totalAmount }}</template>
</el-table-column>
<el-table-column label="交货期" prop="deliveryDate" width="100" />
<el-table-column label="结单日期" prop="actualCloseDate" width="100" />
<el-table-column label="物料数" prop="itemCount" width="70" align="center" />
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.deliveryStatus)" size="small">{{ statusLabel(scope.row.deliveryStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="showOrderDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!orderClientId && !orderLoading" description="请先选择甲方客户" />
<el-empty v-if="orderClientId && !orderList.length && !orderLoading" description="该客户暂无发货记录" />
</el-tab-pane>
</el-tabs>
<!-- 新增/编辑客户弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="600px" append-to-body @close="cancelDialog">
<el-form ref="form" :model="form" :rules="rules" label-width="90px" size="small">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="客户编号" prop="clientNo">
<el-input v-model="form.clientNo" placeholder="如 CU-001" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户名称" prop="clientName">
<el-input v-model="form.clientName" placeholder="客户企业名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人" prop="contact">
<el-input v-model="form.contact" placeholder="联系人姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="手机号/固话" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="电子邮箱" prop="email">
<el-input v-model="form.email" placeholder="电子邮箱" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在城市" prop="city">
<el-input v-model="form.city" placeholder="如 广东深圳" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="详细地址" prop="address">
<el-input v-model="form.address" placeholder="详细地址" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="客户等级" prop="grade">
<el-select v-model="form.grade" style="width:100%">
<el-option label="A级" value="A" />
<el-option label="B级" value="B" />
<el-option label="C级" value="C" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" style="width:100%">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="cancelDialog">取消</el-button>
<el-button type="primary" @click="submitForm">保存</el-button>
</div>
</el-dialog>
<!-- 发货单详情弹窗 -->
<el-dialog title="发货单详情" :visible.sync="detailOpen" width="780px" append-to-body>
<div v-if="detailData" class="detail-card">
<div class="detail-section">
<table class="detail-table">
<tr>
<td class="dt-label">发货单号</td><td class="dt-value"><b>{{ detailData.doNo }}</b></td>
<td class="dt-label">供应商</td><td class="dt-value">{{ detailData.supplierName }}</td>
</tr>
<tr>
<td class="dt-label">总金额</td><td class="dt-value" style="color:#409EFF;font-weight:700">¥{{ detailData.totalAmount }}</td>
<td class="dt-label">状态</td>
<td class="dt-value">
<el-tag :type="statusType(detailData.deliveryStatus)" size="small">{{ statusLabel(detailData.deliveryStatus) }}</el-tag>
</td>
</tr>
<tr>
<td class="dt-label">交货期</td><td class="dt-value">{{ detailData.deliveryDate || '-' }}</td>
<td class="dt-label">结单日期</td><td class="dt-value">{{ detailData.actualCloseDate || '-' }}</td>
</tr>
<tr v-if="detailData.remark">
<td class="dt-label">备注</td><td class="dt-value" colspan="3">{{ detailData.remark }}</td>
</tr>
</table>
</div>
<div class="detail-section">
<div class="section-title">物料明细</div>
<el-table :data="detailData.items || []" border size="small" style="width:100%">
<el-table-column label="物料名称" prop="materialName" min-width="140" />
<el-table-column label="规格" prop="spec" width="120" />
<el-table-column label="单位" prop="unit" width="60" />
<el-table-column label="数量" prop="quantity" width="80" align="right" />
<el-table-column label="单价" width="100" align="right">
<template slot-scope="s">¥{{ s.row.unitPrice }}</template>
</el-table-column>
<el-table-column label="小计" width="100" align="right">
<template slot-scope="s">¥{{ s.row.totalPrice }}</template>
</el-table-column>
</el-table>
</div>
</div>
<div slot="footer">
<el-button @click="detailOpen = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listClient, getClient, addClient, updateClient, delClient, getClientOrders } from "@/api/bid/client"
import { getDelivery } from "@/api/bid/delivery"
export default {
name: "Client",
data() {
return {
// ── Tab ──
activeTab: "list",
// ── 客户列表 ──
loading: false,
clientList: [],
total: 0,
queryParams: { pageNum: 1, pageSize: 20, clientName: "" },
// ── 新增/编辑 ──
dialogOpen: false,
dialogTitle: "",
form: { grade: "B", status: "0" },
rules: {
clientNo: [{ required: true, message: "客户编号不能为空", trigger: "blur" }],
clientName: [{ required: true, message: "客户名称不能为空", trigger: "blur" }]
},
editId: null,
// ── 历史发货单 ──
orderClientId: null,
orderClientName: "",
orderLoading: false,
orderList: [],
clientOptions: [],
// ── 发货单详情 ──
detailOpen: false,
detailData: null
}
},
created() {
this.getList()
this.loadClientOptions()
},
methods: {
// ═══════════ 客户列表 ═══════════
getList() {
this.loading = true
listClient(this.queryParams).then(r => {
this.clientList = r.rows || []
this.total = r.total || 0
this.loading = false
}).catch(() => { this.loading = false })
},
handleSearch() {
this.queryParams.pageNum = 1
this.getList()
this.loadClientOptions()
},
// ═══════════ 新增/编辑 ═══════════
handleAdd() {
this.editId = null
this.form = { grade: "B", status: "0", clientNo: "", clientName: "", contact: "", phone: "", email: "", city: "", address: "", remark: "" }
this.dialogTitle = "新增客户"
this.dialogOpen = true
},
handleEdit(row) {
this.editId = row.clientId
this.form = { ...row }
this.dialogTitle = "编辑客户"
this.dialogOpen = true
},
cancelDialog() {
this.dialogOpen = false
this.$refs.form && this.$refs.form.clearValidate()
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
const action = this.editId ? updateClient(this.form) : addClient(this.form)
action.then(() => {
this.$modal.msgSuccess(this.editId ? "修改成功" : "新增成功")
this.dialogOpen = false
this.getList()
}).catch(() => {})
})
},
// ═══════════ 删除 ═══════════
handleDelete(row) {
this.$modal.confirm('确认删除客户 "' + row.clientName + '"').then(() => {
delClient(row.clientId).then(() => {
this.$modal.msgSuccess("删除成功")
this.getList()
})
}).catch(() => {})
},
// ═══════════ 历史发货单 ═══════════
loadClientOptions() {
listClient({ pageNum: 1, pageSize: 999 }).then(r => {
this.clientOptions = r.rows || []
}).catch(() => {})
},
loadClientOrders(clientId) {
if (!clientId) {
this.orderList = []
this.orderClientName = ""
return
}
this.orderLoading = true
this.orderList = []
// 获取客户名用于显示
const c = this.clientOptions.find(o => o.clientId === clientId)
this.orderClientName = c ? c.clientName : ""
getClientOrders(clientId).then(r => {
this.orderList = (r.data || []).map(o => ({
...o,
totalAmount: o.totalAmount || o.total_amount,
deliveryDate: o.deliveryDate || o.delivery_date,
actualCloseDate: o.actualCloseDate || o.actual_close_date,
deliveryStatus: o.deliveryStatus || o.delivery_status,
itemCount: o.itemCount || o.item_count
}))
this.orderLoading = false
}).catch(() => { this.orderLoading = false })
},
// ═══════════ 发货单详情 ═══════════
showOrderDetail(row) {
getDelivery(row.doId || row.do_id).then(r => {
this.detailData = r.data
this.detailOpen = true
}).catch(() => {})
},
// ═══════════ 工具方法 ═══════════
statusType(s) {
return { pending: "warning", transit: "primary", history: "success" }[s] || ""
},
statusLabel(s) {
return { pending: "待发", transit: "在途", history: "已收货" }[s] || s || "-"
}
}
}
</script>
<style scoped>
.app-container { background: #fff; padding: 20px; border-radius: 4px; min-height: calc(100vh - 104px); }
.toolbar { margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.detail-card { padding: 0; }
.detail-section { margin-bottom: 20px; }
.section-title { font-size: 14px; font-weight: 700; color: #1a2c4e; margin-bottom: 10px; padding-left: 8px; border-left: 4px solid #1171c4; }
.detail-table { width: 100%; border-collapse: collapse; }
.detail-table td { padding: 8px 12px; border: 1px solid #e4e7ed; }
.dt-label { background: #f5f7fa; color: #606266; font-weight: 600; width: 90px; font-size: 12px; }
.dt-value { color: #303133; font-size: 13px; }
</style>

114
sql/fix_menu_all.sql Normal file
View File

@@ -0,0 +1,114 @@
-- ============================================================
-- 菜单全面修复脚本
-- 1. 修复所有乱码菜单名称(根据 perms 字段还原)
-- 2. 新增菜单移至主目录parent_id=0因 智慧报价(2000) 已停用
-- 3. 插入缺失的菜单条目
-- ============================================8===============
SET NAMES utf8mb4;
-- ════════════════════════════════════════════════════════════
-- 第一部分:修复所有乱码菜单名称
-- ════════════════════════════════════════════════════════════
-- bid 模块主菜单parent_id=0 且无 perms 的父节点)
UPDATE sys_menu SET menu_name = '智慧报价' WHERE menu_id = 2000;
-- bid 模块子菜单
UPDATE sys_menu SET menu_name = '物料管理' WHERE perms = 'bid:material:list';
UPDATE sys_menu SET menu_name = '新增' WHERE perms = 'bid:material:add' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '编辑' WHERE perms = 'bid:material:edit' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '删除' WHERE perms = 'bid:material:remove' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '查询' WHERE perms = 'bid:material:query' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '供应商管理' WHERE perms = 'bid:supplier:list';
UPDATE sys_menu SET menu_name = '供应商报价' WHERE perms = 'bid:quotation:list';
UPDATE sys_menu SET menu_name = '报价请求' WHERE perms = 'bid:rfq:list';
UPDATE sys_menu SET menu_name = '智慧比价' WHERE perms = 'bid:comparison:list';
UPDATE sys_menu SET menu_name = '采购单' WHERE perms = 'bid:purchaseorder:list';
UPDATE sys_menu SET menu_name = '供应商评价' WHERE perms = 'bid:evaluation:list';
UPDATE sys_menu SET menu_name = '订单异议' WHERE perms = 'bid:objection:list';
UPDATE sys_menu SET menu_name = '交易记录' WHERE perms = 'bid:transaction:list';
UPDATE sys_menu SET menu_name = '租户管理' WHERE perms = 'bid:tenant:list';
-- 甲方报价单
UPDATE sys_menu SET menu_name = '甲方报价' WHERE perms = 'bid:clientquote:list';
UPDATE sys_menu SET menu_name = '新增报价' WHERE perms = 'bid:clientquote:add' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '编辑报价' WHERE perms = 'bid:clientquote:edit' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '删除报价' WHERE perms = 'bid:clientquote:remove' AND menu_type = 'F';
-- 统计分析
UPDATE sys_menu SET menu_name = '统计分析' WHERE perms = 'bid:report:list';
UPDATE sys_menu SET menu_name = '采购总览看板' WHERE perms = 'bid:report:dashboard';
UPDATE sys_menu SET menu_name = '采购成本分析' WHERE perms = 'bid:report:cost';
UPDATE sys_menu SET menu_name = '供应商绩效' WHERE perms = 'bid:report:supplier';
-- ════════════════════════════════════════════════════════════
-- 第二部分:修复我们的新菜单名称
-- ════════════════════════════════════════════════════════════
UPDATE sys_menu SET menu_name = '订单履约' WHERE perms = 'bid:order:list';
UPDATE sys_menu SET menu_name = '待发订单' WHERE perms = 'bid:order:pending';
UPDATE sys_menu SET menu_name = '历史订单' WHERE perms = 'bid:order:history';
UPDATE sys_menu SET menu_name = '结单时间管理' WHERE perms = 'bid:order:closeDate';
UPDATE sys_menu SET menu_name = '甲方客户' WHERE perms = 'bid:client:list';
UPDATE sys_menu SET menu_name = '编辑客户' WHERE perms = 'bid:client:edit' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '删除客户' WHERE perms = 'bid:client:remove' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '结单录入' WHERE perms = 'bid:order:closeDate:edit' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '操作记录' WHERE perms = 'bid:operationlog:list';
-- ════════════════════════════════════════════════════════════
-- 第三部分将新菜单移至主目录parent_id=0
-- 因为 智慧报价(2000) 已停用,其下的子菜单不可见
-- ════════════════════════════════════════════════════════════
-- 订单履约(2023) → 根目录排序20
UPDATE sys_menu SET parent_id = 0, order_num = 20 WHERE menu_id = 2023;
-- 甲方客户(2028) → 根目录排序21
UPDATE sys_menu SET parent_id = 0, order_num = 21 WHERE menu_id = 2028;
-- 操作记录(2029) → 根目录排序22
UPDATE sys_menu SET parent_id = 0, order_num = 22 WHERE menu_id = 2029;
-- ════════════════════════════════════════════════════════════
-- 第四部分:插入缺失的菜单条目
-- ════════════════════════════════════════════════════════════
-- 在途订单 (2025)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2025, '在途订单', 2023, 2, 'transit', 'bid/order/transit', 1, 0, 'C', '0', '0', 'bid:order:transit', 'truck', 'admin', NOW());
-- 新增客户按钮 (2030)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2030, '新增客户', 2028, 1, '#', NULL, 1, 0, 'F', '0', '0', 'bid:client:add', '#', 'admin', NOW());
-- 查询客户按钮 (2033)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2033, '查询客户', 2028, 4, '#', NULL, 1, 0, 'F', '0', '0', 'bid:client:query', '#', 'admin', NOW());
-- 订单查询按钮 (2034)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2034, '订单查询', 2024, 1, '#', NULL, 1, 0, 'F', '0', '0', 'bid:order:query', '#', 'admin', NOW());
-- 订单状态变更按钮 (2035)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2035, '订单状态变更', 2024, 2, '#', NULL, 1, 0, 'F', '0', '0', 'bid:order:status', '#', 'admin', NOW());
-- 操作记录查询按钮 (2037)
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES(2037, '操作记录查询', 2029, 1, '#', NULL, 1, 0, 'F', '0', '0', 'bid:operationlog:query', '#', 'admin', NOW());
-- ════════════════════════════════════════════════════════════
-- 第五部分:角色-菜单关联(确保 admin 有权限)
-- ════════════════════════════════════════════════════════════
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu_id FROM sys_menu WHERE menu_id IN (2025, 2030, 2033, 2034, 2035, 2037);
-- ════════════════════════════════════════════════════════════
-- 验证
-- ════════════════════════════════════════════════════════════
SELECT menu_id, menu_name, parent_id, perms, order_num,
CASE WHEN status='0' THEN '正常' ELSE '停用' END AS status
FROM sys_menu WHERE menu_id BETWEEN 2000 AND 2037
ORDER BY menu_id;

30
sql/fix_menu_garbled.sql Normal file
View File

@@ -0,0 +1,30 @@
SET NAMES utf8mb4;
-- 修复菜单名称乱码问题
-- 根据截图中显示的权限字符,还原正确的菜单名称
-- 订单管理相关菜单
UPDATE sys_menu SET menu_name = '订单管理' WHERE perms = 'bid:order:list' AND menu_name LIKE '%璁㈠%';
UPDATE sys_menu SET menu_name = '待发订单' WHERE perms = 'bid:order:pending' AND menu_name LIKE '%寰%';
UPDATE sys_menu SET menu_name = '在途订单' WHERE perms = 'bid:order:transit' AND menu_name LIKE '%鍦ㄩ%';
UPDATE sys_menu SET menu_name = '历史订单' WHERE perms = 'bid:order:history' AND menu_name LIKE '%鍘嗗彶%';
UPDATE sys_menu SET menu_name = '结单时间' WHERE perms = 'bid:order:closeDate' AND menu_name LIKE '%缁撳崟%';
-- 甲方客户管理
UPDATE sys_menu SET menu_name = '甲方客户' WHERE perms = 'bid:client:list' AND menu_name LIKE '%鐢插彛%';
-- 操作记录
UPDATE sys_menu SET menu_name = '操作记录' WHERE perms = 'bid:operationlog:list' AND menu_name LIKE '%鎿嶄綔%';
UPDATE sys_menu SET menu_name = '查询' WHERE perms = 'bid:operationlog:query' AND menu_name LIKE '%鏌ヨ%';
-- 如果上述更新没有匹配到使用menu_id直接更新根据截图中的排序号判断
-- 订单管理目录排序号10
UPDATE sys_menu SET menu_name = '订单管理' WHERE menu_id IN (
SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE parent_id = 0 AND order_num = 10) AS t
);
-- 查询更新结果
SELECT menu_id, menu_name, perms, order_num
FROM sys_menu
WHERE perms LIKE 'bid:order:%' OR perms LIKE 'bid:client:%' OR perms LIKE 'bid:operationlog:%'
ORDER BY menu_id;

View File

@@ -0,0 +1,74 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================
-- 菜单名称乱码修复脚本
-- 根据权限字符(perms)和菜单类型还原正确的菜单名称
-- ============================================
-- 1. 订单管理模块
UPDATE sys_menu SET menu_name = '订单管理' WHERE perms = 'bid:order:list';
UPDATE sys_menu SET menu_name = '待发订单' WHERE perms = 'bid:order:pending';
UPDATE sys_menu SET menu_name = '在途订单' WHERE perms = 'bid:order:transit';
UPDATE sys_menu SET menu_name = '历史订单' WHERE perms = 'bid:order:history';
UPDATE sys_menu SET menu_name = '结单时间' WHERE perms = 'bid:order:closeDate';
-- 2. 甲方客户管理
UPDATE sys_menu SET menu_name = '甲方客户' WHERE perms = 'bid:client:list';
-- 3. 操作记录
UPDATE sys_menu SET menu_name = '操作记录' WHERE perms = 'bid:operationlog:list';
UPDATE sys_menu SET menu_name = '查询' WHERE perms = 'bid:operationlog:query' AND menu_type = 'F';
-- 4. 甲方报价(如果有乱码)
UPDATE sys_menu SET menu_name = '甲方报价' WHERE perms = 'bid:clientquote:list';
UPDATE sys_menu SET menu_name = '新增' WHERE perms = 'bid:clientquote:add' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '编辑' WHERE perms = 'bid:clientquote:edit' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '删除' WHERE perms = 'bid:clientquote:remove' AND menu_type = 'F';
UPDATE sys_menu SET menu_name = '导出' WHERE perms = 'bid:clientquote:export' AND menu_type = 'F';
-- 5. 报价请求RFQ
UPDATE sys_menu SET menu_name = '报价请求' WHERE perms = 'bid:rfq:list';
-- 6. 供应商管理
UPDATE sys_menu SET menu_name = '供应商管理' WHERE perms = 'bid:supplier:list';
-- 7. 物料管理
UPDATE sys_menu SET menu_name = '物料管理' WHERE perms = 'bid:material:list';
-- 8. 智慧比价
UPDATE sys_menu SET menu_name = '智慧比价' WHERE perms = 'bid:comparison:list';
-- 9. 统计分析
UPDATE sys_menu SET menu_name = '统计分析' WHERE perms = 'bid:report:list';
-- 10. 采购单
UPDATE sys_menu SET menu_name = '采购单' WHERE perms = 'bid:purchaseorder:list';
-- 11. 供应商评价
UPDATE sys_menu SET menu_name = '供应商评价' WHERE perms = 'bid:evaluation:list';
-- 12. 交易记录
UPDATE sys_menu SET menu_name = '交易记录' WHERE perms = 'bid:transaction:list';
-- ============================================
-- 验证修复结果
-- ============================================
SELECT
menu_id,
menu_name,
perms,
menu_type,
CASE menu_type
WHEN 'M' THEN '目录'
WHEN 'C' THEN '菜单'
WHEN 'F' THEN '按钮'
END AS type_name,
order_num,
status
FROM sys_menu
WHERE perms LIKE 'bid:%'
ORDER BY menu_id;
SET FOREIGN_KEY_CHECKS = 1;

13
sql/fix_remaining.sql Normal file
View File

@@ -0,0 +1,13 @@
SET NAMES utf8mb4;
UPDATE sys_menu SET menu_name = '在途订单' WHERE perms = 'bid:order:transit';
UPDATE sys_menu SET menu_name = '新增客户' WHERE menu_id = 2030;
UPDATE sys_menu SET menu_name = '查询客户' WHERE menu_id = 2033;
UPDATE sys_menu SET menu_name = '订单查询' WHERE menu_id = 2034;
UPDATE sys_menu SET menu_name = '订单状态变更' WHERE menu_id = 2035;
UPDATE sys_menu SET menu_name = '操作记录查询' WHERE menu_id = 2037;
SELECT '✅ 修复结果' AS '';
SELECT menu_id, menu_name, perms FROM sys_menu
WHERE menu_id IN (2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037)
ORDER BY menu_id;