diff --git a/src/components/adminRdmSections.tsx b/src/components/adminRdmSections.tsx index b52d57a..c8192ff 100644 --- a/src/components/adminRdmSections.tsx +++ b/src/components/adminRdmSections.tsx @@ -144,6 +144,18 @@ interface DutyCatalogDoc { assigned: boolean; author?: string; reviewers?: string[]; approver?: string; activeRound?: number } +interface DocsTemplateRow { + templateId: string; version: string; title: string; docTypes: string[]; stage?: string +} + +/** rdm.docs.instantiate 参数:docType 取首个适用类型;空标题交由后端取模板标题。 */ +export function docsInstantiateParams(tpl: DocsTemplateRow, title: string): Record { + return { + docType: tpl.docTypes[0], templateId: tpl.templateId, version: tpl.version, + ...(title.trim() ? { title: title.trim() } : {}), + } +} + interface DutyGetOut { docId: string; title: string; assigned: boolean roles: { author: string; reviewers: string[]; approver: string } @@ -186,6 +198,9 @@ export function RdmDutiesSection({ project }: { project: Project }) { const [err, setErr] = useState('') const [notice, setNotice] = useState('') const [busy, setBusy] = useState(false) + const [tpls, setTpls] = useState(null) + const [tplSel, setTplSel] = useState('') + const [newTitle, setNewTitle] = useState('') const cmd = (id: string, params: Record) => api.rdmProjectCommand(project, id, params) const memberPage = useMemberPickerPage(members) @@ -254,6 +269,9 @@ export function RdmDutiesSection({ project }: { project: Project }) { cmd<{ members: { owner: string; role: string }[] }>('rdm.project.member.list', { projectId }) .then(out => { if (currentProject(epoch)) setMembers(out.members ?? []) }) .catch(() => { if (currentProject(epoch)) setMembers([]) }) + api.rdmCommand('rdm.docs.templates') + .then(rows => { if (currentProject(epoch)) setTpls(rows ?? []) }) + .catch(() => { if (currentProject(epoch)) setTpls([]) }) return () => { requests.current.epoch++; requests.current.selection++; requests.current.status++ requests.current.catalog++; requests.current.action++; requests.current.selected = '' @@ -297,8 +315,39 @@ export function RdmDutiesSection({ project }: { project: Project }) { cmd('rdm.review.assign.doc.batch', { projectId, docs: [...batchSel].map(d => ({ docId: d, author: batchAuthor[0], reviewers: batchReviewers, approver: batchApprover[0] })) })) + /** U4/UI:按已发布 DHF 模板整份生成文档(章节骨架自动播种),随后选中新文档。 */ + const instantiate = () => { + const tpl = (tpls ?? []).find(r => `${r.templateId}@${r.version}` === tplSel) + if (!tpl) return + const title = newTitle.trim() + return run(`已按模板生成「${title || tpl.title}」`, async () => { + const res = await cmd<{ docId: string }>('rdm.docs.instantiate', docsInstantiateParams(tpl, title)) + await refreshCatalog() + await selectDoc(res.docId, true) + }) + } + const tpl = (tpls ?? []).find(r => `${r.templateId}@${r.version}` === tplSel) + return (
+

按模板生成文档

+
+
+ + setNewTitle(e.target.value)} /> + +
+

按已发布 DHF 模板整份生成:章节骨架自动播种;生成后在下方清单选中文档分配三权并走整文审签。

+

文档清单(职责分配对象为整份文档;搜索/浏览同一目录)

diff --git a/test/docs-instantiate-params.test.ts b/test/docs-instantiate-params.test.ts new file mode 100644 index 0000000..29b217c --- /dev/null +++ b/test/docs-instantiate-params.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { docsInstantiateParams } from '../src/components/adminRdmSections' + +const tpl = { + templateId: 'dhf.2750fbaf1f67d10c', version: '1.0.0', title: '项目建议书', + docTypes: ['rd_proposal'], stage: '0. 商务阶段', +} + +describe('docsInstantiateParams', () => { + it('carries docType/templateId/version and trims the title', () => { + expect(docsInstantiateParams(tpl, ' U5 试点项目建议书 ')).toEqual({ + docType: 'rd_proposal', templateId: 'dhf.2750fbaf1f67d10c', + version: '1.0.0', title: 'U5 试点项目建议书', + }) + }) + it('omits blank titles so the backend falls back to the template title', () => { + const p = docsInstantiateParams(tpl, ' ') + expect(Object.hasOwn(p, 'title')).toBe(false) + expect(p).toEqual({ docType: 'rd_proposal', templateId: 'dhf.2750fbaf1f67d10c', version: '1.0.0' }) + }) +})
文档类型 / 阶段当前职责状态