Files
YG_FT/frontend/src/views/data-process/create/StructuredOptionsPanel.vue

241 lines
5.8 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import type {
GenerationControlOptions,
PreprocessOption,
StructuredProcessOptions,
} from './types'
import DatasetSplitEditor from './DatasetSplitEditor.vue'
import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
const props = defineProps<{
options: StructuredProcessOptions
validationAttempted: boolean
qualityValidationMessage: string
}>()
const emit = defineEmits<{
'update:options': [value: StructuredProcessOptions]
}>()
const PREPROCESS_OPTIONS: Array<{
value: PreprocessOption
label: string
description: string
}> = [
{ value: 'clean_invalid', label: '清理无效数据', description: '处理空行、空列和残缺行' },
{ value: 'detect_structure', label: '识别表格结构', description: '识别表头、多级表头和合并单元格' },
{ value: 'deduplicate', label: '重复数据去重', description: '删除完全重复或关键字段重复的数据' },
{ value: 'normalize_format', label: '数据格式标准化', description: '统一日期、数字、单位和枚举值格式' },
{ value: 'filter_anomaly', label: '异常数据过滤', description: '过滤乱码、无效内容和异常记录' },
{ value: 'desensitize', label: '敏感信息脱敏', description: '处理姓名、手机号、邮箱等敏感信息' },
]
function updateField<K extends keyof StructuredProcessOptions>(
field: K,
value: StructuredProcessOptions[K],
) {
emit('update:options', { ...props.options, [field]: value })
}
function updateGenerationOptions(value: GenerationControlOptions) {
emit('update:options', { ...props.options, ...value })
}
function updatePreprocessOptions(value: Array<string | number | boolean>) {
const allowedValues = new Set(PREPROCESS_OPTIONS.map((option) => option.value))
const preprocessOptions = value.filter(
(option): option is PreprocessOption => typeof option === 'string' && allowedValues.has(option as PreprocessOption),
)
updateField('preprocessOptions', preprocessOptions)
}
</script>
<template>
<div class="form-section structured-options-section">
<div class="section-title-row">
<div>
<h3>预处理选项</h3>
<p>选择在生成问答对之前需要执行的数据处理方式</p>
</div>
</div>
<el-checkbox-group
:model-value="options.preprocessOptions"
class="preprocess-option-grid"
@update:model-value="updatePreprocessOptions"
>
<el-checkbox
v-for="option in PREPROCESS_OPTIONS"
:key="option.value"
:value="option.value"
class="preprocess-option"
>
<span class="preprocess-option-copy">
<strong>{{ option.label }}</strong>
<small>{{ option.description }}</small>
</span>
</el-checkbox>
</el-checkbox-group>
</div>
<div class="form-section generation-options-section">
<div class="section-title-row">
<div>
<h3>生成选项</h3>
<p>配置每条结构化记录生成问答对的方式和数量</p>
</div>
</div>
<div class="generation-option-list">
<GenerationOptionsPanel
:options="options"
section="quality"
:validation-message="validationAttempted ? qualityValidationMessage : ''"
@update:options="updateGenerationOptions"
/>
<div class="generation-option-row">
<div class="generation-option-copy">
<strong>每行生成数量</strong>
<small>每行结构化数据生成的问答对数量</small>
</div>
<el-input-number
:model-value="options.qaPairsPerRow"
:min="1"
:max="5"
:step="1"
controls-position="right"
@update:model-value="updateField('qaPairsPerRow', Number($event) || 1)"
/>
</div>
<DatasetSplitEditor
:model-value="options.datasetSplit"
@update:model-value="updateField('datasetSplit', $event)"
/>
</div>
</div>
</template>
<style scoped lang="scss">
.form-section {
padding: 0 0 26px;
margin-bottom: 26px;
border-bottom: 1px solid #edf0f5;
&:last-child {
margin-bottom: 0;
border-bottom: 0;
}
h3 {
margin: 0 0 16px;
color: #2f3747;
font-size: 15px;
font-weight: 650;
}
}
.section-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
h3 {
margin-bottom: 5px;
}
p {
margin: 0;
color: #8a93a3;
font-size: 12px;
}
}
.preprocess-option-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 16px;
}
.preprocess-option {
display: flex;
gap: 10px;
width: 100%;
min-height: 64px;
margin-right: 0;
padding: 12px 14px;
box-sizing: border-box;
align-items: flex-start;
border: 1px solid #e2e5ec;
border-radius: 8px;
transition: border-color 0.18s ease, background-color 0.18s ease;
&:hover {
border-color: #b7b2f7;
}
&.is-checked {
background: #fafaff;
border-color: #8b82f4;
}
:deep(.el-checkbox__input) {
margin-top: 3px;
}
:deep(.el-checkbox__label) {
min-width: 0;
padding-left: 10px;
white-space: normal;
}
}
.preprocess-option-copy,
.generation-option-copy {
display: grid;
gap: 4px;
strong {
color: #344054;
font-size: 13px;
font-weight: 600;
}
small {
color: #8a93a3;
font-size: 12px;
line-height: 1.5;
}
}
.generation-option-list {
display: grid;
gap: 12px;
margin-top: 16px;
}
.generation-option-row {
display: flex;
min-height: 64px;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 12px 14px;
box-sizing: border-box;
border: 1px solid #e2e5ec;
border-radius: 8px;
}
@media (max-width: 900px) {
.preprocess-option-grid {
grid-template-columns: minmax(0, 1fr);
}
.generation-option-row {
align-items: flex-start;
flex-direction: column;
}
}
</style>