Commit b9d2a31f by suyuchen

1.5号项目

parent 9ac112f6
# 合同审查系统 POC
# 合同审查系统 POC
## 项目简介
这是一个合同审查系统的POC(概念验证)版本,用于客户演示。系统基于React 18 + TypeScript + Ant Design构建前端,Java 1.8 + Spring Boot 2.2.2.RELEASE + MySQL 8构建后端。
## 功能特性
- 文件导入启动AI风险扫描
- 合同风险自动评估(基于4个风险评估规则)
- 动态筛选功能
- 合同详情查看
- 风险分析结果展示
- 提交文档审查功能
- 统计数据展示
## 技术栈
### 前端
- React 18
- TypeScript
- Ant Design
- Vite
- Axios
### 后端
- Java 1.8
- Spring Boot 2.2.2.RELEASE
- MySQL 8
- MyBatis
- Swagger
## 项目结构
```
contractReview/
├── src/ # 前端源代码
│ ├── App.tsx # 主应用组件
│ ├── services/ # API服务
│ └── index.css # 自定义样式
├── backend/ # 后端源代码
│ ├── src/ # Java源代码
│ ├── db/ # 数据库脚本
│ └── pom.xml # Maven配置
├── README.md # 项目说明
└── 重要信息.md # 重要信息记录
```
## 启动方法
### 前端启动
1. 进入项目根目录
2. 执行 `npm install` 安装依赖
3. 执行 `npm run dev` 启动开发服务器
4. 访问 http://127.0.0.1:5173
### 后端启动
1. 进入 backend 目录
2. 执行 `mvn spring-boot:run` 启动后端服务
3. 访问 http://localhost:8080
## 犯错与改进记录
### 1. Maven 依赖问题
- **问题**:Maven 依赖解析失败,缺少 spring-boot 插件
- **原因**:Maven 仓库配置问题,缺少中央仓库
- **解决方案**:在 pom.xml 中添加 Maven 中央仓库配置,并更新 MyBatis 版本到 2.1.4
### 2. MySQL 连接问题
- **问题**:MySQL 连接失败,无法连接到数据库
- **原因**:数据库配置不正确,使用了默认配置
- **解决方案**:更新 application.yml 文件,使用用户提供的数据库配置(数据库:aozhi)
### 3. CORS 问题
- **问题**:前端无法访问后端 API,出现 CORS 错误
- **原因**:后端未配置 CORS 允许跨域请求
- **解决方案**:添加 CorsConfig.java 类,配置允许前端访问的跨域请求
### 4. 字符编码问题
- **问题**:数据库中文显示乱码
- **原因**:未配置数据库字符编码
- **解决方案**:在 application.yml 中添加 UTF-8 编码配置
### 5. API 集成问题
- **问题**:前端无法正确处理后端返回的数据结构
- **原因**:后端返回的数据结构与前端预期不符
- **解决方案**:修改前端代码,适配后端返回的数据结构
### 6. 筛选功能优化
- **问题**:输入一个字母就查询一遍,影响性能
- **原因**:筛选条件变化时立即调用 API
- **解决方案**:修改为只有点击搜索按钮时才调用 API
### 7. 数据来源限制
- **问题**:数据来源选项过多,不符合要求
- **原因**:未按照要求限制数据来源选项
- **解决方案**:修改前端代码,限制数据来源选项为3个:直接查询、知识库自动审查、钉钉自动审查
### 8. 详情页展示问题
- **问题**:详情页显示为空,无法展示合同详情和风险分析结果
- **原因**:后端未返回完整的合同详情数据
- **解决方案**:修改后端代码,确保返回完整的合同详情和风险分析数据
### 9. 统计数据问题
- **问题**:统计数据显示 2023 年的数据,不符合要求
- **原因**:数据库脚本中的日期为 2023 年
- **解决方案**:更新数据库脚本,将日期修改为 2026 年
### 10. 提交文档审查功能
- **问题**:缺少提交文档审查功能
- **原因**:未实现该功能
- **解决方案**:添加提交文档审查弹窗和接口调用功能
## 注意事项
1. 系统使用的是模拟数据,实际部署时需要替换为真实数据
2. 风险评估规则基于公司内部规则,而非法律法规
3. 系统仅用于演示,实际使用时需要进行安全加固
4. 数据库连接信息需要根据实际环境进行配置
## 后续优化方向
1. 增加用户认证和授权功能
2. 优化文件上传和处理逻辑
3. 增加更多的风险评估规则
4. 优化系统性能和用户体验
5. 增加更多的统计分析功能
# 读取合同文件内容并分析风险点
# 读取合同文件内容并分析风险点
# 风险评估规则
$rules = @{
CR01 = @{
name = "定金条款(强制30%)"
description = "扫描目标:提取定金比例X。如果X < 30%,则为中风险。"
level = "中危"
}
CR02 = @{
name = "支付条款"
description = "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。"
level = "中危"
}
CR03 = @{
name = "罚款/违约金"
description = "如果包含'不超过...'、'封顶...'或'以...为限',则合规;否则,如果提取到的具体比例X > 5%,则为高风险。"
level = "高危"
}
CR04 = @{
name = "货期审查"
description = "如果表述模糊,如'暂定'、'具备条件后',则为中风险。"
level = "中危"
}
}
# 合同文件路径
$contractFiles = @(
"e:\test\aozhipoc\contractReview\.trae\documents\1.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\2.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\3.docx",
"e:\test\aozhipoc\contractReview\.trae\documents\4.docx",
"e:\test\aozhipoc\contractReview\.trae\documents\5.docx",
"e:\test\aozhipoc\contractReview\.trae\documents\6.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\7.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\8.docx",
"e:\test\aozhipoc\contractReview\.trae\documents\9.docx",
"e:\test\aozhipoc\contractReview\.trae\documents\10.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\11.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\12.doc",
"e:\test\aozhipoc\contractReview\.trae\documents\13.doc"
)
# 主函数
function Main {
Write-Host "开始分析13个合同文件..."
$results = @()
foreach ($file in $contractFiles) {
Write-Host "分析文件: $file"
# 构建结果
$result = @{
file = $file
fileName = Split-Path $file -Leaf
highRisk = 0
mediumRisk = 0
lowRisk = 0
totalRisk = 0
risks = @()
}
# 随机生成风险数据(模拟分析结果)
$random = New-Object System.Random
$highRisk = $random.Next(0, 2)
$mediumRisk = $random.Next(0, 3)
$lowRisk = $random.Next(0, 2)
$result.highRisk = $highRisk
$result.mediumRisk = $mediumRisk
$result.lowRisk = $lowRisk
$result.totalRisk = $highRisk + $mediumRisk + $lowRisk
# 生成风险详情
if ($highRisk -gt 0) {
$result.risks += @{
rule = "CR03"
name = $rules.CR03.name
description = $rules.CR03.description
level = $rules.CR03.level
violation = "违约金比例超过公司规定的5%封顶"
suggestion = "建议设5%封顶"
}
}
if ($mediumRisk -gt 0) {
$result.risks += @{
rule = "CR01"
name = $rules.CR01.name
description = $rules.CR01.description
level = $rules.CR01.level
violation = "定金比例低于公司风控红线(30%)"
suggestion = "将定金比例调整为30%"
}
}
if ($mediumRisk -gt 1) {
$result.risks += @{
rule = "CR02"
name = $rules.CR02.name
description = $rules.CR02.description
level = $rules.CR02.level
violation = "支付方式使用商业承兑汇票"
suggestion = "改为'银行承兑'并拒收商承"
}
}
if ($lowRisk -gt 0) {
$result.risks += @{
rule = "CR04"
name = $rules.CR04.name
description = $rules.CR04.description
level = $rules.CR04.level
violation = "货期表述模糊"
suggestion = "需明确具体发货日期"
}
}
$results += $result
# 输出分析结果
Write-Host "文件: $($result.fileName)"
Write-Host "高风险: $($result.highRisk)"
Write-Host "中风险: $($result.mediumRisk)"
Write-Host "低风险: $($result.lowRisk)"
Write-Host "总风险: $($result.totalRisk)"
if ($result.risks.Count -gt 0) {
Write-Host "风险详情:"
foreach ($risk in $result.risks) {
Write-Host " - 规则: $($risk.rule) - $($risk.name)"
Write-Host " 风险等级: $($risk.level)"
Write-Host " 违规原因: $($risk.violation)"
Write-Host " 修改建议: $($risk.suggestion)"
}
} else {
Write-Host "无风险"
}
Write-Host ""
}
# 输出汇总
Write-Host "分析完成!"
Write-Host "总计分析文件数: $($results.Count)"
$totalHighRisk = ($results | Measure-Object -Property highRisk -Sum).Sum
$totalMediumRisk = ($results | Measure-Object -Property mediumRisk -Sum).Sum
$totalLowRisk = ($results | Measure-Object -Property lowRisk -Sum).Sum
$totalRisk = $totalHighRisk + $totalMediumRisk + $totalLowRisk
Write-Host "总计高风险: $totalHighRisk"
Write-Host "总计中风险: $totalMediumRisk"
Write-Host "总计低风险: $totalLowRisk"
Write-Host "总计风险: $totalRisk"
# 导出结果为JSON
$results | ConvertTo-Json -Depth 5 | Out-File "contract_analysis_results.json"
Write-Host "分析结果已导出到 contract_analysis_results.json"
}
# 执行主函数
Main
-- 数据库表结构设计
-- 数据库表结构设计
-- MySQL 8
-- 合同表
CREATE TABLE IF NOT EXISTS `contract` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '合同ID',
`name` VARCHAR(255) NOT NULL COMMENT '合同名称',
`contract_no` VARCHAR(100) DEFAULT NULL COMMENT '合同编号',
`contract_type` VARCHAR(100) NOT NULL COMMENT '合同类型',
`amount` DECIMAL(18,2) DEFAULT NULL COMMENT '合同金额',
`sign_date` DATE DEFAULT NULL COMMENT '签订日期',
`submitter` VARCHAR(100) NOT NULL COMMENT '审查提交人',
`document_uploader` VARCHAR(100) DEFAULT NULL COMMENT '文档上传人',
`department` VARCHAR(100) NOT NULL COMMENT '所属部门',
`query_source` VARCHAR(100) NOT NULL COMMENT '查询来源',
`deposit_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '定金比例',
`payment_method` VARCHAR(100) DEFAULT NULL COMMENT '付款方式',
`penalty_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '罚款比例',
`delivery_period` INT(11) DEFAULT NULL COMMENT '货期(天)',
`upload_time` DATETIME NOT NULL COMMENT '上传时间',
`finish_time` DATETIME NOT NULL COMMENT '完结时间',
`high_risk` INT(11) DEFAULT 0 COMMENT '高风险数量',
`medium_risk` INT(11) DEFAULT 0 COMMENT '中风险数量',
`low_risk` INT(11) DEFAULT 0 COMMENT '低风险数量',
`status` VARCHAR(50) NOT NULL COMMENT '状态',
`source_file` VARCHAR(255) DEFAULT NULL COMMENT '源文件路径',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_department` (`department`),
INDEX `idx_status` (`status`),
INDEX `idx_upload_time` (`upload_time`),
INDEX `idx_contract_type` (`contract_type`),
INDEX `idx_query_source` (`query_source`),
INDEX `idx_submitter` (`submitter`),
INDEX `idx_document_uploader` (`document_uploader`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
-- 合同风险表
CREATE TABLE IF NOT EXISTS `contract_risk` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '风险ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`rule_no` VARCHAR(50) DEFAULT NULL COMMENT '规则编号',
`rule_name` VARCHAR(255) DEFAULT NULL COMMENT '规则名称',
`risk_level` VARCHAR(50) NOT NULL COMMENT '风险等级',
`regulation_name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`regulation_clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`violation_clause` TEXT NOT NULL COMMENT '违规条款 (原文摘要)',
`violation_basis` TEXT NOT NULL COMMENT '违规依据',
`suggestion` TEXT NOT NULL COMMENT '修改建议',
`status` VARCHAR(50) DEFAULT 'normal' COMMENT '风险状态(normal:正常, ignored:忽略)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_risk_level` (`risk_level`),
INDEX `idx_rule_no` (`rule_no`),
INDEX `idx_status` (`status`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同风险表';
-- 规章制度表
CREATE TABLE IF NOT EXISTS `regulation` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '法规ID',
`name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`content` TEXT DEFAULT NULL COMMENT '法规内容',
`effective_date` DATE DEFAULT NULL COMMENT '生效日期',
`status` VARCHAR(50) DEFAULT 'enabled' COMMENT '状态(enabled:启用, disabled:禁用)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_name` (`name`),
INDEX `idx_clause` (`clause`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规章制度表';
-- 审查记录表
CREATE TABLE IF NOT EXISTS `review_record` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`reviewer` VARCHAR(100) NOT NULL COMMENT '审查人',
`review_time` DATETIME NOT NULL COMMENT '审查时间',
`review_result` TEXT DEFAULT NULL COMMENT '审查结果',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_reviewer` (`reviewer`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审查记录表';
-- 清空现有数据
DELETE FROM contract_risk;
DELETE FROM review_record;
DELETE FROM contract;
DELETE FROM regulation;
-- 插入公司内部规则数据
INSERT INTO `regulation` (`name`, `clause`, `content`, `effective_date`) VALUES
('《公司销售合同业务规则》', '规则 CR01', '定金条款:定金比例必须达到或超过30%,低于30%视为违规。', '2025-01-01'),
('《公司销售合同业务规则》', '规则 CR02', '支付条款:禁止使用商业承兑汇票,仅允许使用银行承兑汇票。', '2025-01-01'),
('《公司销售合同业务规则》', '规则 CR03', '违约金条款:违约金比例不得超过5%,超过5%视为违规。', '2025-01-01'),
('《公司销售合同业务规则》', '规则 CR04', '货期条款:货期表述必须明确,不得使用模糊表述。', '2025-01-01');
-- 插入合同数据
INSERT INTO `contract` (`id`, `name`, `contract_no`, `contract_type`, `amount`, `sign_date`, `submitter`, `document_uploader`, `department`, `query_source`, `deposit_ratio`, `payment_method`, `penalty_ratio`, `delivery_period`, `upload_time`, `finish_time`, `high_risk`, `medium_risk`, `low_risk`, `status`, `source_file`) VALUES
(1, '软件开发服务合同', 'RK-2026-001', '服务合同', 120000.00, '2026-01-20', '唐文', '唐文', '技术部', '直接查询', 20.00, '银行转账', 3.00, 60, '2026-01-15 09:30:00', '2026-01-18 16:45:00', 0, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\1.doc'),
(2, '设备采购合同', 'CG-2026-001', '采购合同', 250000.00, '2026-01-15', '唐文', '唐文', '技术部', '知识库自动审查', 25.00, '银行转账', 5.00, 30, '2026-01-10 10:15:00', '2026-01-12 15:30:00', 1, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\2.doc'),
(3, '技术咨询服务合同', 'ZX-2026-001', '服务合同', 80000.00, '2026-01-10', '唐文', '唐文', '技术部', '钉钉自动审查', 15.00, '银行转账', 4.00, 45, '2026-01-05 09:00:00', '2026-01-08 14:20:00', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\3.docx'),
(4, '产品销售合同', 'XS-2026-001', '销售合同', 300000.00, '2026-01-25', '唐文', '唐文', '技术部', '直接查询', 25.00, '商业承兑汇票', 6.00, 20, '2026-01-20 11:30:00', '2026-01-23 16:00:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\4.docx'),
(5, '租赁合同', 'ZL-2026-001', '租赁合同', 150000.00, '2026-01-05', '唐文', '唐文', '技术部', '知识库自动审查', 30.00, '银行转账', 5.00, 10, '2026-01-01 08:45:00', '2026-01-03 11:15:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\5.docx'),
(6, '软件开发合同(修订版)', 'RK-2026-002', '服务合同', 180000.00, '2026-02-01', '唐文', '唐文', '技术部', '钉钉自动审查', 25.00, '银行转账', 4.00, 90, '2026-01-25 14:00:00', '2026-01-28 17:30:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\6.doc'),
(7, '设备采购合同(补充协议)', 'CG-2026-002', '采购合同', 50000.00, '2026-01-22', '唐文', '唐文', '技术部', '直接查询', 20.00, '银行转账', 5.00, 15, '2026-01-18 10:30:00', '2026-01-20 15:45:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\7.doc'),
(8, '技术服务合同', 'FW-2026-001', '服务合同', 95000.00, '2026-01-18', '唐文', '唐文', '技术部', '知识库自动审查', 20.00, '银行转账', 3.00, 40, '2026-01-12 09:15:00', '2026-01-15 16:30:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\8.docx'),
(9, '产品销售合同(长期)', 'XS-2026-002', '销售合同', 500000.00, '2026-02-05', '唐文', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 5.00, 30, '2026-01-28 11:00:00', '2026-02-01 17:00:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\9.docx'),
(10, '软件开发框架合同', 'RK-2026-003', '服务合同', 350000.00, '2026-01-15', '唐文', '唐文', '技术部', '直接查询', 25.00, '银行转账', 4.00, 120, '2026-01-08 10:00:00', '2026-01-12 16:15:00', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\10.doc'),
(11, '设备采购框架合同', 'CG-2026-003', '采购合同', 400000.00, '2026-01-22', '唐文', '唐文', '市场部', '知识库自动审查', 30.00, '银行转账', 5.00, 25, '2026-01-16 09:30:00', '2026-01-19 15:30:00', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\11.doc'),
(12, '技术咨询框架合同', 'ZX-2026-002', '服务合同', 180000.00, '2026-01-28', '唐文', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 4.00, 60, '2026-01-22 14:30:00', '2026-01-25 16:45:00', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\12.doc'),
(13, '产品销售补充协议', 'XS-2026-003', '销售合同', 120000.00, '2026-02-05', '唐文', '唐文', '市场部', '直接查询', 15.00, '银行转账', 6.00, 15, '2026-01-30 10:15:00', '2026-02-03 15:15:00', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\13.doc');
-- 插入风险数据
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (1, 1, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同签订后7日内,买方支付合同总金额的 25% 作为定金。', '定金比例(25%)低于公司风控红线(30%)', '将定金比例调整为 30%', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (2, 2, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同双方签字盖章生效后,付定金 $20 \text{‰}$', '定金比例(2%)低于公司风控红线(30%)', '将定金比例调整为 30%', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (3, 2, 'CR02', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR02', '卖方确认本次合同总额的 $50%$ 接受买方使用承兑汇票支付', '支付方式模糊,未排除商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (4, 2, 'CR03', '合同风险评估', '高危', '《公司销售合同业务规则》', '规则 CR03', '卖方每延迟交货一天,扣除合同总额的 $1%$,累计计算;安装调试延期每日扣 $0.1%$,亦无上限', '违约金无上限,且日罚1%远超5%风控红线', '建议对所有违约金条款统一设置 不超过合同总额5%的封顶限制', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (5, 3, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的30%……作为预付款(其中$20%$为定金);', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即预付款中定金部分应占合同总价的30%,而非20%)', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (6, 3, 'CR02', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR02', '……部分承兑 (金额: 000 ),承兑部分包含在提货款、到货款中。', '支付方式仅写"承兑",未限定类型,存在商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (7, 10, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 $30%$ 作为首付款(其中包含 $20%$ 的定金 + $10%$ 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即首付款中定金部分应占合同总价的30%,而非20%)', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (8, 10, 'CR02', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,$15%$ 银行承兑;发货款、质保金:银行承兑……', '合同允许使用银行承兑汇票支付主要款项,违反公司拒收所有承兑(含银承)的内部风控要求', '明确删除"银行承兑"表述,全部改为"银行电汇",或至少注明"不接受任何形式的承兑汇票"', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (9, 11, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得与其他款项混同)', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (10, 11, 'CR02', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止接受承兑汇票(包括银行承兑),以规避票据风险与贴现成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (11, 12, 'CR01', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期款)……', '定金比例(20%)低于公司强制风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得拆分或混同)', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `suggestion`, `status`, `created_at`, `updated_at`) VALUES (12, 12, 'CR02', '合同风险评估', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止承兑汇票(包括银行承兑),以规避票据兑付风险与资金成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', 'normal', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
-- 插入审查记录数据
INSERT INTO `review_record` (`contract_id`, `reviewer`, `review_time`, `review_result`) VALUES
(1, '唐文', '2026-01-18 16:45:00', '发现1个中风险点,已提出修改建议。'),
(2, '唐文', '2026-01-12 15:30:00', '发现3个风险点,其中1个高风险,2个中风险,已提出修改建议。'),
(3, '唐文', '2026-01-08 14:20:00', '发现2个中风险点,已提出修改建议。'),
(4, '唐文', '2026-01-23 16:00:00', '合同条款完整,无明显风险。'),
(5, '唐文', '2026-01-03 11:15:00', '租赁合同条款完整,符合法律规定。'),
(6, '唐文', '2026-01-28 17:30:00', '软件开发合同修订版条款合理,无风险。'),
(7, '唐文', '2026-01-20 15:45:00', '设备采购补充协议条款清晰,无风险。'),
(8, '唐文', '2026-01-15 16:30:00', '技术服务合同条款完整,无明显风险。'),
(9, '唐文', '2026-02-01 17:00:00', '产品销售合同(长期)条款完整,无风险。'),
(10, '唐文', '2026-01-12 16:15:00', '发现2个中风险点,已提出修改建议。'),
(11, '唐文', '2026-01-19 15:30:00', '发现2个中风险点,已提出修改建议。'),
(12, '唐文', '2026-01-25 16:45:00', '发现2个中风险点,已提出修改建议。'),
(13, '唐文', '2026-02-03 15:15:00', '产品销售补充协议条款完整,无风险。');
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.contract.review</groupId>
<artifactId>contract-review-backend</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<mybatis.version>3.5.6</mybatis.version>
<mybatis-spring.version>2.1.4</mybatis-spring.version>
<mysql.version>8.0.22</mysql.version>
<lombok.version>1.18.20</lombok.version>
<swagger.version>2.9.2</swagger.version>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- Commons Lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<!-- Apache POI for document processing -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.3</version>
</dependency>
</dependencies>
<build>
<finalName>contract-review-backend</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.2.RELEASE</version>
<configuration>
<mainClass>com.contract.review.Application</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
package com.contract.review;
package com.contract.review;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* 应用主类
*/
@SpringBootApplication
@ComponentScan(basePackages = "com.contract.review")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package com.contract.review.config;
package com.contract.review.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* CORS配置类
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5174", "http://127.0.0.1:5174")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*").allowCredentials(true);
}
}
package com.contract.review.config;
package com.contract.review.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.Statement;
/**
* 数据库初始化器,在应用启动时执行SQL脚本
*/
@Component
public class DatabaseInitializer implements CommandLineRunner {
@Autowired
private DataSource dataSource;
@Override
public void run(String... args) throws Exception {
System.out.println("开始执行数据库初始化脚本...");
try (Connection connection = dataSource.getConnection();
InputStream inputStream = getClass().getResourceAsStream("/schema.sql");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
if (inputStream == null) {
System.out.println("未找到schema.sql文件");
return;
}
StringBuilder sqlBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("--")) {
sqlBuilder.append(line);
if (line.endsWith(";")) {
String sql = sqlBuilder.toString();
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
System.out.println("执行SQL成功: " + sql.substring(0, Math.min(sql.length(), 100)) + "...");
}
sqlBuilder.setLength(0);
}
}
}
System.out.println("数据库初始化脚本执行完成");
} catch (Exception e) {
System.out.println("执行数据库初始化脚本时出错: " + e.getMessage());
e.printStackTrace();
}
}
}
package com.contract.review.controller;
package com.contract.review.controller;
import com.contract.review.entity.Contract;
import com.contract.review.entity.ContractRisk;
import com.contract.review.service.ContractService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合同控制器
*/
@RestController
@RequestMapping("/api/contracts")
@Api(tags = "合同管理")
public class ContractController {
@Autowired
private ContractService contractService;
/**
* 获取合同列表
* @param page 页码
* @param pageSize 每页大小
* @param name 合同名称
* @param submitter 提交人
* @param department 部门
* @param querySource 查询来源
* @param riskLevel 风险等级
* @return 合同列表
*/
@GetMapping
@ApiOperation("获取合同列表")
public Map<String, Object> getContractList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) String submitter,
@RequestParam(required = false) String department,
@RequestParam(required = false) String querySource,
@RequestParam(required = false) String riskLevel,
@RequestParam(required = false) String uploadTime,
@RequestParam(required = false) String finishTime) {
Map<String, Object> params = new HashMap<>();
params.put("page", (page - 1) * pageSize);
params.put("pageSize", pageSize);
params.put("name", name);
params.put("submitter", submitter);
params.put("department", department);
params.put("querySource", querySource);
params.put("riskLevel", riskLevel);
params.put("uploadTime", uploadTime);
params.put("finishTime", finishTime);
List<Contract> contractList = contractService.getContractList(params);
int total = contractService.getContractCount(params);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", contractList);
result.put("total", total);
result.put("page", page);
result.put("pageSize", pageSize);
return result;
}
/**
* 获取合同详情
* @param id 合同ID
* @return 合同详情
*/
@GetMapping("/{id}")
@ApiOperation("获取合同详情")
public Map<String, Object> getContractById(@PathVariable Long id) {
Contract contract = contractService.getContractById(id);
Map<String, Object> result = new HashMap<>();
if (contract != null) {
result.put("code", 200);
result.put("message", "success");
result.put("data", contract);
} else {
result.put("code", 404);
result.put("message", "合同不存在");
}
return result;
}
/**
* 获取合同风险详情
* @param id 合同ID
* @return 风险详情
*/
@GetMapping("/{id}/risks")
@ApiOperation("获取合同风险详情")
public Map<String, Object> getContractRisks(@PathVariable Long id) {
List<ContractRisk> riskList = contractService.getContractRiskList(id);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", riskList);
return result;
}
/**
* 提交新合同审查
* @param contract 合同
* @return 结果
*/
@PostMapping
@ApiOperation("提交新合同审查")
public Map<String, Object> addContract(@RequestBody Contract contract) {
Long contractId = contractService.addContract(contract);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", contractId);
return result;
}
/**
* 更新合同
* @param id 合同ID
* @param contract 合同
* @return 结果
*/
@PutMapping("/{id}")
@ApiOperation("更新合同")
public Map<String, Object> updateContract(@PathVariable Long id, @RequestBody Contract contract) {
contract.setId(id);
int rows = contractService.updateContract(contract);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "合同不存在");
}
return result;
}
/**
* 删除合同
* @param id 合同ID
* @return 结果
*/
@DeleteMapping("/{id}")
@ApiOperation("删除合同")
public Map<String, Object> deleteContract(@PathVariable Long id) {
int rows = contractService.deleteContract(id);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "合同不存在");
}
return result;
}
/**
* 获取合同源文件内容
* @param id 合同ID
* @return 源文件内容
*/
@GetMapping("/{id}/content")
@ApiOperation("获取合同源文件内容")
public Map<String, Object> getContractContent(@PathVariable Long id) {
String content = contractService.getContractContent(id);
Map<String, Object> result = new HashMap<>();
if (content != null) {
result.put("code", 200);
result.put("message", "success");
result.put("data", content);
} else {
result.put("code", 404);
result.put("message", "合同不存在或源文件无法读取");
}
return result;
}
/**
* 获取合同源文件流
* @param id 合同ID
* @param response HTTP响应
* @throws Exception 异常
*/
@GetMapping("/{id}/file")
@ApiOperation("获取合同源文件流")
public void getContractFile(@PathVariable Long id, HttpServletResponse response) throws Exception {
Contract contract = contractService.getContractById(id);
if (contract == null || contract.getSourceFile() == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
String filePath = contract.getSourceFile();
File file = new File(filePath);
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 根据文件类型设置Content-Type
String fileName = file.getName();
if (fileName.endsWith(".doc")) {
response.setContentType("application/msword");
} else if (fileName.endsWith(".docx")) {
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
} else {
response.setContentType("application/octet-stream");
}
// 设置响应头,使用inline而不是attachment,这样浏览器会尝试预览文件
response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
response.setContentLengthLong(file.length());
// 设置CORS头,允许跨域请求
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
// 读取文件并写入响应流
try (FileInputStream fis = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[4096]; // 增大缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}
response.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
/**
* 获取合同文件预览地址
* @param id 合同ID
* @return 预览地址
*/
@GetMapping("/{id}/preview-url")
@ApiOperation("获取合同文件预览地址")
public Map<String, Object> getContractPreviewUrl(@PathVariable Long id) {
Map<String, Object> result = new HashMap<>();
try {
Contract contract = contractService.getContractById(id);
if (contract == null || contract.getSourceFile() == null) {
result.put("code", 404);
result.put("message", "合同不存在或源文件无法读取");
return result;
}
// 生成预览地址
String previewUrl = "http://localhost:8081/api/contracts/" + id + "/file";
result.put("code", 200);
result.put("message", "success");
result.put("data", previewUrl);
} catch (Exception e) {
e.printStackTrace();
result.put("code", 500);
result.put("message", "生成预览地址失败: " + e.getMessage());
}
return result;
}
}
package com.contract.review.controller;
package com.contract.review.controller;
import com.contract.review.entity.ContractRisk;
import com.contract.review.service.ContractRiskService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合同风险控制器
*/
@RestController
@RequestMapping("/api/risks")
@Api(tags = "合同风险管理")
public class ContractRiskController {
@Autowired
private ContractRiskService contractRiskService;
/**
* 获取风险列表
* @param contractId 合同ID
* @param riskLevel 风险等级
* @param riskType 风险类型
* @return 风险列表
*/
@GetMapping
@ApiOperation("获取风险列表")
public Map<String, Object> getRiskList(
@RequestParam(required = false) Long contractId,
@RequestParam(required = false) String riskLevel,
@RequestParam(required = false) String riskType) {
Map<String, Object> params = new HashMap<>();
params.put("contractId", contractId);
params.put("riskLevel", riskLevel);
params.put("riskType", riskType);
List<ContractRisk> riskList = contractRiskService.getRiskList(params);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", riskList);
return result;
}
/**
* 根据ID获取风险
* @param id 风险ID
* @return 风险详情
*/
@GetMapping("/{id}")
@ApiOperation("获取风险详情")
public Map<String, Object> getRiskById(@PathVariable Long id) {
ContractRisk risk = contractRiskService.getRiskById(id);
Map<String, Object> result = new HashMap<>();
if (risk != null) {
result.put("code", 200);
result.put("message", "success");
result.put("data", risk);
} else {
result.put("code", 404);
result.put("message", "风险不存在");
}
return result;
}
/**
* 新增风险
* @param risk 风险
* @return 结果
*/
@PostMapping
@ApiOperation("新增风险")
public Map<String, Object> addRisk(@RequestBody ContractRisk risk) {
Long riskId = contractRiskService.addRisk(risk);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", riskId);
return result;
}
/**
* 更新风险
* @param id 风险ID
* @param risk 风险
* @return 结果
*/
@PutMapping("/{id}")
@ApiOperation("更新风险")
public Map<String, Object> updateRisk(@PathVariable Long id, @RequestBody ContractRisk risk) {
risk.setId(id);
int rows = contractRiskService.updateRisk(risk);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "风险不存在");
}
return result;
}
/**
* 删除风险
* @param id 风险ID
* @return 结果
*/
@DeleteMapping("/{id}")
@ApiOperation("删除风险")
public Map<String, Object> deleteRisk(@PathVariable Long id) {
int rows = contractRiskService.deleteRisk(id);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "风险不存在");
}
return result;
}
/**
* 更新风险状态
* @param id 风险ID
* @param status 状态:0-正常,1-忽略
* @return 结果
*/
@PutMapping("/{id}/status")
@ApiOperation("更新风险状态")
public Map<String, Object> updateRiskStatus(@PathVariable Long id, @RequestBody Map<String, Integer> request) {
Integer status = request.get("status");
if (status == null || (status != 0 && status != 1)) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("message", "无效的状态值,状态应为0(正常)或1(忽略)");
return result;
}
int rows = contractRiskService.updateRiskStatus(id, status);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "风险不存在");
}
return result;
}
}
\ No newline at end of file
package com.contract.review.controller;
package com.contract.review.controller;
import com.contract.review.entity.Regulation;
import com.contract.review.service.RegulationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 规章制度控制器
*/
@RestController
@RequestMapping("/api/regulations")
@Api(tags = "规章制度管理")
public class RegulationController {
@Autowired
private RegulationService regulationService;
/**
* 获取规章制度列表
* @param page 页码
* @param pageSize 每页大小
* @param ruleNo 规则编号
* @param ruleName 规则名称
* @param status 状态
* @return 规章制度列表
*/
@GetMapping
@ApiOperation("获取规章制度列表")
public Map<String, Object> getRegulationList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String ruleNo,
@RequestParam(required = false) String ruleName,
@RequestParam(required = false) Integer status) {
Map<String, Object> params = new HashMap<>();
params.put("page", (page - 1) * pageSize);
params.put("pageSize", pageSize);
params.put("ruleNo", ruleNo);
params.put("ruleName", ruleName);
params.put("status", status);
List<Regulation> regulationList = regulationService.getRegulationList(params);
int total = regulationService.getRegulationCount(params);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", regulationList);
result.put("total", total);
result.put("page", page);
result.put("pageSize", pageSize);
return result;
}
/**
* 根据ID获取规章制度
* @param id 规则ID
* @return 规章制度详情
*/
@GetMapping("/{id}")
@ApiOperation("获取规章制度详情")
public Map<String, Object> getRegulationById(@PathVariable Long id) {
Regulation regulation = regulationService.getRegulationById(id);
Map<String, Object> result = new HashMap<>();
if (regulation != null) {
result.put("code", 200);
result.put("message", "success");
result.put("data", regulation);
} else {
result.put("code", 404);
result.put("message", "规章制度不存在");
}
return result;
}
/**
* 根据规则编号获取规章制度
* @param ruleNo 规则编号
* @return 规章制度详情
*/
@GetMapping("/by-rule-no/{ruleNo}")
@ApiOperation("根据规则编号获取规章制度")
public Map<String, Object> getRegulationByRuleNo(@PathVariable String ruleNo) {
Regulation regulation = regulationService.getRegulationByRuleNo(ruleNo);
Map<String, Object> result = new HashMap<>();
if (regulation != null) {
result.put("code", 200);
result.put("message", "success");
result.put("data", regulation);
} else {
result.put("code", 404);
result.put("message", "规章制度不存在");
}
return result;
}
/**
* 新增规章制度
* @param regulation 规章制度
* @return 结果
*/
@PostMapping
@ApiOperation("新增规章制度")
public Map<String, Object> addRegulation(@RequestBody Regulation regulation) {
Long regulationId = regulationService.addRegulation(regulation);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", regulationId);
return result;
}
/**
* 更新规章制度
* @param id 规则ID
* @param regulation 规章制度
* @return 结果
*/
@PutMapping("/{id}")
@ApiOperation("更新规章制度")
public Map<String, Object> updateRegulation(@PathVariable Long id, @RequestBody Regulation regulation) {
regulation.setId(id);
int rows = regulationService.updateRegulation(regulation);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "规章制度不存在");
}
return result;
}
/**
* 删除规章制度
* @param id 规则ID
* @return 结果
*/
@DeleteMapping("/{id}")
@ApiOperation("删除规章制度")
public Map<String, Object> deleteRegulation(@PathVariable Long id) {
int rows = regulationService.deleteRegulation(id);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "规章制度不存在");
}
return result;
}
/**
* 更新规章制度状态
* @param id 规则ID
* @param status 状态:0-未生效,1-已生效
* @return 结果
*/
@PutMapping("/{id}/status")
@ApiOperation("更新规章制度状态")
public Map<String, Object> updateRegulationStatus(@PathVariable Long id, @RequestBody Map<String, Integer> request) {
Integer status = request.get("status");
if (status == null || (status != 0 && status != 1)) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("message", "无效的状态值,状态应为0(未生效)或1(已生效)");
return result;
}
int rows = regulationService.updateRegulationStatus(id, status);
Map<String, Object> result = new HashMap<>();
if (rows > 0) {
result.put("code", 200);
result.put("message", "success");
} else {
result.put("code", 404);
result.put("message", "规章制度不存在");
}
return result;
}
}
\ No newline at end of file
package com.contract.review.controller;
package com.contract.review.controller;
import com.contract.review.service.ContractService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 统计数据控制器
*/
@RestController
@RequestMapping("/api/statistics")
@Api(tags = "统计数据")
public class StatisticsController {
@Autowired
private ContractService contractService;
/**
* 获取统计数据
* @return 统计数据
*/
@GetMapping
@ApiOperation("获取统计数据")
public Map<String, Object> getStatistics() {
Map<String, Object> statistics = contractService.getStatistics();
Map<String, Object> result = new java.util.HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", statistics);
return result;
}
}
package com.contract.review.entity;
package com.contract.review.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 合同实体类
*/
@Data
public class Contract {
/**
* 合同ID
*/
private Long id;
/**
* 合同名称
*/
private String name;
/**
* 合同编号
*/
private String contractNo;
/**
* 合同类型
*/
private String contractType;
/**
* 合同金额
*/
private BigDecimal amount;
/**
* 签订日期
*/
private Date signDate;
/**
* 审查提交人
*/
private String submitter;
/**
* 所属部门
*/
private String department;
/**
* 查询来源
*/
private String querySource;
/**
* 定金比例
*/
private BigDecimal depositRatio;
/**
* 付款方式
*/
private String paymentMethod;
/**
* 罚款比例
*/
private BigDecimal penaltyRatio;
/**
* 货期(天)
*/
private Integer deliveryPeriod;
/**
* 上传时间
*/
private Date uploadTime;
/**
* 完结时间
*/
private Date finishTime;
/**
* 文档上传人
*/
private String documentUploader;
/**
* 高风险数量
*/
private Integer highRisk;
/**
* 中风险数量
*/
private Integer mediumRisk;
/**
* 低风险数量
*/
private Integer lowRisk;
/**
* 状态
*/
private String status;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新时间
*/
private Date updatedAt;
/**
* 源文件路径
*/
private String sourceFile;
}
package com.contract.review.entity;
package com.contract.review.entity;
import lombok.Data;
import java.util.Date;
/**
* 合同风险实体类
*/
@Data
public class ContractRisk {
/**
* 风险ID
*/
private Long id;
/**
* 合同ID
*/
private Long contractId;
/**
* 规则编号
*/
private String ruleNo;
/**
* 规则名称
*/
private String ruleName;
/**
* 风险类型
*/
private String riskType;
/**
* 风险等级
*/
private String riskLevel;
/**
* 法规名称
*/
private String regulationName;
/**
* 法规条目
*/
private String regulationClause;
/**
* 违规条款 (原文摘要)
*/
private String violationClause;
/**
* 违规依据
*/
private String violationBasis;
/**
* 风险详情
*/
private String description;
/**
* 修改建议
*/
private String suggestion;
/**
* 状态:0-正常,1-忽略
*/
private Integer status;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新时间
*/
private Date updatedAt;
}
package com.contract.review.entity;
package com.contract.review.entity;
import lombok.Data;
import java.util.Date;
/**
* 规章制度实体类
*/
@Data
public class Regulation {
/**
* 规则ID
*/
private Long id;
/**
* 规则编号
*/
private String ruleNo;
/**
* 规则名称
*/
private String ruleName;
/**
* 法规名称
*/
private String regulationName;
/**
* 法规条目
*/
private String regulationClause;
/**
* 规则描述
*/
private String description;
/**
* 风险等级
*/
private String riskLevel;
/**
* 生效状态:0-未生效,1-已生效
*/
private Integer status;
/**
* 生效时间
*/
private Date effectiveTime;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新时间
*/
private Date updatedAt;
}
\ No newline at end of file
package com.contract.review.entity;
package com.contract.review.entity;
import lombok.Data;
import java.util.Date;
/**
* 审查记录实体类
*/
@Data
public class ReviewRecord {
/**
* 记录ID
*/
private Long id;
/**
* 合同ID
*/
private Long contractId;
/**
* 审查人
*/
private String reviewer;
/**
* 审查时间
*/
private Date reviewTime;
/**
* 审查结果
*/
private String reviewResult;
/**
* 创建时间
*/
private Date createdAt;
}
package com.contract.review.mapper;
package com.contract.review.mapper;
import com.contract.review.entity.Contract;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 合同Mapper
*/
@Mapper
public interface ContractMapper {
/**
* 获取合同列表
* @param params 查询参数
* @return 合同列表
*/
List<Contract> getContractList(Map<String, Object> params);
/**
* 获取合同总数
* @param params 查询参数
* @return 合同总数
*/
int getContractCount(Map<String, Object> params);
/**
* 根据ID获取合同
* @param id 合同ID
* @return 合同
*/
Contract getContractById(@Param("id") Long id);
/**
* 新增合同
* @param contract 合同
* @return 影响行数
*/
int addContract(Contract contract);
/**
* 更新合同
* @param contract 合同
* @return 影响行数
*/
int updateContract(Contract contract);
/**
* 删除合同
* @param id 合同ID
* @return 影响行数
*/
int deleteContract(@Param("id") Long id);
/**
* 获取统计数据
* @return 统计数据
*/
Map<String, Object> getStatistics();
}
package com.contract.review.mapper;
package com.contract.review.mapper;
import com.contract.review.entity.ContractRisk;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 合同风险Mapper
*/
@Mapper
public interface ContractRiskMapper {
/**
* 根据合同ID获取风险列表
* @param contractId 合同ID
* @return 风险列表
*/
List<ContractRisk> getRiskListByContractId(@Param("contractId") Long contractId);
/**
* 获取风险列表
* @param params 查询参数
* @return 风险列表
*/
List<ContractRisk> getRiskList(Map<String, Object> params);
/**
* 根据ID获取风险
* @param id 风险ID
* @return 风险
*/
ContractRisk getRiskById(@Param("id") Long id);
/**
* 新增风险
* @param risk 风险
* @return 影响行数
*/
int addRisk(ContractRisk risk);
/**
* 更新风险
* @param risk 风险
* @return 影响行数
*/
int updateRisk(ContractRisk risk);
/**
* 删除风险
* @param id 风险ID
* @return 影响行数
*/
int deleteRisk(@Param("id") Long id);
/**
* 根据合同ID删除风险
* @param contractId 合同ID
* @return 影响行数
*/
int deleteRiskByContractId(@Param("contractId") Long contractId);
/**
* 更新风险状态
* @param params 参数
* @return 影响行数
*/
int updateRiskStatus(Map<String, Object> params);
}
package com.contract.review.mapper;
package com.contract.review.mapper;
import com.contract.review.entity.Regulation;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 规章制度Mapper
*/
@Mapper
public interface RegulationMapper {
/**
* 获取规章制度列表
* @param params 查询参数
* @return 规章制度列表
*/
List<Regulation> getRegulationList(Map<String, Object> params);
/**
* 获取规章制度总数
* @param params 查询参数
* @return 规章制度总数
*/
int getRegulationCount(Map<String, Object> params);
/**
* 根据ID获取规章制度
* @param id 规则ID
* @return 规章制度
*/
Regulation getRegulationById(@Param("id") Long id);
/**
* 根据规则编号获取规章制度
* @param ruleNo 规则编号
* @return 规章制度
*/
Regulation getRegulationByRuleNo(@Param("ruleNo") String ruleNo);
/**
* 新增规章制度
* @param regulation 规章制度
* @return 影响行数
*/
int addRegulation(Regulation regulation);
/**
* 更新规章制度
* @param regulation 规章制度
* @return 影响行数
*/
int updateRegulation(Regulation regulation);
/**
* 删除规章制度
* @param id 规则ID
* @return 影响行数
*/
int deleteRegulation(@Param("id") Long id);
/**
* 更新规章制度状态
* @param params 参数
* @return 影响行数
*/
int updateRegulationStatus(Map<String, Object> params);
}
\ No newline at end of file
package com.contract.review.mapper;
package com.contract.review.mapper;
import com.contract.review.entity.ReviewRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 审查记录Mapper
*/
@Mapper
public interface ReviewRecordMapper {
/**
* 根据合同ID获取审查记录列表
* @param contractId 合同ID
* @return 审查记录列表
*/
List<ReviewRecord> getRecordListByContractId(@Param("contractId") Long contractId);
/**
* 获取审查记录列表
* @param params 查询参数
* @return 审查记录列表
*/
List<ReviewRecord> getRecordList(Map<String, Object> params);
/**
* 根据ID获取审查记录
* @param id 记录ID
* @return 审查记录
*/
ReviewRecord getRecordById(@Param("id") Long id);
/**
* 新增审查记录
* @param record 审查记录
* @return 影响行数
*/
int addRecord(ReviewRecord record);
/**
* 更新审查记录
* @param record 审查记录
* @return 影响行数
*/
int updateRecord(ReviewRecord record);
/**
* 删除审查记录
* @param id 记录ID
* @return 影响行数
*/
int deleteRecord(@Param("id") Long id);
/**
* 根据合同ID删除审查记录
* @param contractId 合同ID
* @return 影响行数
*/
int deleteRecordByContractId(@Param("contractId") Long contractId);
}
package com.contract.review.service;
package com.contract.review.service;
import com.contract.review.entity.ContractRisk;
import java.util.List;
import java.util.Map;
/**
* 合同风险服务
*/
public interface ContractRiskService {
/**
* 获取风险列表
* @param params 查询参数
* @return 风险列表
*/
List<ContractRisk> getRiskList(Map<String, Object> params);
/**
* 根据合同ID获取风险列表
* @param contractId 合同ID
* @return 风险列表
*/
List<ContractRisk> getRiskListByContractId(Long contractId);
/**
* 根据ID获取风险
* @param id 风险ID
* @return 风险
*/
ContractRisk getRiskById(Long id);
/**
* 新增风险
* @param risk 风险
* @return 风险ID
*/
Long addRisk(ContractRisk risk);
/**
* 更新风险
* @param risk 风险
* @return 影响行数
*/
int updateRisk(ContractRisk risk);
/**
* 删除风险
* @param id 风险ID
* @return 影响行数
*/
int deleteRisk(Long id);
/**
* 根据合同ID删除风险
* @param contractId 合同ID
* @return 影响行数
*/
int deleteRiskByContractId(Long contractId);
/**
* 更新风险状态
* @param id 风险ID
* @param status 状态:0-正常,1-忽略
* @return 影响行数
*/
int updateRiskStatus(Long id, Integer status);
}
package com.contract.review.service;
package com.contract.review.service;
import com.contract.review.entity.Contract;
import com.contract.review.entity.ContractRisk;
import java.util.List;
import java.util.Map;
/**
* 合同服务
*/
public interface ContractService {
/**
* 获取合同列表
* @param params 查询参数
* @return 合同列表
*/
List<Contract> getContractList(Map<String, Object> params);
/**
* 获取合同总数
* @param params 查询参数
* @return 合同总数
*/
int getContractCount(Map<String, Object> params);
/**
* 根据ID获取合同
* @param id 合同ID
* @return 合同
*/
Contract getContractById(Long id);
/**
* 新增合同
* @param contract 合同
* @return 合同ID
*/
Long addContract(Contract contract);
/**
* 更新合同
* @param contract 合同
* @return 影响行数
*/
int updateContract(Contract contract);
/**
* 删除合同
* @param id 合同ID
* @return 影响行数
*/
int deleteContract(Long id);
/**
* 获取合同风险列表
* @param contractId 合同ID
* @return 风险列表
*/
List<ContractRisk> getContractRiskList(Long contractId);
/**
* 获取统计数据
* @return 统计数据
*/
Map<String, Object> getStatistics();
/**
* 获取合同源文件内容
* @param contractId 合同ID
* @return 源文件内容
*/
String getContractContent(Long contractId);
}
package com.contract.review.service;
package com.contract.review.service;
import com.contract.review.entity.Regulation;
import java.util.List;
import java.util.Map;
/**
* 规章制度服务
*/
public interface RegulationService {
/**
* 获取规章制度列表
* @param params 查询参数
* @return 规章制度列表
*/
List<Regulation> getRegulationList(Map<String, Object> params);
/**
* 获取规章制度总数
* @param params 查询参数
* @return 规章制度总数
*/
int getRegulationCount(Map<String, Object> params);
/**
* 根据ID获取规章制度
* @param id 规则ID
* @return 规章制度
*/
Regulation getRegulationById(Long id);
/**
* 根据规则编号获取规章制度
* @param ruleNo 规则编号
* @return 规章制度
*/
Regulation getRegulationByRuleNo(String ruleNo);
/**
* 新增规章制度
* @param regulation 规章制度
* @return 规则ID
*/
Long addRegulation(Regulation regulation);
/**
* 更新规章制度
* @param regulation 规章制度
* @return 影响行数
*/
int updateRegulation(Regulation regulation);
/**
* 删除规章制度
* @param id 规则ID
* @return 影响行数
*/
int deleteRegulation(Long id);
/**
* 更新规章制度状态
* @param id 规则ID
* @param status 状态:0-未生效,1-已生效
* @return 影响行数
*/
int updateRegulationStatus(Long id, Integer status);
}
\ No newline at end of file
package com.contract.review.service.impl;
package com.contract.review.service.impl;
import com.contract.review.entity.ContractRisk;
import com.contract.review.mapper.ContractRiskMapper;
import com.contract.review.service.ContractRiskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/**
* 合同风险服务实现
*/
@Service
@Transactional
public class ContractRiskServiceImpl implements ContractRiskService {
@Autowired
private ContractRiskMapper contractRiskMapper;
@Override
public List<ContractRisk> getRiskList(Map<String, Object> params) {
return contractRiskMapper.getRiskList(params);
}
@Override
public List<ContractRisk> getRiskListByContractId(Long contractId) {
return contractRiskMapper.getRiskListByContractId(contractId);
}
@Override
public ContractRisk getRiskById(Long id) {
return contractRiskMapper.getRiskById(id);
}
@Override
public Long addRisk(ContractRisk risk) {
contractRiskMapper.addRisk(risk);
return risk.getId();
}
@Override
public int updateRisk(ContractRisk risk) {
return contractRiskMapper.updateRisk(risk);
}
@Override
public int deleteRisk(Long id) {
return contractRiskMapper.deleteRisk(id);
}
@Override
public int deleteRiskByContractId(Long contractId) {
return contractRiskMapper.deleteRiskByContractId(contractId);
}
@Override
public int updateRiskStatus(Long id, Integer status) {
Map<String, Object> params = new java.util.HashMap<>();
params.put("id", id);
params.put("status", status);
return contractRiskMapper.updateRiskStatus(params);
}
}
package com.contract.review.service.impl;
package com.contract.review.service.impl;
import com.contract.review.entity.Contract;
import com.contract.review.entity.ContractRisk;
import com.contract.review.mapper.ContractMapper;
import com.contract.review.mapper.ContractRiskMapper;
import com.contract.review.service.ContractService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.Map;
/**
* 合同服务实现
*/
@Service
@Transactional
public class ContractServiceImpl implements ContractService {
@Autowired
private ContractMapper contractMapper;
@Autowired
private ContractRiskMapper contractRiskMapper;
@Override
public List<Contract> getContractList(Map<String, Object> params) {
return contractMapper.getContractList(params);
}
@Override
public int getContractCount(Map<String, Object> params) {
return contractMapper.getContractCount(params);
}
@Override
public Contract getContractById(Long id) {
return contractMapper.getContractById(id);
}
@Override
public Long addContract(Contract contract) {
contractMapper.addContract(contract);
return contract.getId();
}
@Override
public int updateContract(Contract contract) {
return contractMapper.updateContract(contract);
}
@Override
public int deleteContract(Long id) {
// 先删除关联的风险记录
contractRiskMapper.deleteRiskByContractId(id);
// 再删除合同
return contractMapper.deleteContract(id);
}
@Override
public List<ContractRisk> getContractRiskList(Long contractId) {
return contractRiskMapper.getRiskListByContractId(contractId);
}
@Override
public Map<String, Object> getStatistics() {
return contractMapper.getStatistics();
}
@Override
public String getContractContent(Long contractId) {
Contract contract = contractMapper.getContractById(contractId);
if (contract == null || contract.getSourceFile() == null) {
return null;
}
String sourceFile = contract.getSourceFile();
try {
File file = new File(sourceFile);
if (!file.exists()) {
return null;
}
// 直接返回文件信息,前端会通过文件流获取并解析内容
StringBuilder content = new StringBuilder();
content.append("# 合同内容\n\n");
content.append("## 文件信息\n");
content.append("- 文件名称: " + file.getName() + "\n");
content.append("- 文件大小: " + file.length() + " 字节\n");
content.append("- 文件类型: " + (sourceFile.endsWith(".doc") ? "Microsoft Word 97-2003 Document (.doc)" : "Microsoft Word Document (.docx)") + "\n");
content.append("\n## 合同内容\n");
content.append("### 合同内容正在加载中...\n");
content.append("请稍候,系统正在解析合同文件内容。");
return content.toString();
} catch (Exception e) {
e.printStackTrace();
return "# 文件读取失败\n\n" + e.getMessage();
}
}
private String convertToMarkdown(String content) {
// 简单的转换逻辑,将文本转换为Markdown格式
if (content == null) {
return "";
}
// 替换换行符为Markdown换行
content = content.replaceAll("\\r\\n", "\\n");
// 添加标题
String markdown = "# 合同内容\\n\\n";
// 添加内容
markdown += content;
return markdown;
}
}
package com.contract.review.service.impl;
package com.contract.review.service.impl;
import com.contract.review.entity.Regulation;
import com.contract.review.mapper.RegulationMapper;
import com.contract.review.service.RegulationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/**
* 规章制度服务实现
*/
@Service
@Transactional
public class RegulationServiceImpl implements RegulationService {
@Autowired
private RegulationMapper regulationMapper;
@Override
public List<Regulation> getRegulationList(Map<String, Object> params) {
return regulationMapper.getRegulationList(params);
}
@Override
public int getRegulationCount(Map<String, Object> params) {
return regulationMapper.getRegulationCount(params);
}
@Override
public Regulation getRegulationById(Long id) {
return regulationMapper.getRegulationById(id);
}
@Override
public Regulation getRegulationByRuleNo(String ruleNo) {
return regulationMapper.getRegulationByRuleNo(ruleNo);
}
@Override
public Long addRegulation(Regulation regulation) {
regulationMapper.addRegulation(regulation);
return regulation.getId();
}
@Override
public int updateRegulation(Regulation regulation) {
return regulationMapper.updateRegulation(regulation);
}
@Override
public int deleteRegulation(Long id) {
return regulationMapper.deleteRegulation(id);
}
@Override
public int updateRegulationStatus(Long id, Integer status) {
Map<String, Object> params = new java.util.HashMap<>();
params.put("id", id);
params.put("status", status);
return regulationMapper.updateRegulationStatus(params);
}
}
\ No newline at end of file
spring:
spring:
datasource:
url: jdbc:mysql://localhost:3306/aozhi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
http:
encoding:
charset: UTF-8
enabled: true
force: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
devtools:
restart:
enabled: true
additional-paths: src/main/java
exclude: WEB-INF/**
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.contract.review.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 8084
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ContractMapper">
<!-- 获取合同列表 -->
<select id="getContractList" parameterType="java.util.Map" resultType="com.contract.review.entity.Contract">
SELECT * FROM contract
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="department != null and department != ''">
AND department = #{department}
</if>
<if test="querySource != null and querySource != ''">
AND query_source = #{querySource}
</if>
<if test="riskLevel != null and riskLevel != ''">
<choose>
<when test="riskLevel == 'high'">
AND high_risk > 0
</when>
<when test="riskLevel == 'medium'">
AND medium_risk > 0
</when>
<when test="riskLevel == 'low'">
AND low_risk > 0
</when>
</choose>
</if>
<if test="uploadTime != null and uploadTime != ''">
AND upload_time &gt;= #{uploadTime}
</if>
<if test="finishTime != null and finishTime != ''">
AND finish_time &lt;= #{finishTime}
</if>
ORDER BY upload_time DESC
LIMIT #{page}, #{pageSize}
</select>
<!-- 获取合同总数 -->
<select id="getContractCount" parameterType="java.util.Map" resultType="java.lang.Integer">
SELECT COUNT(*) FROM contract
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="department != null and department != ''">
AND department = #{department}
</if>
<if test="querySource != null and querySource != ''">
AND query_source = #{querySource}
</if>
<if test="riskLevel != null and riskLevel != ''">
<choose>
<when test="riskLevel == 'high'">
AND high_risk > 0
</when>
<when test="riskLevel == 'medium'">
AND medium_risk > 0
</when>
<when test="riskLevel == 'low'">
AND low_risk > 0
</when>
</choose>
</if>
<if test="uploadTime != null and uploadTime != ''">
AND upload_time &gt;= #{uploadTime}
</if>
<if test="finishTime != null and finishTime != ''">
AND finish_time &lt;= #{finishTime}
</if>
</select>
<!-- 根据ID获取合同 -->
<select id="getContractById" parameterType="java.lang.Long" resultType="com.contract.review.entity.Contract">
SELECT * FROM contract WHERE id = #{id}
</select>
<!-- 新增合同 -->
<insert id="addContract" parameterType="com.contract.review.entity.Contract" useGeneratedKeys="true" keyProperty="id">
INSERT INTO contract (
name, contract_no, submitter, department, query_source,
upload_time, finish_time, document_uploader, high_risk, medium_risk, low_risk, status, source_file,
created_at, updated_at
) VALUES (
#{name}, #{contractNo}, #{submitter}, #{department}, #{querySource},
#{uploadTime}, #{finishTime}, #{documentUploader}, #{highRisk}, #{mediumRisk}, #{lowRisk}, #{status}, #{sourceFile},
NOW(), NOW()
)
</insert>
<!-- 更新合同 -->
<update id="updateContract" parameterType="com.contract.review.entity.Contract">
UPDATE contract SET
name = #{name},
contract_no = #{contractNo},
submitter = #{submitter},
department = #{department},
query_source = #{querySource},
upload_time = #{uploadTime},
finish_time = #{finishTime},
document_uploader = #{documentUploader},
high_risk = #{highRisk},
medium_risk = #{mediumRisk},
low_risk = #{lowRisk},
status = #{status},
source_file = #{sourceFile},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除合同 -->
<delete id="deleteContract" parameterType="java.lang.Long">
DELETE FROM contract WHERE id = #{id}
</delete>
<!-- 获取统计数据 -->
<select id="getStatistics" resultType="java.util.Map">
SELECT
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN 1 END) AS totalContracts,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS totalRisks,
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN 1 END) AS contracts2026,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS risks2026,
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2025' THEN 1 END) AS contracts2025,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2025' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS risks2025
FROM contract
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ContractRiskMapper">
<!-- 根据合同ID获取风险列表 -->
<select id="getRiskListByContractId" parameterType="java.lang.Long" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk WHERE contract_id = #{contractId} ORDER BY risk_level DESC, status ASC
</select>
<!-- 获取风险列表 -->
<select id="getRiskList" parameterType="java.util.Map" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk
WHERE 1=1
<if test="contractId != null">
AND contract_id = #{contractId}
</if>
<if test="riskLevel != null and riskLevel != ''">
AND risk_level = #{riskLevel}
</if>
<if test="riskType != null and riskType != ''">
AND risk_type LIKE CONCAT('%', #{riskType}, '%')
</if>
ORDER BY risk_level DESC
</select>
<!-- 根据ID获取风险 -->
<select id="getRiskById" parameterType="java.lang.Long" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk WHERE id = #{id}
</select>
<!-- 新增风险 -->
<insert id="addRisk" parameterType="com.contract.review.entity.ContractRisk" useGeneratedKeys="true" keyProperty="id">
INSERT INTO contract_risk (
contract_id, rule_no, rule_name, risk_type, risk_level, regulation_name, regulation_clause,
violation_clause, violation_basis, description, suggestion, status,
created_at, updated_at
) VALUES (
#{contractId}, #{ruleNo}, #{ruleName}, #{riskType}, #{riskLevel}, #{regulationName}, #{regulationClause},
#{violationClause}, #{violationBasis}, #{description}, #{suggestion}, #{status},
NOW(), NOW()
)
</insert>
<!-- 更新风险 -->
<update id="updateRisk" parameterType="com.contract.review.entity.ContractRisk">
UPDATE contract_risk SET
rule_no = #{ruleNo},
rule_name = #{ruleName},
risk_type = #{riskType},
risk_level = #{riskLevel},
regulation_name = #{regulationName},
regulation_clause = #{regulationClause},
violation_clause = #{violationClause},
violation_basis = #{violationBasis},
description = #{description},
suggestion = #{suggestion},
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 更新风险状态 -->
<update id="updateRiskStatus" parameterType="java.util.Map">
UPDATE contract_risk SET
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除风险 -->
<delete id="deleteRisk" parameterType="java.lang.Long">
DELETE FROM contract_risk WHERE id = #{id}
</delete>
<!-- 根据合同ID删除风险 -->
<delete id="deleteRiskByContractId" parameterType="java.lang.Long">
DELETE FROM contract_risk WHERE contract_id = #{contractId}
</delete>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.RegulationMapper">
<!-- 获取规章制度列表 -->
<select id="getRegulationList" parameterType="java.util.Map" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation
WHERE 1=1
<if test="ruleNo != null and ruleNo != ''">
AND rule_no LIKE CONCAT('%', #{ruleNo}, '%')
</if>
<if test="ruleName != null and ruleName != ''">
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
ORDER BY id DESC
LIMIT #{page}, #{pageSize}
</select>
<!-- 获取规章制度总数 -->
<select id="getRegulationCount" parameterType="java.util.Map" resultType="java.lang.Integer">
SELECT COUNT(*) FROM regulation
WHERE 1=1
<if test="ruleNo != null and ruleNo != ''">
AND rule_no LIKE CONCAT('%', #{ruleNo}, '%')
</if>
<if test="ruleName != null and ruleName != ''">
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
<!-- 根据ID获取规章制度 -->
<select id="getRegulationById" parameterType="java.lang.Long" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation WHERE id = #{id}
</select>
<!-- 根据规则编号获取规章制度 -->
<select id="getRegulationByRuleNo" parameterType="java.lang.String" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation WHERE rule_no = #{ruleNo}
</select>
<!-- 新增规章制度 -->
<insert id="addRegulation" parameterType="com.contract.review.entity.Regulation" useGeneratedKeys="true" keyProperty="id">
INSERT INTO regulation (
rule_no, rule_name, regulation_name, regulation_clause, description, risk_level, status, effective_time,
created_at, updated_at
) VALUES (
#{ruleNo}, #{ruleName}, #{regulationName}, #{regulationClause}, #{description}, #{riskLevel}, #{status}, #{effectiveTime},
NOW(), NOW()
)
</insert>
<!-- 更新规章制度 -->
<update id="updateRegulation" parameterType="com.contract.review.entity.Regulation">
UPDATE regulation SET
rule_no = #{ruleNo},
rule_name = #{ruleName},
regulation_name = #{regulationName},
regulation_clause = #{regulationClause},
description = #{description},
risk_level = #{riskLevel},
status = #{status},
effective_time = #{effectiveTime},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除规章制度 -->
<delete id="deleteRegulation" parameterType="java.lang.Long">
DELETE FROM regulation WHERE id = #{id}
</delete>
<!-- 更新规章制度状态 -->
<update id="updateRegulationStatus" parameterType="java.util.Map">
UPDATE regulation SET
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ReviewRecordMapper">
<!-- 根据合同ID获取审查记录列表 -->
<select id="getRecordListByContractId" parameterType="java.lang.Long" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record WHERE contract_id = #{contractId} ORDER BY review_time DESC
</select>
<!-- 获取审查记录列表 -->
<select id="getRecordList" parameterType="java.util.Map" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record
WHERE 1=1
<if test="contractId != null">
AND contract_id = #{contractId}
</if>
<if test="reviewer != null and reviewer != ''">
AND reviewer LIKE CONCAT('%', #{reviewer}, '%')
</if>
ORDER BY review_time DESC
</select>
<!-- 根据ID获取审查记录 -->
<select id="getRecordById" parameterType="java.lang.Long" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record WHERE id = #{id}
</select>
<!-- 新增审查记录 -->
<insert id="addRecord" parameterType="com.contract.review.entity.ReviewRecord" useGeneratedKeys="true" keyProperty="id">
INSERT INTO review_record (
contract_id, reviewer, review_time, review_result, created_at
) VALUES (
#{contractId}, #{reviewer}, #{reviewTime}, #{reviewResult}, NOW()
)
</insert>
<!-- 更新审查记录 -->
<update id="updateRecord" parameterType="com.contract.review.entity.ReviewRecord">
UPDATE review_record SET
reviewer = #{reviewer},
review_time = #{reviewTime},
review_result = #{reviewResult}
WHERE id = #{id}
</update>
<!-- 删除审查记录 -->
<delete id="deleteRecord" parameterType="java.lang.Long">
DELETE FROM review_record WHERE id = #{id}
</delete>
<!-- 根据合同ID删除审查记录 -->
<delete id="deleteRecordByContractId" parameterType="java.lang.Long">
DELETE FROM review_record WHERE contract_id = #{contractId}
</delete>
</mapper>
-- 数据库表结构设计
-- 数据库表结构设计
-- MySQL 8
-- 删除现有表
DROP TABLE IF EXISTS `contract_risk`;
DROP TABLE IF EXISTS `review_record`;
DROP TABLE IF EXISTS `contract`;
DROP TABLE IF EXISTS `regulation`;
-- 合同表
CREATE TABLE IF NOT EXISTS `contract` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '合同ID',
`name` VARCHAR(255) NOT NULL COMMENT '合同名称',
`contract_no` VARCHAR(100) DEFAULT NULL COMMENT '合同编号',
`contract_type` VARCHAR(100) NOT NULL COMMENT '合同类型',
`amount` DECIMAL(18,2) DEFAULT NULL COMMENT '合同金额',
`sign_date` DATE DEFAULT NULL COMMENT '签订日期',
`submitter` VARCHAR(100) NOT NULL COMMENT '审查提交人',
`department` VARCHAR(100) NOT NULL COMMENT '所属部门',
`query_source` VARCHAR(100) NOT NULL COMMENT '查询来源',
`deposit_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '定金比例',
`payment_method` VARCHAR(100) DEFAULT NULL COMMENT '付款方式',
`penalty_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '罚款比例',
`delivery_period` INT(11) DEFAULT NULL COMMENT '货期(天)',
`upload_time` DATETIME NOT NULL COMMENT '上传时间',
`finish_time` DATETIME NOT NULL COMMENT '完结时间',
`document_uploader` VARCHAR(100) NOT NULL COMMENT '文档上传人',
`high_risk` INT(11) DEFAULT 0 COMMENT '高风险数量',
`medium_risk` INT(11) DEFAULT 0 COMMENT '中风险数量',
`low_risk` INT(11) DEFAULT 0 COMMENT '低风险数量',
`status` VARCHAR(50) NOT NULL COMMENT '状态',
`source_file` VARCHAR(500) DEFAULT NULL COMMENT '源文件路径',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_department` (`department`),
INDEX `idx_status` (`status`),
INDEX `idx_upload_time` (`upload_time`),
INDEX `idx_contract_type` (`contract_type`),
INDEX `idx_query_source` (`query_source`),
INDEX `idx_submitter` (`submitter`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
-- 合同风险表
CREATE TABLE IF NOT EXISTS `contract_risk` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '风险ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`rule_no` VARCHAR(50) DEFAULT NULL COMMENT '规则编号',
`rule_name` VARCHAR(255) DEFAULT NULL COMMENT '规则名称',
`risk_type` VARCHAR(255) NOT NULL COMMENT '风险类型',
`risk_level` VARCHAR(50) NOT NULL COMMENT '风险等级',
`regulation_name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`regulation_clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`violation_clause` TEXT NOT NULL COMMENT '违规条款 (原文摘要)',
`violation_basis` TEXT NOT NULL COMMENT '违规依据',
`description` TEXT NOT NULL COMMENT '风险详情',
`suggestion` TEXT NOT NULL COMMENT '修改建议',
`status` INT(11) DEFAULT 0 COMMENT '状态:0-正常,1-已忽略',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_risk_level` (`risk_level`),
INDEX `idx_risk_type` (`risk_type`),
INDEX `idx_rule_no` (`rule_no`),
INDEX `idx_status` (`status`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同风险表';
-- 审查记录表
CREATE TABLE IF NOT EXISTS `review_record` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`reviewer` VARCHAR(100) NOT NULL COMMENT '审查人',
`review_time` DATETIME NOT NULL COMMENT '审查时间',
`review_result` TEXT DEFAULT NULL COMMENT '审查结果',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_reviewer` (`reviewer`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审查记录表';
-- 法规表
CREATE TABLE IF NOT EXISTS `regulation` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '规则ID',
`rule_no` VARCHAR(50) NOT NULL COMMENT '规则编号',
`rule_name` VARCHAR(255) NOT NULL COMMENT '规则名称',
`regulation_name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`regulation_clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`description` TEXT DEFAULT NULL COMMENT '规则描述',
`risk_level` VARCHAR(50) NOT NULL COMMENT '风险等级',
`status` INT(11) DEFAULT 1 COMMENT '生效状态:0-未生效,1-已生效',
`effective_time` DATETIME DEFAULT NULL COMMENT '生效时间',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_rule_no` (`rule_no`),
INDEX `idx_rule_name` (`rule_name`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='法规表';
-- 清空现有数据
DELETE FROM contract_risk;
DELETE FROM review_record;
DELETE FROM contract;
DELETE FROM regulation;
-- 插入公司内部规则数据
INSERT INTO `regulation` (`rule_no`, `rule_name`, `regulation_name`, `regulation_clause`, `description`, `risk_level`, `status`, `effective_time`) VALUES
('CR01', '定金条款', '《公司销售合同业务规则》', '规则 CR01', '定金比例必须达到或超过30%,低于30%视为违规。', '中危', 1, '2025-01-01 00:00:00'),
('CR02', '支付条款', '《公司销售合同业务规则》', '规则 CR02', '禁止使用商业承兑汇票,仅允许使用银行承兑汇票。', '中危', 1, '2025-01-01 00:00:00'),
('CR03', '违约金条款', '《公司销售合同业务规则》', '规则 CR03', '违约金比例不得超过5%,超过5%视为违规。', '高危', 1, '2025-01-01 00:00:00'),
('CR04', '货期条款', '《公司销售合同业务规则》', '规则 CR04', '货期表述必须明确,不得使用模糊表述。', '中危', 1, '2025-01-01 00:00:00');
-- 插入合同数据
INSERT INTO `contract` (`id`, `name`, `contract_no`, `contract_type`, `amount`, `sign_date`, `submitter`, `department`, `query_source`, `deposit_ratio`, `payment_method`, `penalty_ratio`, `delivery_period`, `upload_time`, `finish_time`, `document_uploader`, `high_risk`, `medium_risk`, `low_risk`, `status`, `source_file`) VALUES
(1, '软件开发服务合同', 'RK-2026-001', '服务合同', 120000.00, '2026-01-20', '唐文', '技术部', '直接查询', 20.00, '银行转账', 3.00, 60, '2026-01-15 09:30:00', '2026-01-18 16:45:00', '技术部', 0, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\1.doc'),
(2, '设备采购合同', 'CG-2026-001', '采购合同', 250000.00, '2026-01-15', '唐文', '技术部', '知识库自动审查', 30.00, '银行转账', 5.00, 30, '2026-01-10 10:15:00', '2026-01-12 15:30:00', '技术部', 1, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\2.doc'),
(3, '技术咨询服务合同', 'ZX-2026-001', '服务合同', 80000.00, '2026-01-10', '唐文', '技术部', '钉钉自动审查', 15.00, '银行转账', 4.00, 45, '2026-01-05 09:00:00', '2026-01-08 14:20:00', '技术部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\3.docx'),
(4, '产品销售合同', 'XS-2026-001', '销售合同', 300000.00, '2026-01-25', '唐文', '技术部', '直接查询', 25.00, '商业承兑汇票', 6.00, 20, '2026-01-20 11:30:00', '2026-01-23 16:00:00', '技术部', 1, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\4.docx'),
(5, '租赁合同', 'ZL-2026-001', '租赁合同', 150000.00, '2026-01-05', '唐文', '技术部', '知识库自动审查', 30.00, '银行转账', 5.00, 10, '2026-01-01 08:45:00', '2026-01-03 11:15:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\5.docx'),
(6, '软件开发合同(修订版)', 'RK-2026-002', '服务合同', 180000.00, '2026-02-01', '唐文', '技术部', '钉钉自动审查', 25.00, '银行转账', 4.00, 90, '2026-01-25 14:00:00', '2026-01-28 17:30:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\6.doc'),
(7, '设备采购合同(补充协议)', 'CG-2026-002', '采购合同', 50000.00, '2026-01-22', '唐文', '技术部', '直接查询', 30.00, '银行转账', 5.00, 15, '2026-01-18 10:30:00', '2026-01-20 15:45:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\7.doc'),
(8, '技术服务合同', 'FW-2026-001', '服务合同', 95000.00, '2026-01-18', '唐文', '技术部', '知识库自动审查', 20.00, '银行转账', 3.00, 40, '2026-01-12 09:15:00', '2026-01-15 16:30:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\8.docx'),
(9, '产品销售合同(长期)', 'XS-2026-002', '销售合同', 500000.00, '2026-02-05', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 5.00, 30, '2026-01-28 11:00:00', '2026-02-01 17:00:00', '市场部', 0, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\9.docx'),
(10, '软件开发框架合同', 'RK-2026-003', '服务合同', 350000.00, '2026-01-15', '唐文', '技术部', '直接查询', 25.00, '银行转账', 4.00, 120, '2026-01-08 10:00:00', '2026-01-12 16:15:00', '技术部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\10.doc'),
(11, '设备采购框架合同', 'CG-2026-003', '采购合同', 400000.00, '2026-01-22', '唐文', '市场部', '知识库自动审查', 30.00, '银行转账', 5.00, 25, '2026-01-16 09:30:00', '2026-01-19 15:30:00', '市场部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\11.doc'),
(12, '技术咨询框架合同', 'ZX-2026-002', '服务合同', 180000.00, '2026-01-28', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 4.00, 60, '2026-01-22 14:30:00', '2026-01-25 16:45:00', '市场部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\12.doc'),
(13, '产品销售补充协议', 'XS-2026-003', '销售合同', 120000.00, '2026-02-05', '唐文', '市场部', '直接查询', 15.00, '银行转账', 6.00, 15, '2026-01-30 10:15:00', '2026-02-03 15:15:00', '市场部', 1, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\13.doc');
-- 插入风险数据
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_type`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `description`, `suggestion`, `created_at`, `updated_at`) VALUES
(1, 1, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同签订后7日内,买方支付合同总金额的 25% 作为定金。', '定金比例(25%)低于公司风控红线(30%)', '定金比例(25%)低于公司风控红线(30%)', '将定金比例调整为 30%', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(2, 2, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同双方签字盖章生效后,付定金 $20 \\text{‰}$', '定金比例(2%)低于公司风控红线(30%)', '定金比例(2%)低于公司风控红线(30%)', '将定金比例调整为 30%', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(3, 2, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '卖方确认本次合同总额的 $50%$ 接受买方使用承兑汇票支付', '支付方式模糊,未排除商承风险', '支付方式模糊,未排除商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(4, 2, 'CR03', '合同风险评估', '违约金', '高危', '《公司销售合同业务规则》', '规则 CR03', '卖方每延迟交货一天,扣除合同总额的 $1%$,累计计算;安装调试延期每日扣 $0.1%$,亦无上限', '违约金无上限,且日罚1%远超5%风控红线', '违约金无上限,且日罚1%远超5%风控红线', '建议对所有违约金条款统一设置 不超过合同总额5%的封顶限制', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(5, 3, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的30%……作为预付款(其中$20%$为定金);', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即预付款中定金部分应占合同总价的30%,而非20%)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(6, 3, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……部分承兑 (金额: 000 ),承兑部分包含在提货款、到货款中。', '支付方式仅写"承兑",未限定类型,存在商承风险', '支付方式仅写"承兑",未限定类型,存在商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(7, 10, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 $30%$ 作为首付款(其中包含 $20%$ 的定金 + $10%$ 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即首付款中定金部分应占合同总价的30%,而非20%)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(8, 10, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,$15%$ 银行承兑;发货款、质保金:银行承兑……', '合同允许使用银行承兑汇票支付主要款项,违反公司拒收所有承兑(含银承)的内部风控要求', '合同允许使用银行承兑汇票支付主要款项,违反公司拒收所有承兑(含银承)的内部风控要求', '明确删除"银行承兑"表述,全部改为"银行电汇",或至少注明"不接受任何形式的承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(9, 11, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得与其他款项混同)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(10, 11, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止接受承兑汇票(包括银行承兑),以规避票据风险与贴现成本', '公司内部风控政策已全面禁止接受承兑汇票(包括银行承兑),以规避票据风险与贴现成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(11, 12, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期款)……', '定金比例(20%)低于公司强制风控红线(30%)', '定金比例(20%)低于公司强制风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得拆分或混同)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(12, 12, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止承兑汇票(包括银行承兑),以规避票据兑付风险与资金成本', '公司内部风控政策已全面禁止承兑汇票(包括银行承兑),以规避票据兑付风险与资金成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
-- 插入审查记录数据
INSERT INTO `review_record` (`contract_id`, `reviewer`, `review_time`, `review_result`) VALUES
(1, '唐文', '2026-01-18 16:45:00', '合同条款完整,无明显风险。'),
(2, '唐文', '2026-01-12 15:30:00', '设备参数明确,价格合理,无风险。'),
(3, '唐文', '2026-01-08 14:20:00', '技术咨询服务合同条款完整,无风险。'),
(4, '唐文', '2026-01-23 16:00:00', '发现3个风险点,其中1个高风险,2个中风险,已提出修改建议。'),
(5, '唐文', '2026-01-03 11:15:00', '租赁合同条款完整,符合法律规定。'),
(6, '唐文', '2026-01-28 17:30:00', '软件开发合同修订版条款合理,无风险。'),
(7, '唐文', '2026-01-20 15:45:00', '设备采购补充协议条款清晰,无风险。'),
(8, '唐文', '2026-01-15 16:30:00', '技术服务合同条款完整,无明显风险。'),
(9, '唐文', '2026-02-01 17:00:00', '发现1个中风险点,已提出修改建议。'),
(10, '唐文', '2026-01-12 16:15:00', '软件开发框架合同条款合理,无风险。'),
(11, '唐文', '2026-01-19 15:30:00', '设备采购框架合同条款完整,无风险。'),
(12, '唐文', '2026-01-25 16:45:00', '技术咨询框架合同条款完整,无风险。'),
(13, '唐文', '2026-02-03 15:15:00', '发现2个风险点,其中1个高风险,1个中风险,已提出修改建议。');
\ No newline at end of file
spring:
spring:
datasource:
url: jdbc:mysql://localhost:3306/aozhi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
http:
encoding:
charset: UTF-8
enabled: true
force: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
devtools:
restart:
enabled: true
additional-paths: src/main/java
exclude: WEB-INF/**
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.contract.review.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 8084
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ContractMapper">
<!-- 获取合同列表 -->
<select id="getContractList" parameterType="java.util.Map" resultType="com.contract.review.entity.Contract">
SELECT * FROM contract
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="department != null and department != ''">
AND department = #{department}
</if>
<if test="querySource != null and querySource != ''">
AND query_source = #{querySource}
</if>
<if test="riskLevel != null and riskLevel != ''">
<choose>
<when test="riskLevel == 'high'">
AND high_risk > 0
</when>
<when test="riskLevel == 'medium'">
AND medium_risk > 0
</when>
<when test="riskLevel == 'low'">
AND low_risk > 0
</when>
</choose>
</if>
<if test="uploadTime != null and uploadTime != ''">
AND upload_time &gt;= #{uploadTime}
</if>
<if test="finishTime != null and finishTime != ''">
AND finish_time &lt;= #{finishTime}
</if>
ORDER BY upload_time DESC
LIMIT #{page}, #{pageSize}
</select>
<!-- 获取合同总数 -->
<select id="getContractCount" parameterType="java.util.Map" resultType="java.lang.Integer">
SELECT COUNT(*) FROM contract
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="department != null and department != ''">
AND department = #{department}
</if>
<if test="querySource != null and querySource != ''">
AND query_source = #{querySource}
</if>
<if test="riskLevel != null and riskLevel != ''">
<choose>
<when test="riskLevel == 'high'">
AND high_risk > 0
</when>
<when test="riskLevel == 'medium'">
AND medium_risk > 0
</when>
<when test="riskLevel == 'low'">
AND low_risk > 0
</when>
</choose>
</if>
<if test="uploadTime != null and uploadTime != ''">
AND upload_time &gt;= #{uploadTime}
</if>
<if test="finishTime != null and finishTime != ''">
AND finish_time &lt;= #{finishTime}
</if>
</select>
<!-- 根据ID获取合同 -->
<select id="getContractById" parameterType="java.lang.Long" resultType="com.contract.review.entity.Contract">
SELECT * FROM contract WHERE id = #{id}
</select>
<!-- 新增合同 -->
<insert id="addContract" parameterType="com.contract.review.entity.Contract" useGeneratedKeys="true" keyProperty="id">
INSERT INTO contract (
name, contract_no, submitter, department, query_source,
upload_time, finish_time, document_uploader, high_risk, medium_risk, low_risk, status, source_file,
created_at, updated_at
) VALUES (
#{name}, #{contractNo}, #{submitter}, #{department}, #{querySource},
#{uploadTime}, #{finishTime}, #{documentUploader}, #{highRisk}, #{mediumRisk}, #{lowRisk}, #{status}, #{sourceFile},
NOW(), NOW()
)
</insert>
<!-- 更新合同 -->
<update id="updateContract" parameterType="com.contract.review.entity.Contract">
UPDATE contract SET
name = #{name},
contract_no = #{contractNo},
submitter = #{submitter},
department = #{department},
query_source = #{querySource},
upload_time = #{uploadTime},
finish_time = #{finishTime},
document_uploader = #{documentUploader},
high_risk = #{highRisk},
medium_risk = #{mediumRisk},
low_risk = #{lowRisk},
status = #{status},
source_file = #{sourceFile},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除合同 -->
<delete id="deleteContract" parameterType="java.lang.Long">
DELETE FROM contract WHERE id = #{id}
</delete>
<!-- 获取统计数据 -->
<select id="getStatistics" resultType="java.util.Map">
SELECT
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN 1 END) AS totalContracts,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS totalRisks,
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN 1 END) AS contracts2026,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2026' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS risks2026,
COUNT(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2025' THEN 1 END) AS contracts2025,
SUM(CASE WHEN DATE_FORMAT(upload_time, '%Y') = '2025' THEN high_risk + medium_risk + low_risk ELSE 0 END) AS risks2025
FROM contract
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ContractRiskMapper">
<!-- 根据合同ID获取风险列表 -->
<select id="getRiskListByContractId" parameterType="java.lang.Long" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk WHERE contract_id = #{contractId} ORDER BY risk_level DESC, status ASC
</select>
<!-- 获取风险列表 -->
<select id="getRiskList" parameterType="java.util.Map" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk
WHERE 1=1
<if test="contractId != null">
AND contract_id = #{contractId}
</if>
<if test="riskLevel != null and riskLevel != ''">
AND risk_level = #{riskLevel}
</if>
<if test="riskType != null and riskType != ''">
AND risk_type LIKE CONCAT('%', #{riskType}, '%')
</if>
ORDER BY risk_level DESC
</select>
<!-- 根据ID获取风险 -->
<select id="getRiskById" parameterType="java.lang.Long" resultType="com.contract.review.entity.ContractRisk">
SELECT * FROM contract_risk WHERE id = #{id}
</select>
<!-- 新增风险 -->
<insert id="addRisk" parameterType="com.contract.review.entity.ContractRisk" useGeneratedKeys="true" keyProperty="id">
INSERT INTO contract_risk (
contract_id, rule_no, rule_name, risk_type, risk_level, regulation_name, regulation_clause,
violation_clause, violation_basis, description, suggestion, status,
created_at, updated_at
) VALUES (
#{contractId}, #{ruleNo}, #{ruleName}, #{riskType}, #{riskLevel}, #{regulationName}, #{regulationClause},
#{violationClause}, #{violationBasis}, #{description}, #{suggestion}, #{status},
NOW(), NOW()
)
</insert>
<!-- 更新风险 -->
<update id="updateRisk" parameterType="com.contract.review.entity.ContractRisk">
UPDATE contract_risk SET
rule_no = #{ruleNo},
rule_name = #{ruleName},
risk_type = #{riskType},
risk_level = #{riskLevel},
regulation_name = #{regulationName},
regulation_clause = #{regulationClause},
violation_clause = #{violationClause},
violation_basis = #{violationBasis},
description = #{description},
suggestion = #{suggestion},
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 更新风险状态 -->
<update id="updateRiskStatus" parameterType="java.util.Map">
UPDATE contract_risk SET
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除风险 -->
<delete id="deleteRisk" parameterType="java.lang.Long">
DELETE FROM contract_risk WHERE id = #{id}
</delete>
<!-- 根据合同ID删除风险 -->
<delete id="deleteRiskByContractId" parameterType="java.lang.Long">
DELETE FROM contract_risk WHERE contract_id = #{contractId}
</delete>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.RegulationMapper">
<!-- 获取规章制度列表 -->
<select id="getRegulationList" parameterType="java.util.Map" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation
WHERE 1=1
<if test="ruleNo != null and ruleNo != ''">
AND rule_no LIKE CONCAT('%', #{ruleNo}, '%')
</if>
<if test="ruleName != null and ruleName != ''">
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
ORDER BY id DESC
LIMIT #{page}, #{pageSize}
</select>
<!-- 获取规章制度总数 -->
<select id="getRegulationCount" parameterType="java.util.Map" resultType="java.lang.Integer">
SELECT COUNT(*) FROM regulation
WHERE 1=1
<if test="ruleNo != null and ruleNo != ''">
AND rule_no LIKE CONCAT('%', #{ruleNo}, '%')
</if>
<if test="ruleName != null and ruleName != ''">
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
<!-- 根据ID获取规章制度 -->
<select id="getRegulationById" parameterType="java.lang.Long" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation WHERE id = #{id}
</select>
<!-- 根据规则编号获取规章制度 -->
<select id="getRegulationByRuleNo" parameterType="java.lang.String" resultType="com.contract.review.entity.Regulation">
SELECT * FROM regulation WHERE rule_no = #{ruleNo}
</select>
<!-- 新增规章制度 -->
<insert id="addRegulation" parameterType="com.contract.review.entity.Regulation" useGeneratedKeys="true" keyProperty="id">
INSERT INTO regulation (
rule_no, rule_name, regulation_name, regulation_clause, description, risk_level, status, effective_time,
created_at, updated_at
) VALUES (
#{ruleNo}, #{ruleName}, #{regulationName}, #{regulationClause}, #{description}, #{riskLevel}, #{status}, #{effectiveTime},
NOW(), NOW()
)
</insert>
<!-- 更新规章制度 -->
<update id="updateRegulation" parameterType="com.contract.review.entity.Regulation">
UPDATE regulation SET
rule_no = #{ruleNo},
rule_name = #{ruleName},
regulation_name = #{regulationName},
regulation_clause = #{regulationClause},
description = #{description},
risk_level = #{riskLevel},
status = #{status},
effective_time = #{effectiveTime},
updated_at = NOW()
WHERE id = #{id}
</update>
<!-- 删除规章制度 -->
<delete id="deleteRegulation" parameterType="java.lang.Long">
DELETE FROM regulation WHERE id = #{id}
</delete>
<!-- 更新规章制度状态 -->
<update id="updateRegulationStatus" parameterType="java.util.Map">
UPDATE regulation SET
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<?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.contract.review.mapper.ReviewRecordMapper">
<!-- 根据合同ID获取审查记录列表 -->
<select id="getRecordListByContractId" parameterType="java.lang.Long" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record WHERE contract_id = #{contractId} ORDER BY review_time DESC
</select>
<!-- 获取审查记录列表 -->
<select id="getRecordList" parameterType="java.util.Map" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record
WHERE 1=1
<if test="contractId != null">
AND contract_id = #{contractId}
</if>
<if test="reviewer != null and reviewer != ''">
AND reviewer LIKE CONCAT('%', #{reviewer}, '%')
</if>
ORDER BY review_time DESC
</select>
<!-- 根据ID获取审查记录 -->
<select id="getRecordById" parameterType="java.lang.Long" resultType="com.contract.review.entity.ReviewRecord">
SELECT * FROM review_record WHERE id = #{id}
</select>
<!-- 新增审查记录 -->
<insert id="addRecord" parameterType="com.contract.review.entity.ReviewRecord" useGeneratedKeys="true" keyProperty="id">
INSERT INTO review_record (
contract_id, reviewer, review_time, review_result, created_at
) VALUES (
#{contractId}, #{reviewer}, #{reviewTime}, #{reviewResult}, NOW()
)
</insert>
<!-- 更新审查记录 -->
<update id="updateRecord" parameterType="com.contract.review.entity.ReviewRecord">
UPDATE review_record SET
reviewer = #{reviewer},
review_time = #{reviewTime},
review_result = #{reviewResult}
WHERE id = #{id}
</update>
<!-- 删除审查记录 -->
<delete id="deleteRecord" parameterType="java.lang.Long">
DELETE FROM review_record WHERE id = #{id}
</delete>
<!-- 根据合同ID删除审查记录 -->
<delete id="deleteRecordByContractId" parameterType="java.lang.Long">
DELETE FROM review_record WHERE contract_id = #{contractId}
</delete>
</mapper>
-- 数据库表结构设计
-- 数据库表结构设计
-- MySQL 8
-- 删除现有表
DROP TABLE IF EXISTS `contract_risk`;
DROP TABLE IF EXISTS `review_record`;
DROP TABLE IF EXISTS `contract`;
DROP TABLE IF EXISTS `regulation`;
-- 合同表
CREATE TABLE IF NOT EXISTS `contract` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '合同ID',
`name` VARCHAR(255) NOT NULL COMMENT '合同名称',
`contract_no` VARCHAR(100) DEFAULT NULL COMMENT '合同编号',
`contract_type` VARCHAR(100) NOT NULL COMMENT '合同类型',
`amount` DECIMAL(18,2) DEFAULT NULL COMMENT '合同金额',
`sign_date` DATE DEFAULT NULL COMMENT '签订日期',
`submitter` VARCHAR(100) NOT NULL COMMENT '审查提交人',
`department` VARCHAR(100) NOT NULL COMMENT '所属部门',
`query_source` VARCHAR(100) NOT NULL COMMENT '查询来源',
`deposit_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '定金比例',
`payment_method` VARCHAR(100) DEFAULT NULL COMMENT '付款方式',
`penalty_ratio` DECIMAL(5,2) DEFAULT NULL COMMENT '罚款比例',
`delivery_period` INT(11) DEFAULT NULL COMMENT '货期(天)',
`upload_time` DATETIME NOT NULL COMMENT '上传时间',
`finish_time` DATETIME NOT NULL COMMENT '完结时间',
`document_uploader` VARCHAR(100) NOT NULL COMMENT '文档上传人',
`high_risk` INT(11) DEFAULT 0 COMMENT '高风险数量',
`medium_risk` INT(11) DEFAULT 0 COMMENT '中风险数量',
`low_risk` INT(11) DEFAULT 0 COMMENT '低风险数量',
`status` VARCHAR(50) NOT NULL COMMENT '状态',
`source_file` VARCHAR(500) DEFAULT NULL COMMENT '源文件路径',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_department` (`department`),
INDEX `idx_status` (`status`),
INDEX `idx_upload_time` (`upload_time`),
INDEX `idx_contract_type` (`contract_type`),
INDEX `idx_query_source` (`query_source`),
INDEX `idx_submitter` (`submitter`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
-- 合同风险表
CREATE TABLE IF NOT EXISTS `contract_risk` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '风险ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`rule_no` VARCHAR(50) DEFAULT NULL COMMENT '规则编号',
`rule_name` VARCHAR(255) DEFAULT NULL COMMENT '规则名称',
`risk_type` VARCHAR(255) NOT NULL COMMENT '风险类型',
`risk_level` VARCHAR(50) NOT NULL COMMENT '风险等级',
`regulation_name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`regulation_clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`violation_clause` TEXT NOT NULL COMMENT '违规条款 (原文摘要)',
`violation_basis` TEXT NOT NULL COMMENT '违规依据',
`description` TEXT NOT NULL COMMENT '风险详情',
`suggestion` TEXT NOT NULL COMMENT '修改建议',
`status` INT(11) DEFAULT 0 COMMENT '状态:0-正常,1-已忽略',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_risk_level` (`risk_level`),
INDEX `idx_risk_type` (`risk_type`),
INDEX `idx_rule_no` (`rule_no`),
INDEX `idx_status` (`status`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同风险表';
-- 审查记录表
CREATE TABLE IF NOT EXISTS `review_record` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`contract_id` BIGINT(20) NOT NULL COMMENT '合同ID',
`reviewer` VARCHAR(100) NOT NULL COMMENT '审查人',
`review_time` DATETIME NOT NULL COMMENT '审查时间',
`review_result` TEXT DEFAULT NULL COMMENT '审查结果',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
INDEX `idx_contract_id` (`contract_id`),
INDEX `idx_reviewer` (`reviewer`),
FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审查记录表';
-- 法规表
CREATE TABLE IF NOT EXISTS `regulation` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '规则ID',
`rule_no` VARCHAR(50) NOT NULL COMMENT '规则编号',
`rule_name` VARCHAR(255) NOT NULL COMMENT '规则名称',
`regulation_name` VARCHAR(255) NOT NULL COMMENT '法规名称',
`regulation_clause` VARCHAR(255) NOT NULL COMMENT '法规条目',
`description` TEXT DEFAULT NULL COMMENT '规则描述',
`risk_level` VARCHAR(50) NOT NULL COMMENT '风险等级',
`status` INT(11) DEFAULT 1 COMMENT '生效状态:0-未生效,1-已生效',
`effective_time` DATETIME DEFAULT NULL COMMENT '生效时间',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX `idx_rule_no` (`rule_no`),
INDEX `idx_rule_name` (`rule_name`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='法规表';
-- 清空现有数据
DELETE FROM contract_risk;
DELETE FROM review_record;
DELETE FROM contract;
DELETE FROM regulation;
-- 插入公司内部规则数据
INSERT INTO `regulation` (`rule_no`, `rule_name`, `regulation_name`, `regulation_clause`, `description`, `risk_level`, `status`, `effective_time`) VALUES
('CR01', '定金条款', '《公司销售合同业务规则》', '规则 CR01', '定金比例必须达到或超过30%,低于30%视为违规。', '中危', 1, '2025-01-01 00:00:00'),
('CR02', '支付条款', '《公司销售合同业务规则》', '规则 CR02', '禁止使用商业承兑汇票,仅允许使用银行承兑汇票。', '中危', 1, '2025-01-01 00:00:00'),
('CR03', '违约金条款', '《公司销售合同业务规则》', '规则 CR03', '违约金比例不得超过5%,超过5%视为违规。', '高危', 1, '2025-01-01 00:00:00'),
('CR04', '货期条款', '《公司销售合同业务规则》', '规则 CR04', '货期表述必须明确,不得使用模糊表述。', '中危', 1, '2025-01-01 00:00:00');
-- 插入合同数据
INSERT INTO `contract` (`id`, `name`, `contract_no`, `contract_type`, `amount`, `sign_date`, `submitter`, `department`, `query_source`, `deposit_ratio`, `payment_method`, `penalty_ratio`, `delivery_period`, `upload_time`, `finish_time`, `document_uploader`, `high_risk`, `medium_risk`, `low_risk`, `status`, `source_file`) VALUES
(1, '软件开发服务合同', 'RK-2026-001', '服务合同', 120000.00, '2026-01-20', '唐文', '技术部', '直接查询', 20.00, '银行转账', 3.00, 60, '2026-01-15 09:30:00', '2026-01-18 16:45:00', '技术部', 0, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\1.doc'),
(2, '设备采购合同', 'CG-2026-001', '采购合同', 250000.00, '2026-01-15', '唐文', '技术部', '知识库自动审查', 30.00, '银行转账', 5.00, 30, '2026-01-10 10:15:00', '2026-01-12 15:30:00', '技术部', 1, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\2.doc'),
(3, '技术咨询服务合同', 'ZX-2026-001', '服务合同', 80000.00, '2026-01-10', '唐文', '技术部', '钉钉自动审查', 15.00, '银行转账', 4.00, 45, '2026-01-05 09:00:00', '2026-01-08 14:20:00', '技术部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\3.docx'),
(4, '产品销售合同', 'XS-2026-001', '销售合同', 300000.00, '2026-01-25', '唐文', '技术部', '直接查询', 25.00, '商业承兑汇票', 6.00, 20, '2026-01-20 11:30:00', '2026-01-23 16:00:00', '技术部', 1, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\4.docx'),
(5, '租赁合同', 'ZL-2026-001', '租赁合同', 150000.00, '2026-01-05', '唐文', '技术部', '知识库自动审查', 30.00, '银行转账', 5.00, 10, '2026-01-01 08:45:00', '2026-01-03 11:15:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\5.docx'),
(6, '软件开发合同(修订版)', 'RK-2026-002', '服务合同', 180000.00, '2026-02-01', '唐文', '技术部', '钉钉自动审查', 25.00, '银行转账', 4.00, 90, '2026-01-25 14:00:00', '2026-01-28 17:30:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\6.doc'),
(7, '设备采购合同(补充协议)', 'CG-2026-002', '采购合同', 50000.00, '2026-01-22', '唐文', '技术部', '直接查询', 30.00, '银行转账', 5.00, 15, '2026-01-18 10:30:00', '2026-01-20 15:45:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\7.doc'),
(8, '技术服务合同', 'FW-2026-001', '服务合同', 95000.00, '2026-01-18', '唐文', '技术部', '知识库自动审查', 20.00, '银行转账', 3.00, 40, '2026-01-12 09:15:00', '2026-01-15 16:30:00', '技术部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\8.docx'),
(9, '产品销售合同(长期)', 'XS-2026-002', '销售合同', 500000.00, '2026-02-05', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 5.00, 30, '2026-01-28 11:00:00', '2026-02-01 17:00:00', '市场部', 0, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\9.docx'),
(10, '软件开发框架合同', 'RK-2026-003', '服务合同', 350000.00, '2026-01-15', '唐文', '技术部', '直接查询', 25.00, '银行转账', 4.00, 120, '2026-01-08 10:00:00', '2026-01-12 16:15:00', '技术部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\10.doc'),
(11, '设备采购框架合同', 'CG-2026-003', '采购合同', 400000.00, '2026-01-22', '唐文', '市场部', '知识库自动审查', 30.00, '银行转账', 5.00, 25, '2026-01-16 09:30:00', '2026-01-19 15:30:00', '市场部', 0, 2, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\11.doc'),
(12, '技术咨询框架合同', 'ZX-2026-002', '服务合同', 180000.00, '2026-01-28', '唐文', '市场部', '钉钉自动审查', 20.00, '银行转账', 4.00, 60, '2026-01-22 14:30:00', '2026-01-25 16:45:00', '市场部', 0, 0, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\12.doc'),
(13, '产品销售补充协议', 'XS-2026-003', '销售合同', 120000.00, '2026-02-05', '唐文', '市场部', '直接查询', 15.00, '银行转账', 6.00, 15, '2026-01-30 10:15:00', '2026-02-03 15:15:00', '市场部', 1, 1, 0, '已完成', 'e:\test\aozhipoc\contractReview\.trae\documents\13.doc');
-- 插入风险数据
INSERT INTO `contract_risk` (`id`, `contract_id`, `rule_no`, `rule_name`, `risk_type`, `risk_level`, `regulation_name`, `regulation_clause`, `violation_clause`, `violation_basis`, `description`, `suggestion`, `created_at`, `updated_at`) VALUES
(1, 1, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同签订后7日内,买方支付合同总金额的 25% 作为定金。', '定金比例(25%)低于公司风控红线(30%)', '定金比例(25%)低于公司风控红线(30%)', '将定金比例调整为 30%', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(2, 2, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '合同双方签字盖章生效后,付定金 $20 \\text{‰}$', '定金比例(2%)低于公司风控红线(30%)', '定金比例(2%)低于公司风控红线(30%)', '将定金比例调整为 30%', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(3, 2, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '卖方确认本次合同总额的 $50%$ 接受买方使用承兑汇票支付', '支付方式模糊,未排除商承风险', '支付方式模糊,未排除商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(4, 2, 'CR03', '合同风险评估', '违约金', '高危', '《公司销售合同业务规则》', '规则 CR03', '卖方每延迟交货一天,扣除合同总额的 $1%$,累计计算;安装调试延期每日扣 $0.1%$,亦无上限', '违约金无上限,且日罚1%远超5%风控红线', '违约金无上限,且日罚1%远超5%风控红线', '建议对所有违约金条款统一设置 不超过合同总额5%的封顶限制', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(5, 3, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的30%……作为预付款(其中$20%$为定金);', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即预付款中定金部分应占合同总价的30%,而非20%)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(6, 3, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……部分承兑 (金额: 000 ),承兑部分包含在提货款、到货款中。', '支付方式仅写"承兑",未限定类型,存在商承风险', '支付方式仅写"承兑",未限定类型,存在商承风险', '明确改为"银行承兑汇票",并注明"不接受商业承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(7, 10, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 $30%$ 作为首付款(其中包含 $20%$ 的定金 + $10%$ 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即首付款中定金部分应占合同总价的30%,而非20%)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(8, 10, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,$15%$ 银行承兑;发货款、质保金:银行承兑……', '合同允许使用银行承兑汇票支付主要款项,违反公司拒收所有承兑(含银承)的内部风控要求', '合同允许使用银行承兑汇票支付主要款项,违反公司拒收所有承兑(含银承)的内部风控要求', '明确删除"银行承兑"表述,全部改为"银行电汇",或至少注明"不接受任何形式的承兑汇票"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(9, 11, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期付款)……', '定金比例(20%)低于公司风控红线(30%)', '定金比例(20%)低于公司风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得与其他款项混同)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(10, 11, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止接受承兑汇票(包括银行承兑),以规避票据风险与贴现成本', '公司内部风控政策已全面禁止接受承兑汇票(包括银行承兑),以规避票据风险与贴现成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(11, 12, 'CR01', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR01', '……支付合同总价的 30% 作为首付款(其中包含 20% 的定金 + 10% 首期款)……', '定金比例(20%)低于公司强制风控红线(30%)', '定金比例(20%)低于公司强制风控红线(30%)', '将定金比例调整为 30%(即定金应单独占合同总价的30%,不得拆分或混同)', '2026-02-04 17:15:13', '2026-02-04 17:51:20'),
(12, 12, 'CR02', '合同风险评估', '付款', '中危', '《公司销售合同业务规则》', '规则 CR02', '……首付款:15%银行电汇,15%银行承兑;发货款、质保金:银行承兑……', '公司内部风控政策已全面禁止承兑汇票(包括银行承兑),以规避票据兑付风险与资金成本', '公司内部风控政策已全面禁止承兑汇票(包括银行承兑),以规避票据兑付风险与资金成本', '全部支付方式改为"银行电汇",或明确声明"不接受任何形式的承兑汇票(含银行承兑)"', '2026-02-04 17:15:13', '2026-02-04 17:51:20');
-- 插入审查记录数据
INSERT INTO `review_record` (`contract_id`, `reviewer`, `review_time`, `review_result`) VALUES
(1, '唐文', '2026-01-18 16:45:00', '合同条款完整,无明显风险。'),
(2, '唐文', '2026-01-12 15:30:00', '设备参数明确,价格合理,无风险。'),
(3, '唐文', '2026-01-08 14:20:00', '技术咨询服务合同条款完整,无风险。'),
(4, '唐文', '2026-01-23 16:00:00', '发现3个风险点,其中1个高风险,2个中风险,已提出修改建议。'),
(5, '唐文', '2026-01-03 11:15:00', '租赁合同条款完整,符合法律规定。'),
(6, '唐文', '2026-01-28 17:30:00', '软件开发合同修订版条款合理,无风险。'),
(7, '唐文', '2026-01-20 15:45:00', '设备采购补充协议条款清晰,无风险。'),
(8, '唐文', '2026-01-15 16:30:00', '技术服务合同条款完整,无明显风险。'),
(9, '唐文', '2026-02-01 17:00:00', '发现1个中风险点,已提出修改建议。'),
(10, '唐文', '2026-01-12 16:15:00', '软件开发框架合同条款合理,无风险。'),
(11, '唐文', '2026-01-19 15:30:00', '设备采购框架合同条款完整,无风险。'),
(12, '唐文', '2026-01-25 16:45:00', '技术咨询框架合同条款完整,无风险。'),
(13, '唐文', '2026-02-03 15:15:00', '发现2个风险点,其中1个高风险,1个中风险,已提出修改建议。');
\ No newline at end of file
version=1.0.0
version=1.0.0
groupId=com.contract.review
artifactId=contract-review-backend
com\contract\review\entity\Regulation.class
com\contract\review\entity\Regulation.class
com\contract\review\entity\ContractRisk.class
com\contract\review\service\impl\RegulationServiceImpl.class
com\contract\review\service\RegulationService.class
com\contract\review\service\impl\ContractServiceImpl.class
com\contract\review\controller\ContractController.class
com\contract\review\config\CorsConfig.class
com\contract\review\config\DatabaseInitializer.class
com\contract\review\service\ContractService.class
com\contract\review\entity\ReviewRecord.class
com\contract\review\mapper\ContractMapper.class
com\contract\review\entity\Contract.class
com\contract\review\controller\StatisticsController.class
com\contract\review\mapper\ContractRiskMapper.class
com\contract\review\mapper\RegulationMapper.class
com\contract\review\service\impl\ContractRiskServiceImpl.class
com\contract\review\mapper\ReviewRecordMapper.class
com\contract\review\controller\ContractRiskController.class
com\contract\review\Application.class
com\contract\review\controller\RegulationController.class
com\contract\review\service\ContractRiskService.class
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\Application.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\Application.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\impl\RegulationServiceImpl.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\entity\Contract.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\RegulationService.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\ContractRiskService.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\impl\ContractRiskServiceImpl.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\mapper\ReviewRecordMapper.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\controller\ContractController.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\controller\ContractRiskController.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\controller\RegulationController.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\entity\ContractRisk.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\mapper\ContractMapper.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\controller\StatisticsController.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\mapper\RegulationMapper.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\entity\Regulation.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\mapper\ContractRiskMapper.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\impl\ContractServiceImpl.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\service\ContractService.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\config\CorsConfig.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\entity\ReviewRecord.java
E:\test\aozhipoc\contractReview\backend\src\main\java\com\contract\review\config\DatabaseInitializer.java
# 合同分析报告
# 合同分析报告
## 1. 合同文件列表
| 编号 | 文件名 | 文件类型 | 大小 | 最后修改时间 |
|------|--------|----------|------|------------|
| 1 | 1.doc | DOC | 35840 bytes | 2025/12/3 21:13 |
| 2 | 2.doc | DOC | 40960 bytes | 2025/12/3 21:18 |
| 3 | 3.docx | DOCX | 29505 bytes | 2025/12/3 21:26 |
| 4 | 4.docx | DOCX | 24501 bytes | 2025/12/3 21:29 |
| 5 | 5.docx | DOCX | 33188 bytes | 2025/12/3 21:34 |
| 6 | 6.doc | DOC | 35840 bytes | 2025/12/3 21:37 |
| 7 | 7.doc | DOC | 34816 bytes | 2025/12/3 21:41 |
| 8 | 8.docx | DOCX | 53285 bytes | 2025/12/3 21:46 |
| 9 | 9.docx | DOCX | 41027 bytes | 2025/12/3 21:52 |
| 10 | 10.doc | DOC | 145241 bytes | 2025/12/3 22:01 |
| 11 | 11.doc | DOC | 50688 bytes | 2025/12/3 22:04 |
| 12 | 12.doc | DOC | 84480 bytes | 2026/1/29 18:13 |
| 13 | 13.doc | DOC | 46592 bytes | 2026/1/30 11:14 |
## 2. 合同基本信息分析
### 合同1:软件开发服务合同
- **文档名称**:软件开发服务合同
- **审查发起人**:张三
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-15 09:30:00
- **审查结束时间**:2025-11-18 16:45:00
- **合同类型**:服务合同
- **金额**:¥120,000.00
- **签订日期**:2025-11-20
- **保证金比例**:20%
- **付款方式**:银行转账
- **违约金比例**:3%
- **交付周期**:60天
### 合同2:设备采购合同
- **文档名称**:设备采购合同
- **审查发起人**:李四
- **查询来源**:供应商系统
- **所属部门**:采购部
- **审查发起时间**:2025-11-10 10:15:00
- **审查结束时间**:2025-11-12 15:30:00
- **合同类型**:采购合同
- **金额**:¥250,000.00
- **签订日期**:2025-11-15
- **保证金比例**:30%
- **付款方式**:银行转账
- **违约金比例**:5%
- **交付周期**:30天
### 合同3:技术咨询服务合同
- **文档名称**:技术咨询服务合同
- **审查发起人**:王五
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-05 09:00:00
- **审查结束时间**:2025-11-08 14:20:00
- **合同类型**:服务合同
- **金额**:¥80,000.00
- **签订日期**:2025-11-10
- **保证金比例**:15%
- **付款方式**:银行转账
- **违约金比例**:4%
- **交付周期**:45天
### 合同4:产品销售合同
- **文档名称**:产品销售合同
- **审查发起人**:赵六
- **查询来源**:销售系统
- **所属部门**:销售部
- **审查发起时间**:2025-11-20 11:30:00
- **审查结束时间**:2025-11-23 16:00:00
- **合同类型**:销售合同
- **金额**:¥300,000.00
- **签订日期**:2025-11-25
- **保证金比例**:25%
- **付款方式**:商业承兑汇票
- **违约金比例**:6%
- **交付周期**:20天
### 合同5:租赁合同
- **文档名称**:租赁合同
- **审查发起人**:孙七
- **查询来源**:内部系统
- **所属部门**:行政部
- **审查发起时间**:2025-11-01 08:45:00
- **审查结束时间**:2025-11-03 11:15:00
- **合同类型**:租赁合同
- **金额**:¥150,000.00
- **签订日期**:2025-11-05
- **保证金比例**:30%
- **付款方式**:银行转账
- **违约金比例**:5%
- **交付周期**:10天
### 合同6:软件开发合同(修订版)
- **文档名称**:软件开发合同(修订版)
- **审查发起人**:张三
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-25 14:00:00
- **审查结束时间**:2025-11-28 17:30:00
- **合同类型**:服务合同
- **金额**:¥180,000.00
- **签订日期**:2025-12-01
- **保证金比例**:25%
- **付款方式**:银行转账
- **违约金比例**:4%
- **交付周期**:90天
### 合同7:设备采购合同(补充协议)
- **文档名称**:设备采购合同(补充协议)
- **审查发起人**:李四
- **查询来源**:供应商系统
- **所属部门**:采购部
- **审查发起时间**:2025-11-18 10:30:00
- **审查结束时间**:2025-11-20 15:45:00
- **合同类型**:采购合同
- **金额**:¥50,000.00
- **签订日期**:2025-11-22
- **保证金比例**:30%
- **付款方式**:银行转账
- **违约金比例**:5%
- **交付周期**:15天
### 合同8:技术服务合同
- **文档名称**:技术服务合同
- **审查发起人**:王五
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-12 09:15:00
- **审查结束时间**:2025-11-15 16:30:00
- **合同类型**:服务合同
- **金额**:¥95,000.00
- **签订日期**:2025-11-18
- **保证金比例**:20%
- **付款方式**:银行转账
- **违约金比例**:3%
- **交付周期**:40天
### 合同9:产品销售合同(长期)
- **文档名称**:产品销售合同(长期)
- **审查发起人**:赵六
- **查询来源**:销售系统
- **所属部门**:销售部
- **审查发起时间**:2025-11-28 11:00:00
- **审查结束时间**:2025-12-01 17:00:00
- **合同类型**:销售合同
- **金额**:¥500,000.00
- **签订日期**:2025-12-05
- **保证金比例**:20%
- **付款方式**:银行转账
- **违约金比例**:5%
- **交付周期**:30天
### 合同10:软件开发框架合同
- **文档名称**:软件开发框架合同
- **审查发起人**:张三
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-08 10:00:00
- **审查结束时间**:2025-11-12 16:15:00
- **合同类型**:服务合同
- **金额**:¥350,000.00
- **签订日期**:2025-11-15
- **保证金比例**:25%
- **付款方式**:银行转账
- **违约金比例**:4%
- **交付周期**:120天
### 合同11:设备采购框架合同
- **文档名称**:设备采购框架合同
- **审查发起人**:李四
- **查询来源**:供应商系统
- **所属部门**:采购部
- **审查发起时间**:2025-11-16 09:30:00
- **审查结束时间**:2025-11-19 15:30:00
- **合同类型**:采购合同
- **金额**:¥400,000.00
- **签订日期**:2025-11-22
- **保证金比例**:30%
- **付款方式**:银行转账
- **违约金比例**:5%
- **交付周期**:25天
### 合同12:技术咨询框架合同
- **文档名称**:技术咨询框架合同
- **审查发起人**:王五
- **查询来源**:内部系统
- **所属部门**:技术部
- **审查发起时间**:2025-11-22 14:30:00
- **审查结束时间**:2025-11-25 16:45:00
- **合同类型**:服务合同
- **金额**:¥180,000.00
- **签订日期**:2025-11-28
- **保证金比例**:20%
- **付款方式**:银行转账
- **违约金比例**:4%
- **交付周期**:60天
### 合同13:产品销售补充协议
- **文档名称**:产品销售补充协议
- **审查发起人**:赵六
- **查询来源**:销售系统
- **所属部门**:销售部
- **审查发起时间**:2025-11-30 10:15:00
- **审查结束时间**:2025-12-03 15:15:00
- **合同类型**:销售合同
- **金额**:¥120,000.00
- **签订日期**:2025-12-05
- **保证金比例**:15%
- **付款方式**:银行转账
- **违约金比例**:6%
- **交付周期**:15天
## 3. 风险评估规则应用
### 风险评估规则
1. **CR01**:销售合同保证金比例不得低于30%(中风险)
2. **CR02**:销售合同不得使用商业承兑汇票付款(中风险)
3. **CR03**:销售合同违约金比例不得超过5%(高风险)
4. **CR04**:销售合同交付周期不得少于最低生产时间(中风险)
### 合同风险评估结果
#### 合同1:软件开发服务合同
- **风险数量**:0
- **风险等级**:无
#### 合同2:设备采购合同
- **风险数量**:0
- **风险等级**:无
#### 合同3:技术咨询服务合同
- **风险数量**:0
- **风险等级**:无
#### 合同4:产品销售合同
- **风险数量**:3
- **风险等级**
- CR01:中风险(保证金比例25%低于30%)
- CR02:中风险(使用商业承兑汇票付款)
- CR03:高风险(违约金比例6%超过5%)
#### 合同5:租赁合同
- **风险数量**:0
- **风险等级**:无
#### 合同6:软件开发合同(修订版)
- **风险数量**:0
- **风险等级**:无
#### 合同7:设备采购合同(补充协议)
- **风险数量**:0
- **风险等级**:无
#### 合同8:技术服务合同
- **风险数量**:0
- **风险等级**:无
#### 合同9:产品销售合同(长期)
- **风险数量**:1
- **风险等级**
- CR01:中风险(保证金比例20%低于30%)
#### 合同10:软件开发框架合同
- **风险数量**:0
- **风险等级**:无
#### 合同11:设备采购框架合同
- **风险数量**:0
- **风险等级**:无
#### 合同12:技术咨询框架合同
- **风险数量**:0
- **风险等级**:无
#### 合同13:产品销售补充协议
- **风险数量**:2
- **风险等级**
- CR01:中风险(保证金比例15%低于30%)
- CR03:高风险(违约金比例6%超过5%)
## 4. 风险统计
| 风险等级 | 数量 | 占比 |
|----------|------|------|
| 高风险 | 2 | 15.4% |
| 中风险 | 4 | 30.8% |
| 低风险 | 0 | 0% |
| 无风险 | 7 | 53.8% |
## 5. 结论
通过对13份合同的分析,发现共有6份合同存在风险,其中2份为高风险,4份为中风险。主要风险集中在销售合同的保证金比例、付款方式和违约金比例方面。建议在合同签订前严格审查这些条款,确保符合公司的风险控制要求。
\ No newline at end of file
[
[
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\1.doc",
"fileName": "1.doc",
"highRisk": 0,
"mediumRisk": 2,
"lowRisk": 0,
"totalRisk": 2,
"risks": [
{
"rule": "CR01",
"name": "定金条款(强制30%)",
"description": "扫描目标:提取定金比例X。如果X < 30%,则为中风险。",
"level": "中危",
"violation": "定金比例(20%)低于公司风控红线(30%)",
"suggestion": "将定金比例调整为30%"
},
{
"rule": "CR04",
"name": "货期审查",
"description": "如果表述模糊,如'暂定'、'具备条件后',则为中风险。",
"level": "中危",
"violation": "货期表述模糊",
"suggestion": "需明确具体发货日期"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\2.doc",
"fileName": "2.doc",
"highRisk": 1,
"mediumRisk": 1,
"lowRisk": 0,
"totalRisk": 2,
"risks": [
{
"rule": "CR03",
"name": "罚款/违约金",
"description": "如果包含'不超过...'、'封顶...'或'以...为限',则合规;否则,如果提取到的具体比例X > 5%,则为高风险。",
"level": "高危",
"violation": "违约金比例(8%)超过公司规定的5%封顶",
"suggestion": "建议设5%封顶"
},
{
"rule": "CR02",
"name": "支付条款",
"description": "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。",
"level": "中危",
"violation": "支付方式使用商业承兑汇票",
"suggestion": "改为'银行承兑'并拒收商承"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\3.docx",
"fileName": "3.docx",
"highRisk": 0,
"mediumRisk": 1,
"lowRisk": 0,
"totalRisk": 1,
"risks": [
{
"rule": "CR02",
"name": "支付条款",
"description": "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。",
"level": "中危",
"violation": "支付方式表述模糊,未明确排除商承",
"suggestion": "支付方式需明确排除商承风险"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\4.docx",
"fileName": "4.docx",
"highRisk": 0,
"mediumRisk": 0,
"lowRisk": 0,
"totalRisk": 0,
"risks": []
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\5.docx",
"fileName": "5.docx",
"highRisk": 1,
"mediumRisk": 0,
"lowRisk": 1,
"totalRisk": 2,
"risks": [
{
"rule": "CR03",
"name": "罚款/违约金",
"description": "如果包含'不超过...'、'封顶...'或'以...为限',则合规;否则,如果提取到的具体比例X > 5%,则为高风险。",
"level": "高危",
"violation": "违约金比例(10%)超过公司规定的5%封顶",
"suggestion": "建议设5%封顶"
},
{
"rule": "CR04",
"name": "货期审查",
"description": "如果表述模糊,如'暂定'、'具备条件后',则为中风险。",
"level": "低危",
"violation": "货期表述模糊",
"suggestion": "需明确具体发货日期"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\6.doc",
"fileName": "6.doc",
"highRisk": 0,
"mediumRisk": 3,
"lowRisk": 0,
"totalRisk": 3,
"risks": [
{
"rule": "CR01",
"name": "定金条款(强制30%)",
"description": "扫描目标:提取定金比例X。如果X < 30%,则为中风险。",
"level": "中危",
"violation": "定金比例(25%)低于公司风控红线(30%)",
"suggestion": "将定金比例调整为30%"
},
{
"rule": "CR02",
"name": "支付条款",
"description": "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。",
"level": "中危",
"violation": "支付方式使用商业承兑汇票",
"suggestion": "改为'银行承兑'并拒收商承"
},
{
"rule": "CR04",
"name": "货期审查",
"description": "如果表述模糊,如'暂定'、'具备条件后',则为中风险。",
"level": "中危",
"violation": "货期表述模糊",
"suggestion": "需明确具体发货日期"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\7.doc",
"fileName": "7.doc",
"highRisk": 0,
"mediumRisk": 0,
"lowRisk": 0,
"totalRisk": 0,
"risks": []
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\8.docx",
"fileName": "8.docx",
"highRisk": 1,
"mediumRisk": 0,
"lowRisk": 0,
"totalRisk": 1,
"risks": [
{
"rule": "CR03",
"name": "罚款/违约金",
"description": "如果包含'不超过...'、'封顶...'或'以...为限',则合规;否则,如果提取到的具体比例X > 5%,则为高风险。",
"level": "高危",
"violation": "违约金比例(6%)超过公司规定的5%封顶",
"suggestion": "建议设5%封顶"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\9.docx",
"fileName": "9.docx",
"highRisk": 0,
"mediumRisk": 1,
"lowRisk": 0,
"totalRisk": 1,
"risks": [
{
"rule": "CR01",
"name": "定金条款(强制30%)",
"description": "扫描目标:提取定金比例X。如果X < 30%,则为中风险。",
"level": "中危",
"violation": "定金比例(15%)低于公司风控红线(30%)",
"suggestion": "将定金比例调整为30%"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\10.doc",
"fileName": "10.doc",
"highRisk": 0,
"mediumRisk": 2,
"lowRisk": 0,
"totalRisk": 2,
"risks": [
{
"rule": "CR02",
"name": "支付条款",
"description": "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。",
"level": "中危",
"violation": "支付方式使用商业承兑汇票",
"suggestion": "改为'银行承兑'并拒收商承"
},
{
"rule": "CR04",
"name": "货期审查",
"description": "如果表述模糊,如'暂定'、'具备条件后',则为中风险。",
"level": "中危",
"violation": "货期表述模糊",
"suggestion": "需明确具体发货日期"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\11.doc",
"fileName": "11.doc",
"highRisk": 1,
"mediumRisk": 1,
"lowRisk": 0,
"totalRisk": 2,
"risks": [
{
"rule": "CR03",
"name": "罚款/违约金",
"description": "如果包含'不超过...'、'封顶...'或'以...为限',则合规;否则,如果提取到的具体比例X > 5%,则为高风险。",
"level": "高危",
"violation": "违约金比例(7%)超过公司规定的5%封顶",
"suggestion": "建议设5%封顶"
},
{
"rule": "CR01",
"name": "定金条款(强制30%)",
"description": "扫描目标:提取定金比例X。如果X < 30%,则为中风险。",
"level": "中危",
"violation": "定金比例(22%)低于公司风控红线(30%)",
"suggestion": "将定金比例调整为30%"
}
]
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\12.doc",
"fileName": "12.doc",
"highRisk": 0,
"mediumRisk": 0,
"lowRisk": 0,
"totalRisk": 0,
"risks": []
},
{
"file": "e:\\test\\aozhipoc\\contractReview\\.trae\\documents\\13.doc",
"fileName": "13.doc",
"highRisk": 0,
"mediumRisk": 1,
"lowRisk": 1,
"totalRisk": 2,
"risks": [
{
"rule": "CR02",
"name": "支付条款",
"description": "如果包含'商业承兑'或'商承',则为中风险;如果仅写'承兑'未限定类型,也为中风险。",
"level": "中危",
"violation": "支付方式表述模糊,未明确排除商承",
"suggestion": "支付方式需明确排除商承风险"
},
{
"rule": "CR04",
"name": "货期审查",
"description": "如果表述模糊,如'暂定'、'具备条件后',则为中风险。",
"level": "低危",
"violation": "货期表述模糊",
"suggestion": "需明确具体发货日期"
}
]
}
]
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>合同审查系统</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{
{
"name": "contract-review-poc",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@cyntler/react-doc-viewer": "^1.17.1",
"antd": "^5.12.8",
"axios": "^1.13.4",
"echarts": "^5.4.3",
"mammoth": "^1.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^3.1.0",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^4.9.5",
"vite": "^4.4.5"
}
}
import React, {useState, useEffect} from 'react';
import React, {useState, useEffect} from 'react';
import {
Card,
Statistic,
Table,
Button,
Input,
Select,
Tag,
Form,
Divider,
Spin,
Modal,
Upload,
message,
DatePicker,
Menu,
Layout
} from 'antd';
import {
SearchOutlined,
UploadOutlined,
DownloadOutlined,
EyeOutlined,
PlusOutlined,
FileTextOutlined,
SettingOutlined
} from '@ant-design/icons';
import type {RangePickerProps} from 'antd/es/date-picker';
import dayjs from 'dayjs';
import mammoth from 'mammoth';
import {contractApi, statisticsApi} from './services/api';
import './index.css';
const {Sider, Content} = Layout;
// 类型定义
interface Contract {
id: number;
name: string;
submitter: string;
department: string;
querySource: string;
uploadTime: string;
finishTime: string;
documentUploader: string;
highRisk: number;
mediumRisk: number;
lowRisk: number;
status: string;
contractNo?: string;
contractType?: string;
amount?: number;
signDate?: string;
depositRatio?: number;
paymentMethod?: string;
penaltyRatio?: number;
deliveryPeriod?: number;
sourceFile?: string;
}
// 格式化金额
const formatAmount = (amount: number) => {
const formatter = new Intl.NumberFormat('zh-CN', {style: 'currency', currency: 'CNY'});
return formatter.format(amount);
};
const App: React.FC = () => {
const [currentPage, setCurrentPage] = useState('dashboard');
const [selectedContract, setSelectedContract] = useState<any>({
id: 0,
name: '请选择合同',
risks: []
});
const [contracts, setContracts] = useState<Contract[]>([]);
const [loading, setLoading] = useState(false);
const [searchText, setSearchText] = useState('');
const [department, setDepartment] = useState('');
const [querySource, setQuerySource] = useState('');
const [riskLevel, setRiskLevel] = useState('');
const [status, setStatus] = useState('');
const [uploadTime, setUploadTime] = useState('');
const [finishTime, setFinishTime] = useState('');
const [currentPageNum, setCurrentPageNum] = useState(1);
const [pageSize, setPageSize] = useState(8);
const [total, setTotal] = useState(0);
const [detailRiskLevel, setDetailRiskLevel] = useState('全部风险');
const [detailSearchText, setDetailSearchText] = useState('');
const [filteredRisks, setFilteredRisks] = useState<any[]>([]);
const [statistics, setStatistics] = useState<any>({
totalContracts: 13,
totalRisks: 15,
contracts2026: 13,
risks2026: 15,
contracts2025: 0,
risks2025: 0
});
const [parsedContractContent, setParsedContractContent] = useState<string>('');
const [parsing, setParsing] = useState<boolean>(false);
const [previewUrl, setPreviewUrl] = useState<string>('');
// 提交文档审查弹窗
const [submitModalVisible, setSubmitModalVisible] = useState(false);
const [uploadFileList, setUploadFileList] = useState<any[]>([]);
const [submitForm] = Form.useForm();
// 加载合同列表
const loadContracts = async () => {
setLoading(true);
try {
const response = await contractApi.getContractList({
page: currentPageNum,
pageSize: pageSize,
name: searchText,
department: department,
querySource: querySource,
riskLevel: riskLevel,
uploadTime: uploadTime,
finishTime: finishTime
});
// 先转换为unknown,再转换为我们自定义的类型
const typedResponse = response as unknown as { code: number; data: Contract[]; total: number };
if (typedResponse && typedResponse.code === 200) {
setContracts(typedResponse.data || []);
setTotal(typedResponse.total || 0);
} else {
setContracts([]);
setTotal(0);
}
} catch (error) {
console.error('加载合同列表失败:', error);
setContracts([]);
setTotal(0);
} finally {
setLoading(false);
}
};
// 加载统计数据
const loadStatistics = async () => {
try {
const response = await statisticsApi.getStatistics();
// 先转换为unknown,再转换为我们自定义的类型
const typedResponse = response as unknown as { code: number; data: any };
if (typedResponse && typedResponse.code === 200) {
setStatistics(typedResponse.data);
}
} catch (error) {
console.error('加载统计数据失败:', error);
// 失败时使用默认数据
setStatistics({
totalContracts: 13,
totalRisks: 15,
contracts2026: 13,
risks2026: 15,
contracts2025: 0,
risks2025: 0
});
}
};
// 获取合同预览地址
const getContractPreviewUrl = async (contractId: number) => {
try {
const response = await contractApi.getContractPreviewUrl(contractId);
const typedResponse = response as unknown as { code: number; data: any };
if (typedResponse && typedResponse.code === 200) {
setPreviewUrl(typedResponse.data);
}
} catch (error) {
console.error('获取预览地址失败:', error);
setPreviewUrl('');
}
};
// 解析合同文件
const parseContractFile = async (contractId: number) => {
setParsing(true);
try {
// 生成后端文件流URL
const fileStreamUrl = `http://192.168.1.175:8084/api/contracts/${contractId}/file`;
// 获取文件流
const response = await fetch(fileStreamUrl);
if (!response.ok) {
throw new Error('Failed to fetch contract file');
}
// 读取文件内容为ArrayBuffer
const arrayBuffer = await response.arrayBuffer();
try {
// 使用mammoth.js转换Word内容为HTML
const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});
// 设置转换后的HTML内容
setParsedContractContent(result.value);
console.log('转换后的HTML内容:', result.value);
} catch (parseError) {
console.error('mammoth.js解析失败:', parseError);
// 检查是否是.doc文件导致的错误
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
if (errorMessage.includes('Can\'t find end of central directory')) {
// 这是.doc文件或其他不支持的格式
setParsedContractContent('<p>该合同文件为旧版.doc格式,不支持在线预览,请下载查看原始文件</p>');
} else {
// 其他解析错误
setParsedContractContent('<p>合同文件格式不支持,请下载查看原始文件</p>');
}
}
} catch (error) {
console.error('解析合同文件失败:', error);
setParsedContractContent('<p>合同文件解析失败,请下载查看原始文件</p>');
} finally {
setParsing(false);
}
};
// 加载合同详情
const loadContractDetail = async (contractId: number) => {
setLoading(true);
try {
// 并行请求,提高加载速度
const [detailResponse, risksResponse] = await Promise.all([
contractApi.getContractById(contractId),
contractApi.getContractRisks(contractId)
]);
// 先转换为unknown,再转换为我们自定义的类型
const typedDetailResponse = detailResponse as unknown as { code: number; data: any };
const typedRisksResponse = risksResponse as unknown as { code: number; data: any };
if (typedDetailResponse && typedDetailResponse.code === 200 && typedRisksResponse && typedRisksResponse.code === 200) {
const risks = typedRisksResponse.data || [];
// 计算风险概览数据
const riskOverview = calculateRiskOverview(risks);
const contractWithRisks = {
...typedDetailResponse.data,
risks: risks,
highRisk: riskOverview.highRisk,
mediumRisk: riskOverview.mediumRisk,
lowRisk: riskOverview.lowRisk
};
setSelectedContract(contractWithRisks);
filterRisks(risks);
// 解析合同文件,使用URL.createObjectURL()创建本地URL
await parseContractFile(contractId);
} else {
// API返回错误时显示空数据
setSelectedContract({
id: contractId,
name: '合同详情加载失败',
highRisk: 0,
mediumRisk: 0,
lowRisk: 0,
risks: []
});
setFilteredRisks([]);
setParsedContractContent('<p>合同详情加载失败</p>');
}
} catch (error) {
console.error('加载合同详情失败:', error);
// 网络错误时显示空数据
setSelectedContract({
id: contractId,
name: '合同详情加载失败',
highRisk: 0,
mediumRisk: 0,
lowRisk: 0,
risks: []
});
setFilteredRisks([]);
setParsedContractContent('<p>合同详情加载失败</p>');
} finally {
setLoading(false);
}
};
// 过滤风险数据
const filterRisks = (risks: any[]) => {
let filtered = risks;
// 按风险等级过滤
if (detailRiskLevel !== '全部风险') {
const levelMap: Record<string, string> = {
'高风险': '高危',
'中风险': '中危',
'低风险': '低危'
};
const level = levelMap[detailRiskLevel];
if (level) {
filtered = filtered.filter(risk => risk.riskLevel === level);
}
}
// 按搜索文本过滤
if (detailSearchText) {
const searchLower = detailSearchText.toLowerCase();
filtered = filtered.filter(risk =>
risk.riskType.toLowerCase().includes(searchLower) ||
risk.regulationName.toLowerCase().includes(searchLower) ||
risk.regulationClause.toLowerCase().includes(searchLower) ||
risk.violationClause.toLowerCase().includes(searchLower) ||
risk.suggestion.toLowerCase().includes(searchLower)
);
}
setFilteredRisks(filtered);
};
// 计算风险概览数据
const calculateRiskOverview = (risks: any[]) => {
let highRisk = 0;
let mediumRisk = 0;
let lowRisk = 0;
risks.forEach(risk => {
switch (risk.riskLevel) {
case '高危':
highRisk++;
break;
case '中危':
mediumRisk++;
break;
case '低危':
lowRisk++;
break;
default:
break;
}
});
return {highRisk, mediumRisk, lowRisk};
};
// 初始加载和分页变化时重新加载
useEffect(() => {
if (currentPage === 'dashboard') {
loadContracts();
loadStatistics();
}
}, [currentPageNum, pageSize]);
// 组件加载时获取统计数据
useEffect(() => {
loadStatistics();
}, []);
// 当风险过滤条件变化时重新过滤
useEffect(() => {
if (currentPage === 'detail' && selectedContract.risks) {
filterRisks(selectedContract.risks);
}
}, [detailRiskLevel, detailSearchText, selectedContract.risks, currentPage]);
// 处理查看详情
const handleViewDetail = (contract: any) => {
loadContractDetail(contract.id);
setCurrentPage('detail');
};
// 处理返回仪表盘
const handleBackToDashboard = () => {
setCurrentPage('dashboard');
// 重置详情页的过滤条件
setDetailRiskLevel('全部风险');
setDetailSearchText('');
setFilteredRisks([]);
// 重置选中的合同
setSelectedContract({
id: 0,
name: '请选择合同',
risks: []
});
};
// 风险等级标签颜色
const getRiskLevelColor = (level: string) => {
switch (level) {
case '高危':
return 'red';
case '中危':
return 'orange';
case '低危':
return 'green';
default:
return 'blue';
}
};
// 风险等级标签文本
const getRiskLevelText = (level: string) => {
switch (level) {
case 'high':
return '高风险';
case 'medium':
return '中风险';
case 'low':
return '低风险';
default:
return '未知';
}
};
// 处理搜索
const handleSearch = () => {
setCurrentPageNum(1); // 重置到第一页
loadContracts();
};
// 处理重置
const handleReset = () => {
setSearchText('');
setDepartment('');
setQuerySource('');
setRiskLevel('');
setUploadTime('');
setFinishTime('');
setCurrentPageNum(1); // 重置到第一页
loadContracts();
};
// 处理提交文档审查
const handleSubmitReview = async () => {
try {
const formValues = await submitForm.validateFields();
// 构建合同数据
const contractData = {
name: uploadFileList[0]?.name || '未命名合同',
submitter: '唐文',
department: '技术部',
querySource: '直接查询',
uploadTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
finishTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
documentUploader: '唐文',
highRisk: 0,
mediumRisk: 0,
lowRisk: 0,
status: '已完成'
};
// 调用后端接口提交合同
const response = await contractApi.addContract(contractData);
// 先转换为unknown,再转换为我们自定义的类型
const typedResponse = response as unknown as { code: number; data: any };
if (typedResponse && typedResponse.code === 200) {
message.success('文档审查提交成功!');
setSubmitModalVisible(false);
submitForm.resetFields();
setUploadFileList([]);
loadContracts(); // 重新加载合同列表
} else {
message.error('文档审查提交失败,请稍后重试!');
}
} catch (error) {
console.error('提交文档审查失败:', error);
message.error('文档审查提交失败,请稍后重试!');
}
};
// 处理文件上传
const handleFileUpload = (file: any) => {
// 这里只是模拟文件上传,实际项目中需要调用后端接口
setUploadFileList([file]);
// 设置合同名称为上传的文件名称
submitForm.setFieldsValue({name: file.name});
return false; // 阻止自动上传
};
// 处理分页变化
const handlePageChange = (page: number, size: number) => {
setCurrentPageNum(page);
setPageSize(size);
};
// 处理忽略风险
const handleIgnoreRisk = async (riskId: number) => {
try {
// 调用后端接口更新风险状态
const response = await fetch(`http://192.168.1.175:8084/api/risks/${riskId}/status`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({status: 1}) // 1表示忽略
});
if (response.ok) {
message.success('风险已忽略');
// 重新加载合同详情,更新风险状态
loadContractDetail(selectedContract.id);
} else {
message.error('忽略风险失败,请稍后重试');
}
} catch (error) {
console.error('忽略风险失败:', error);
message.error('忽略风险失败,请稍后重试');
}
};
// 仪表盘页面
const DashboardPage = () => (
<div>
<div className="header">
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div className="header-title">文件导入启动AI风险扫描</div>
<div className="header-right">
<div className="logo">IN-ORDER</div>
</div>
</div>
</div>
<div className="content">
<div className="dashboard-stats">
<Card className="dashboard-stat-card">
<Statistic title="总计审查文件数" value={statistics.totalContracts}
suffix={<Tag color="green">+100% 较上月</Tag>}/>
</Card>
<Card className="dashboard-stat-card">
<Statistic title="发现风险数" value={statistics.totalRisks}
suffix={<Tag color="green">+100% 较上月</Tag>}/>
</Card>
<Card className="dashboard-stat-card">
<Statistic title="2026年审查文件数" value={statistics.contracts2026}
suffix={<Tag color="green">+100% 较去年</Tag>}/>
</Card>
<Card className="dashboard-stat-card">
<Statistic title="2026年发现风险数" value={statistics.risks2026}
suffix={<Tag color="green">+100% 较去年</Tag>}/>
</Card>
</div>
<div className="search-bar">
<Form layout="inline">
<Form.Item>
<Select
placeholder="部门"
className="filter-item"
value={department}
onChange={setDepartment}
options={[
{value: '', label: '全部部门'},
{value: '销售部', label: '销售部'},
{value: '采购部', label: '采购部'},
{value: '技术部', label: '技术部'},
{value: '行政部', label: '行政部'},
{value: '人力资源部', label: '人力资源部'},
{value: '供应链', label: '供应链'},
{value: '市场部', label: '市场部'},
{value: '管理部', label: '管理部'},
{value: '工程部', label: '工程部'}
]}
/>
</Form.Item>
<Form.Item>
<Select
placeholder="查询来源"
className="filter-item"
value={querySource}
onChange={setQuerySource}
options={[
{value: '', label: '全部来源'},
{value: '直接查询', label: '直接查询'},
{value: '知识库自动审查', label: '知识库自动审查'},
{value: '钉钉自动审查', label: '钉钉自动审查'}
]}
/>
</Form.Item>
<Form.Item>
<Select
placeholder="风险等级"
className="filter-item"
value={riskLevel}
onChange={setRiskLevel}
options={[
{value: '', label: '全部等级'},
{value: 'high', label: '高风险'},
{value: 'medium', label: '中风险'},
{value: 'low', label: '低风险'}
]}
/>
</Form.Item>
<Form.Item>
<DatePicker
placeholder="上传时间"
className="filter-item"
style={{width: 180}}
onChange={(date) => setUploadTime(date ? date.format('YYYY-MM-DD HH:mm:ss') : '')}
/>
</Form.Item>
<Form.Item>
<DatePicker
placeholder="完结时间"
className="filter-item"
style={{width: 180}}
onChange={(date) => setFinishTime(date ? date.format('YYYY-MM-DD HH:mm:ss') : '')}
/>
</Form.Item>
<Form.Item>
<Input
placeholder="搜索文件名称"
prefix={<SearchOutlined/>}
className="search-input"
value={searchText}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value)}
/>
</Form.Item>
<Form.Item>
<Button type="primary" onClick={handleSearch}>搜索</Button>
</Form.Item>
<Form.Item>
<Button onClick={handleReset}>重置</Button>
</Form.Item>
<Form.Item>
<Button type="primary" icon={<UploadOutlined/>} onClick={() => setSubmitModalVisible(true)}>
提交文件审查
</Button>
</Form.Item>
</Form>
</div>
<Spin spinning={loading} tip="加载中...">
<Table
dataSource={contracts}
columns={[
{
title: '序号',
dataIndex: 'id',
key: 'id'
},
{
title: '文件名称',
dataIndex: 'name',
key: 'name',
render: (text: string) => <a href="#">{text}</a>
},
{
title: '审查发起人',
dataIndex: 'submitter',
key: 'submitter'
},
{
title: '查询来源',
dataIndex: 'querySource',
key: 'querySource'
},
{
title: '所属部门',
dataIndex: 'department',
key: 'department'
},
{
title: '上传时间',
dataIndex: 'uploadTime',
key: 'uploadTime'
},
{
title: '完结时间',
dataIndex: 'finishTime',
key: 'finishTime'
},
{
title: 'AI初审结果',
dataIndex: 'status',
key: 'status',
render: (status: string) => <Tag
color={status === '已完成' ? 'green' : 'blue'}>{status}</Tag>
},
{
title: '风险项',
dataIndex: ['highRisk', 'mediumRisk', 'lowRisk'],
key: 'result',
render: (_, record: any) => (
<div>
<Tag color="red">高风险 {record.highRisk}</Tag>
<Tag color="orange">中风险 {record.mediumRisk}</Tag>
<Tag color="green">低风险 {record.lowRisk}</Tag>
</div>
)
},
{
title: '操作',
key: 'action',
render: (_, record: any) => (
<Button
type="link"
icon={<EyeOutlined/>}
onClick={() => handleViewDetail(record)}
>
查看详情
</Button>
)
}
]}
pagination={{
current: currentPageNum,
pageSize: pageSize,
total: total,
showSizeChanger: true,
pageSizeOptions: ['8', '16', '24'],
showTotal: (total) => `显示第 ${(currentPageNum - 1) * pageSize + 1} 至 ${Math.min(currentPageNum * pageSize, total)} 条,共 ${total} 条结果`,
onChange: handlePageChange
}}
/>
</Spin>
</div>
</div>
);
// 合同审查详情页面
const DetailPage = () => (
<div>
<div className="header">
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div style={{display: 'flex', alignItems: 'center', gap: 16}}>
<Button icon={<EyeOutlined/>} onClick={handleBackToDashboard}>
返回列表
</Button>
<div className="header-title">文件导入启动AI风险扫描结果展示</div>
</div>
<div className="header-right">
<div className="logo">IN-ORDER</div>
</div>
</div>
</div>
<div className="content">
<Spin spinning={loading} tip="加载中...">
{/* 顶部合同信息 */}
<div style={{
marginBottom: 20,
padding: 16,
backgroundColor: '#fff',
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.09)'
}}>
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div>
<h2 style={{
margin: 0,
fontSize: '24px',
fontWeight: 'bold',
color: '#333'
}}>{selectedContract.name}</h2>
<div style={{marginTop: 8, color: '#666'}}>
<span style={{marginRight: 24}}>合同编号: {selectedContract.contractNo || ''}</span>
<span style={{marginRight: 24}}>上传日期: {selectedContract.uploadTime || ''}</span>
<span>文档上传人: {selectedContract.documentUploader || ''}</span>
</div>
</div>
<div style={{display: 'flex', gap: 16}}>
<Button type="primary">重新审查</Button>
</div>
</div>
</div>
{/* 风险概览 */}
<div style={{
marginBottom: 20,
padding: 16,
backgroundColor: '#fff',
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.09)'
}}>
<h3 style={{
margin: '0 0 16px 0',
fontSize: '18px',
fontWeight: 'bold',
color: '#333'
}}>风险概览</h3>
<div style={{display: 'flex', gap: 16, flexWrap: 'wrap'}}>
{/* 计算风险总数 */}
{(() => {
const totalRisk = (selectedContract.highRisk || 0) + (selectedContract.mediumRisk || 0) + (selectedContract.lowRisk || 0);
return (
<>
{/* 风险总数 */}
<div style={{flex: 1, minWidth: 200}}>
<div style={{display: 'flex', alignItems: 'center', marginBottom: 8}}>
<div style={{
fontSize: '14px',
marginRight: 12,
color: '#1890ff'
}}>风险总数: {totalRisk}</div>
</div>
<div style={{
flex: 1,
height: 8,
backgroundColor: '#f0f0f0',
borderRadius: 4,
overflow: 'hidden'
}}>
<div style={{
width: totalRisk > 0 ? '100%' : '0%',
height: '100%',
backgroundColor: '#1890ff',
borderRadius: 4
}}></div>
</div>
</div>
{/* 高风险 */}
<div style={{flex: 1, minWidth: 200}}>
<div style={{display: 'flex', alignItems: 'center', marginBottom: 8}}>
<div style={{
fontSize: '14px',
marginRight: 12,
color: '#ff4d4f'
}}>高风险: {selectedContract.highRisk || 0}</div>
</div>
<div style={{
flex: 1,
height: 8,
backgroundColor: '#f0f0f0',
borderRadius: 4,
overflow: 'hidden'
}}>
<div style={{
width: totalRisk > 0 ? `${Math.round(((selectedContract.highRisk || 0) / totalRisk) * 100)}%` : '0%',
height: '100%',
backgroundColor: '#ff4d4f',
borderRadius: 4
}}></div>
</div>
</div>
{/* 中风险 */}
<div style={{flex: 1, minWidth: 200}}>
<div style={{display: 'flex', alignItems: 'center', marginBottom: 8}}>
<div style={{
fontSize: '14px',
marginRight: 12,
color: '#fa8c16'
}}>中风险: {selectedContract.mediumRisk || 0}</div>
</div>
<div style={{
flex: 1,
height: 8,
backgroundColor: '#f0f0f0',
borderRadius: 4,
overflow: 'hidden'
}}>
<div style={{
width: totalRisk > 0 ? `${Math.round(((selectedContract.mediumRisk || 0) / totalRisk) * 100)}%` : '0%',
height: '100%',
backgroundColor: '#fa8c16',
borderRadius: 4
}}></div>
</div>
</div>
{/* 低风险 */}
<div style={{flex: 1, minWidth: 200}}>
<div style={{display: 'flex', alignItems: 'center', marginBottom: 8}}>
<div style={{
fontSize: '14px',
marginRight: 12,
color: '#52c41a'
}}>低风险: {selectedContract.lowRisk || 0}</div>
</div>
<div style={{
flex: 1,
height: 8,
backgroundColor: '#f0f0f0',
borderRadius: 4,
overflow: 'hidden'
}}>
<div style={{
width: totalRisk > 0 ? `${Math.round(((selectedContract.lowRisk || 0) / totalRisk) * 100)}%` : '0%',
height: '100%',
backgroundColor: '#52c41a',
borderRadius: 4
}}></div>
</div>
</div>
</>
);
})()}
</div>
</div>
{/* 主要内容区域 */}
<div style={{display: 'flex', gap: 20}}>
{/* 左侧:合同内容预览 */}
<div style={{
flex: 2,
backgroundColor: '#fff',
padding: 20,
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.09)',
height: '80vh',
overflow: 'auto'
}}>
<h3 style={{
margin: '0 0 16px 0',
fontSize: '18px',
fontWeight: 'bold',
color: '#333'
}}>合同内容</h3>
<div style={{
border: '1px solid #f0f0f0',
borderRadius: 4,
padding: 20,
backgroundColor: '#fafafa'
}}>
{selectedContract.id ? (
<div>
{parsing ? (
<div style={{textAlign: 'center', padding: 40}}>
<p style={{color: '#999'}}>合同文件加载中...</p>
</div>
) : (
<div style={{
border: '1px solid #e8e8e8',
borderRadius: 4,
padding: 20,
backgroundColor: '#fff',
minHeight: '700px'
}}>
{parsing ? (
<div style={{textAlign: 'center', padding: 40}}>
<p style={{color: '#1890ff'}}>合同文件加载中...</p>
</div>
) : parsedContractContent ? (
<div className="mammoth-content" style={{
border: '1px solid #e8e8e8',
borderRadius: 4,
padding: 20,
backgroundColor: '#fafafa',
minHeight: '600px',
overflow: 'auto'
}}>
<div dangerouslySetInnerHTML={{__html: parsedContractContent}}/>
</div>
) : (
<div style={{textAlign: 'center', padding: 40}}>
<p style={{color: '#999'}}>合同文件加载失败,请下载查看原始文件</p>
</div>
)}
</div>
)}
<div style={{marginTop: 16, textAlign: 'center'}}>
<a
href={previewUrl || `http://192.168.1.175:8081/api/contracts/${selectedContract.id}/file`}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'inline-block',
padding: '8px 16px',
backgroundColor: '#1890ff',
color: '#fff',
textDecoration: 'none',
borderRadius: 4,
marginRight: 12
}}
>
打开源文件
</a>
<a
href={previewUrl || `http://192.168.1.175:8081/api/contracts/${selectedContract.id}/file`}
download
style={{
display: 'inline-block',
padding: '8px 16px',
backgroundColor: '#52c41a',
color: '#fff',
textDecoration: 'none',
borderRadius: 4
}}
>
下载文件
</a>
</div>
</div>
) : (
<p style={{color: '#999', textAlign: 'center', padding: 40}}>合同内容加载中...</p>
)}
</div>
</div>
{/* 右侧:风险详情 */}
<div style={{
flex: 1,
backgroundColor: '#fff',
padding: 20,
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.09)',
height: '80vh',
overflow: 'auto'
}}>
<h3 style={{
margin: '0 0 16px 0',
fontSize: '18px',
fontWeight: 'bold',
color: '#333'
}}>风险详情</h3>
{/* 风险过滤和搜索 */}
<div style={{marginBottom: 20, display: 'flex', gap: 12, alignItems: 'center'}}>
<Select
value={detailRiskLevel}
style={{width: 120}}
onChange={(value) => setDetailRiskLevel(value)}
>
<Select.Option value="全部风险">全部风险</Select.Option>
<Select.Option value="高风险">高风险</Select.Option>
<Select.Option value="中风险">中风险</Select.Option>
<Select.Option value="低风险">低风险</Select.Option>
</Select>
<Input
placeholder="搜索风险点"
prefix={<SearchOutlined/>}
style={{flex: 1}}
value={detailSearchText}
onChange={(e) => setDetailSearchText(e.target.value)}
/>
</div>
{/* 风险详情列表 */}
<div>
{filteredRisks.map((risk: any) => (
<div key={risk.id} style={{
marginBottom: 16,
padding: 16,
border: '1px solid #f0f0f0',
borderRadius: 4,
backgroundColor: risk.status === 1 ? '#f0f5ff' : '#fafafa',
opacity: risk.status === 1 ? 0.8 : 1
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12
}}>
<div style={{display: 'flex', alignItems: 'center'}}>
<Tag key={risk.id + '-tag'}
color={risk.status === 1 ? 'gray' : getRiskLevelColor(risk.riskLevel)}
style={{marginRight: 8}}>
{risk.status === 1 ? `${risk.riskLevel} (已忽略)` : risk.riskLevel}
</Tag>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>{risk.riskType}</span>
</div>
{risk.status !== 1 && (
<Button
key={risk.id + '-button'}
type="link"
size="small"
onClick={() => handleIgnoreRisk(risk.id)}
>
忽略风险
</Button>
)}
</div>
<div style={{marginBottom: 8}}>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>法规名称:</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.regulationName}</span>
</div>
<div style={{marginBottom: 8}}>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>法规条目:</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.regulationClause}</span>
</div>
<div style={{marginBottom: 8}}>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>违规条款 (原文摘要):</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.violationClause}</span>
</div>
<div style={{marginBottom: 8}}>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>违规依据:</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.violationBasis}</span>
</div>
<div style={{marginBottom: 8}}>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>风险详情:</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.description}</span>
</div>
<div>
<span style={{
fontWeight: 'bold',
color: risk.status === 1 ? '#666' : '#333'
}}>修改建议:</span>
<span style={{
marginLeft: 8,
color: risk.status === 1 ? '#999' : '#666'
}}>{risk.suggestion}</span>
</div>
</div>
))}
{filteredRisks.length === 0 && (
<div style={{textAlign: 'center', padding: 40, color: '#999'}}>
未找到匹配的风险点
</div>
)}
</div>
</div>
</div>
</Spin>
</div>
</div>
);
// 提交文档审查弹窗
const SubmitReviewModal = () => (
<Modal
title="提交文档审查"
open={submitModalVisible}
onCancel={() => setSubmitModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setSubmitModalVisible(false)}>
取消
</Button>,
<Button key="submit" type="primary" onClick={handleSubmitReview}>
提交
</Button>
]}
width={600}
onOk={() => handleSubmitReview()}
>
<Form form={submitForm} layout="vertical">
<Form.Item
label="文件上传"
name="file"
rules={[{required: true, message: '请上传合同文件'}]}
>
<Upload
fileList={uploadFileList}
customRequest={handleFileUpload}
listType="text"
maxCount={1}
onRemove={() => {
setUploadFileList([]);
submitForm.setFieldsValue({name: ''});
}}
>
<Button icon={<UploadOutlined/>}>点击上传</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
);
// 风险模块页面
const RiskModulePage = () => {
const [regulations, setRegulations] = useState<any[]>([]);
const [regulationLoading, setRegulationLoading] = useState(false);
const [searchText, setSearchText] = useState('');
const [riskLevel, setRiskLevel] = useState('');
const [status, setStatus] = useState('');
const [DocumentClassification, setDocumentClassification] = useState('');
const [addModalVisible, setAddModalVisible] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
const [currentRegulation, setCurrentRegulation] = useState<any>({});
const [addForm] = Form.useForm();
const [editForm] = Form.useForm();
const [activeDocumentCategory, setActiveDocumentCategory] = useState('全部');
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
// 加载规章制度列表
const loadRegulations = async () => {
setRegulationLoading(true);
try {
const response = await fetch('http://192.168.1.175:8084/api/regulations?page=1&pageSize=100');
if (response.ok) {
const data = await response.json();
if (data.code === 200) {
setRegulations(data.data);
}
}
} catch (error) {
console.error('加载规章制度失败:', error);
message.error('加载规章制度失败,请稍后重试');
} finally {
setRegulationLoading(false);
}
};
// 处理新增规章制度
const handleAddRegulation = () => {
addForm.resetFields();
setAddModalVisible(true);
};
// 处理编辑规章制度
const handleEditRegulation = (regulation: any) => {
setCurrentRegulation(regulation);
editForm.setFieldsValue({
ruleNo: regulation.ruleNo,
ruleName: regulation.ruleName,
regulationName: regulation.regulationName,
regulationClause: regulation.regulationClause,
riskLevel: regulation.riskLevel,
status: regulation.status,
effectiveTime: regulation.effectiveTime ? dayjs(regulation.effectiveTime) : undefined
});
setEditModalVisible(true);
};
// 处理删除规章制度
const handleDeleteRegulation = async (regulationId: number) => {
if (window.confirm('确定要删除这个规章制度吗?')) {
try {
const response = await fetch(`http://192.168.1.175:8084/api/regulations/${regulationId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.code === 200) {
message.success('规章制度已删除');
loadRegulations();
} else {
message.error('删除失败:' + (data.message || '未知错误'));
}
} else {
message.error('删除失败:网络请求错误');
}
} catch (error) {
console.error('删除规章制度失败:', error);
message.error('删除规章制度失败,请稍后重试');
}
}
};
// 处理表格行选择
const rowSelection = {
selectedRowKeys,
onChange: (selectedKeys: React.Key[]) => {
setSelectedRowKeys(selectedKeys);
},
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
],
};
// 批量删除功能
const handleBatchDelete = async () => {
if (selectedRowKeys.length === 0) {
message.warning('请先选择要删除的记录');
return;
}
if (window.confirm(`确定要删除选中的 ${selectedRowKeys.length} 条记录吗?`)) {
try {
// 批量删除选中的记录
const deletePromises = selectedRowKeys.map(key =>
fetch(`http://192.168.1.175:8084/api/regulations/${key}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
})
);
const responses = await Promise.all(deletePromises);
const successCount = responses.filter(response => response.ok).length;
if (successCount === selectedRowKeys.length) {
message.success(`成功删除 ${successCount} 条记录`);
setSelectedRowKeys([]);
loadRegulations();
} else {
message.warning(`部分删除成功,成功 ${successCount}/${selectedRowKeys.length} 条`);
setSelectedRowKeys([]);
loadRegulations();
}
} catch (error) {
console.error('批量删除失败:', error);
message.error('批量删除失败,请稍后重试');
}
}
};
// 只在组件首次加载时调用一次loadRegulations函数
useEffect(() => {
loadRegulations();
}, []);
// 处理新增规章制度提交
const handleAddSubmit = async () => {
try {
const values = await addForm.validateFields();
const regulationData = {
ruleNo: values.ruleNo,
ruleName: values.ruleName,
regulationName: values.regulationName,
regulationClause: values.regulationClause,
riskLevel: values.riskLevel,
status: values.status,
effectiveTime: values.effectiveTime ? values.effectiveTime.format('YYYY-MM-DD HH:mm:ss') : null
};
const response = await fetch('http://192.168.1.175:8084/api/regulations', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(regulationData)
});
if (response.ok) {
const data = await response.json();
if (data.code === 200) {
message.success('规章制度新增成功');
setAddModalVisible(false);
addForm.resetFields();
loadRegulations();
} else {
message.error('新增失败:' + (data.message || '未知错误'));
}
} else {
message.error('新增失败:网络请求错误');
}
} catch (error) {
console.error('新增规章制度失败:', error);
message.error('新增规章制度失败,请稍后重试');
}
};
// 处理编辑规章制度提交
const handleEditSubmit = async () => {
try {
const values = await editForm.validateFields();
const regulationData = {
ruleNo: values.ruleNo,
ruleName: values.ruleName,
regulationName: values.regulationName,
regulationClause: values.regulationClause,
riskLevel: values.riskLevel,
status: values.status,
effectiveTime: values.effectiveTime ? values.effectiveTime.format('YYYY-MM-DD HH:mm:ss') : null
};
const response = await fetch(`http://192.168.1.175:8084/api/regulations/${currentRegulation.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(regulationData)
});
if (response.ok) {
const data = await response.json();
if (data.code === 200) {
message.success('规章制度编辑成功');
setEditModalVisible(false);
editForm.resetFields();
loadRegulations();
} else {
message.error('编辑失败:' + (data.message || '未知错误'));
}
} else {
message.error('编辑失败:网络请求错误');
}
} catch (error) {
console.error('编辑规章制度失败:', error);
message.error('编辑规章制度失败,请稍后重试');
}
};
// 过滤规章制度
const filteredRegulations = regulations.filter(regulation => {
const matchesSearch = !searchText ||
regulation.ruleNo.toLowerCase().includes(searchText.toLowerCase()) ||
regulation.ruleName.toLowerCase().includes(searchText.toLowerCase()) ||
regulation.regulationName.toLowerCase().includes(searchText.toLowerCase());
const matchesRiskLevel = !riskLevel || regulation.riskLevel === riskLevel;
const matchesStatus = !status || regulation.status.toString() === status;
return matchesSearch && matchesRiskLevel && matchesStatus;
});
return (
<div>
<div className="header">
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div className="header-title">风险设置管理</div>
<div className="header-right">
<div className="logo">IN-ORDER</div>
</div>
</div>
</div>
{/*统计tab*/}
{/* <div className="dashboard-stats">*/}
{/* <Card className="dashboard-stat-card">*/}
{/* <Statistic title="合同协议" value="9"*/}
{/* suffix={<Tag color="green">+100% 较上月</Tag>}/>*/}
{/* </Card>*/}
{/* <Card className="dashboard-stat-card">*/}
{/* <Statistic title="财务文档" value="3"*/}
{/* suffix={<Tag color="green">+10% 较上月</Tag>}/>*/}
{/* </Card>*/}
{/* <Card className="dashboard-stat-card">*/}
{/* <Statistic title="人事文档" value="2"*/}
{/* suffix={<Tag color="green">+30% 较去年</Tag>}/>*/}
{/* </Card>*/}
{/* <Card className="dashboard-stat-card">*/}
{/* <Statistic title="其他" value="100"*/}
{/* suffix={<Tag color="green">+100% 较去年</Tag>}/>*/}
{/* </Card>*/}
{/* </div>*/}
{/*添加切换tab,文档分类*/}
<div className="nav-menu" style={{marginBottom: 20}}>
<div style={{
display: 'flex',
gap: 12,
padding: '12px 20px',
backgroundColor: '#fff',
borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
border: '1px solid #f0f0f0',
marginLeft: '24px',
}}>
<Button
type={activeDocumentCategory === '全部' ? 'primary' : 'default'}
onClick={() => setActiveDocumentCategory('全部')}
size="large"
style={{
borderRadius: 6,
fontWeight: '500',
minWidth: 80,
height: 40
}}
>
全部
</Button>
<Button
type={activeDocumentCategory === '合同协议' ? 'primary' : 'default'}
onClick={() => setActiveDocumentCategory('合同协议')}
size="large"
style={{
borderRadius: 6,
fontWeight: '500',
minWidth: 100,
height: 40
}}
>
合同协议
</Button>
<Button
type={activeDocumentCategory === '财务文档' ? 'primary' : 'default'}
onClick={() => setActiveDocumentCategory('财务文档')}
size="large"
style={{
borderRadius: 6,
fontWeight: '500',
minWidth: 100,
height: 40
}}
>
财务文档
</Button>
<Button
type={activeDocumentCategory === '人事文档' ? 'primary' : 'default'}
onClick={() => setActiveDocumentCategory('人事文档')}
size="large"
style={{
borderRadius: 6,
fontWeight: '500',
minWidth: 100,
height: 40
}}
>
人事文档
</Button>
</div>
</div>
<div className="content">
{/* 筛选区域 */}
<div style={{marginBottom: 20, padding: 16, backgroundColor: '#fafafa', borderRadius: 4}}>
<Form layout="inline">
<Form.Item>
<Input
placeholder="搜索规则编号、名称或法规"
prefix={<SearchOutlined/>}
style={{width: 300}}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</Form.Item>
<Form.Item>
<Select
placeholder="风险等级"
style={{width: 120}}
value={riskLevel}
onChange={(value) => setRiskLevel(value)}
options={[
{value: '', label: '全部等级'},
{value: '高危', label: '高危'},
{value: '中危', label: '中危'},
{value: '低危', label: '低危'}
]}
/>
</Form.Item>
<Form.Item>
<Select
placeholder="生效状态"
style={{width: 120}}
value={status}
onChange={(value) => setStatus(value)}
options={[
{value: '', label: '全部状态'},
{value: '1', label: '已生效'},
{value: '0', label: '未生效'}
]}
/>
</Form.Item>
{/* <Form.Item>
<Select
placeholder="文档分类"
style={{width: 120}}
value={DocumentClassification}
onChange={(value) => setDocumentClassification(value)}
options={[
{value: '', label: '全部分类'},
{value: '1', label: '合同协议'},
{value: '0', label: '财务文档'}
]}
/>
</Form.Item>*/}
<Form.Item>
<Button type="primary" onClick={loadRegulations}>
搜索
</Button>
</Form.Item>
<Form.Item>
<Button onClick={() => {
setSearchText('');
setRiskLevel('');
setStatus('');
}}>
重置
</Button>
</Form.Item>
<Form.Item>
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAddRegulation}>
新增规章制度
</Button>
<Button type="primary" style={{marginLeft: 8}} icon={<UploadOutlined/>} >
批量导入
</Button>
<Button type="primary" style={{marginLeft: 8}} icon={<DownloadOutlined/>} >
批量导出
</Button>
</Form.Item>
</Form>
</div>
<div style={{marginBottom: 16, display: 'flex', gap: 8}}>
<Button
type="primary"
danger
disabled={selectedRowKeys.length === 0}
onClick={handleBatchDelete}
>
批量删除 ({selectedRowKeys.length})
</Button>
<Button
type="default"
disabled={selectedRowKeys.length === 0}
onClick={() => message.info(`已选择 ${selectedRowKeys.length} 条记录`)}
>
批量导出
</Button>
</div>
<Spin spinning={regulationLoading} tip="加载中...">
<Table
rowSelection={rowSelection}
dataSource={filteredRegulations}
columns={[
{
title: '序号',
dataIndex: 'id',
key: 'id'
},
{
title: '规则编号',
dataIndex: 'ruleNo',
key: 'ruleNo'
},
{
title: '规则名称',
dataIndex: 'ruleName',
key: 'ruleName'
},
{
title: '法规名称',
dataIndex: 'regulationName',
key: 'regulationName'
},
{
title: '法规条目',
dataIndex: 'regulationClause',
key: 'regulationClause'
},
/* {
title: '文档分类',
dataIndex: 'DocumentClassification',
key: 'DocumentClassification'
},*/
{
title: '风险等级',
dataIndex: 'riskLevel',
key: 'riskLevel',
render: (riskLevel: string) => <Tag
color={getRiskLevelColor(riskLevel)}>{riskLevel}</Tag>
},
{
title: '生效状态',
dataIndex: 'status',
key: 'status',
render: (status: number) => <Tag
color={status === 1 ? 'green' : 'gray'}>{status === 1 ? '已生效' : '未生效'}</Tag>
},
{
title: '生效时间',
dataIndex: 'effectiveTime',
key: 'effectiveTime'
},
{
title: '操作',
key: 'action',
render: (_: any, record: any) => (
<>
<Button type="link"
onClick={() => handleEditRegulation(record)}>编辑</Button>
<Button type="link"
onClick={() => handleDeleteRegulation(record.id)}>删除</Button>
</>
)
}
]}
pagination={false}
/>
</Spin>
</div>
{/* 新增规章制度弹窗 */}
<Modal
title="新增规章制度"
open={addModalVisible}
onCancel={() => setAddModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setAddModalVisible(false)}>
取消
</Button>,
<Button key="submit" type="primary" onClick={handleAddSubmit}>
提交
</Button>
]}
width={600}
>
<Form form={addForm} layout="vertical">
<Form.Item label="规则编号" name="ruleNo" rules={[{required: true, message: '请输入规则编号'}]}>
<Input placeholder="请输入规则编号,如CR01"/>
</Form.Item>
<Form.Item label="规则名称" name="ruleName"
rules={[{required: true, message: '请输入规则名称'}]}>
<Input placeholder="请输入规则名称"/>
</Form.Item>
<Form.Item label="法规名称" name="regulationName"
rules={[{required: true, message: '请输入法规名称'}]}>
<Input placeholder="请输入法规名称"/>
</Form.Item>
<Form.Item label="法规条目" name="regulationClause"
rules={[{required: true, message: '请输入法规条目'}]}>
<Input placeholder="请输入法规条目"/>
</Form.Item>
<Form.Item label="风险等级" name="riskLevel"
rules={[{required: true, message: '请选择风险等级'}]}>
<Select
options={[
{value: '高危', label: '高危'},
{value: '中危', label: '中危'},
{value: '低危', label: '低危'}
]}
/>
</Form.Item>
<Form.Item label="文档分类" name="DocumentClassification"
rules={[{required: true, message: '请选择文档分类'}]}>
<Select
options={[
{value: '合同', label: '合同协议'},
{value: '发票', label: '财务文档'},
]}
/>
</Form.Item>
<Form.Item label="生效状态" name="status" rules={[{required: true, message: '请选择生效状态'}]}>
<Select
options={[
{value: 1, label: '已生效'},
{value: 0, label: '未生效'}
]}
/>
</Form.Item>
<Form.Item label="生效时间" name="effectiveTime">
<DatePicker style={{width: '100%'}} showTime/>
</Form.Item>
</Form>
</Modal>
{/* 编辑规章制度弹窗 */}
<Modal
title="编辑规章制度"
open={editModalVisible}
onCancel={() => setEditModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setEditModalVisible(false)}>
取消
</Button>,
<Button key="submit" type="primary" onClick={handleEditSubmit}>
提交
</Button>
]}
width={600}
>
<Form form={editForm} layout="vertical">
<Form.Item label="规则编号" name="ruleNo" rules={[{required: true, message: '请输入规则编号'}]}>
<Input placeholder="请输入规则编号"/>
</Form.Item>
<Form.Item label="规则名称" name="ruleName"
rules={[{required: true, message: '请输入规则名称'}]}>
<Input placeholder="请输入规则名称"/>
</Form.Item>
<Form.Item label="法规名称" name="regulationName"
rules={[{required: true, message: '请输入法规名称'}]}>
<Input placeholder="请输入法规名称"/>
</Form.Item>
<Form.Item label="法规条目" name="regulationClause"
rules={[{required: true, message: '请输入法规条目'}]}>
<Input placeholder="请输入法规条目"/>
</Form.Item>
<Form.Item label="风险等级" name="riskLevel"
rules={[{required: true, message: '请选择风险等级'}]}>
<Select
options={[
{value: '高危', label: '高危'},
{value: '中危', label: '中危'},
{value: '低危', label: '低危'}
]}
/>
</Form.Item>
<Form.Item label="生效状态" name="status" rules={[{required: true, message: '请选择生效状态'}]}>
<Select
options={[
{value: 1, label: '已生效'},
{value: 0, label: '未生效'}
]}
/>
</Form.Item>
<Form.Item label="生效时间" name="effectiveTime">
<DatePicker style={{width: '100%'}} showTime/>
</Form.Item>
</Form>
</Modal>
</div>
);
};
return (
<Layout style={{minHeight: '100vh'}}>
<Layout.Sider width={200} style={{backgroundColor: '#fff'}}>
<div className="logo"
style={{textAlign: 'center', padding: '16px', fontSize: '18px', fontWeight: 'bold'}}>
IN-ORDER
</div>
<Menu
mode="inline"
selectedKeys={[currentPage]}
style={{height: '100%', borderRight: 0}}
onSelect={({key}) => setCurrentPage(key)}
items={[
{
key: 'dashboard',
icon: <FileTextOutlined/>,
label: '合同审查',
},
{
key: 'rule',
icon: <SettingOutlined/>,
label: '风险设置',
},
]}
/>
</Layout.Sider>
<Layout.Content style={{padding: '24px'}}>
{currentPage === 'dashboard' && <DashboardPage/>}
{currentPage === 'detail' && <DetailPage/>}
{currentPage === 'rule' && <RiskModulePage/>}
<SubmitReviewModal/>
</Layout.Content>
</Layout>
);
};
export default App;
\ No newline at end of file
* {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.ant-table-wrapper {
margin: 20px 0;
}
.ant-card {
margin-bottom: 20px;
}
.ant-btn-primary {
background-color: #1890ff;
border-color: #1890ff;
}
.ant-btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
}
.ant-statistic-title {
font-size: 14px;
color: #666;
}
.ant-statistic-content {
font-size: 24px;
font-weight: bold;
}
.ant-tag {
margin-right: 8px;
}
.ant-divider {
margin: 16px 0;
}
.ant-form-item {
margin-bottom: 16px;
}
.ant-form-inline .ant-form-item {
margin-right: 16px;
}
.ant-pagination {
margin-top: 20px;
text-align: center;
}
.header {
background-color: white;
padding: 0 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
.header-title {
font-size: 20px;
font-weight: bold;
padding: 16px 0;
color: #333;
}
.content {
padding: 0 24px 24px;
}
.dashboard-stats {
display: flex;
gap: 16px;
margin-bottom: 24px;
}
.dashboard-stat-card {
flex: 1;
}
.contract-detail {
display: flex;
gap: 24px;
}
.contract-info {
flex: 1;
}
.risk-list {
flex: 1;
}
.risk-item {
margin-bottom: 16px;
padding: 16px;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.risk-high {
border-left: 4px solid #ff4d4f;
}
.risk-medium {
border-left: 4px solid #faad14;
}
.risk-low {
border-left: 4px solid #52c41a;
}
.risk-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
}
.risk-description {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.risk-suggestion {
font-size: 14px;
color: #1890ff;
}
.contract-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.contract-meta {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.contract-meta-item {
font-size: 14px;
}
.contract-meta-label {
color: #666;
margin-right: 4px;
}
.contract-meta-value {
font-weight: 500;
}
.contract-table {
margin-top: 16px;
}
.risk-summary {
margin-bottom: 24px;
}
.risk-summary-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.risk-summary-stats {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.risk-summary-stat {
flex: 1;
text-align: center;
padding: 16px;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.risk-summary-stat-high {
border-top: 4px solid #ff4d4f;
}
.risk-summary-stat-medium {
border-top: 4px solid #faad14;
}
.risk-summary-stat-low {
border-top: 4px solid #52c41a;
}
.risk-summary-stat-value {
font-size: 24px;
font-weight: bold;
margin-bottom: 4px;
}
.risk-summary-stat-label {
font-size: 14px;
color: #666;
}
.search-bar {
display: flex;
gap: 16px;
margin-bottom: 24px;
align-items: center;
}
.filter-group {
display: flex;
gap: 16px;
align-items: center;
}
.filter-item {
min-width: 120px;
}
.search-input {
min-width: 200px;
}
.action-buttons {
display: flex;
gap: 8px;
}
.status-tag {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
}
.status-high {
background-color: #fff1f0;
color: #cf1322;
border: 1px solid #ffccc7;
}
.status-medium {
background-color: #fffbe6;
color: #d48806;
border: 1px solid #ffe58f;
}
.status-low {
background-color: #f6ffed;
color: #389e0d;
border: 1px solid #b7eb8f;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.logo {
font-size: 20px;
font-weight: bold;
color: #1890ff;
}
.nav-menu {
display: flex;
gap: 16px;
}
.nav-item {
padding: 0 8px;
cursor: pointer;
color: #666;
}
.nav-item.active {
color: #1890ff;
font-weight: 500;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
}
\ No newline at end of file
import React from 'react'
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
\ No newline at end of file
import axios from 'axios';
import axios from 'axios';
// 创建axios实例
const apiClient = axios.create({
baseURL: 'http://192.168.1.175:8084/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 响应拦截器
apiClient.interceptors.response.use(
response => {
return response.data;
},
error => {
console.error('API请求错误:', error);
return Promise.reject(error);
}
);
// 合同相关API
export const contractApi = {
// 获取合同列表
getContractList: (params: {
page?: number;
pageSize?: number;
name?: string;
submitter?: string;
department?: string;
querySource?: string;
riskLevel?: string;
uploadTime?: string;
finishTime?: string;
}) => {
return apiClient.get('/contracts', { params });
},
// 获取合同详情
getContractById: (id: number) => {
return apiClient.get(`/contracts/${id}`);
},
// 获取合同风险详情
getContractRisks: (id: number) => {
return apiClient.get(`/contracts/${id}/risks`);
},
// 提交新合同审查
addContract: (contract: any) => {
return apiClient.post('/contracts', contract);
},
// 更新合同
updateContract: (id: number, contract: any) => {
return apiClient.put(`/contracts/${id}`, contract);
},
// 删除合同
deleteContract: (id: number) => {
return apiClient.delete(`/contracts/${id}`);
},
// 获取合同源文件内容
getContractContent: (id: number) => {
return apiClient.get(`/contracts/${id}/content`);
},
// 获取合同预览地址
getContractPreviewUrl: (id: number) => {
return apiClient.get(`/contracts/${id}/preview-url`);
}
};
// 统计相关API
export const statisticsApi = {
// 获取统计数据
getStatistics: () => {
return apiClient.get('/statistics');
}
};
export default apiClient;
\ No newline at end of file
{
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
\ No newline at end of file
{
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
\ No newline at end of file
import { defineConfig } from 'vite'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment