This commit is contained in:
2025-12-05 00:09:32 +08:00
parent bb3d22eabf
commit 3d1dbb3f97
214 changed files with 113060 additions and 1232 deletions
+54
View File
@@ -0,0 +1,54 @@
-- ============================================================
-- 添加文档列表子菜单路由
-- 说明:为 /documents 父级菜单添加"文档列表"子菜单
-- 生成时间: 2025-11-17
-- ============================================================
-- 添加 /documents 索引路由作为子菜单
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/documents',
'DocumentsList',
'文档列表',
2, -- parent_id = 2 (Documents 父级菜单)
NULL,
1,
false,
true,
'views/documents/Index.vue',
'{}'
);
-- 调整文档上传的排序,让文档列表排在第一位
UPDATE sys_routes
SET sort_order = 2
WHERE id = 32; -- /documents/upload
-- 验证结果
SELECT id, route_path, route_name, route_title, parent_id, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 2 OR id = 2
ORDER BY parent_id NULLS FIRST, sort_order;
-- ============================================================
-- 说明
-- ============================================================
-- 现在的菜单结构:
-- 文档管理 (/documents) [id=2]
-- ├─ 文档列表 (/documents) [新增,sort_order=1]
-- └─ 文档上传 (/documents/upload) [id=32sort_order=2]
--
-- 注意:虽然父级和子菜单路径相同,但 Remix 会根据路由文件正确处理
-- - documents.tsx: 父级布局
-- - documents._index.tsx: 索引路由(文档列表)
-- ============================================================
+61
View File
@@ -0,0 +1,61 @@
-- ============================================================
-- 添加文档列表路由
-- 说明:将文档列表作为"文档管理"的子菜单
-- 生成时间: 2025-11-17
-- ============================================================
-- 添加 /documents/list 路由(文档列表)
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/documents/list',
'DocumentsList',
'文档列表',
2, -- parent_id = 2 (Documents 父级菜单)
NULL,
1,
false,
true,
'views/documents/List.vue',
'{}'
);
-- 调整文档上传的排序,让文档列表排在第一位
UPDATE sys_routes
SET sort_order = 2
WHERE id = 32; -- /documents/upload
-- 调整编辑文档的排序
UPDATE sys_routes
SET sort_order = 3
WHERE id = 33; -- /documents/edit
-- 调整下载文档的排序
UPDATE sys_routes
SET sort_order = 4
WHERE id = 34; -- /documents/download
-- 验证结果
SELECT id, route_path, route_name, route_title, parent_id, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 2 OR id = 2
ORDER BY parent_id NULLS FIRST, sort_order;
-- ============================================================
-- 预期结果
-- ============================================================
-- 文档管理 (/documents) [id=2, parent_id=null]
-- ├─ 文档列表 (/documents/list) [新增, sort_order=1]
-- ├─ 文档上传 (/documents/upload) [id=32, sort_order=2]
-- ├─ 编辑文档 (/documents/edit) [id=33, sort_order=3, is_hidden=true]
-- └─ 下载文档 (/documents/download) [id=34, sort_order=4, is_hidden=true]
-- ============================================================
+109
View File
@@ -0,0 +1,109 @@
-- 添加入口模块管理路由到 sys_routes 表
-- 执行此脚本将在系统菜单中添加"入口模块管理"菜单项
-- 1. 插入父级菜单:入口模块管理
INSERT INTO sys_routes (
route_path,
route_name,
component,
parent_id,
route_title,
icon,
sort_order,
is_hidden,
is_cache,
meta,
status,
created_at,
updated_at
) VALUES (
'/entry-modules',
'EntryModules',
'views/entry-modules/Index.vue',
NULL,
'入口模块管理',
'ri-apps-2-line',
8,
false,
true,
'{"requiresAuth": true, "requiresRole": ["admin", "developer", "provincial_admin"]}'::jsonb,
0,
NOW(),
NOW()
)
ON CONFLICT (route_path) DO UPDATE SET
route_title = EXCLUDED.route_title,
icon = EXCLUDED.icon,
sort_order = EXCLUDED.sort_order,
updated_at = NOW();
-- 2. 插入子菜单:新建入口模块(如果需要在菜单中显示)
-- 注意:获取父级菜单的ID
WITH parent_route AS (
SELECT id FROM sys_routes WHERE route_path = '/entry-modules'
)
INSERT INTO sys_routes (
route_path,
route_name,
component,
parent_id,
route_title,
icon,
sort_order,
is_hidden,
is_cache,
meta,
status,
created_at,
updated_at
)
SELECT
'/entry-modules/new',
'EntryModulesNew',
'views/entry-modules/New.vue',
parent_route.id,
'新建入口模块',
'ri-add-circle-line',
1,
true, -- 隐藏,不在菜单中显示
false,
'{"requiresAuth": true, "requiresRole": ["admin", "developer", "provincial_admin"]}'::jsonb,
0,
NOW(),
NOW()
FROM parent_route
ON CONFLICT (route_path) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
updated_at = NOW();
-- 3. 查询确认插入结果
SELECT
id,
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden
FROM sys_routes
WHERE route_path LIKE '/entry-modules%'
ORDER BY sort_order;
-- 4. 为管理员角色分配权限(假设角色ID为1是admin)
-- 获取新插入的路由ID
WITH entry_routes AS (
SELECT id FROM sys_routes WHERE route_path LIKE '/entry-modules%'
)
INSERT INTO role_route (role_id, route_id, created_at, updated_at)
SELECT
1, -- 假设 role_id = 1 是 admin 角色
entry_routes.id,
NOW(),
NOW()
FROM entry_routes
ON CONFLICT (role_id, route_id) DO NOTHING;
-- 提示信息
SELECT '✅ 入口模块管理路由已成功添加到系统菜单中!' AS message;
SELECT '📌 请确保已为相应角色分配权限,路由才会在菜单中显示。' AS reminder;
+43
View File
@@ -0,0 +1,43 @@
-- ========================================
-- 为评查点系统添加外键约束
-- ========================================
-- 1️⃣ 为 evaluation_points 表添加外键约束
-- evaluation_point_groups_id 引用 evaluation_point_groups.id
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id)
ON DELETE SET NULL -- 删除分组时,将评查点的分组ID设为NULL
ON UPDATE CASCADE; -- 更新分组ID时,级联更新
-- 2️⃣ 为 evaluation_point_groups 表添加自引用外键约束
-- pid 引用同一个表的 id(父子关系)
ALTER TABLE evaluation_point_groups
ADD CONSTRAINT fk_evaluation_point_groups_parent
FOREIGN KEY (pid)
REFERENCES evaluation_point_groups(id)
ON DELETE CASCADE -- 删除父分组时,级联删除子分组
ON UPDATE CASCADE; -- 更新父分组ID时,级联更新
-- 添加注释
COMMENT ON CONSTRAINT fk_evaluation_points_group ON evaluation_points IS '评查点所属分组外键约束';
COMMENT ON CONSTRAINT fk_evaluation_point_groups_parent ON evaluation_point_groups IS '评查点分组父子关系自引用外键约束';
-- 验证外键约束是否创建成功
SELECT
tc.table_name,
tc.constraint_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name IN ('evaluation_points', 'evaluation_point_groups')
ORDER BY tc.table_name, tc.constraint_name;
+53
View File
@@ -0,0 +1,53 @@
-- ========================================
-- 简化版外键约束(不需要自引用)
-- ========================================
-- 步骤 1: 将所有 pid=0 改为 NULL(标准做法)
UPDATE evaluation_point_groups
SET pid = NULL
WHERE pid = 0;
-- 步骤 2: 为 evaluation_points 添加外键约束
-- 2.1 子分组外键:evaluation_point_groups_id → evaluation_point_groups.id
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_child_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id)
ON DELETE SET NULL
ON UPDATE CASCADE;
-- 2.2 父分组外键:evaluation_point_groups_pid → evaluation_point_groups.id
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_parent_group
FOREIGN KEY (evaluation_point_groups_pid)
REFERENCES evaluation_point_groups(id)
ON DELETE SET NULL
ON UPDATE CASCADE;
-- ⚠️ 注意:不再需要 evaluation_point_groups 表的自引用外键
-- 因为 evaluation_points 已经直接存储了父级分组ID
-- 添加注释
COMMENT ON CONSTRAINT fk_evaluation_points_child_group ON evaluation_points
IS '评查点所属规则组外键约束(二级分组)';
COMMENT ON CONSTRAINT fk_evaluation_points_parent_group ON evaluation_points
IS '评查点类型外键约束(父级分组)';
-- 步骤 3: 验证外键约束是否创建成功
SELECT
tc.table_name AS "表名",
tc.constraint_name AS "约束名",
kcu.column_name AS "列名",
ccu.table_name AS "引用表名",
ccu.column_name AS "引用列名"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'evaluation_points'
AND kcu.column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
ORDER BY kcu.column_name;
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
# ============================================================
# 清理 app/routes 中未使用的路由文件
# 生成时间: 2025-11-17
# ============================================================
echo "🗑️ 开始清理未使用的路由文件..."
# 定义项目根目录
PROJECT_ROOT="D:/remix_project/docreview"
# 定义要删除的文件列表
FILES_TO_DELETE=(
"app/routes/contract-search.tsx"
"app/routes/contract-search._index.tsx"
"app/routes/debug.tsx"
)
# 定义要删除的目录
DIRS_TO_DELETE=(
"app/routes/examples"
)
echo ""
echo "📋 将删除以下文件:"
for file in "${FILES_TO_DELETE[@]}"; do
if [ -f "$PROJECT_ROOT/$file" ]; then
echo "$file"
else
echo " ⚠️ $file (不存在,跳过)"
fi
done
echo ""
echo "📁 将删除以下目录:"
for dir in "${DIRS_TO_DELETE[@]}"; do
if [ -d "$PROJECT_ROOT/$dir" ]; then
echo "$dir"
else
echo " ⚠️ $dir (不存在,跳过)"
fi
done
echo ""
read -p "❓ 确认删除以上文件和目录吗?(y/n): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
echo "❌ 已取消删除操作"
exit 0
fi
echo ""
echo "🔧 开始删除..."
# 删除文件
for file in "${FILES_TO_DELETE[@]}"; do
if [ -f "$PROJECT_ROOT/$file" ]; then
rm "$PROJECT_ROOT/$file"
echo " ✅ 已删除: $file"
fi
done
# 删除目录
for dir in "${DIRS_TO_DELETE[@]}"; do
if [ -d "$PROJECT_ROOT/$dir" ]; then
rm -rf "$PROJECT_ROOT/$dir"
echo " ✅ 已删除目录: $dir"
fi
done
echo ""
echo "✅ 清理完成!"
echo ""
echo "💡 提示:"
echo " 1. 请运行 'npm run typecheck' 检查是否有引用这些文件的代码"
echo " 2. 请运行 'git status' 查看变更"
echo " 3. 如果一切正常,请提交这些更改"
@@ -0,0 +1,45 @@
-- 创建评查点视图,包含所属规则组和评查点类型信息
-- 这个视图通过表连接自动关联 evaluation_point_groups 表(两次关联)
CREATE OR REPLACE VIEW evaluation_points_with_groups AS
SELECT
ep.id,
ep.code,
ep.name,
ep.evaluation_point_groups_id,
ep.risk,
ep.description,
ep.is_enabled,
ep.references_laws,
ep.extraction_config,
ep.evaluation_config,
ep.pass_message,
ep.fail_message,
ep.suggestion_message,
ep.suggestion_message_type,
ep.post_action,
ep.action_config,
ep.created_at,
ep.updated_at,
-- 所属规则组(二级分组)
child_group.id as group_id,
child_group.name as group_name,
child_group.pid as group_pid,
-- 评查点类型(父级分组)
parent_group.id as type_id,
parent_group.name as type_name
FROM evaluation_points ep
-- 第一次关联:获取二级分组(所属规则组)
LEFT JOIN evaluation_point_groups child_group
ON ep.evaluation_point_groups_id = child_group.id
-- 第二次关联:获取父级分组(评查点类型)
LEFT JOIN evaluation_point_groups parent_group
ON child_group.pid = parent_group.id;
-- 添加注释
COMMENT ON VIEW evaluation_points_with_groups IS '评查点视图,包含所属规则组和评查点类型信息,通过两次表连接实现';
+30
View File
@@ -0,0 +1,30 @@
-- ========================================
-- 修复重复的外键约束问题
-- ========================================
-- 问题:evaluation_point_groups_id 列有两个外键约束,导致 PostgREST 嵌套查询失败
-- 步骤 1: 删除旧的外键约束
ALTER TABLE evaluation_points
DROP CONSTRAINT IF EXISTS fk_evaluation_points_group;
-- 步骤 2: 验证删除结果(应该只剩下 2 个外键)
SELECT
tc.constraint_name AS "约束名",
kcu.column_name AS "列名",
ccu.table_name AS "引用表",
ccu.column_name AS "引用列"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'evaluation_points'
AND kcu.column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
ORDER BY kcu.column_name;
-- 预期结果(应该只有 2 个外键):
-- 约束名 | 列名 | 引用表 | 引用列
-- ------------------------------------------|-------------------------------|---------------------------|-------
-- fk_evaluation_points_child_group | evaluation_point_groups_id | evaluation_point_groups | id
-- fk_evaluation_points_parent_group | evaluation_point_groups_pid | evaluation_point_groups | id
+55
View File
@@ -0,0 +1,55 @@
-- ========================================
-- 修复 pid=0 问题并添加外键约束
-- ========================================
-- 步骤 1: 将所有 pid=0 的记录改为 NULL(表示顶级分组)
UPDATE evaluation_point_groups
SET pid = NULL
WHERE pid = 0;
-- 验证修改结果
SELECT COUNT(*) as "顶级分组数量(pid为NULL"
FROM evaluation_point_groups
WHERE pid IS NULL;
-- 步骤 2: 添加外键约束
-- 2.1 为 evaluation_points 表添加外键约束
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id)
ON DELETE SET NULL -- 删除分组时,将评查点的分组ID设为NULL
ON UPDATE CASCADE; -- 更新分组ID时,级联更新
-- 2.2 为 evaluation_point_groups 表添加自引用外键约束
-- pid 引用同一个表的 id(父子关系)
-- NULL 值表示顶级分组,不受外键约束限制
ALTER TABLE evaluation_point_groups
ADD CONSTRAINT fk_evaluation_point_groups_parent
FOREIGN KEY (pid)
REFERENCES evaluation_point_groups(id)
ON DELETE CASCADE -- 删除父分组时,级联删除子分组
ON UPDATE CASCADE; -- 更新父分组ID时,级联更新
-- 添加注释
COMMENT ON CONSTRAINT fk_evaluation_points_group ON evaluation_points IS '评查点所属分组外键约束';
COMMENT ON CONSTRAINT fk_evaluation_point_groups_parent ON evaluation_point_groups IS '评查点分组父子关系自引用外键约束(pid=NULL表示顶级分组)';
-- 步骤 3: 验证外键约束是否创建成功
SELECT
tc.table_name AS "表名",
tc.constraint_name AS "约束名",
kcu.column_name AS "列名",
ccu.table_name AS "引用表名",
ccu.column_name AS "引用列名"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name IN ('evaluation_points', 'evaluation_point_groups')
ORDER BY tc.table_name, tc.constraint_name;
+200
View File
@@ -0,0 +1,200 @@
-- ============================================================
-- 角色路由权限初始化脚本
-- 说明:为各角色分配路由权限(只分配一级菜单权限,子路由自动继承)
-- 生成时间: 2025-11-17
-- ============================================================
-- 清空现有权限(如果需要重新初始化)
-- TRUNCATE TABLE role_route;
-- ============================================================
-- 1. 超级管理员 (super_admin) - 拥有所有权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 12, id FROM sys_routes WHERE parent_id IS NULL AND is_hidden = false;
-- ============================================================
-- 2. 系统管理员 (admin) - 拥有所有权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 1, id FROM sys_routes WHERE parent_id IS NULL AND is_hidden = false;
-- ============================================================
-- 3. 系统管理员 (system_admin) - 拥有所有权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 13, id FROM sys_routes WHERE parent_id IS NULL AND is_hidden = false;
-- ============================================================
-- 4. 业务管理员 (business_admin) - 核心业务权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 14, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/contract-template', -- 合同模板
'/cross-checking', -- 交叉评查
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 5. 文档管理员 (document_admin) - 文档相关权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 15, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/contract-template' -- 合同模板
);
-- ============================================================
-- 6. 评查管理员 (evaluation_admin) - 评查相关权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 16, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 7. 交叉评查管理员 (crossreview_admin) - 交叉评查权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 17, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/cross-checking' -- 交叉评查
);
-- ============================================================
-- 8. 评查员 (auditor) - 执行评查任务
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 18, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/cross-checking', -- 交叉评查
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 9. 上传者 (uploader) - 仅上传文档
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 19, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents' -- 文档管理
);
-- ============================================================
-- 10. 部门主管 (deptLeader) - 部门管理权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 3, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/contract-template', -- 合同模板
'/cross-checking', -- 交叉评查
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 11. 小组组长 (groupLeader) - 小组管理权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 4, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/rules', -- 评查规则库
'/cross-checking', -- 交叉评查
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 12. 普通员工 (common) - 基本权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 2, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home', -- 系统概览
'/documents', -- 文档管理
'/chat-with-llm' -- AI法务助手
);
-- ============================================================
-- 13. 访客 (guest) - 只读权限
-- ============================================================
INSERT INTO role_route (role_id, route_id)
SELECT 20, id FROM sys_routes
WHERE parent_id IS NULL AND is_hidden = false
AND route_path IN (
'/home' -- 系统概览
);
-- ============================================================
-- 验证权限配置
-- ============================================================
-- 查看各角色的路由权限数量
SELECT
r.role_name,
r.role_key,
COUNT(rr.route_id) as route_count
FROM roles r
LEFT JOIN role_route rr ON r.id = rr.role_id
GROUP BY r.id, r.role_name, r.role_key
ORDER BY route_count DESC;
-- 查看管理员角色的具体路由权限
SELECT
r.role_name,
sr.route_path,
sr.route_title,
sr.sort_order
FROM role_route rr
JOIN roles r ON rr.role_id = r.id
JOIN sys_routes sr ON rr.route_id = sr.id
WHERE r.role_key IN ('admin', 'super_admin')
ORDER BY r.role_name, sr.sort_order;
-- ============================================================
-- 完成!
-- ============================================================
-- 提示:执行此脚本后,请清除浏览器缓存并重新登录系统
@@ -0,0 +1,82 @@
-- 合同起草功能数据库迁移脚本
-- 创建时间: 2025-01-04
-- 1. 创建起草合同表
CREATE TABLE IF NOT EXISTS drafted_contracts (
id SERIAL PRIMARY KEY,
template_id INTEGER NOT NULL,
file_path TEXT NOT NULL,
title TEXT NOT NULL,
placeholder_values JSONB DEFAULT '{}'::jsonb,
status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'completed', 'archived')),
created_by INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT fk_template
FOREIGN KEY (template_id)
REFERENCES contract_templates(id)
ON DELETE CASCADE,
CONSTRAINT fk_created_by
FOREIGN KEY (created_by)
REFERENCES auth.users(id)
ON DELETE SET NULL
);
-- 2. 创建索引优化查询性能
CREATE INDEX idx_drafted_contracts_template_id ON drafted_contracts(template_id);
CREATE INDEX idx_drafted_contracts_created_by ON drafted_contracts(created_by);
CREATE INDEX idx_drafted_contracts_status ON drafted_contracts(status);
CREATE INDEX idx_drafted_contracts_created_at ON drafted_contracts(created_at DESC);
-- 3. 创建更新时间触发器
CREATE OR REPLACE FUNCTION update_drafted_contracts_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_drafted_contracts_updated_at
BEFORE UPDATE ON drafted_contracts
FOR EACH ROW
EXECUTE FUNCTION update_drafted_contracts_updated_at();
-- 4. 添加注释
COMMENT ON TABLE drafted_contracts IS '起草的合同记录';
COMMENT ON COLUMN drafted_contracts.id IS '主键ID';
COMMENT ON COLUMN drafted_contracts.template_id IS '关联的合同模板ID';
COMMENT ON COLUMN drafted_contracts.file_path IS '起草后的文件路径(MinIO';
COMMENT ON COLUMN drafted_contracts.title IS '合同标题';
COMMENT ON COLUMN drafted_contracts.placeholder_values IS '占位符填充值(JSON格式)';
COMMENT ON COLUMN drafted_contracts.status IS '状态:draft-草稿,completed-已完成,archived-已归档';
COMMENT ON COLUMN drafted_contracts.created_by IS '创建人用户ID';
COMMENT ON COLUMN drafted_contracts.created_at IS '创建时间';
COMMENT ON COLUMN drafted_contracts.updated_at IS '更新时间';
-- 5. 为 contract_templates 表添加占位符配置字段
ALTER TABLE contract_templates
ADD COLUMN IF NOT EXISTS placeholder_schema JSONB DEFAULT NULL;
COMMENT ON COLUMN contract_templates.placeholder_schema IS '占位符配置SchemaJSON格式)';
-- 6. 示例:为测试模板添加占位符配置(可选,根据实际情况调整)
-- UPDATE contract_templates
-- SET placeholder_schema = '{
-- "fields": [
-- {"key": "甲方名称", "label": "甲方名称", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方地址", "label": "甲方地址", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "甲方信息"},
-- {"key": "乙方名称", "label": "乙方名称", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方地址", "label": "乙方地址", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "乙方信息"},
-- {"key": "合同金额", "label": "合同金额(元)", "type": "number", "required": true, "group": "合同条款"},
-- {"key": "签订日期", "label": "签订日期", "type": "date", "required": true, "group": "合同条款"},
-- {"key": "合同编号", "label": "合同编号", "type": "text", "required": false, "group": "基本信息"}
-- ]
-- }'::jsonb
-- WHERE id = 1;
+282
View File
@@ -0,0 +1,282 @@
# 文档版本管理功能 - 部署指南(使用函数名前缀)
## 📋 方案说明
使用 **函数名前缀 `documents_`** 来区分版本管理函数,无需创建 schema。
**3个新函数**
- `documents_get_latest_documents_with_version_info` - 获取文档列表
- `documents_count_latest_documents_with_filters` - 统计文档总数
- `documents_get_document_history` - 获取历史版本
---
## 🚀 快速部署(2步)
### 步骤1:在 Navicat 中执行 SQL 脚本
#### 方法A:查询窗口
1. 打开 Navicat,连接到 PostgreSQL 数据库
2. 点击 **"查询"** → **"新建查询"**
3. 打开 `add_document_version_management.sql` 文件
4. 复制所有内容到查询窗口
5. 点击 **"运行"** ▶️ 按钮
#### 方法B:运行 SQL 文件
1. 右键点击数据库
2. 选择 **"运行 SQL 文件"**
3. 选择 `add_document_version_management.sql`
4. 点击 **"开始"**
### 步骤2:重启应用
```bash
npm run build
pm2 restart all
```
**就这么简单!**
---
## 🔍 验证函数是否创建成功
在 Navicat 中执行:
```sql
-- 查看新创建的函数
SELECT proname
FROM pg_proc
WHERE proname LIKE 'documents_%';
```
**应该返回 3 个函数名**
```
documents_get_latest_documents_with_version_info
documents_count_latest_documents_with_filters
documents_get_document_history
```
### 在 Navicat 中查看函数
```
你的数据库
└─ public (schema)
└─ 函数
├─ documents_get_latest_documents_with_version_info(...)
├─ documents_count_latest_documents_with_filters(...)
└─ documents_get_document_history(...)
```
---
## 🧪 测试函数
在 Navicat 查询窗口执行:
```sql
-- 测试1:获取文档列表(假设用户ID为1)
SELECT * FROM documents_get_latest_documents_with_version_info(1, 1, 10);
-- 测试2:统计文档总数
SELECT documents_count_latest_documents_with_filters(1);
-- 测试3:获取历史版本(如果有同名文档)
SELECT * FROM documents_get_document_history('测试文档.pdf', 1, 123);
```
---
## 📁 在 Navicat 中操作函数
### 查看函数定义
1. 在左侧树形结构中找到 `public``函数`
2. 找到 `documents_get_latest_documents_with_version_info`
3. 右键 → **"设计函数"** 或 **"查看 SQL"**
### 测试函数(图形界面)
1. 右键点击函数
2. 选择 **"执行函数"**
3. 输入参数值(如 `p_user_id: 1, p_page: 1, p_page_size: 10`
4. 点击 **"运行"**
### 删除函数(如果需要重建)
```sql
DROP FUNCTION IF EXISTS documents_get_latest_documents_with_version_info;
DROP FUNCTION IF EXISTS documents_count_latest_documents_with_filters;
DROP FUNCTION IF EXISTS documents_get_document_history;
-- 然后重新执行 SQL 脚本
```
---
## ✅ 功能验证
### 1. 访问文档列表页面
打开浏览器:`http://your-domain/documents`
### 2. 检查显示效果
**有历史版本的文档**
```
[▶️] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
```
**单版本文档**
```
📄 新文档.pdf
合同审查
问题数量:4
```
### 3. 测试展开功能
点击 [▶️] 图标,应该能看到:
```
[🔽] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
├─ 🕒 v2 版本
│ 问题数量:5 🔴 ↑ +2
├─ 🕒 v3 版本
│ 问题数量:3 🟢 ↓ -1
└─ 🕒 v4 版本 (最早)
问题数量:4
```
---
## ❌ 故障排查
### 问题1:函数未创建
**症状**:查询不到函数
**解决**
```sql
-- 检查是否有错误
-- 在 Navicat 的"消息"窗口查看错误信息
-- 重新执行 SQL 脚本
```
### 问题2:前端报错
**症状**:浏览器控制台显示 `function does not exist`
**解决**
```bash
# 1. 确认函数已创建
# 在 Navicat 中执行:
SELECT proname FROM pg_proc WHERE proname LIKE 'documents_%';
# 2. 确认前端代码已更新
# 检查 app/api/files/documents.ts 中的函数名
# 3. 重新构建应用
npm run build
pm2 restart all
```
### 问题3:数据不显示
**症状**:页面显示空白或加载失败
**检查清单**
- ✅ SQL 脚本是否执行成功
- ✅ 函数是否创建在 `public` schema 中
- ✅ 应用是否重启
- ✅ 浏览器控制台是否有错误
**解决方案**
```bash
# 1. 检查 PostgREST 日志
journalctl -u postgrest -n 50
# 2. 检查应用日志
pm2 logs
# 3. 清除浏览器缓存
# 按 Ctrl + Shift + R (Windows) 或 Cmd + Shift + R (Mac)
```
---
## 📊 性能优化
SQL 脚本自动创建了 4 个索引来优化查询性能:
```sql
-- 1. 用户+名称+时间索引
idx_documents_user_name_created
-- 2. 名称+用户+时间索引
idx_documents_name_user_created
-- 3. 创建时间索引
idx_documents_created_at
-- 4. 文件状态索引
idx_documents_status
```
查看索引:
```sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'documents';
```
---
## 🔐 权限配置(可选)
如果你使用了特定的 PostgREST 用户,需要授权:
```sql
-- 查看你的 PostgREST 用户名
SELECT usename FROM pg_user;
-- 授权函数(替换 'authenticator' 为实际用户名)
GRANT EXECUTE ON FUNCTION documents_get_latest_documents_with_version_info TO authenticator;
GRANT EXECUTE ON FUNCTION documents_count_latest_documents_with_filters TO authenticator;
GRANT EXECUTE ON FUNCTION documents_get_document_history TO authenticator;
```
---
## 📚 相关文档
- **SQL 脚本**`add_document_version_management.sql`
- **完整技术文档**`docs/文档版本管理设计方案.md`
- **UI 原型**`docs/UI设计原型.html`
---
## ✅ 部署检查清单
- [ ] SQL 脚本执行成功
- [ ] 3个函数已创建
- [ ] 应用已重新构建和重启
- [ ] 文档列表页面正常显示
- [ ] 展开/折叠功能正常
- [ ] 问题数量差异正确显示(红色/绿色)
---
## 🎉 完成!
现在你可以:
- ✅ 查看同名文档的版本历史
- ✅ 展开/折叠历史版本
- ✅ 对比问题数量变化
- ✅ 享受高性能的版本管理功能
**部署完成,开始使用吧!** 🚀
@@ -0,0 +1,340 @@
# 文档版本管理 - 使用 documents Schema 部署指南
## 📋 文件说明
本次使用 **`documents` schema** 来组织所有文档版本管理相关的 RPC 函数。
**SQL 脚本位置**`add_document_version_management_with_schema.sql`
---
## 🚀 快速部署(3步)
### 步骤1:在 Navicat 中执行 SQL 脚本
#### 方法A:通过查询窗口
1. 打开 Navicat,连接到你的 PostgreSQL 数据库
2. 点击 **"查询"** → **"新建查询"**
3. 打开 `add_document_version_management_with_schema.sql` 文件
4. 复制所有内容到查询窗口
5. **⚠️ 重要:将所有的 `authenticator` 替换为你的实际 PostgREST 用户名**
6. 点击 **"运行"** 按钮
#### 方法B:通过文件导入
1. 在 Navicat 中右键点击数据库
2. 选择 **"运行 SQL 文件"**
3. 选择 `add_document_version_management_with_schema.sql`
4. 点击 **"开始"**
### 步骤2:验证函数是否创建成功
在 Navicat 查询窗口执行:
```sql
-- 查看 documents schema 中的所有函数
SELECT proname, proargnames
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'documents';
```
**应该看到 3 个函数**
- `get_latest_documents_with_version_info`
- `count_latest_documents_with_filters`
- `get_document_history`
### 步骤3:测试函数(可选)
```sql
-- 测试1:获取文档列表(假设用户ID为1)
SELECT * FROM documents.get_latest_documents_with_version_info(1, 1, 10);
-- 测试2:统计文档数量
SELECT documents.count_latest_documents_with_filters(1);
```
---
## 📁 Navicat 中查看函数
执行成功后,在 Navicat 左侧树形结构中:
```
你的数据库
└─ 模式 (Schemas)
├─ public
└─ documents ← 新创建的 schema
├─ 表
└─ 函数 ← 3个新函数在这里
├─ get_latest_documents_with_version_info
├─ count_latest_documents_with_filters
└─ get_document_history
```
---
## ⚙️ PostgREST 配置
确保你的 PostgREST 配置文件包含 `documents` schema
```conf
# /etc/postgrest/config (或其他配置文件路径)
# 添加 documents 到 db-schemas
db-schemas = "public, documents"
# 或者,如果之前没有配置,添加这一行
db-schemas = "public, documents"
```
修改配置后重启 PostgREST
```bash
sudo systemctl restart postgrest
```
---
## 🔐 权限配置
### 查找你的 PostgREST 用户名
```sql
-- 方法1:查看所有数据库用户
SELECT usename FROM pg_user;
-- 方法2:查看当前连接用户
SELECT current_user;
```
**常见的 PostgREST 用户名**
- `authenticator`
- `web_anon`
- `postgres`
- `api_user`
### 授权函数给用户
将下面的 `YOUR_POSTGREST_USER` 替换为你的实际用户名:
```sql
-- 授权 schema 使用权限
GRANT USAGE ON SCHEMA documents TO YOUR_POSTGREST_USER;
-- 授权函数执行权限
GRANT EXECUTE ON FUNCTION documents.get_latest_documents_with_version_info TO YOUR_POSTGREST_USER;
GRANT EXECUTE ON FUNCTION documents.count_latest_documents_with_filters TO YOUR_POSTGREST_USER;
GRANT EXECUTE ON FUNCTION documents.get_document_history TO YOUR_POSTGREST_USER;
```
---
## 🎯 前端代码已自动适配
前端代码已经修改为使用 `documents` schema
```typescript
// app/api/files/documents.ts
// ✅ 已修改为:
postgrestPost('rpc/documents.get_latest_documents_with_version_info', ...)
postgrestPost('rpc/documents.count_latest_documents_with_filters', ...)
postgrestPost('rpc/documents.get_document_history', ...)
```
**无需手动修改前端代码!**
---
## 🧪 完整测试流程
### 1. 在 Navicat 中测试 SQL
```sql
-- 测试获取文档列表
SELECT * FROM documents.get_latest_documents_with_version_info(
p_user_id := 1,
p_page := 1,
p_page_size := 10
);
-- 测试搜索功能
SELECT * FROM documents.get_latest_documents_with_version_info(
p_user_id := 1,
p_page := 1,
p_page_size := 10,
p_search_name := '合同'
);
-- 测试历史版本查询
SELECT * FROM documents.get_document_history('测试文档.pdf', 1, 123);
```
### 2. 测试 PostgREST API
```bash
# 测试获取文档列表
curl -X POST http://your-api-url/rpc/documents.get_latest_documents_with_version_info \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"p_user_id": 1,
"p_page": 1,
"p_page_size": 10
}'
```
### 3. 测试前端页面
1. 重启应用:
```bash
npm run build
pm2 restart all
```
2. 访问文档列表页面:`http://your-domain/documents`
3. 检查功能:
- ✅ 文档列表正常显示
- ✅ 有历史版本的文档显示展开图标
- ✅ 点击展开图标能看到历史版本
- ✅ 问题数量差异正确显示(红色/绿色)
---
## ❌ 故障排查
### 问题1:函数未创建成功
**症状**:执行查询时提示 `function does not exist`
**解决方案**
```sql
-- 检查 documents schema 是否存在
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'documents';
-- 如果不存在,手动创建
CREATE SCHEMA IF NOT EXISTS documents;
-- 重新执行 SQL 脚本
```
### 问题2:权限不足
**症状**:前端报错 `permission denied`
**解决方案**
```sql
-- 检查当前用户权限
SELECT grantee, privilege_type
FROM information_schema.role_usage_grants
WHERE object_schema = 'documents';
-- 重新授权(替换 YOUR_USER 为实际用户)
GRANT USAGE ON SCHEMA documents TO YOUR_USER;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA documents TO YOUR_USER;
```
### 问题3:前端调用失败
**症状**:浏览器控制台报错 `404 Not Found`
**检查清单**
1. PostgREST 配置是否包含 `documents` schema
2. PostgREST 是否已重启
3. 前端代码是否已更新并重新构建
**解决方案**
```bash
# 1. 检查 PostgREST 配置
cat /etc/postgrest/config
# 2. 确保包含:
# db-schemas = "public, documents"
# 3. 重启 PostgREST
sudo systemctl restart postgrest
# 4. 重新构建前端
npm run build
pm2 restart all
```
---
## 📊 性能优化
SQL 脚本已自动创建以下索引:
```sql
-- 1. 用户+名称+时间索引
CREATE INDEX idx_documents_user_name_created
ON documents(user_id, name, created_at DESC);
-- 2. 名称+用户+时间索引
CREATE INDEX idx_documents_name_user_created
ON documents(name, user_id, created_at DESC);
-- 3. 创建时间索引
CREATE INDEX idx_documents_created_at
ON documents(created_at DESC);
-- 4. 文件状态索引
CREATE INDEX idx_documents_status
ON documents(status);
```
查看索引是否创建成功:
```sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'documents'
AND schemaname = 'public';
```
---
## ✅ 部署检查清单
- [ ] SQL 脚本执行成功
- [ ] 3个函数都已创建在 `documents` schema 中
- [ ] 已授权给 PostgREST 用户
- [ ] PostgREST 配置已更新
- [ ] PostgREST 已重启
- [ ] 前端应用已重新构建
- [ ] 文档列表页面正常显示
- [ ] 展开/折叠功能正常工作
- [ ] 问题数量差异正确显示
---
## 🎉 完成!
部署完成后,你应该能看到:
```
文档列表页面
├─ [▶️] 📄 合同审查.pdf
│ 合同审查 v1 (共3个历史版本)
│ 问题数量:3 🟢 ↓ -2
├─ [▶️] 📄 许可证申请.docx
│ 许可证审查 v1 (共2个历史版本)
│ 问题数量:5 🔴 ↑ +1
└─ 📄 新文档.pdf
合同审查
问题数量:4
```
点击展开图标后:
```
[🔽] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
├─ 🕒 v2 版本 (问题:5 🔴 ↑ +2)
├─ 🕒 v3 版本 (问题:3 🟢 ↓ -1)
└─ 🕒 v4 版本 (最早) (问题:4)
```
**功能完美运行!** 🚀
@@ -0,0 +1,255 @@
-- ============================================
-- 文档版本管理 RPC 函数(第4版 - 显式类型转换)
-- 功能:支持同名文档的版本管理和历史查询
-- 修复:所有字段显式转换为函数返回类型
-- 创建时间:2025-11-14
-- ============================================
-- ====================
-- 删除旧函数
-- ====================
DROP FUNCTION IF EXISTS documents_get_latest_documents_with_version_info(integer,integer,integer,text,text,integer[],integer,text,text,text);
DROP FUNCTION IF EXISTS documents_count_latest_documents_with_filters(integer,text,text,integer[],integer,text,text,text);
DROP FUNCTION IF EXISTS documents_get_document_history(text,integer,integer);
-- ====================
-- 1. 获取最新版本的文档列表(带版本信息)
-- ====================
CREATE FUNCTION documents_get_latest_documents_with_version_info(
p_user_id integer,
p_page integer DEFAULT 1,
p_page_size integer DEFAULT 10,
p_search_name text DEFAULT NULL,
p_search_document_number text DEFAULT NULL,
p_search_document_types integer[] DEFAULT NULL,
p_search_audit_status integer DEFAULT NULL,
p_search_file_status text DEFAULT NULL,
p_search_date_from text DEFAULT NULL,
p_search_date_to text DEFAULT NULL
)
RETURNS TABLE (
id integer,
name text,
document_number text,
type_id integer,
type_name text,
file_size integer,
audit_status integer,
status text,
false_count bigint,
created_at timestamp with time zone,
updated_at timestamp with time zone,
path text,
is_test_document boolean,
ocr_result jsonb,
history_count bigint,
previous_issues bigint
) AS $$
BEGIN
RETURN QUERY
WITH latest_docs AS (
-- 使用 DISTINCT ON 获取每个文档名称的最新版本
SELECT DISTINCT ON (d.name)
d.id,
d.name,
d.user_id,
d.created_at
FROM documents d
WHERE d.user_id = p_user_id
-- 文档名称搜索
AND (p_search_name IS NULL OR d.name ILIKE '%' || p_search_name || '%')
-- 文档编号搜索
AND (p_search_document_number IS NULL OR d.document_number ILIKE '%' || p_search_document_number || '%')
-- 文档类型筛选
AND (p_search_document_types IS NULL OR d.type_id = ANY(p_search_document_types))
-- 审核状态筛选
AND (p_search_audit_status IS NULL OR d.audit_status = p_search_audit_status)
-- 文件状态筛选
AND (p_search_file_status IS NULL OR d.status = p_search_file_status)
-- 日期范围筛选
AND (p_search_date_from IS NULL OR d.created_at >= p_search_date_from::timestamp)
AND (p_search_date_to IS NULL OR d.created_at <= p_search_date_to::timestamp)
ORDER BY d.name, d.created_at DESC
)
SELECT
d.id::integer,
d.name::text,
d.document_number::text,
d.type_id::integer,
COALESCE(dt.name::text, '') as type_name,
d.file_size::integer,
d.audit_status::integer,
d.status::text,
-- 计算当前文档的问题数量(从 evaluation_results 表统计)
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er
WHERE er.document_id = d.id
AND (er.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as false_count,
d.created_at::timestamp with time zone,
d.updated_at::timestamp with time zone,
d.path::text,
d.is_test_document::boolean,
d.ocr_result::jsonb,
-- 计算历史版本数量(不包含当前版本)
COALESCE((
SELECT COUNT(*)
FROM documents d2
WHERE d2.name = d.name
AND d2.user_id = d.user_id
AND d2.id != d.id
), 0)::bigint as history_count,
-- 获取上一个版本的问题数量
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er2
WHERE er2.document_id = (
SELECT d3.id
FROM documents d3
WHERE d3.name = d.name
AND d3.user_id = d.user_id
AND d3.created_at < d.created_at
ORDER BY d3.created_at DESC
LIMIT 1
)
AND (er2.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as previous_issues
FROM documents d
INNER JOIN latest_docs ld ON d.id = ld.id
LEFT JOIN document_types dt ON d.type_id = dt.id
ORDER BY d.created_at DESC
LIMIT p_page_size OFFSET (p_page - 1) * p_page_size;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_get_latest_documents_with_version_info IS '获取最新版本的文档列表,包含历史版本数量和上一个版本的问题数量(从 evaluation_results 表计算)';
-- ====================
-- 2. 统计最新版本文档的总数
-- ====================
CREATE FUNCTION documents_count_latest_documents_with_filters(
p_user_id integer,
p_search_name text DEFAULT NULL,
p_search_document_number text DEFAULT NULL,
p_search_document_types integer[] DEFAULT NULL,
p_search_audit_status integer DEFAULT NULL,
p_search_file_status text DEFAULT NULL,
p_search_date_from text DEFAULT NULL,
p_search_date_to text DEFAULT NULL
)
RETURNS integer AS $$
DECLARE
doc_count integer;
BEGIN
WITH latest_docs AS (
SELECT DISTINCT ON (d.name)
d.id
FROM documents d
WHERE d.user_id = p_user_id
AND (p_search_name IS NULL OR d.name ILIKE '%' || p_search_name || '%')
AND (p_search_document_number IS NULL OR d.document_number ILIKE '%' || p_search_document_number || '%')
AND (p_search_document_types IS NULL OR d.type_id = ANY(p_search_document_types))
AND (p_search_audit_status IS NULL OR d.audit_status = p_search_audit_status)
AND (p_search_file_status IS NULL OR d.status = p_search_file_status)
AND (p_search_date_from IS NULL OR d.created_at >= p_search_date_from::timestamp)
AND (p_search_date_to IS NULL OR d.created_at <= p_search_date_to::timestamp)
ORDER BY d.name, d.created_at DESC
)
SELECT COUNT(*)::integer INTO doc_count FROM latest_docs;
RETURN doc_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_count_latest_documents_with_filters IS '统计符合条件的最新版本文档总数(用于分页)';
-- ====================
-- 3. 获取文档的历史版本列表
-- ====================
CREATE FUNCTION documents_get_document_history(
p_document_name text,
p_user_id integer,
p_exclude_id integer
)
RETURNS TABLE (
id integer,
name text,
document_number text,
type_id integer,
type_name text,
file_size integer,
audit_status integer,
status text,
false_count bigint,
created_at timestamp with time zone,
updated_at timestamp with time zone,
path text,
is_test_document boolean,
ocr_result jsonb
) AS $$
BEGIN
RETURN QUERY
SELECT
d.id::integer,
d.name::text,
d.document_number::text,
d.type_id::integer,
COALESCE(dt.name::text, '') as type_name,
d.file_size::integer,
d.audit_status::integer,
d.status::text,
-- 计算每个历史版本的问题数量
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er
WHERE er.document_id = d.id
AND (er.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as false_count,
d.created_at::timestamp with time zone,
d.updated_at::timestamp with time zone,
d.path::text,
d.is_test_document::boolean,
d.ocr_result::jsonb
FROM documents d
LEFT JOIN document_types dt ON d.type_id = dt.id
WHERE d.name = p_document_name
AND d.user_id = p_user_id
AND d.id != p_exclude_id
ORDER BY d.created_at DESC;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_get_document_history IS '获取指定文档名称的所有历史版本(不包含指定的当前版本ID),从 evaluation_results 表计算问题数量';
-- ====================
-- 4. 创建索引优化查询性能
-- ====================
-- 复合索引:user_id + name + created_at
CREATE INDEX IF NOT EXISTS idx_documents_user_name_created
ON documents(user_id, name, created_at DESC);
-- 复合索引:name + user_id + created_at
CREATE INDEX IF NOT EXISTS idx_documents_name_user_created
ON documents(name, user_id, created_at DESC);
-- 单列索引:created_at
CREATE INDEX IF NOT EXISTS idx_documents_created_at
ON documents(created_at DESC);
-- 单列索引:status
CREATE INDEX IF NOT EXISTS idx_documents_status
ON documents(status);
-- evaluation_results 的 document_id
CREATE INDEX IF NOT EXISTS idx_evaluation_results_document_id
ON evaluation_results(document_id);
-- evaluation_results 的 evaluated_results->>'result'
CREATE INDEX IF NOT EXISTS idx_evaluation_results_result
ON evaluation_results((evaluated_results ->> 'result'));
@@ -0,0 +1,36 @@
-- 入口模块地区管理 - 数据迁移脚本
-- 将 areas 字段从字符串数组转换为对象数组,支持启用/禁用状态
-- 1. 添加备份列
ALTER TABLE entry_modules ADD COLUMN IF NOT EXISTS areas_backup JSONB;
-- 2. 备份原数据
UPDATE entry_modules SET areas_backup = areas WHERE areas IS NOT NULL;
-- 3. 转换数据格式
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(
jsonb_build_object(
'area', area_name,
'enabled', true,
'sort_order', row_number
) ORDER BY row_number
)
FROM (
SELECT area_name, ROW_NUMBER() OVER () as row_number
FROM jsonb_array_elements_text(areas) AS area_name
) AS numbered_areas
)
WHERE areas IS NOT NULL AND jsonb_typeof(areas) = 'array';
-- 4. 添加注释
COMMENT ON COLUMN entry_modules.areas IS '地区配置: [{"area": "地区名", "enabled": true/false, "sort_order": 排序号}]';
-- 5. 查看迁移结果
SELECT id, name, areas_backup AS old_format, areas AS new_format
FROM entry_modules WHERE areas IS NOT NULL;
-- 回滚(如需要):
-- UPDATE entry_modules SET areas = areas_backup WHERE areas_backup IS NOT NULL;
-- ALTER TABLE entry_modules DROP COLUMN areas_backup;
@@ -0,0 +1,50 @@
-- 入口模块地区管理 - 常用操作
-- 查看所有配置
SELECT id, name, jsonb_pretty(areas) AS configs FROM entry_modules;
-- 禁用某个入口在某个地区(示例:禁用id=1在"云浮"
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(CASE WHEN elem->>'area' = '云浮'
THEN jsonb_set(elem, '{enabled}', 'false'::jsonb) ELSE elem END)
FROM jsonb_array_elements(areas) AS elem
), updated_at = NOW()
WHERE id = 1;
-- 启用某个入口在某个地区(示例:启用id=1在"云浮"
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(CASE WHEN elem->>'area' = '云浮'
THEN jsonb_set(elem, '{enabled}', 'true'::jsonb) ELSE elem END)
FROM jsonb_array_elements(areas) AS elem
), updated_at = NOW()
WHERE id = 1;
-- 添加新地区(示例:为id=1添加"揭阳"
UPDATE entry_modules
SET areas = areas || jsonb_build_array(
jsonb_build_object('area', '揭阳', 'enabled', true, 'sort_order',
(SELECT COALESCE(MAX((elem->>'sort_order')::int), 0) + 1
FROM jsonb_array_elements(areas) AS elem))
), updated_at = NOW()
WHERE id = 1 AND NOT EXISTS (
SELECT 1 FROM jsonb_array_elements(areas) AS elem WHERE elem->>'area' = '揭阳'
);
-- 删除某个地区配置(示例:从id=1删除"云浮"
UPDATE entry_modules
SET areas = (SELECT jsonb_agg(elem) FROM jsonb_array_elements(areas) AS elem
WHERE elem->>'area' != '云浮'
), updated_at = NOW()
WHERE id = 1;
-- 查看某个地区已启用的模块(示例:查看"梅州")
SELECT id, name FROM entry_modules
WHERE areas @> '[{"area": "梅州", "enabled": true}]'::jsonb;
-- 统计每个地区的已启用模块数量
SELECT elem->>'area' AS area, COUNT(*) AS count
FROM entry_modules, jsonb_array_elements(areas) AS elem
WHERE (elem->>'enabled')::boolean = true
GROUP BY elem->>'area';
@@ -0,0 +1,11 @@
-- 删除 /documents/download 路由记录
-- 该路由已不再使用,项目统一使用 /api/pdf-proxy 进行文件下载
-- 删除路由记录
DELETE FROM sys_routes WHERE id = 34 AND route_path = '/documents/download';
-- 验证删除结果
SELECT id, route_path, route_name, route_title, parent_id, is_hidden
FROM sys_routes
WHERE route_path LIKE '/documents%'
ORDER BY sort_order;
+41
View File
@@ -0,0 +1,41 @@
-- ========================================
-- 重命名外键约束,使其与代码中的名称一致
-- ========================================
-- 步骤 1: 删除旧的外键约束
ALTER TABLE evaluation_points
DROP CONSTRAINT IF EXISTS fk_evaluation_points_group;
-- 步骤 2: 创建新的外键约束,使用正确的名称
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_child_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id)
ON DELETE SET NULL
ON UPDATE CASCADE;
-- 添加注释
COMMENT ON CONSTRAINT fk_evaluation_points_child_group ON evaluation_points
IS '评查点所属规则组外键约束(子分组)';
-- 步骤 3: 验证外键约束(应该是正确的名称)
SELECT
tc.constraint_name AS "约束名",
kcu.column_name AS "列名",
ccu.table_name AS "引用表",
ccu.column_name AS "引用列"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'evaluation_points'
AND kcu.column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
ORDER BY kcu.column_name;
-- 预期结果:
-- 约束名 | 列名 | 引用表 | 引用列
-- ------------------------------------------|-------------------------------|---------------------------|-------
-- fk_evaluation_points_child_group | evaluation_point_groups_id | evaluation_point_groups | id
-- fk_evaluation_points_parent_group | evaluation_point_groups_pid | evaluation_point_groups | id
+245
View File
@@ -0,0 +1,245 @@
-- ============================================================
-- 系统路由表 (sys_routes) 更新脚本
-- 目的:根据实际 app/routes 目录和期望菜单结构更新路由配置
-- 生成时间: 2025-11-17
-- ============================================================
-- ============================================================
-- 第 1 步:删除废弃的路由
-- ============================================================
-- 删除废弃的一级菜单及其子路由
DELETE FROM sys_routes WHERE id IN (3, 4, 5, 6);
-- ============================================================
-- 第 2 步:调整现有路由的层级结构
-- ============================================================
-- 2.1 将 rule-groups 改为 rules 的子级(评查点分组)
UPDATE sys_routes
SET parent_id = 41,
sort_order = 1,
route_title = '评查点分组'
WHERE id = 43;
-- 2.2 将 prompts 改为 settings 的子级(稍后创建 settings 父级)
-- 注意:先创建 settings 路由后再执行此更新
-- UPDATE sys_routes SET parent_id = 53, sort_order = 2 WHERE id = 45;
-- 2.3 将 document-types 改为 settings 的子级
-- UPDATE sys_routes SET parent_id = 53, sort_order = 1 WHERE id = 47;
-- 2.4 更新合同模板搜索的标题
UPDATE sys_routes
SET route_title = '智能搜索'
WHERE id = 39;
-- ============================================================
-- 第 3 步:添加新的一级菜单 - 系统设置
-- ============================================================
INSERT INTO sys_routes (
id,
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
53,
'/settings',
'Settings',
'系统设置',
NULL,
'el-icon-setting',
20,
false,
true,
'views/settings/Index.vue',
'{}'
);
-- 现在更新 prompts 和 document-types 的父级
UPDATE sys_routes SET parent_id = 53, sort_order = 2, route_title = '提示词管理' WHERE id = 45;
UPDATE sys_routes SET parent_id = 53, sort_order = 1, route_title = '文档类型' WHERE id = 47;
-- ============================================================
-- 第 4 步:添加缺失的路由
-- ============================================================
-- 4.1 添加 /rules/list - 评查点列表(作为 rules 的子级)
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/rules/list',
'RulesList',
'评查点列表',
41,
NULL,
2,
false,
true,
'views/rules/List.vue',
'{}'
);
-- 4.2 添加 /rules-files - 评查文件列表(作为 rules 的子级)
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/rules-files',
'RulesFiles',
'评查文件列表',
41,
NULL,
3,
false,
true,
'views/rules/Files.vue',
'{}'
);
-- 4.3 添加 /contract-template/list - 合同列表(作为 contract-template 的子级)
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/contract-template/list',
'ContractTemplateList',
'合同列表',
38,
NULL,
2,
false,
true,
'views/contract-template/List.vue',
'{}'
);
-- 4.4 添加 /reviews - 评查详情页(隐藏路由,不在侧边栏显示)
INSERT INTO sys_routes (
route_path,
route_name,
route_title,
parent_id,
icon,
sort_order,
is_hidden,
is_cache,
component,
meta
) VALUES (
'/reviews',
'Reviews',
'评查详情',
NULL,
NULL,
99,
true,
true,
'views/reviews/Index.vue',
'{}'
);
-- ============================================================
-- 第 5 步:调整排序顺序,使菜单符合期望结构
-- ============================================================
-- 期望的一级菜单顺序:
-- 1. 系统概览
-- 2. 文档管理
-- 3. 评查规则库
-- 4. 合同模板
-- 5. 系统设置
UPDATE sys_routes SET sort_order = 1 WHERE id = 31; -- /home (系统概览)
UPDATE sys_routes SET sort_order = 2 WHERE id = 2; -- /documents (文档管理)
UPDATE sys_routes SET sort_order = 3 WHERE id = 41; -- /rules (评查规则库)
UPDATE sys_routes SET sort_order = 4 WHERE id = 38; -- /contract-template (合同模板)
UPDATE sys_routes SET sort_order = 5 WHERE id = 53; -- /settings (系统设置)
-- 其他菜单(交叉评查、AI助手等)排在后面
UPDATE sys_routes SET sort_order = 6 WHERE id = 35; -- /cross-checking (交叉评查)
UPDATE sys_routes SET sort_order = 7 WHERE id = 40; -- /chat-with-llm (AI法务助手)
UPDATE sys_routes SET sort_order = 8 WHERE id = 51; -- /files (文件管理)
UPDATE sys_routes SET sort_order = 9 WHERE id = 49; -- /config-lists (配置列表)
-- ============================================================
-- 第 6 步:隐藏不需要在菜单中显示的路由
-- ============================================================
-- 隐藏入口页(不在侧边栏显示)
UPDATE sys_routes SET is_hidden = true WHERE id = 30; -- /
-- ============================================================
-- 第 7 步:验证更新结果
-- ============================================================
-- 查看更新后的一级菜单
SELECT id, route_path, route_name, route_title, parent_id, icon, sort_order, is_hidden
FROM sys_routes
WHERE parent_id IS NULL
ORDER BY sort_order;
-- 查看 /rules 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, icon, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 41
ORDER BY sort_order;
-- 查看 /contract-template 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, icon, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 38
ORDER BY sort_order;
-- 查看 /settings 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, icon, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 53
ORDER BY sort_order;
-- ============================================================
-- 第 8 步:清理 role_route 表中的废弃路由关联
-- ============================================================
-- 删除指向已废弃路由的角色-路由关联
DELETE FROM role_route
WHERE route_id IN (3, 4, 5, 6);
-- ============================================================
-- 完成!
-- ============================================================
-- 提示:执行此脚本后,请重新登录系统以刷新路由权限
+119
View File
@@ -0,0 +1,119 @@
-- ============================================================
-- 系统路由表 (sys_routes) 剩余更新脚本
-- 说明:部分更新已完成(废弃路由删除、settings创建等)
-- 此脚本仅包含剩余需要执行的步骤
-- 生成时间: 2025-11-17
-- ============================================================
-- ============================================================
-- 第 1 步:添加缺失的路由
-- ============================================================
-- 1.1 添加 /rules/list 列表页(作为 rules 的子级)
INSERT INTO sys_routes (
route_path, route_name, route_title,
parent_id, icon, sort_order,
is_hidden, is_cache, component, meta
) VALUES (
'/rules/list', 'RulesList', '评查点列表',
41, NULL, 2,
false, true, 'views/rules/List.vue', '{}'
);
-- 1.2 添加 /rules-files(作为 rules 的子级)
INSERT INTO sys_routes (
route_path, route_name, route_title,
parent_id, icon, sort_order,
is_hidden, is_cache, component, meta
) VALUES (
'/rules-files', 'RulesFiles', '评查文件列表',
41, NULL, 3,
false, true, 'views/rules/Files.vue', '{}'
);
-- 1.3 添加 /contract-template/list(作为 contract-template 的子级)
INSERT INTO sys_routes (
route_path, route_name, route_title,
parent_id, icon, sort_order,
is_hidden, is_cache, component, meta
) VALUES (
'/contract-template/list', 'ContractTemplateList', '合同列表',
38, NULL, 2,
false, true, 'views/contract-template/List.vue', '{}'
);
-- 1.4 添加 /reviews 评查详情页(隐藏路由)
INSERT INTO sys_routes (
route_path, route_name, route_title,
parent_id, icon, sort_order,
is_hidden, is_cache, component, meta
) VALUES (
'/reviews', 'Reviews', '评查详情',
NULL, NULL, 99,
true, true, 'views/reviews/Index.vue', '{}'
);
-- ============================================================
-- 第 2 步:调整排序顺序
-- ============================================================
-- 期望的一级菜单顺序:
-- 1. 系统概览
-- 2. 文档管理
-- 3. 评查规则库
-- 4. 合同模板
-- 5. 系统设置
-- 6. 交叉评查
-- 7. AI法务助手
-- 8. 文件管理
-- 9. 配置列表
UPDATE sys_routes SET sort_order = 1 WHERE id = 31; -- /home (系统概览)
UPDATE sys_routes SET sort_order = 2 WHERE id = 2; -- /documents (文档管理)
UPDATE sys_routes SET sort_order = 3 WHERE id = 41; -- /rules (评查规则库)
UPDATE sys_routes SET sort_order = 4 WHERE id = 38; -- /contract-template (合同模板)
UPDATE sys_routes SET sort_order = 5 WHERE id = 53; -- /settings (系统设置)
UPDATE sys_routes SET sort_order = 6 WHERE id = 35; -- /cross-checking (交叉评查)
UPDATE sys_routes SET sort_order = 7 WHERE id = 40; -- /chat-with-llm (AI法务助手)
UPDATE sys_routes SET sort_order = 8 WHERE id = 51; -- /files (文件管理)
UPDATE sys_routes SET sort_order = 9 WHERE id = 49; -- /config-lists (配置列表)
-- ============================================================
-- 第 3 步:隐藏入口页
-- ============================================================
UPDATE sys_routes SET is_hidden = true WHERE id = 30; -- /
-- ============================================================
-- 第 4 步:验证更新结果
-- ============================================================
-- 查看更新后的一级菜单
SELECT id, route_path, route_name, route_title, parent_id, icon, sort_order, is_hidden
FROM sys_routes
WHERE parent_id IS NULL
ORDER BY sort_order;
-- 查看 /rules 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 41
ORDER BY sort_order;
-- 查看 /contract-template 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 38
ORDER BY sort_order;
-- 查看 /settings 的子菜单
SELECT id, route_path, route_name, route_title, parent_id, sort_order, is_hidden
FROM sys_routes
WHERE parent_id = 53
ORDER BY sort_order;
-- ============================================================
-- 完成!
-- ============================================================
-- 提示:执行此脚本后,请重新登录系统以刷新路由权限