# 档案管理模块 - 前端联调方案 > 模块:`yudao-module-archive`(主模块)+ `yudao-module-archive-api`(跨模块归档 RPC) > 后端接口前缀:`/archive/category`、`/archive/document`、`/archive/borrow`、`/archive/stat` > 附件关联:`recordType = archive_document`,`application = file` > 权限点前缀:`archive:category:*`、`archive:document:*`、`archive:borrow:*`、`archive:stat:query` ## 一、涉及页面(前端文件清单) | 文件 | 说明 | |------|------| | `src/packages/constants/src/dict-enum.ts` | 新增 `ARCHIVE_DICT`(6 个字典类型)并展开进 `DICT_TYPE` | | `src/api/archive/category/index.ts` | 档案分类 API | | `src/api/archive/document/index.ts` | 档案 API | | `src/api/archive/borrow/index.ts` | 借阅单 API | | `src/api/archive/stat/index.ts` | 档案统计 API | | `src/views/archive/category/index.vue` | 分类树表页(左树右表形态,`treeConfig` 展开) | | `src/views/archive/category/data.ts` | 分类表单 + 搜索表单 + 表格列 | | `src/views/archive/category/modules/form.vue` | 分类新增/编辑弹窗(含「新增下级」) | | `src/views/archive/document/index.vue` | 档案列表页(检索 + 导出 + 附件标记) | | `src/views/archive/document/data.ts` | 档案表单 + 搜索表单 + 表格列 | | `src/views/archive/document/modules/form.vue` | 档案新增/编辑/详情弹窗(实体位置四字段 + 电子附件上传) | | `src/views/archive/borrow/index.vue` | 借阅列表页(逾期 Tag、提交审批、归还登记) | | `src/views/archive/borrow/data.ts` | 借阅表单 + 搜索表单 + 表格列 + `BorrowStatus` 常量 | | `src/views/archive/borrow/modules/form.vue` | 借阅登记/编辑/详情弹窗 | | `src/views/archive/borrow/modules/process-select-modal.vue` | 选择审批流程弹窗 | | `src/views/archive/borrow/modules/return-form.vue` | 归还登记弹窗 | | `src/views/archive/borrow/detail/approve/index.vue` | **BPM 审批详情页**,路径必须与 `bpm_process_definition_info.form_custom_view_path` 完全一致 | | `src/views/archive/stat/index.vue` | 档案统计页(4 卡片 + 3 图表) | | `src/views/archive/stat/data.ts` | 卡片定义 + 饼图/柱图 option 构造 | > 字典枚举必须登记到 `dict-enum.ts`,否则 `DICT_TYPE.ARCHIVE_*` 取不到值,列表列与表单下拉会空白。 **菜单已配置完成**,本项目为动态路由,前端无需手工加路由文件: | 菜单 | id | 路径 | 组件 | 权限标识 | |------|----|------|------|----------| | 档案管理(顶级目录 `parent_id=0`,`path=/archive`) | 1075600 | `/archive` | — | — | | 档案分类 | 1075601 | `category` | `archive/category/index` | `archive:category:query` | | 档案管理 | 1075602 | `document` | `archive/document/index` | `archive:document:query` | | 借阅登记 | 1075603 | `borrow` | `archive/borrow/index` | `archive:borrow:query` | | 档案统计 | 1075604 | `stat` | `archive/stat/index` | `archive:stat:query` | 按钮权限:`archive:category:create/update/delete`、`archive:document:create/update/delete/export/sensitive`、`archive:borrow:create/update/delete/submit/return`、`archive:stat:query`。 > ⚠ **授权易错点**:这些菜单与权限点**未授予任何角色**,因此只有超级管理员(`super_admin`,代码直返全部菜单)能看到;普通角色需在「系统管理 → 角色」勾选授权。 > ⚠ **敏感权限**:`archive:document:sensitive`(菜单 1075625「保密档案查看」)决定能否看到密级=保密的档案。**未授权时后端会强制过滤**,不是前端隐藏。 **字典已配置完成**,前端直接用 `getDictOptions` / `DictTag` 取值,无需自建枚举: | 字典类型 | 名称 | 取值 | |----------|------|------| | `archive_category_status` | 档案分类状态 | 0 启用 / 1 停用 | | `archive_document_status` | 档案状态 | 10 在库 / 20 借出中 | | `archive_secret_level` | 档案密级 | 1 公开 / 2 内部 / 3 保密 | | `archive_carrier_type` | 档案载体 | 1 电子 / 2 实体 / 3 电子+实体 | | `archive_source_type` | 档案来源 | 1 手工录入 / 2 课题归档 / 3 会员归档 / 4 财务归档 | | `archive_borrow_status` | 档案借阅状态 | 0 草稿 / 15 审批中 / 20 借出中 / 30 已归还 / 35 已驳回 | --- ## 二、业务流程与数据带入 ### 2.1 档案登记(手工) ``` 选择档案分类(树形下拉,默认带出该分类的 default_secret_level) │ ├── 档案标题、密级、载体、归档日期(必填) ├── 实体位置:书架号 / 层号 / 列号 / 册号(档案盒号)/ 存放位置描述 └── 电子文件:走 /system/storage-blob/upload 拿 blobId,随 create/update 一起提交 │ 保存(create) ▼ 后端生成档案编号 DA+yyyyMMdd+6位流水,落库并 bindAttachments │ 列表可检索(标题/关键词/来源单号/分类/密级/载体/状态/归档日期区间) ``` - **附件字段**:表单字段名 `blobIds`(number[])。**全量替换语义** —— 清空全部附件时必须显式传 `[]`,传 `null` 视为「不动附件」。 - **打开编辑/详情时**:需把 `attachmentList` 映射成上传组件可识别的对象(`uid`/`name`/`url`/`status`/`id`),提交时按 `valueKey: 'id'` 提取为 blobId。 - **只读字段**:`no`(档案编号)、`status`(档案状态)、`sourceType` / `sourceNo`(来源)、`archivistName`(归档人)由后端维护,表单只展示不回传。 ### 2.2 借阅闭环 ``` 登记借阅(选择在库档案 + 借阅人 + 借阅日期 + 归还期限) │ 保存(草稿 0) │ 提交审批(先调 approve-process-list 拿流程 key,再 submit) ▼ 审批中 15 ──[BPM 通过]──► 借出中 20 ──[归还登记]──► 已归还 30 │ │ │ (同时把档案置为「借出中」) │ └──[BPM 驳回]──► 已驳回 35 ──(改后可重新提交) ``` **关键带入规则**: 1. **借阅登记时**:选中的档案会以 `no + title` 形式展示;提交时只传 `documentId`,档案编号与标题由后端快照到借阅单(`documentNo` / `documentTitle`)。 2. **提交审批时**:**流程 key 必须来自后端 `approve-process-list`,禁止前端写死**。列表为空时应提示「暂无可用审批流程,请先到 BPM 流程设计器发布档案借阅审批流程」。 3. **归还登记时**:只传 `id` + `returnRemark`。归还成功后档案自动回到「在库」,借阅单变「已归还」并写入 `returnTime` / `returnConfirmUserName`。 ### 2.3 逾期提醒(实时计算,不落库、不发通知) - 后端在 `page` / `overdue-page` 返回时实时计算:`overdue = (status == 20 && returnDeadline < 今天)`,`overdueDays = 今天 - returnDeadline`。 - 前端**不需要**自行计算,直接用 `row.overdue` / `row.overdueDays` 渲染红色 Tag。 - `overdueOnly` 不是查询参数,而是独立接口 `GET /archive/borrow/overdue-page`;前端用「只看逾期」按钮切换调用的接口即可。 ### 2.4 三方归档联动(本模块的外部入口) | 来源模块 | 接口 | 权限点 | 触发前提 | |----------|------|--------|----------| | 课题 | `POST /research/application/archive?id` | `research:application:archive` | 申报单 `status=20 已立项` 且未归档 | | 会员 | `POST /ums/member/archive?id` | `ums:member:archive` | 会员 `status=1 正常` 且未归档 | | 财务 | `POST /finance/reimbursement/archive?id` | `finance:reimbursement:archive`(既有) | 报销单 `status=20 已通过` 且未归档 | - 三个接口**均返回生成的档案编号**(`CommonResult`,如 `DA20260929000001`),前端 toast 直接提示「归档成功,已生成档案 XXX」。 - 三方列表页的响应体新增两个字段用于控制按钮显隐与「已归档」标记: | 字段 | 类型 | 说明 | |------|------|------| | `archiveDocumentId` | number | 归档生成的档案 ID(非空即已归档) | | `archived` | boolean | 是否已归档 | - 生成的档案:`sourceType` 为 2 课题 / 3 会员 / 4 财务,`sourceNo` 取来源单号(立项编号 / 会员编号 / 报销单号),分类固定取 `research` / `member` / `finance`。 - **财务归档默认密级为「保密」**(分类 `finance` 的 `default_secret_level=3`),因此财务档案在无 `archive:document:sensitive` 权限时**在档案列表里看不到**。 - **重复归档会报错**,提示中带来源单号与已生成的档案编号;前端应在归档成功后刷新列表(`archiveDocumentId` 非空后按钮自动隐藏)。 - **财务归档是叠加语义**:既有逻辑把报销单 20→40 并写 `archiveTime`,本次在此基础上追加生成档案,两者同事务。 --- ## 三、API ### 3.1 档案分类 `/archive/category` | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | POST | `/archive/category/create` | `archive:category:create` | 新增分类 | | PUT | `/archive/category/update` | `archive:category:update` | 修改分类 | | DELETE | `/archive/category/delete?id` | `archive:category:delete` | 删除(有子分类/有档案时拒绝) | | GET | `/archive/category/get?id` | `archive:category:query` | 分类详情 | | GET | `/archive/category/list` | `archive:category:query` | 分类列表(**平铺**,供树表 `transform` 用) | | GET | `/archive/category/tree` | `archive:category:query` | 分类树(供左树与下拉用) | **`/list` 请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `name` | String | 否 | 分类名称,模糊匹配 | | `status` | Integer | 否 | 状态:0 启用 / 1 停用 | **新增/修改请求体:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | Long | 修改必填 | 编号 | | `parentId` | Long | 是 | 父分类 ID,0=顶级 | | `name` | String | 是 | 分类名称 | | `code` | String | 是 | 分类编码,唯一(member/research/finance 等,归档联动按此查找) | | `sort` | Integer | 否 | 显示排序 | | `description` | String | 否 | 分类说明 | | `defaultSecretLevel` | Integer | 是 | 默认密级(1/2/3) | | `status` | Integer | 否 | 状态(0 启用 / 1 停用) | | `remark` | String | 否 | 备注 | **响应字段:** `id`、`parentId`、`name`、`code`、`sort`、`description`、`defaultSecretLevel`、`status`、`remark`、`createTime`、`children`(树形接口才返回)。 **树表接入要点**:`/list` 返回**平铺**数组,前端 VXE 表格用 `treeConfig: { parentField: 'parentId', rowField: 'id', transform: true, expandAll: true, reserve: true }` 自行转树,`pagerConfig.enabled = false`,名称列加 `treeNode: true`。 ### 3.2 档案 `/archive/document` | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | POST | `/archive/document/create` | `archive:document:create` | 新增档案 | | PUT | `/archive/document/update` | `archive:document:update` | 修改(借出中拒绝) | | DELETE | `/archive/document/delete?id` | `archive:document:delete` | 删除(借出中/存在未归还借阅单拒绝) | | GET | `/archive/document/get?id` | `archive:document:query` | 详情(保密档案需 `archive:document:sensitive`) | | GET | `/archive/document/page` | `archive:document:query` | 分页(无敏感权限时自动过滤保密档案) | | GET | `/archive/document/export-excel` | `archive:document:export` | 导出 Excel(同上过滤) | **`/page` 请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNo` / `pageSize` | Integer | 是 | 分页 | | `no` | String | 否 | 档案编号,模糊匹配 | | `title` | String | 否 | 档案标题,模糊匹配 | | `categoryId` | Long | 否 | 档案分类 ID | | `secretLevel` | Integer | 否 | 密级 | | `carrierType` | Integer | 否 | 载体 | | `sourceType` | Integer | 否 | 来源类型 | | `sourceNo` | String | 否 | 来源单号,模糊匹配 | | `tags` | String | 否 | 关键词,模糊匹配 tags 字段 | | `status` | Integer | 否 | 档案状态 | | `archiveDate` | LocalDate[] | 否 | 归档日期区间,**格式 `yyyy-MM-dd`**(RangePicker 必须设 `valueFormat: 'YYYY-MM-DD'`,不能带时分秒) | **新增/修改请求体(SaveReqVO 只接受以下字段):** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | Long | 修改必填 | 编号 | | `title` | String | 是 | 档案标题 | | `categoryId` | Long | 是 | 档案分类 ID | | `secretLevel` | Integer | 是 | 密级 | | `carrierType` | Integer | 是 | 载体 | | `shelfNo` / `layerNo` / `columnNo` / `boxNo` | String | 否 | 实体位置四字段 | | `storePlace` | String | 否 | 存放位置描述 | | `archiveDate` | String(`yyyy-MM-dd`) | 是 | 归档日期 | | `summary` | String | 否 | 摘要 | | `tags` | String | 否 | 关键词,英文逗号分隔 | | `remark` | String | 否 | 备注 | | `blobIds` | Long[] | 否 | 附件 ID 全量列表,**清空须传 `[]`** | **响应新增/只读字段:** `no`、`categoryName`、`sourceType`、`sourceNo`、`sourceId`、`sourceTitle`、`archivistUserId`、`archivistName`、`status`、`attachmentList`、`hasAttachment`。 > ⚠ `no` / `status` / `source*` / `archivist*` **不要在提交体里回传**(后端 SaveReqVO 不接收,回传无意义且会被忽略)。 ### 3.3 借阅 `/archive/borrow` | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | POST | `/archive/borrow/create` | `archive:borrow:create` | 登记借阅(草稿) | | PUT | `/archive/borrow/update` | `archive:borrow:update` | 修改(仅草稿/已驳回) | | DELETE | `/archive/borrow/delete?id` | `archive:borrow:delete` | 删除(仅草稿) | | GET | `/archive/borrow/get?id` | `archive:borrow:query` | 详情 | | GET | `/archive/borrow/page` | `archive:borrow:query` | 分页 | | GET | `/archive/borrow/overdue-page` | `archive:borrow:query` | **逾期未还**分页(借出中 且 归还期限已过) | | POST | `/archive/borrow/submit?id&processDefinitionKey` | `archive:borrow:submit` | 提交审批 | | GET | `/archive/borrow/approve-process-list` | `archive:borrow:query` | 审批流程定义列表(`{ id, key, name }`) | | POST | `/archive/borrow/return` | `archive:borrow:return` | 归还登记 | **`/page` 与 `/overdue-page` 请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNo` / `pageSize` | Integer | 是 | 分页 | | `no` | String | 否 | 借阅单号,模糊匹配 | | `documentNo` | String | 否 | 档案编号,模糊匹配 | | `documentTitle` | String | 否 | 档案标题,模糊匹配 | | `borrowerName` | String | 否 | 借阅人姓名,模糊匹配 | | `status` | Integer | 否 | 状态 | | `borrowDate` | LocalDate[] | 否 | 借阅日期区间,`yyyy-MM-dd` | | `returnDeadline` | LocalDate[] | 否 | 归还期限区间,`yyyy-MM-dd` | **新增/修改请求体:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | Long | 修改必填 | 编号 | | `documentId` | Long | 是 | 档案 ID(须为**在库**档案) | | `borrowerUserId` | Long | 否 | 借阅人用户 ID | | `borrowerName` | String | 是 | 借阅人姓名 | | `borrowerDept` | String | 否 | 借阅人部门 | | `borrowerPhone` | String | 否 | 联系电话 | | `borrowDate` | String(`yyyy-MM-dd`) | 是 | 借阅日期 | | `borrowPurpose` | String | 否 | 借阅目的 | | `returnDeadline` | String(`yyyy-MM-dd`) | 是 | 归还期限(不得早于借阅日期) | | `remark` | String | 否 | 备注 | **归还登记请求体:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | Long | 是 | 借阅单编号 | | `returnRemark` | String | 否 | 归还备注 | **响应新增只读字段:** `no`、`documentNo`、`documentTitle`、`approveTime`、`returnTime`、`returnConfirmUserId`、`returnConfirmUserName`、`rejectReason`、`rejectTime`、`overdue`、`overdueDays`、`processInstanceId`。 **响应示例:** ```json { "code": 0, "data": { "list": [ { "id": 1, "no": "JY20260929000001", "documentId": 1, "documentNo": "DA20260929000001", "documentTitle": "2026年度学会工作总结", "borrowerName": "张三", "borrowerDept": "秘书处", "borrowDate": "2026-09-20", "returnDeadline": "2026-10-05", "status": 20, "approveTime": "2026-09-20 10:12:33", "overdue": false, "overdueDays": 0 } ], "total": 1 }, "msg": "" } ``` ### 3.4 统计 `/archive/stat` | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | GET | `/archive/stat/overview` | `archive:stat:query` | 统计总览(存量 + 流转 + 三组分布) | **响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | `totalCount` | Long | 档案总数 | | `inStockCount` | Long | 在库档案数 | | `borrowedCount` | Long | 借出中**档案**数 | | `borrowingCount` | Long | 借出中**借阅单**数 | | `overdueCount` | Long | 逾期未还借阅单数 | | `byCategory` | Array | 分类分布,元素 `{ categoryId, name, total }` | | `bySecretLevel` | Array | 密级分布,元素 `{ value, name, total }` | | `byCarrierType` | Array | 载体分布,元素 `{ value, name, total }` | > `bySecretLevel` / `byCarrierType` 的 `name` 已由后端转成字典标签,前端**直接当图例名用**,无需再查字典。 ### 3.5 跨模块归档(本模块对外) | 方法 | 路径 | 权限 | 返回 | |------|------|------|------| | POST | `/research/application/archive?id` | `research:application:archive` | `CommonResult`(档案编号) | | POST | `/ums/member/archive?id` | `ums:member:archive` | `CommonResult`(档案编号) | | POST | `/finance/reimbursement/archive?id` | `finance:reimbursement:archive` | `CommonResult`(档案编号) | **响应示例:** ```json { "code": 0, "data": "DA20260929000001", "msg": "" } ``` --- ## 四、字段展示规则 | 字段 | 展示位置 | 规则 | |------|----------|------| | `no` | 档案列表、详情 | 后端生成,只读 | | `title` | 列表、详情、表单 | 列表可模糊搜索 | | `categoryName` | 列表 | 后端 join 分类冗余,列表直接展示名称而非 ID | | `secretLevel` | 列表、表单 | `CellDict` 取 `archive_secret_level`;**保密档案无权限时根本不返回** | | `carrierType` | 列表、表单 | `CellDict` 取 `archive_carrier_type` | | `sourceType` | 列表 | `CellDict` 取 `archive_source_type` | | `sourceNo` | 列表、详情 | 来源单号(立项编号/会员编号/报销单号) | | `storePlace` + 四位置字段 | 列表、详情 | 实体位置;电子载体时可为空 | | `archiveDate` | 列表、详情、表单 | `yyyy-MM-dd` | | `status`(档案) | 列表、表单(只读) | `CellDict` 取 `archive_document_status` | | `hasAttachment` | 列表 | 用 slot 渲染「有 / 无」Tag | | `overdue` / `overdueDays` | 借阅列表、借阅详情 | 用 slot 渲染,逾期时红色 Tag「逾期 N 天」 | | `status`(借阅) | 借阅列表、审批详情 | `CellDict` 取 `archive_borrow_status` | | `documentNo` / `documentTitle` | 借阅列表 | 归档时的快照,档案改名不影响历史借阅单 | | `approveTime` / `returnTime` | 借阅列表 | `formatDateTime` 格式化 | | `archiveDocumentId` | 课题 / 会员 / 财务列表 | 非空即显示「已归档」,并隐藏「归档」按钮 | --- ## 五、业务规则说明 | 场景 | 规则 | |------|------| | 删除分类 | 有子分类或有档案时拒绝,提示中回显分类名与具体原因 | | 修改分类父级 | 不能把分类设为自身或其子分类 | | 修改档案 | 档案处于「借出中」时拒绝修改 | | 删除档案 | 档案处于「借出中」或存在未归还借阅单时拒绝 | | 删除借阅单 | 仅「草稿」可删除 | | 提交借阅审批 | 仅「草稿」「已驳回」可提交;提交前校验同档案无「审批中/借出中」借阅单 | | 归还期限 | 不得早于借阅日期 | | 审批通过 | 借阅单 → 借出中并写 `approveTime`;**同时**把档案置为「借出中」 | | 审批驳回 | 借阅单 → 已驳回并写 `rejectReason` / `rejectTime`,改后可重新提交 | | 归还登记 | 仅「借出中」可登记;成功后档案回到「在库」 | | 逾期 | 实时计算,不落库、不发站内信、不建定时任务;仅列表 Tag 与「只看逾期」接口体现 | | 敏感档案 | 无 `archive:document:sensitive` 时,`/page`、`/get`、`/export-excel` 三个入口均过滤/拒绝 | | 归档幂等 | 同一来源单据重复归档会报错,提示中带来源单号与已生成档案编号 | | 财务归档 | 报销单 20→40(既有)**并**生成「保密」档案(本次新增),同事务 | --- ## 六、注意事项 1. **附件一律走 system 模块**:`POST /system/storage-blob/upload` → 拿 `blobId`;表单字段用全局 `FileUpload` 组件绑定 `blobIds`。**禁止**在档案表加 `file_url` 列、禁止新增上传接口、禁止用 infra 的 `FileApi`。 2. **`blobIds` 是全量替换语义**:编辑时若清空了全部附件,必须提交 `[]`;提交 `null` 后端会视为「不动附件」。打开编辑弹窗时应把 `attachmentList` 映射成 `{ uid, name, url, status: 'done', id }` 数组。 3. **日期区间一律 `valueFormat: 'YYYY-MM-DD'`**:后端 `LocalDate[]` 只接受 `yyyy-MM-dd`,带时分秒会解析失败。 4. **流程 key 不写死**:提交借阅审批前必须先调 `approve-process-list`;返回空数组时提示用户先去 BPM 发布流程。 5. **审批详情页路径是硬约定**:`src/views/archive/borrow/detail/approve/index.vue` 必须与 `bpm_process_definition_info.form_custom_view_path` 一致,且组件需接受 `props: { id: string }`(BPM 审批中心通过 `businessKey` 传入借阅单 ID)。改动路径会导致待办审批页打不开。 6. **档案「借出中」是并发敏感的**:审批通过时会条件更新档案状态,若档案已被另一条借阅单占用会返回业务异常;前端提示用户刷新后重试即可,不要静默忽略。 7. **不要在归档按钮里判断 `archived` 之外的字段**:按钮显隐条件建议用 `status === 已通过/已立项/正常 && !archiveDocumentId`,归档成功后刷新列表即可自动隐藏。 8. **超管与普通角色的可见性差异**:菜单与权限点默认只对超管可见,普通角色需先在「系统管理 → 角色」授权,否则整块 `/archive` 菜单不出现。