fix(web): restore V21 workspace Chinese copy

This commit is contained in:
Xiaoxia AI
2026-06-25 00:51:00 +08:00
parent c744ef0502
commit dd390c10e4
2 changed files with 86 additions and 86 deletions
+48 -48
View File
@@ -476,31 +476,31 @@ const ProjectAssets: React.FC = () => {
};
const assetPill = (asset: AssetItem) => {
if (asset.mime_type?.startsWith('audio')) return { text: '??', cls: 'audio' };
if (asset.mime_type?.startsWith('image')) return { text: '??', cls: 'video' };
if (String(asset.metadata?.review_status || '') === 'pending_review') return { text: '???', cls: 'warn' };
if (String(asset.metadata?.review_status || '') === 'rejected') return { text: '???', cls: 'bad' };
return { text: asset.status === 'ready' ? '??' : '??', cls: asset.status === 'ready' ? 'ok' : 'video' };
if (asset.mime_type?.startsWith('audio')) return { text: '音频', cls: 'audio' };
if (asset.mime_type?.startsWith('image')) return { text: '图片', cls: 'video' };
if (String(asset.metadata?.review_status || '') === 'pending_review') return { text: '待复核', cls: 'warn' };
if (String(asset.metadata?.review_status || '') === 'rejected') return { text: '已驳回', cls: 'bad' };
return { text: asset.status === 'ready' ? '就绪' : '导入中', cls: asset.status === 'ready' ? 'ok' : 'video' };
};
const assetMeta = (asset: AssetItem) => {
const duration = Number(asset.metadata?.duration_seconds || asset.metadata?.duration || 0);
const durationText = duration ? `${Math.round(duration)}s` : asset.mime_type?.startsWith('audio') ? '????' : '9:16';
const classification = String(asset.metadata?.classification || '???');
const review = String(asset.metadata?.review_status || asset.status || '???');
return `${durationText} ? ${classification} ? ${review}`;
const durationText = duration ? `${Math.round(duration)}s` : asset.mime_type?.startsWith('audio') ? '音频素材' : '9:16';
const classification = String(asset.metadata?.classification || '未分类');
const review = String(asset.metadata?.review_status || asset.status || '未复核');
return `${durationText} · ${classification} · ${review}`;
};
return (
<div className="xx-page-card">
<div className="xx-page-head">
<div>
<h1>???</h1>
<p>?????? 9:16 ??????????????????</p>
<h1>素材库</h1>
<p>上传和管理 9:16 视频、图片、配音素材,系统会自动分类并给出剪辑可用度。</p>
</div>
<div className="xx-action-row">
<Button icon={<PlusOutlined />} onClick={() => setCreateLibraryOpen(true)}>?????</Button>
<Button className="xx-btn-primary" type="primary" disabled={!workspaceId || !libraryId} onClick={() => document.getElementById('asset-upload-dropzone')?.scrollIntoView({ behavior: 'smooth', block: 'center' })}>????</Button>
<Button icon={<PlusOutlined />} onClick={() => setCreateLibraryOpen(true)}>新建素材库</Button>
<Button className="xx-btn-primary" type="primary" disabled={!workspaceId || !libraryId} onClick={() => document.getElementById('asset-upload-dropzone')?.scrollIntoView({ behavior: 'smooth', block: 'center' })}>上传素材</Button>
</div>
</div>
@@ -511,48 +511,48 @@ const ProjectAssets: React.FC = () => {
style={{ width: 160 }}
value={smartViewFilter}
options={[
{ label: '????', value: 'all' },
{ label: '????', value: 'recommended' },
{ label: '????', value: 'needs_attention' },
{ label: '?????', value: 'high_risk' },
{ label: '?????', value: 'unclassified' },
{ label: '????', value: 'recent' },
{ label: '?????', value: 'unused' },
{ label: '?????', value: 'used' },
{ label: '?????', value: 'pending_review' },
{ label: '????', value: 'voice' },
{ label: '全部素材', value: 'all' },
{ label: '推荐可用', value: 'recommended' },
{ label: '需要处理', value: 'needs_attention' },
{ label: '高风险素材', value: 'high_risk' },
{ label: '未分类素材', value: 'unclassified' },
{ label: '最近上传', value: 'recent' },
{ label: '未使用素材', value: 'unused' },
{ label: '已使用素材', value: 'used' },
{ label: '待复核素材', value: 'pending_review' },
{ label: '配音素材', value: 'voice' },
]}
onChange={setSmartViewFilter}
/>
<Select style={{ width: 170 }} value={batchMode} options={batchModeOptions} onChange={(value) => setBatchMode(value)} />
<Select style={{ width: 240 }} placeholder="?????" value={libraryId || undefined} options={libraryOptions} onChange={setLibraryId} loading={librariesQuery.isLoading} />
<Button icon={<TagsOutlined />} disabled={!selectedRowKeys.length || batchProgress.running} onClick={handleBatchClassification}>???? ({selectedRowKeys.length})</Button>
<Button icon={<ReloadOutlined />} onClick={() => { librariesQuery.refetch(); assetsQuery.refetch(); diagnosisQuery.refetch(); }}>??</Button>
<Select style={{ width: 240 }} placeholder="选择素材库" value={libraryId || undefined} options={libraryOptions} onChange={setLibraryId} loading={librariesQuery.isLoading} />
<Button icon={<TagsOutlined />} disabled={!selectedRowKeys.length || batchProgress.running} onClick={handleBatchClassification}>批量分类 ({selectedRowKeys.length})</Button>
<Button icon={<ReloadOutlined />} onClick={() => { librariesQuery.refetch(); assetsQuery.refetch(); diagnosisQuery.refetch(); }}>刷新</Button>
</div>
{!workspaceId && <Alert style={{ marginBottom: 16 }} type="warning" showIcon message="???????????" description="???????????????????????????????????" />}
{librariesQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="???????" />}
{assetsQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{ingestJobQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{classificationJobQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{ingestJobQuery.data && <Alert style={{ marginBottom: 16 }} type={ingestJobQuery.data.status === 'failed' ? 'error' : 'info'} message={`???????${ingestJobQuery.data.status}`} description={ingestJobQuery.data.error_message || `?? ID: ${ingestJobQuery.data.id}`} />}
{classificationJobQuery.data && <Alert style={{ marginBottom: 16 }} type={classificationJobQuery.data.status === 'failed' ? 'error' : 'success'} message={`???????${classificationJobQuery.data.status}`} description={classificationJobQuery.data.error_message || `?????${classificationJobQuery.data.classification || '???'}`} />}
{!workspaceId && <Alert style={{ marginBottom: 16 }} type="warning" showIcon message="缺少工作空间信息" description="请从项目详情页进入素材库,系统需要确认当前工作空间。" />}
{librariesQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="素材库加载失败" />}
{assetsQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="素材加载失败" />}
{ingestJobQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="导入任务加载失败" />}
{classificationJobQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="分类任务加载失败" />}
{ingestJobQuery.data && <Alert style={{ marginBottom: 16 }} type={ingestJobQuery.data.status === 'failed' ? 'error' : 'info'} message={`导入任务:${ingestJobQuery.data.status}`} description={ingestJobQuery.data.error_message || `任务 ID: ${ingestJobQuery.data.id}`} />}
{classificationJobQuery.data && <Alert style={{ marginBottom: 16 }} type={classificationJobQuery.data.status === 'failed' ? 'error' : 'success'} message={`分类任务:${classificationJobQuery.data.status}`} description={classificationJobQuery.data.error_message || `分类结果:${classificationJobQuery.data.classification || '未返回'}`} />}
{batchProgress.total > 0 && (
<div className="xx-section-block">
<div className="xx-section-title"><h3>??????</h3></div>
<div className="xx-section-title"><h3>批量分类进度</h3></div>
<Progress percent={batchPercent} status={batchProgress.running ? 'active' : batchFailedIds.length > 0 ? 'exception' : 'success'} />
<Space size="large" wrap><span>???{batchProgress.total}</span><span>????{batchProgress.submitted}</span><span>????{batchProgress.completed}</span><span>???{batchProgress.failed}</span><span>???{batchProgress.skipped}</span></Space>
<Space size="large" wrap><span>总数 {batchProgress.total}</span><span>已提交 {batchProgress.submitted}</span><span>已完成 {batchProgress.completed}</span><span>失败 {batchProgress.failed}</span><span>跳过 {batchProgress.skipped}</span></Space>
</div>
)}
{diagnosisQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{diagnosisQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="素材诊断加载失败" />}
{diagnosisQuery.data && (
<div className="xx-diagnosis-strip">
<div className="xx-kpi-card"><b>{diagnosisQuery.data.readiness_score}</b><span>{diagnosisQuery.data.readiness_label}</span></div>
<Space size={[8, 8]} wrap>
<Tag color="blue">??? {diagnosisQuery.data.total_assets}</Tag><Tag color="green">Ready {diagnosisQuery.data.ready_assets}</Tag><Tag color="purple">?? {diagnosisQuery.data.video_assets}</Tag><Tag color="orange">?? {diagnosisQuery.data.image_assets}</Tag><Tag color="cyan">?? {diagnosisQuery.data.voice_assets}</Tag><Tag color="gold">????? {diagnosisQuery.data.estimated_video_count}</Tag>
{diagnosisQuery.data.smart_views.map((item) => <Tag key={item.key} color={smartViewFilter === item.key ? 'blue' : undefined} style={{ cursor: 'pointer' }} onClick={() => setSmartViewFilter(item.key)}>{item.label}?{item.count}</Tag>)}
<Tag color="blue">总数 {diagnosisQuery.data.total_assets}</Tag><Tag color="green">Ready {diagnosisQuery.data.ready_assets}</Tag><Tag color="purple">视频 {diagnosisQuery.data.video_assets}</Tag><Tag color="orange">图片 {diagnosisQuery.data.image_assets}</Tag><Tag color="cyan">配音 {diagnosisQuery.data.voice_assets}</Tag><Tag color="gold">预计成片 {diagnosisQuery.data.estimated_video_count}</Tag>
{diagnosisQuery.data.smart_views.map((item) => <Tag key={item.key} color={smartViewFilter === item.key ? 'blue' : undefined} style={{ cursor: 'pointer' }} onClick={() => setSmartViewFilter(item.key)}>{item.label}:{item.count}</Tag>)}
</Space>
</div>
)}
@@ -560,8 +560,8 @@ const ProjectAssets: React.FC = () => {
<div id="asset-upload-dropzone">
<Dragger name="file" multiple customRequest={customUpload} showUploadList={false} disabled={!workspaceId || !libraryId} style={{ marginBottom: 24 }}>
<p className="ant-upload-drag-icon"><InboxOutlined /></p>
<p className="ant-upload-text">????????????????</p>
<p className="ant-upload-hint">?????????????????????? {uploading ? `(${activeUploadCount} ??????)` : '????????'}</p>
<p className="ant-upload-text">点击或拖拽素材到这里上传</p>
<p className="ant-upload-hint">支持视频、图片、配音素材批量上传 {uploading ? `(${activeUploadCount} 个文件上传中)` : '上传后自动导入并分类'}</p>
</Dragger>
</div>
@@ -575,25 +575,25 @@ const ProjectAssets: React.FC = () => {
const isAutoClassifying = asset.metadata?.auto_classification === 'queued';
return (
<article className={`xx-vertical-card ${selected ? 'selected' : ''}`} key={asset.id} onClick={() => setSelectedRowKeys((keys) => selected ? keys.filter((key) => key !== asset.id) : [...keys, asset.id])}>
<div className={`xx-vertical-thumb ${assetTone(asset, index)}`}><button className="xx-mini-play" type="button">?</button></div>
<div className={`xx-vertical-thumb ${assetTone(asset, index)}`}><button className="xx-mini-play" type="button">▶</button></div>
<div className="xx-vertical-row"><b title={asset.name}>{asset.name}</b><span className={`xx-pill ${pill.cls}`}>{pill.text}</span></div>
<p className="xx-vertical-meta">{assetMeta(asset)}</p>
<div style={{ marginTop: 8 }}>{renderClassification(asset)}</div>
<div className="xx-action-row" style={{ marginTop: 10 }} onClick={(event) => event.stopPropagation()}>
<Button size="small" icon={<TagsOutlined />} loading={isCurrentClassifying && classificationMutation.isPending} disabled={isAutoClassifying || (!!classifyingAssetId && classifyingAssetId !== asset.id) || batchProgress.running} onClick={() => { setClassifyingAssetId(asset.id); classificationMutation.mutate({ workspace_id: asset.workspace_id, project_id: asset.project_id, asset_id: asset.id }); }}>{isAutoClassifying ? '???' : hasClassification ? '???' : '??'}</Button>
<Button size="small" disabled={reviewMutation.isPending} onClick={() => reviewMutation.mutate({ assetId: asset.id, reviewStatus: 'approved' })}>??</Button>
<Button size="small" danger disabled={reviewMutation.isPending} onClick={() => reviewMutation.mutate({ assetId: asset.id, reviewStatus: 'rejected' })}>??</Button>
<Button size="small" icon={<TagsOutlined />} loading={isCurrentClassifying && classificationMutation.isPending} disabled={isAutoClassifying || (!!classifyingAssetId && classifyingAssetId !== asset.id) || batchProgress.running} onClick={() => { setClassifyingAssetId(asset.id); classificationMutation.mutate({ workspace_id: asset.workspace_id, project_id: asset.project_id, asset_id: asset.id }); }}>{isAutoClassifying ? '排队中' : hasClassification ? '重分类' : '分类'}</Button>
<Button size="small" disabled={reviewMutation.isPending} onClick={() => reviewMutation.mutate({ assetId: asset.id, reviewStatus: 'approved' })}>通过</Button>
<Button size="small" danger disabled={reviewMutation.isPending} onClick={() => reviewMutation.mutate({ assetId: asset.id, reviewStatus: 'rejected' })}>驳回</Button>
</div>
</article>
);
})}
</div>
) : <div className="xx-empty-state"><div className="xx-empty-icon">+</div><b>?????</b><span>??????????? 9:16 ???</span></div>}
) : <div className="xx-empty-state"><div className="xx-empty-icon">+</div><b>暂无素材</b><span>上传素材后会显示为 9:16 卡片。</span></div>}
<Modal title="?????" open={createLibraryOpen} onCancel={() => setCreateLibraryOpen(false)} onOk={() => form.submit()} confirmLoading={createLibraryMutation.isPending} okButtonProps={{ disabled: !workspaceId }}>
<Modal title="新建素材库" open={createLibraryOpen} onCancel={() => setCreateLibraryOpen(false)} onOk={() => form.submit()} confirmLoading={createLibraryMutation.isPending} okButtonProps={{ disabled: !workspaceId }}>
<Form form={form} layout="vertical" onFinish={(values) => createLibraryMutation.mutate({ workspace_id: workspaceId, project_id: projectId, name: values.name, kind: values.kind })}>
<Form.Item label="?????" name="name" rules={[{ required: true, message: '????????' }]}><Input placeholder="????????" /></Form.Item>
<Form.Item label="?????" name="kind" initialValue="video" rules={[{ required: true }]}><Select options={[{ label: '???', value: 'video' }, { label: '???', value: 'voice' }, { label: '???', value: 'image' }]} /></Form.Item>
<Form.Item label="素材库名称" name="name" rules={[{ required: true, message: '请输入素材库名称' }]}><Input placeholder="例如:产品口播素材" /></Form.Item>
<Form.Item label="素材类型" name="kind" initialValue="video" rules={[{ required: true }]}><Select options={[{ label: '视频', value: 'video' }, { label: '配音', value: 'voice' }, { label: '图片', value: 'image' }]} /></Form.Item>
</Form>
</Modal>
</div>
@@ -180,11 +180,11 @@ const ProjectGeneration: React.FC = () => {
<div className="xx-page-card">
<div className="xx-page-head">
<div>
<h1>????</h1>
<p>????????????????????????????</p>
<h1>剪辑参数</h1>
<p>选择素材、标题、模板和配音,先生成剪辑计划,再确认发起视频生成。</p>
</div>
<Button className="xx-btn-primary" type="primary" onClick={() => form.submit()} loading={generationMutation.isPending} disabled={!canSubmitGeneration || !editPlan}>
???????
确认计划并生成
</Button>
</div>
@@ -193,31 +193,31 @@ const ProjectGeneration: React.FC = () => {
style={{ marginBottom: 16 }}
type="error"
showIcon
message="?????????"
description="?????????????????????????????"
message="缺少工作空间信息"
description="请从项目详情页进入视频剪辑,系统需要确认当前工作空间和用户权限。"
/>
)}
{librariesQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="???????" />}
{taskQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{resultsQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" />}
{librariesQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="素材库加载失败" />}
{taskQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="生成任务加载失败" />}
{resultsQuery.isError && <Alert style={{ marginBottom: 16 }} type="error" showIcon message="生成结果加载失败" />}
{diagnosisQuery.data && (
<Alert
style={{ marginBottom: 16 }}
type={hasCriticalGap ? 'error' : diagnosisQuery.data.gaps.length ? 'warning' : 'success'}
showIcon
message={`??????${diagnosisQuery.data.readiness_score} / ${diagnosisQuery.data.readiness_label}`}
message={`素材就绪度:${diagnosisQuery.data.readiness_score} / ${diagnosisQuery.data.readiness_label}`}
description={
diagnosisQuery.data.gaps.length
? diagnosisQuery.data.gaps.map((gap) => `${gap.message}?${gap.recommendation}`).join('?')
: '????????????????'
? diagnosisQuery.data.gaps.map((gap) => `${gap.message}:${gap.recommendation}`).join(';')
: '素材状态良好,可以继续生成剪辑计划。'
}
/>
)}
{diagnosisQuery.isError && (
<Alert style={{ marginBottom: 16 }} type="error" showIcon message="????????" description="????????????????????" />
<Alert style={{ marginBottom: 16 }} type="error" showIcon message="素材诊断加载失败" description="请稍后刷新页面重试。" />
)}
<div className="xx-generation-layout">
@@ -238,29 +238,29 @@ const ProjectGeneration: React.FC = () => {
}
>
<div className="xx-section-block">
<div className="xx-section-title"><h3>????</h3><span className="xx-hint">????+B-roll</span></div>
<Form.Item label="???" name="asset_library_id" rules={[{ required: true, message: '??????' }]}>
<Select options={assetLibraryOptions} placeholder="????/?????" loading={librariesQuery.isLoading} />
<div className="xx-section-title"><h3>素材与画面</h3><span className="xx-hint">主素材 + B-roll</span></div>
<Form.Item label="素材库" name="asset_library_id" rules={[{ required: true, message: '请选择素材库' }]}>
<Select options={assetLibraryOptions} placeholder="选择视频/图片素材库" loading={librariesQuery.isLoading} />
</Form.Item>
</div>
<div className="xx-section-block">
<div className="xx-section-title"><h3>?????</h3></div>
<Form.Item label="????" name="title_id">
<Select allowClear options={titleOptions} placeholder="?????????" loading={titlesQuery.isLoading} />
<div className="xx-section-title"><h3>标题与模板</h3></div>
<Form.Item label="标题" name="title_id">
<Select allowClear options={titleOptions} placeholder="选择标题库中的标题" loading={titlesQuery.isLoading} />
</Form.Item>
<Form.Item label="????" name="template_id">
<Select allowClear options={templateOptions} placeholder="??????" loading={templatesQuery.isLoading} />
<Form.Item label="剪辑模板" name="template_id">
<Select allowClear options={templateOptions} placeholder="选择剪辑模板" loading={templatesQuery.isLoading} />
</Form.Item>
</div>
<div className="xx-section-block">
<div className="xx-section-title"><h3>??</h3></div>
<Form.Item label="???" name="voice_library_id">
<Select allowClear options={voiceLibraryOptions} placeholder="?????" loading={librariesQuery.isLoading} />
<div className="xx-section-title"><h3>配音与策略</h3></div>
<Form.Item label="配音库" name="voice_library_id">
<Select allowClear options={voiceLibraryOptions} placeholder="选择配音库" loading={librariesQuery.isLoading} />
</Form.Item>
<Form.Item label="?? ID" name="strategy_id">
<Input placeholder="????? default-strategy" />
<Form.Item label="策略 ID" name="strategy_id">
<Input placeholder="可选,例如 default-strategy" />
</Form.Item>
</div>
</Form>
@@ -268,10 +268,10 @@ const ProjectGeneration: React.FC = () => {
<aside className="xx-plan-panel">
<div className="xx-preview-phone">
<span className="xx-play">?</span>
<div>3 ???????30 ?????</div>
<span className="xx-play">▶</span>
<div>3 段素材 · 约 30 秒成片</div>
</div>
<h3>??????</h3>
<h3>剪辑计划预览</h3>
{editPlan ? (
<>
<Typography.Paragraph>{editPlan.summary}</Typography.Paragraph>
@@ -280,7 +280,7 @@ const ProjectGeneration: React.FC = () => {
dataSource={editPlan.clips}
renderItem={(clip) => (
<List.Item>
<Typography.Text>{clip.sequence}. {clip.asset_name} / {Math.round(clip.duration)}? / {clip.reason}</Typography.Text>
<Typography.Text>{clip.sequence}. {clip.asset_name} / {Math.round(clip.duration)}秒 / {clip.reason}</Typography.Text>
</List.Item>
)}
/>
@@ -288,8 +288,8 @@ const ProjectGeneration: React.FC = () => {
) : (
<div className="xx-empty-state" style={{ padding: 18 }}>
<div className="xx-empty-icon">AI</div>
<b>???????</b>
<span>?????????????????</span>
<b>还没有剪辑计划</b>
<span>先选择素材库和模板,生成计划后再确认生成视频。</span>
</div>
)}
<div className="xx-action-row" style={{ marginTop: 16 }}>
@@ -297,7 +297,7 @@ const ProjectGeneration: React.FC = () => {
onClick={() => {
const values = form.getFieldsValue();
if (!values.asset_library_id) {
message.warning('???????');
message.warning('请先选择素材库');
return;
}
editPlanMutation.mutate({
@@ -313,10 +313,10 @@ const ProjectGeneration: React.FC = () => {
loading={editPlanMutation.isPending}
disabled={!canSubmitGeneration}
>
??????
重新生成计划
</Button>
<Button className="xx-btn-primary" type="primary" onClick={() => form.submit()} loading={generationMutation.isPending} disabled={!canSubmitGeneration || !editPlan}>
???????
确认计划并生成
</Button>
</div>
</aside>
@@ -326,19 +326,19 @@ const ProjectGeneration: React.FC = () => {
<Alert
style={{ marginTop: 16 }}
type={taskQuery.data.status === 'failed' ? 'error' : taskQuery.data.status === 'completed' ? 'success' : 'info'}
message={`?????${humanizeGenerationStatus(taskQuery.data.status)}`}
description={taskQuery.data.status === 'failed' ? failedReason : `?? ${taskQuery.data.progress}% / ??? ${taskQuery.data.result_count}`}
message={`生成状态:${humanizeGenerationStatus(taskQuery.data.status)}`}
description={taskQuery.data.status === 'failed' ? failedReason : `进度 ${taskQuery.data.progress}% / 结果 ${taskQuery.data.result_count}`}
/>
)}
{resultsQuery.data?.length ? (
<div className="xx-section-block" style={{ marginTop: 16 }}>
<div className="xx-section-title"><h3>??????</h3></div>
<div className="xx-section-title"><h3>生成结果</h3></div>
<Space direction="vertical">
{resultsQuery.data.map((item) => (
<Space key={item.id}>
<Typography.Text>{item.name}</Typography.Text>
<Button type="link" onClick={() => openSignedDownload(item.id)}>??/??</Button>
<Button type="link" onClick={() => openSignedDownload(item.id)}>预览/下载</Button>
</Space>
))}
</Space>