吸收数字员工产物生命周期治理
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
|
||||
import { Download, Eye, FolderOpen } from 'lucide-react';
|
||||
import { Download, Eye, FolderOpen, History } from 'lucide-react';
|
||||
import GlassCard from '@/components/digital/GlassCard';
|
||||
import CardTitleBar from '@/components/digital/CardTitleBar';
|
||||
import {
|
||||
getDigitalEmployeeWorkday,
|
||||
postDigitalEmployeeWorkdayAction,
|
||||
accessArtifact,
|
||||
getArtifactDetail,
|
||||
getArtifactVersions,
|
||||
setArtifactRetention,
|
||||
} from '@/lib/api';
|
||||
import type {
|
||||
DigitalEmployeeDailyReport,
|
||||
@@ -185,6 +188,7 @@ function ArtifactSourceList({ artifacts }: { artifacts: DigitalEmployeeReportArt
|
||||
const [busyKey, setBusyKey] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
const [preview, setPreview] = useState<{ title: string; text: string } | null>(null);
|
||||
const [detail, setDetail] = useState<{ artifact: DigitalEmployeeReportArtifactSource; item: any; versions: any[] } | null>(null);
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
return <p className="de-empty-text" style={{ padding: '18px 0 6px' }}>运行产物出现后,这里会展示文件、输出和来源记录。</p>;
|
||||
@@ -217,6 +221,47 @@ function ArtifactSourceList({ artifacts }: { artifacts: DigitalEmployeeReportArt
|
||||
}
|
||||
};
|
||||
|
||||
const loadDetail = async (artifact: DigitalEmployeeReportArtifactSource) => {
|
||||
if (!artifact.id || busyKey) return;
|
||||
setBusyKey(`${artifact.id}:detail`);
|
||||
setNotice(null);
|
||||
try {
|
||||
const [item, versions] = await Promise.all([
|
||||
getArtifactDetail(artifact.id),
|
||||
getArtifactVersions(artifact.id),
|
||||
]);
|
||||
setDetail({ artifact, item, versions });
|
||||
} catch (error) {
|
||||
setNotice(error instanceof Error ? error.message : '读取产物详情失败');
|
||||
} finally {
|
||||
setBusyKey(null);
|
||||
}
|
||||
};
|
||||
|
||||
const runRetention = async (action: 'mark_keep' | 'mark_expire' | 'mark_review' | 'clear_retention') => {
|
||||
const artifact = detail?.artifact;
|
||||
if (!artifact?.id || busyKey) return;
|
||||
setBusyKey(`${artifact.id}:${action}`);
|
||||
setNotice(null);
|
||||
try {
|
||||
const result = await setArtifactRetention(artifact.id, action, { reason: 'digital_employee_artifact_governance' });
|
||||
if (!result?.ok) {
|
||||
setNotice(result?.error || '保留策略写入失败');
|
||||
return;
|
||||
}
|
||||
const [item, versions] = await Promise.all([
|
||||
getArtifactDetail(artifact.id),
|
||||
getArtifactVersions(artifact.id),
|
||||
]);
|
||||
setDetail({ artifact: { ...artifact, retention_status: String(item?.retention_status || artifact.retention_status || 'unmanaged') }, item, versions });
|
||||
setNotice('保留策略已记录');
|
||||
} catch (error) {
|
||||
setNotice(error instanceof Error ? error.message : '保留策略写入失败');
|
||||
} finally {
|
||||
setBusyKey(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="de-artifact-source-list" aria-label="日报产物来源">
|
||||
@@ -232,6 +277,13 @@ function ArtifactSourceList({ artifacts }: { artifacts: DigitalEmployeeReportArt
|
||||
<button type="button" className="de-icon-btn" disabled={!previewable || busyKey !== null} onClick={() => runAccess(artifact, 'preview')} title="预览" aria-label="预览"><Eye size={15} /></button>
|
||||
<button type="button" className="de-icon-btn" disabled={!downloadable || busyKey !== null} onClick={() => runAccess(artifact, 'download')} title="下载" aria-label="下载"><Download size={15} /></button>
|
||||
<button type="button" className="de-icon-btn" disabled={!openable || busyKey !== null} onClick={() => runAccess(artifact, 'open_location')} title="位置" aria-label="位置"><FolderOpen size={15} /></button>
|
||||
<button type="button" className="de-icon-btn" disabled={busyKey !== null} onClick={() => loadDetail(artifact)} title="版本" aria-label="版本"><History size={15} /></button>
|
||||
</div>
|
||||
<div className="de-artifact-source-meta">
|
||||
<span>{artifact.version || 'local-v1'}</span>
|
||||
<span>{artifact.retention_status || 'unmanaged'}</span>
|
||||
{artifact.delivery_status ? <span>{artifact.delivery_status}</span> : null}
|
||||
{artifact.access?.last_access_status ? <span>{artifact.access.last_access_status}</span> : null}
|
||||
</div>
|
||||
{artifact.uri ? <code>{artifact.uri}</code> : <em>{artifact.kind || 'artifact'}</em>}
|
||||
</div>
|
||||
@@ -245,6 +297,26 @@ function ArtifactSourceList({ artifacts }: { artifacts: DigitalEmployeeReportArt
|
||||
<pre>{preview.text}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
{detail ? (
|
||||
<div className="de-artifact-preview">
|
||||
<div><strong>{detail.artifact.name}</strong><button type="button" className="de-btn-secondary" onClick={() => setDetail(null)}>关闭</button></div>
|
||||
<div className="de-artifact-retention-actions">
|
||||
<button type="button" className="de-btn-secondary" disabled={busyKey !== null} onClick={() => runRetention('mark_keep')}>保留</button>
|
||||
<button type="button" className="de-btn-secondary" disabled={busyKey !== null} onClick={() => runRetention('mark_expire')}>到期</button>
|
||||
<button type="button" className="de-btn-secondary" disabled={busyKey !== null} onClick={() => runRetention('mark_review')}>复核</button>
|
||||
<button type="button" className="de-btn-secondary" disabled={busyKey !== null} onClick={() => runRetention('clear_retention')}>清除</button>
|
||||
</div>
|
||||
<div className="de-artifact-version-list">
|
||||
{(detail.versions.length > 0 ? detail.versions : [detail.item]).filter(Boolean).map((item) => (
|
||||
<div key={String(item.artifact_id || item.version_key || item.created_at)}>
|
||||
<strong>{String(item.version || 'local-v1')}</strong>
|
||||
<span>{String(item.delivery_summary?.delivery_status || item.source_label || '未记录')}</span>
|
||||
<em>{String(item.created_at || '')}</em>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user