2026-04-23 11:09:06 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<section class="mapping-panel">
|
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
<div>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<h2 class="panel-title">映射结果</h2>
|
|
|
|
|
|
<p class="panel-description">展示和导出JSON与XML的映射结果,以及JSON的映射序列配置。</p>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="panel-actions">
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:icon="Connection"
|
|
|
|
|
|
:loading="isGeneratingXml"
|
|
|
|
|
|
:disabled="!canGenerateXmlMapping"
|
|
|
|
|
|
@click="emit('generate-xml-mapping')"
|
|
|
|
|
|
>
|
|
|
|
|
|
生成XML映射
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</el-button>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<div class="export-actions">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
:icon="Download"
|
|
|
|
|
|
:disabled="!canExportActiveMapping"
|
|
|
|
|
|
@click="emit('export-mapping', activeExportType)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ exportButtonText }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-dropdown trigger="click" :disabled="!canExportAnyMapping" @command="handleExportCommand">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
class="export-menu-button"
|
|
|
|
|
|
:icon="ArrowDown"
|
|
|
|
|
|
:disabled="!canExportAnyMapping"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
<el-dropdown-item command="json" :disabled="!canExportJsonMapping">
|
|
|
|
|
|
导出JSON映射
|
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item command="xml" :disabled="!canExportXmlMapping">
|
|
|
|
|
|
导出XML映射
|
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
|
</div>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
<el-tag :type="responseStatusTagType" effect="light">{{ responseStatusText }}</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="panel-content panel-content--fixed">
|
|
|
|
|
|
<div class="panel-section result-card grow-card preview-tab-section">
|
|
|
|
|
|
<el-tabs v-model="activeTabProxy" class="preview-tabs">
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<el-tab-pane label="JSON映射" name="json">
|
2026-04-23 11:09:06 +08:00
|
|
|
|
<div class="mapping-json-scroll">
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<JsonMappingTree
|
|
|
|
|
|
v-if="mappingJsonPreview"
|
|
|
|
|
|
:source="mappingJsonPreview"
|
|
|
|
|
|
:meta-text="mappingMetaText"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #actions>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:icon="Setting"
|
|
|
|
|
|
:disabled="!mappingJsonPreview"
|
|
|
|
|
|
@click="openSequenceDialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
序列配置
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:icon="Warning"
|
|
|
|
|
|
@click="problemDialogVisible = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ problemButtonText }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</JsonMappingTree>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
<el-empty v-else description="当前返回未包含 mappingJson" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<el-tab-pane v-if="showXmlMappingTab" label="XML映射" name="xml">
|
|
|
|
|
|
<div class="mapping-json-scroll">
|
|
|
|
|
|
<div class="match-result-actions">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
:icon="Document"
|
|
|
|
|
|
:disabled="!methodDescribe"
|
|
|
|
|
|
@click="matchResultDialogVisible = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
匹配结果展示
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="xmlMappingPreview" class="xml-file-viewer">
|
|
|
|
|
|
<div class="xml-file-header">
|
|
|
|
|
|
<span class="xml-file-name">XML 文件</span>
|
|
|
|
|
|
<span class="xml-file-meta">{{ xmlMetaText }}</span>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</div>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<pre class="xml-file-content">{{ xmlMappingPreview }}</pre>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</div>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
<el-empty v-else :description="xmlEmptyText" />
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-08 09:54:52 +08:00
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="problemDialogVisible"
|
|
|
|
|
|
title="问题列表"
|
|
|
|
|
|
width="720px"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
top="8vh"
|
|
|
|
|
|
class="mapping-problem-dialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="problemList.length" class="problem-dialog-list">
|
|
|
|
|
|
<div v-for="(problem, index) in problemList" :key="`${index}-${problem}`" class="problem-item">
|
|
|
|
|
|
<span class="problem-index">{{ index + 1 }}</span>
|
|
|
|
|
|
<span class="problem-text">{{ problem }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-empty v-else :description="problemEmptyText" />
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="matchResultDialogVisible"
|
|
|
|
|
|
title="匹配结果展示"
|
|
|
|
|
|
width="640px"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
top="12vh"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="match-result-detail">
|
|
|
|
|
|
{{ methodDescribe || '当前接口返回未包含 methodDescribe' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="sequenceDialogVisible"
|
|
|
|
|
|
title="序列配置"
|
|
|
|
|
|
width="920px"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
top="8vh"
|
|
|
|
|
|
class="sequence-config-dialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template v-if="sequenceConfigRows.length">
|
|
|
|
|
|
<div class="dialog-search-bar">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="sequenceSearchKeyword"
|
|
|
|
|
|
:prefix-icon="Search"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
placeholder="按顶层、类型、上层描述、name 或路径检索"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span class="dialog-search-count">
|
|
|
|
|
|
{{ filteredSequenceRows.length }} / {{ sequenceConfigRows.length }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="sequenceConfigGroups.length" class="sequence-config-list">
|
|
|
|
|
|
<section v-for="group in sequenceConfigGroups" :key="group.topKey" class="sequence-top-group">
|
|
|
|
|
|
<div class="sequence-top-header">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 class="sequence-top-title">{{ group.topDesc || group.topKey }}</h3>
|
|
|
|
|
|
<p class="sequence-top-key">{{ group.topKey }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-tag type="info" effect="light">{{ group.rowCount }} 项</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="sequence-type-list">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="typeGroup in group.typeGroups"
|
|
|
|
|
|
:key="typeGroup.typeName"
|
|
|
|
|
|
class="sequence-type-card"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="sequence-type-header">
|
|
|
|
|
|
<div class="sequence-type-title">{{ typeGroup.typeName }}</div>
|
|
|
|
|
|
<el-tag type="primary" effect="plain" size="small">{{ typeGroup.rows.length }} 项</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-for="row in typeGroup.rows" :key="row.id" class="sequence-config-item">
|
|
|
|
|
|
<div class="sequence-config-info">
|
|
|
|
|
|
<div class="sequence-config-title">{{ row.parentDesc }}</div>
|
|
|
|
|
|
<div class="sequence-config-subtitle">
|
|
|
|
|
|
{{ row.name }}
|
|
|
|
|
|
<span class="sequence-config-path">{{ row.pathText }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-form label-position="top" size="small" class="sequence-config-form">
|
|
|
|
|
|
<el-form-item label="start">
|
|
|
|
|
|
<el-input v-model="row.start" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="end">
|
|
|
|
|
|
<el-input v-model="row.end" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-empty v-else description="当前检索条件下没有匹配的序列。" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<el-empty v-else description="当前 JSON 映射中未分析到包含 start 和 end 的序列。" />
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="sequenceDialogVisible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" :disabled="!sequenceConfigRows.length" @click="confirmSequenceConfig">
|
|
|
|
|
|
确定
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-05-08 09:54:52 +08:00
|
|
|
|
import { ArrowDown, Connection, Document, Download, Search, Setting, Warning } from '@element-plus/icons-vue'
|
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
|
import JsonMappingTree from './JsonMappingTree.vue'
|
2026-04-23 11:09:06 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
|
name: 'MappingResultPanel'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
type TagType = 'success' | 'warning' | 'info' | 'primary' | 'danger'
|
2026-05-08 09:54:52 +08:00
|
|
|
|
type ResultTab = 'json' | 'xml' | 'problem'
|
|
|
|
|
|
type ExportMappingType = 'json' | 'xml'
|
|
|
|
|
|
type JsonObject = Record<string, unknown>
|
|
|
|
|
|
type JsonPath = Array<string | number>
|
|
|
|
|
|
|
|
|
|
|
|
interface SequenceConfigRow {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
path: JsonPath
|
|
|
|
|
|
pathText: string
|
|
|
|
|
|
topKey: string
|
|
|
|
|
|
topDesc: string
|
|
|
|
|
|
parentDesc: string
|
|
|
|
|
|
desc: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
start: string
|
|
|
|
|
|
end: string
|
|
|
|
|
|
startValueType: string
|
|
|
|
|
|
endValueType: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SequenceConfigTypeGroup {
|
|
|
|
|
|
typeName: string
|
|
|
|
|
|
rows: SequenceConfigRow[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SequenceConfigGroup {
|
|
|
|
|
|
topKey: string
|
|
|
|
|
|
topDesc: string
|
|
|
|
|
|
rowCount: number
|
|
|
|
|
|
typeGroups: SequenceConfigTypeGroup[]
|
|
|
|
|
|
}
|
2026-04-23 11:09:06 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
responseStatusText: string
|
|
|
|
|
|
responseStatusTagType: TagType
|
2026-05-08 09:54:52 +08:00
|
|
|
|
activeResultTab: ResultTab
|
2026-04-23 11:09:06 +08:00
|
|
|
|
mappingMetaText: string
|
|
|
|
|
|
mappingJsonPreview: string
|
2026-05-08 09:54:52 +08:00
|
|
|
|
xmlMetaText: string
|
|
|
|
|
|
xmlMappingPreview: string
|
|
|
|
|
|
xmlEmptyText: string
|
2026-04-23 11:09:06 +08:00
|
|
|
|
problemTabLabel: string
|
|
|
|
|
|
problemList: string[]
|
|
|
|
|
|
problemEmptyText: string
|
2026-05-08 09:54:52 +08:00
|
|
|
|
methodDescribe: string
|
|
|
|
|
|
canExportJsonMapping: boolean
|
|
|
|
|
|
canExportXmlMapping: boolean
|
|
|
|
|
|
canGenerateXmlMapping: boolean
|
|
|
|
|
|
isGeneratingXml: boolean
|
|
|
|
|
|
showXmlMappingTab: boolean
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
2026-05-08 09:54:52 +08:00
|
|
|
|
(event: 'update:activeResultTab', value: ResultTab): void
|
|
|
|
|
|
(event: 'export-mapping', value: ExportMappingType): void
|
|
|
|
|
|
(event: 'generate-xml-mapping'): void
|
|
|
|
|
|
(event: 'update-mapping-json', value: string): void
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const activeTabProxy = computed({
|
2026-05-08 09:54:52 +08:00
|
|
|
|
get: () => (props.activeResultTab === 'problem' ? 'json' : props.activeResultTab),
|
2026-04-23 11:09:06 +08:00
|
|
|
|
set: value => emit('update:activeResultTab', value)
|
|
|
|
|
|
})
|
2026-05-08 09:54:52 +08:00
|
|
|
|
|
|
|
|
|
|
const canExportAnyMapping = computed(() => props.canExportJsonMapping || props.canExportXmlMapping)
|
|
|
|
|
|
const activeExportType = computed<ExportMappingType>(() => {
|
|
|
|
|
|
if (props.activeResultTab === 'xml') return 'xml'
|
|
|
|
|
|
if (props.canExportJsonMapping) return 'json'
|
|
|
|
|
|
if (props.canExportXmlMapping) return 'xml'
|
|
|
|
|
|
return 'json'
|
|
|
|
|
|
})
|
|
|
|
|
|
const canExportActiveMapping = computed(() =>
|
|
|
|
|
|
activeExportType.value === 'json' ? props.canExportJsonMapping : props.canExportXmlMapping
|
|
|
|
|
|
)
|
|
|
|
|
|
const exportButtonText = computed(() => (activeExportType.value === 'xml' ? '导出XML映射' : '导出JSON映射'))
|
|
|
|
|
|
const problemButtonText = computed(() =>
|
|
|
|
|
|
props.problemList.length ? `问题列表(${props.problemList.length})` : '问题列表'
|
|
|
|
|
|
)
|
|
|
|
|
|
const problemDialogVisible = ref(false)
|
|
|
|
|
|
const matchResultDialogVisible = ref(false)
|
|
|
|
|
|
const sequenceDialogVisible = ref(false)
|
|
|
|
|
|
const sequenceConfigRows = ref<SequenceConfigRow[]>([])
|
|
|
|
|
|
const sequenceSearchKeyword = ref('')
|
|
|
|
|
|
const normalizedSequenceSearchKeyword = computed(() => sequenceSearchKeyword.value.trim().toLowerCase())
|
|
|
|
|
|
const filteredSequenceRows = computed(() => {
|
|
|
|
|
|
const keyword = normalizedSequenceSearchKeyword.value
|
|
|
|
|
|
|
|
|
|
|
|
if (!keyword) return sequenceConfigRows.value
|
|
|
|
|
|
|
|
|
|
|
|
return sequenceConfigRows.value.filter(row =>
|
|
|
|
|
|
[row.topKey, row.topDesc, row.desc, row.parentDesc, row.name, row.pathText, row.start, row.end].some(value =>
|
|
|
|
|
|
value.toLowerCase().includes(keyword)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
const sequenceConfigGroups = computed<SequenceConfigGroup[]>(() => {
|
|
|
|
|
|
const groupMap = new Map<string, SequenceConfigRow[]>()
|
|
|
|
|
|
|
|
|
|
|
|
filteredSequenceRows.value.forEach(row => {
|
|
|
|
|
|
const groupRows = groupMap.get(row.topKey) || []
|
|
|
|
|
|
|
|
|
|
|
|
groupRows.push(row)
|
|
|
|
|
|
groupMap.set(row.topKey, groupRows)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return Array.from(groupMap.entries()).map(([topKey, rows]) => {
|
|
|
|
|
|
const typeMap = new Map<string, SequenceConfigRow[]>()
|
|
|
|
|
|
|
|
|
|
|
|
rows.forEach(row => {
|
|
|
|
|
|
const typeRows = typeMap.get(row.desc) || []
|
|
|
|
|
|
|
|
|
|
|
|
typeRows.push(row)
|
|
|
|
|
|
typeMap.set(row.desc, typeRows)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
topKey,
|
|
|
|
|
|
topDesc: rows[0]?.topDesc || '',
|
|
|
|
|
|
rowCount: rows.length,
|
|
|
|
|
|
typeGroups: Array.from(typeMap.entries()).map(([typeName, typeRows]) => ({
|
|
|
|
|
|
typeName,
|
|
|
|
|
|
rows: typeRows
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const handleExportCommand = (command: string | number | object) => {
|
|
|
|
|
|
if (command === 'json' || command === 'xml') {
|
|
|
|
|
|
emit('export-mapping', command)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isRecord = (value: unknown): value is JsonObject => Boolean(value && typeof value === 'object' && !Array.isArray(value))
|
|
|
|
|
|
|
|
|
|
|
|
const toDisplayText = (value: unknown, fallback: string) => {
|
|
|
|
|
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
|
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
|
|
|
|
return fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isZeroValue = (value: unknown) => {
|
|
|
|
|
|
if (typeof value === 'number') return value === 0
|
|
|
|
|
|
if (typeof value === 'string') return Number(value.trim()) === 0
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getPathText = (path: JsonPath) => (path.length ? path.map(item => String(item)).join(' / ') : '$')
|
|
|
|
|
|
|
|
|
|
|
|
const getTopKey = (path: JsonPath) => (path.length ? String(path[0]) : '$')
|
|
|
|
|
|
|
|
|
|
|
|
const resolveTopDesc = (source: unknown, path: JsonPath) => {
|
|
|
|
|
|
if (!path.length || !isRecord(source)) return ''
|
|
|
|
|
|
|
|
|
|
|
|
const topValue = source[String(path[0])]
|
|
|
|
|
|
|
|
|
|
|
|
return isRecord(topValue) ? toDisplayText(topValue.desc, '') : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc = '', rootSource = source): SequenceConfigRow[] => {
|
|
|
|
|
|
if (Array.isArray(source)) {
|
|
|
|
|
|
return source.flatMap((item, index) => collectSequenceRows(item, [...path, index], parentDesc, rootSource))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isRecord(source)) return []
|
|
|
|
|
|
|
|
|
|
|
|
const currentDesc = toDisplayText(source.desc, '')
|
|
|
|
|
|
const nextParentDesc = currentDesc || parentDesc
|
|
|
|
|
|
const childrenRows = Object.entries(source).flatMap(([key, value]) =>
|
|
|
|
|
|
collectSequenceRows(value, [...path, key], nextParentDesc, rootSource)
|
|
|
|
|
|
)
|
|
|
|
|
|
const hasStart = Object.prototype.hasOwnProperty.call(source, 'start')
|
|
|
|
|
|
const hasEnd = Object.prototype.hasOwnProperty.call(source, 'end')
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasStart || !hasEnd) return childrenRows
|
|
|
|
|
|
if (isZeroValue(source.start) && isZeroValue(source.end)) return childrenRows
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: path.length ? path.join('/') : '$',
|
|
|
|
|
|
path,
|
|
|
|
|
|
pathText: getPathText(path),
|
|
|
|
|
|
topKey: getTopKey(path),
|
|
|
|
|
|
topDesc: resolveTopDesc(rootSource, path),
|
|
|
|
|
|
parentDesc: parentDesc || '未配置上层描述',
|
|
|
|
|
|
desc: toDisplayText(source.desc, '未命名序列'),
|
|
|
|
|
|
name: toDisplayText(source.name, '未配置 name'),
|
|
|
|
|
|
start: source.start === undefined || source.start === null ? '' : String(source.start),
|
|
|
|
|
|
end: source.end === undefined || source.end === null ? '' : String(source.end),
|
|
|
|
|
|
startValueType: typeof source.start,
|
|
|
|
|
|
endValueType: typeof source.end
|
|
|
|
|
|
},
|
|
|
|
|
|
...childrenRows
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getObjectByPath = (source: unknown, path: JsonPath) => {
|
|
|
|
|
|
return path.reduce<unknown>((target, key) => {
|
|
|
|
|
|
if (!target || typeof target !== 'object') return undefined
|
|
|
|
|
|
return (target as Record<string | number, unknown>)[key]
|
|
|
|
|
|
}, source)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeSequenceValue = (value: string, valueType: string) => {
|
|
|
|
|
|
if (valueType !== 'number') return value
|
|
|
|
|
|
|
|
|
|
|
|
const numericValue = Number(value)
|
|
|
|
|
|
if (!Number.isFinite(numericValue)) {
|
|
|
|
|
|
throw new Error('start 和 end 需要填写有效数字')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return numericValue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const openSequenceDialog = () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(props.mappingJsonPreview) as unknown
|
|
|
|
|
|
|
|
|
|
|
|
sequenceConfigRows.value = collectSequenceRows(parsed)
|
|
|
|
|
|
sequenceSearchKeyword.value = ''
|
|
|
|
|
|
sequenceDialogVisible.value = true
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
ElMessage.warning('当前 JSON 映射内容无法解析,不能配置序列')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const confirmSequenceConfig = () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const nextJson = JSON.parse(props.mappingJsonPreview) as unknown
|
|
|
|
|
|
|
|
|
|
|
|
sequenceConfigRows.value.forEach(row => {
|
|
|
|
|
|
const target = getObjectByPath(nextJson, row.path)
|
|
|
|
|
|
|
|
|
|
|
|
if (!isRecord(target)) return
|
|
|
|
|
|
|
|
|
|
|
|
// 关键业务节点:序列配置只回写 start/end,避免弹窗编辑影响映射 JSON 的其他字段结构。
|
|
|
|
|
|
target.start = normalizeSequenceValue(row.start, row.startValueType)
|
|
|
|
|
|
target.end = normalizeSequenceValue(row.end, row.endValueType)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
emit('update-mapping-json', JSON.stringify(nextJson, null, 4))
|
|
|
|
|
|
sequenceDialogVisible.value = false
|
|
|
|
|
|
ElMessage.success('序列配置已同步到 JSON 映射')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.warning(error instanceof Error ? error.message : '序列配置同步失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-23 11:09:06 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.mapping-panel {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-actions {
|
|
|
|
|
|
display: flex;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
flex-wrap: wrap;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
align-items: center;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
justify-content: flex-end;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.export-actions {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.export-menu-button {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 11:09:06 +08:00
|
|
|
|
.panel-title {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-description {
|
|
|
|
|
|
margin: 8px 0 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
color: #4b5563;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-content--fixed {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-section {
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.result-card {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.grow-card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tab-section {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tabs__header) {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tabs__nav-wrap::after) {
|
|
|
|
|
|
background-color: #e5e7eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tabs__item) {
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #4b5563;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tabs__item.is-active) {
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tabs__content) {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-tabs :deep(.el-tab-pane) {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.mapping-json-scroll {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mapping-json-text {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid #dbe3f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
font-family: Consolas, 'Courier New', monospace;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
color: #172033;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.xml-file-viewer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
border: 1px solid #dbe3f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.xml-file-header {
|
2026-04-23 11:09:06 +08:00
|
|
|
|
display: flex;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
flex: 0 0 auto;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
min-height: 40px;
|
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
white-space: nowrap;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.xml-file-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1f2937;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.xml-file-meta {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #64748b;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
text-overflow: ellipsis;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.xml-file-content {
|
2026-04-23 11:09:06 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 16px;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
overflow: auto;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
font-family: Consolas, 'Courier New', monospace;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
color: #172033;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
white-space: pre;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.problem-dialog-list {
|
2026-04-23 11:09:06 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
2026-05-08 09:54:52 +08:00
|
|
|
|
max-height: 62vh;
|
|
|
|
|
|
padding-right: 4px;
|
|
|
|
|
|
overflow: auto;
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.problem-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
|
border: 1px solid #f3d19e;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #fff7ed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.problem-index {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex: 0 0 24px;
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #f97316;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.problem-text {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
color: #7c2d12;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.match-result-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.match-result-detail {
|
|
|
|
|
|
max-height: 58vh;
|
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
border: 1px solid #dbe3f0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dialog-search-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dialog-search-count {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
max-height: 62vh;
|
|
|
|
|
|
padding-right: 4px;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-top-group {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid #dbe3f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-top-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-top-title {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-top-key {
|
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-type-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-type-card {
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-type-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-type-title {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
color: #111827;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-item {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) 300px;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
border: 1px solid #dbe3f0;
|
|
|
|
|
|
border-width: 1px 0 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-item:last-child {
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-info {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-title {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-subtitle {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-path {
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-form {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-form :deep(.el-form-item) {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-form :deep(.el-form-item__label) {
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 11:09:06 +08:00
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.mapping-panel {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-header,
|
2026-05-08 09:54:52 +08:00
|
|
|
|
.panel-actions {
|
2026-04-23 11:09:06 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
2026-05-08 09:54:52 +08:00
|
|
|
|
|
|
|
|
|
|
.sequence-config-item {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sequence-config-form {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
2026-04-23 11:09:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|