refactor(data): 收口关系表运行时数据源
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled

- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读
- 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移
- 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
2026-07-09 14:59:49 +08:00
parent 1e6fb0c7aa
commit 2e595c7e72
98 changed files with 2938 additions and 1441 deletions

View File

@@ -25,13 +25,30 @@ const KIND_LABEL: Record<GovernanceKind, string> = {
requirement_source: '需求来源',
};
const TASK_CATEGORY_GROUPS = [
{ value: 'development', label: '开发' },
{ value: 'testing', label: '测试' },
{ value: 'implementation', label: '实施' },
{ value: 'other', label: '其他' },
];
const SOURCE_TYPE_GROUPS = [
{ value: 'customer', label: '客户' },
{ value: 'internal', label: '内部' },
{ value: 'operation', label: '运营' },
{ value: 'aftersale', label: '售后' },
{ value: 'market', label: '市场' },
{ value: 'competitor', label: '竞品' },
{ value: 'management', label: '管理层' },
];
function GovernancePageInner() {
const user = useAuthStore((s) => s.user);
const role = useMemberStore((s) => s.roles.find((item) => item.id === user?.roleId));
const [kind, setKind] = useState<GovernanceKind>('task_category');
const [items, setItems] = useState<GovernanceItem[]>([]);
const [name, setName] = useState('');
const [group, setGroup] = useState('other');
const [group, setGroup] = useState(defaultGroupForKind('task_category'));
const [exportText, setExportText] = useState('');
const actorId = user?.id ?? '';
const permissions = role?.permissions ?? [];
@@ -45,9 +62,13 @@ function GovernancePageInner() {
void reload().catch(() => setItems([]));
}, [kind]);
useEffect(() => {
setGroup(defaultGroupForKind(kind));
}, [kind]);
const create = async () => {
if (!actorId || !name.trim()) return;
await api.post('/governance/dictionaries', { actorId, permissions, kind, name: name.trim(), group });
await api.post('/governance/dictionaries', { actorId, permissions, kind, name: name.trim(), group: groupForPayload(kind, group) });
setName('');
await reload();
};
@@ -99,9 +120,15 @@ function GovernancePageInner() {
</div>
<section className="rounded-lg border border-[var(--line)] bg-[var(--bg-card)]">
<div className="grid grid-cols-[1fr_140px_auto] gap-2 border-b border-[var(--line)] p-3">
<div className={`grid gap-2 border-b border-[var(--line)] p-3 ${groupOptionsForKind(kind) ? 'grid-cols-[1fr_140px_auto]' : 'grid-cols-[1fr_auto]'}`}>
<input value={name} onChange={(event) => setName(event.target.value)} placeholder={`新增${KIND_LABEL[kind]}`} className="h-8 rounded-md border border-[var(--line)] bg-[var(--bg)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
<input value={group} onChange={(event) => setGroup(event.target.value)} placeholder="分组" className="h-8 rounded-md border border-[var(--line)] bg-[var(--bg)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
{groupOptionsForKind(kind) && (
<select value={group} onChange={(event) => setGroup(event.target.value)} className="h-8 rounded-md border border-[var(--line)] bg-[var(--bg)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none">
{groupOptionsForKind(kind)?.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
)}
<button onClick={create} disabled={!name.trim()} className="inline-flex h-8 items-center gap-1 rounded-md bg-[var(--accent)] px-3 text-[12px] font-medium text-white disabled:opacity-50">
<Plus className="h-3.5 w-3.5" />
</button>
@@ -143,6 +170,22 @@ function GovernancePageInner() {
);
}
function groupOptionsForKind(kind: GovernanceKind) {
if (kind === 'task_category') return TASK_CATEGORY_GROUPS;
if (kind === 'requirement_source') return SOURCE_TYPE_GROUPS;
return null;
}
function defaultGroupForKind(kind: GovernanceKind) {
if (kind === 'task_category') return 'development';
if (kind === 'requirement_source') return 'customer';
return '';
}
function groupForPayload(kind: GovernanceKind, group: string) {
return groupOptionsForKind(kind) ? group : undefined;
}
export default function GovernancePage() {
return (
<RouteGuard permission="governance:manage">