style: 图形化映射来源支持折叠

This commit is contained in:
2026-06-25 05:28:10 +08:00
parent a91b2ccde0
commit 7edad317ad
3 changed files with 68 additions and 27 deletions
+5
View File
@@ -814,3 +814,8 @@
- CellData「规则设置」弹窗不再整体滚动;高级匹配、目标表、主键字段和主键表达式固定显示,图形化模式下仅来源列表滚动。
- “添加来源”按钮固定在来源列表下方,始终可见;弹窗底部保存按钮也保持可见。
- 验证:`frontend` `npm run build` 通过;构建产物已清理。
## 2026-06-25:图形化来源支持折叠
- CellData 图形化映射中每个来源卡片支持展开/收起,来源头展示目录与 CSV 前缀摘要;“添加来源”按钮移动到映射规则标题区右侧。
- 验证:`frontend` `npm run build` 通过;构建产物已清理。
+40 -27
View File
@@ -719,6 +719,7 @@
<div class="cell-data-section-title">
<strong>映射规则</strong>
<n-space size="small">
<n-button v-if="cellDataMappingMode === 'form'" size="small" @click="addCellDataMappingSource">添加来源</n-button>
<n-radio-group v-model:value="cellDataMappingMode" size="small" @update:value="switchCellDataMappingMode">
<n-radio-button value="form">图形化</n-radio-button>
<n-radio-button value="json">JSON</n-radio-button>
@@ -743,40 +744,43 @@
</div>
<div class="cell-data-source-list">
<div v-for="(source, sourceIndex) in cellDataMappingForm.sources" :key="sourceIndex" class="cell-data-source-card">
<div class="cell-data-source-header">
<strong>来源 {{ sourceIndex + 1 }}</strong>
<n-button quaternary circle size="small" type="error" @click="removeCellDataMappingSource(sourceIndex)">
<div class="cell-data-source-header" @click="toggleCellDataMappingSource(sourceIndex)">
<div class="cell-data-source-title">
<span class="cell-data-source-toggle">{{ source.collapsed ? '▶' : '▼' }}</span>
<strong>来源 {{ sourceIndex + 1 }}</strong>
<span>{{ source.band || '未填写目录' }} / {{ source.file_prefix || '未填写前缀' }}</span>
</div>
<n-button quaternary circle size="small" type="error" @click.stop="removeCellDataMappingSource(sourceIndex)">
<template #icon>
<n-icon><CloseOutline /></n-icon>
</template>
</n-button>
</div>
<div class="cell-data-source-meta">
<n-form-item label="目录">
<n-input v-model:value="source.band" placeholder="如 2.6G" />
</n-form-item>
<n-form-item label="CSV 前缀">
<n-input v-model:value="source.file_prefix" placeholder="如 NR_CellInfo" />
</n-form-item>
</div>
<div class="cell-data-field-list">
<div v-for="(field, fieldIndex) in source.fields" :key="fieldIndex" class="cell-data-field-row">
<n-input v-model:value="field.target" size="small" placeholder="目标字段" />
<n-select v-model:value="field.mode" size="small" :options="mappingFieldModeOptions" />
<n-input v-model:value="field.value" size="small" :placeholder="field.mode === 'value' ? '固定值' : 'CSV 字段名'" />
<n-button quaternary circle size="small" type="error" @click="removeCellDataMappingField(sourceIndex, fieldIndex)">
<template #icon>
<n-icon><CloseOutline /></n-icon>
</template>
</n-button>
<template v-if="!source.collapsed">
<div class="cell-data-source-meta">
<n-form-item label="目录">
<n-input v-model:value="source.band" placeholder="如 2.6G" />
</n-form-item>
<n-form-item label="CSV 前缀">
<n-input v-model:value="source.file_prefix" placeholder="如 NR_CellInfo" />
</n-form-item>
</div>
</div>
<n-button size="small" @click="addCellDataMappingField(sourceIndex)">添加字段</n-button>
<div class="cell-data-field-list">
<div v-for="(field, fieldIndex) in source.fields" :key="fieldIndex" class="cell-data-field-row">
<n-input v-model:value="field.target" size="small" placeholder="目标字段" />
<n-select v-model:value="field.mode" size="small" :options="mappingFieldModeOptions" />
<n-input v-model:value="field.value" size="small" :placeholder="field.mode === 'value' ? '固定值' : 'CSV 字段名'" />
<n-button quaternary circle size="small" type="error" @click="removeCellDataMappingField(sourceIndex, fieldIndex)">
<template #icon>
<n-icon><CloseOutline /></n-icon>
</template>
</n-button>
</div>
</div>
<n-button size="small" @click="addCellDataMappingField(sourceIndex)">添加字段</n-button>
</template>
</div>
</div>
<div class="cell-data-mapping-actions">
<n-button size="small" @click="addCellDataMappingSource">添加来源</n-button>
</div>
</div>
</section>
</div>
@@ -833,6 +837,7 @@ interface MappingFieldForm {
interface MappingSourceForm {
band: string;
file_prefix: string;
collapsed: boolean;
fields: MappingFieldForm[];
}
@@ -1500,6 +1505,7 @@ function applyCellDataMappingForm(mapping: Record<string, unknown>) {
cellDataMappingForm.sources.push({
band: String(source.band || ''),
file_prefix: String(source.file_prefix || ''),
collapsed: false,
fields: Object.entries(fields).map(([target, rule]) => {
if (typeof rule === 'object' && rule !== null && 'value' in rule) {
return { target, mode: 'value', value: String((rule as Record<string, unknown>).value ?? '') };
@@ -1536,7 +1542,14 @@ function mappingFormToJson(): Record<string, unknown> {
}
function addCellDataMappingSource() {
cellDataMappingForm.sources.push({ band: '', file_prefix: '', fields: [] });
cellDataMappingForm.sources.push({ band: '', file_prefix: '', collapsed: false, fields: [] });
}
function toggleCellDataMappingSource(index: number) {
const source = cellDataMappingForm.sources[index];
if (source) {
source.collapsed = !source.collapsed;
}
}
function removeCellDataMappingSource(index: number) {
+23
View File
@@ -1722,6 +1722,29 @@ select {
.cell-data-source-header {
grid-template-columns: minmax(0, 1fr) auto;
cursor: pointer;
}
.cell-data-source-title {
display: flex;
min-width: 0;
align-items: center;
gap: 8px;
}
.cell-data-source-title span:last-child {
min-width: 0;
overflow: hidden;
color: var(--td-text-color-secondary);
font-size: 12px;
white-space: nowrap;
text-overflow: ellipsis;
}
.cell-data-source-toggle {
flex: 0 0 auto;
color: var(--td-text-color-placeholder);
font-size: 11px;
}
.cell-data-source-meta {