feat: add slot definition management page [AC-MRS-07,08,16]
This commit is contained in:
parent
eb7bc7722b
commit
127ce5d8a9
|
|
@ -54,10 +54,18 @@
|
||||||
<el-icon><Warning /></el-icon>
|
<el-icon><Warning /></el-icon>
|
||||||
<span>输出护栏</span>
|
<span>输出护栏</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link to="/admin/mid-platform-playground" class="nav-item" :class="{ active: isActive('/admin/mid-platform-playground') }">
|
||||||
|
<el-icon><ChatLineRound /></el-icon>
|
||||||
|
<span>中台联调</span>
|
||||||
|
</router-link>
|
||||||
<router-link to="/admin/metadata-schemas" class="nav-item" :class="{ active: isActive('/admin/metadata-schemas') }">
|
<router-link to="/admin/metadata-schemas" class="nav-item" :class="{ active: isActive('/admin/metadata-schemas') }">
|
||||||
<el-icon><Setting /></el-icon>
|
<el-icon><Setting /></el-icon>
|
||||||
<span>元数据配置</span>
|
<span>元数据配置</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link to="/admin/slot-definitions" class="nav-item" :class="{ active: isActive('/admin/slot-definitions') }">
|
||||||
|
<el-icon><Grid /></el-icon>
|
||||||
|
<span>槽位定义</span>
|
||||||
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
@ -90,7 +98,7 @@ import { ref, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useTenantStore } from '@/stores/tenant'
|
import { useTenantStore } from '@/stores/tenant'
|
||||||
import { getTenantList, type Tenant } from '@/api/tenant'
|
import { getTenantList, type Tenant } from '@/api/tenant'
|
||||||
import { Odometer, FolderOpened, Cpu, Monitor, Connection, ChatDotSquare, Document, Aim, Share, Warning, Setting } from '@element-plus/icons-vue'
|
import { Odometer, FolderOpened, Cpu, Monitor, Connection, ChatDotSquare, Document, Aim, Share, Warning, Setting, ChatLineRound, Grid } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import type {
|
||||||
|
SlotDefinition,
|
||||||
|
SlotDefinitionCreateRequest,
|
||||||
|
SlotDefinitionUpdateRequest,
|
||||||
|
RuntimeSlotValue
|
||||||
|
} from '@/types/slot-definition'
|
||||||
|
import type { FieldRole } from '@/types/metadata'
|
||||||
|
|
||||||
|
export const slotDefinitionApi = {
|
||||||
|
list: (required?: boolean) =>
|
||||||
|
request<SlotDefinition[]>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/admin/slot-definitions',
|
||||||
|
params: required !== undefined ? { required } : {}
|
||||||
|
}),
|
||||||
|
|
||||||
|
get: (id: string) =>
|
||||||
|
request<SlotDefinition>({ method: 'GET', url: `/admin/slot-definitions/${id}` }),
|
||||||
|
|
||||||
|
create: (data: SlotDefinitionCreateRequest) =>
|
||||||
|
request<SlotDefinition>({ method: 'POST', url: '/admin/slot-definitions', data }),
|
||||||
|
|
||||||
|
update: (id: string, data: SlotDefinitionUpdateRequest) =>
|
||||||
|
request<SlotDefinition>({ method: 'PUT', url: `/admin/slot-definitions/${id}`, data }),
|
||||||
|
|
||||||
|
delete: (id: string) =>
|
||||||
|
request({ method: 'DELETE', url: `/admin/slot-definitions/${id}` }),
|
||||||
|
|
||||||
|
getByRole: (role: FieldRole) =>
|
||||||
|
request<SlotDefinition[]>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/mid/slots/by-role',
|
||||||
|
params: { role }
|
||||||
|
}),
|
||||||
|
|
||||||
|
getSlotValue: (slotKey: string, userId?: string, sessionId?: string) =>
|
||||||
|
request<RuntimeSlotValue>({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/mid/slots/${slotKey}`,
|
||||||
|
params: {
|
||||||
|
...(userId ? { user_id: userId } : {}),
|
||||||
|
...(sessionId ? { session_id: sessionId } : {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {
|
||||||
|
SlotDefinition,
|
||||||
|
SlotDefinitionCreateRequest,
|
||||||
|
SlotDefinitionUpdateRequest,
|
||||||
|
RuntimeSlotValue
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/dashboard'
|
redirect: '/dashboard'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/share/:token',
|
||||||
|
name: 'SharedSession',
|
||||||
|
component: () => import('@/views/share/index.vue'),
|
||||||
|
meta: { title: '共享对话', public: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
|
|
@ -59,6 +65,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
component: () => import('@/views/admin/metadata-schema/index.vue'),
|
component: () => import('@/views/admin/metadata-schema/index.vue'),
|
||||||
meta: { title: '元数据模式配置' }
|
meta: { title: '元数据模式配置' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/slot-definitions',
|
||||||
|
name: 'SlotDefinition',
|
||||||
|
component: () => import('@/views/admin/slot-definition/index.vue'),
|
||||||
|
meta: { title: '槽位定义管理' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/intent-rules',
|
path: '/admin/intent-rules',
|
||||||
name: 'IntentRule',
|
name: 'IntentRule',
|
||||||
|
|
@ -77,6 +89,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
component: () => import('@/views/admin/guardrail/index.vue'),
|
component: () => import('@/views/admin/guardrail/index.vue'),
|
||||||
meta: { title: '输出护栏管理' }
|
meta: { title: '输出护栏管理' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/mid-platform-playground',
|
||||||
|
name: 'MidPlatformPlayground',
|
||||||
|
component: () => import('@/views/admin/mid-platform-playground/index.vue'),
|
||||||
|
meta: { title: '中台联调工作台' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/decomposition-templates',
|
path: '/admin/decomposition-templates',
|
||||||
name: 'DecompositionTemplate',
|
name: 'DecompositionTemplate',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
export type SlotType = 'string' | 'number' | 'boolean' | 'enum' | 'array_enum'
|
||||||
|
export type ExtractStrategy = 'rule' | 'llm' | 'user_input'
|
||||||
|
export type SlotSource = 'user_confirmed' | 'rule_extracted' | 'llm_inferred' | 'default'
|
||||||
|
|
||||||
|
export interface SlotDefinition {
|
||||||
|
id: string
|
||||||
|
tenant_id: string
|
||||||
|
slot_key: string
|
||||||
|
type: SlotType
|
||||||
|
required: boolean
|
||||||
|
extract_strategy?: ExtractStrategy
|
||||||
|
validation_rule?: string
|
||||||
|
ask_back_prompt?: string
|
||||||
|
default_value?: string | number | boolean | string[]
|
||||||
|
linked_field_id?: string
|
||||||
|
linked_field?: LinkedField
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkedField {
|
||||||
|
id: string
|
||||||
|
field_key: string
|
||||||
|
label: string
|
||||||
|
type: string
|
||||||
|
field_roles: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlotDefinitionCreateRequest {
|
||||||
|
tenant_id?: string
|
||||||
|
slot_key: string
|
||||||
|
type: SlotType
|
||||||
|
required: boolean
|
||||||
|
extract_strategy?: ExtractStrategy
|
||||||
|
validation_rule?: string
|
||||||
|
ask_back_prompt?: string
|
||||||
|
default_value?: string | number | boolean | string[]
|
||||||
|
linked_field_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlotDefinitionUpdateRequest {
|
||||||
|
type?: SlotType
|
||||||
|
required?: boolean
|
||||||
|
extract_strategy?: ExtractStrategy
|
||||||
|
validation_rule?: string
|
||||||
|
ask_back_prompt?: string
|
||||||
|
default_value?: string | number | boolean | string[]
|
||||||
|
linked_field_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeSlotValue {
|
||||||
|
key: string
|
||||||
|
value: string | number | boolean | string[] | undefined
|
||||||
|
source: SlotSource
|
||||||
|
confidence: number
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SLOT_TYPE_OPTIONS = [
|
||||||
|
{ value: 'string', label: '文本' },
|
||||||
|
{ value: 'number', label: '数字' },
|
||||||
|
{ value: 'boolean', label: '布尔值' },
|
||||||
|
{ value: 'enum', label: '单选枚举' },
|
||||||
|
{ value: 'array_enum', label: '多选枚举' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const EXTRACT_STRATEGY_OPTIONS = [
|
||||||
|
{ value: 'rule', label: '规则提取', description: '通过预定义规则从对话中提取' },
|
||||||
|
{ value: 'llm', label: 'LLM 推断', description: '通过大语言模型推断槽位值' },
|
||||||
|
{ value: 'user_input', label: '用户输入', description: '通过追问提示语让用户主动输入' }
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,470 @@
|
||||||
|
<template>
|
||||||
|
<div class="slot-definition-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-section">
|
||||||
|
<h1 class="page-title">槽位定义管理</h1>
|
||||||
|
<p class="page-desc">配置对话流程中的结构化槽位,用于信息收集和槽位填充。[AC-MRS-07,08]</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-select v-model="filterRequired" placeholder="按必填筛选" clearable style="width: 140px;">
|
||||||
|
<el-option label="必填" :value="true" />
|
||||||
|
<el-option label="可选" :value="false" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" @click="handleCreate">
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
新建槽位
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-card shadow="hover" class="slot-card" v-loading="loading">
|
||||||
|
<el-table :data="slots" stripe style="width: 100%">
|
||||||
|
<el-table-column prop="slot_key" label="槽位标识" min-width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<code class="slot-key">{{ row.slot_key }}</code>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="type" label="类型" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" type="info">{{ getTypeLabel(row.type) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="required" label="必填" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.required ? 'danger' : 'info'" size="small">
|
||||||
|
{{ row.required ? '必填' : '可选' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="extract_strategy" label="提取策略" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.extract_strategy" size="small">
|
||||||
|
{{ getExtractStrategyLabel(row.extract_strategy) }}
|
||||||
|
</el-tag>
|
||||||
|
<span v-else class="no-value">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="linked_field" label="关联字段" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.linked_field" class="linked-field">
|
||||||
|
<code class="field-key">{{ row.linked_field.field_key }}</code>
|
||||||
|
<el-tag size="small" type="success" class="linked-tag">已关联</el-tag>
|
||||||
|
</div>
|
||||||
|
<span v-else class="no-value">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="ask_back_prompt" label="追问提示语" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.ask_back_prompt" class="ask-back-prompt">{{ row.ask_back_prompt }}</span>
|
||||||
|
<span v-else class="no-value">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link size="small" @click="handleEdit(row)">
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" link size="small" @click="handleDelete(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-empty v-if="!loading && slots.length === 0" description="暂无槽位定义" />
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="isEdit ? '编辑槽位定义' : '新建槽位定义'"
|
||||||
|
width="650px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form :model="formData" :rules="formRules" ref="formRef" label-width="100px">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="槽位标识" prop="slot_key">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.slot_key"
|
||||||
|
placeholder="如:grade, subject"
|
||||||
|
:disabled="isEdit"
|
||||||
|
/>
|
||||||
|
<div class="field-hint">仅允许小写字母、数字、下划线</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="槽位类型" prop="type">
|
||||||
|
<el-select v-model="formData.type" style="width: 100%;">
|
||||||
|
<el-option
|
||||||
|
v-for="opt in SLOT_TYPE_OPTIONS"
|
||||||
|
:key="opt.value"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="是否必填" prop="required">
|
||||||
|
<el-switch v-model="formData.required" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="提取策略" prop="extract_strategy">
|
||||||
|
<el-select v-model="formData.extract_strategy" style="width: 100%;" clearable placeholder="选择提取策略">
|
||||||
|
<el-option
|
||||||
|
v-for="opt in EXTRACT_STRATEGY_OPTIONS"
|
||||||
|
:key="opt.value"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
>
|
||||||
|
<div class="extract-option">
|
||||||
|
<span>{{ opt.label }}</span>
|
||||||
|
<span class="extract-desc">{{ opt.description }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="关联字段" prop="linked_field_id">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.linked_field_id"
|
||||||
|
style="width: 100%;"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="选择关联的元数据字段"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="field in slotFields"
|
||||||
|
:key="field.id"
|
||||||
|
:label="`${field.label} (${field.field_key})`"
|
||||||
|
:value="field.id"
|
||||||
|
>
|
||||||
|
<div class="field-option">
|
||||||
|
<span class="field-label">{{ field.label }}</span>
|
||||||
|
<code class="field-key-small">{{ field.field_key }}</code>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
<div class="field-hint">关联字段后,槽位值可同步到元数据字段</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="校验规则" prop="validation_rule">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.validation_rule"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="正则表达式或 JSON Schema 格式"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="追问提示语" prop="ask_back_prompt">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.ask_back_prompt"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="当槽位缺失时,系统将使用此提示语追问用户"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="默认值" prop="default_value">
|
||||||
|
<el-input v-model="formData.default_value" placeholder="可选默认值" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||||
|
{{ isEdit ? '保存' : '创建' }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, watch } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { slotDefinitionApi } from '@/api/slot-definition'
|
||||||
|
import { metadataSchemaApi } from '@/api/metadata-schema'
|
||||||
|
import {
|
||||||
|
SLOT_TYPE_OPTIONS,
|
||||||
|
EXTRACT_STRATEGY_OPTIONS,
|
||||||
|
type SlotDefinition,
|
||||||
|
type SlotDefinitionCreateRequest,
|
||||||
|
type SlotDefinitionUpdateRequest,
|
||||||
|
type SlotType,
|
||||||
|
type ExtractStrategy
|
||||||
|
} from '@/types/slot-definition'
|
||||||
|
import type { MetadataFieldDefinition } from '@/types/metadata'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const slots = ref<SlotDefinition[]>([])
|
||||||
|
const slotFields = ref<MetadataFieldDefinition[]>([])
|
||||||
|
const filterRequired = ref<boolean | undefined>(undefined)
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
id: '',
|
||||||
|
slot_key: '',
|
||||||
|
type: 'string' as SlotType,
|
||||||
|
required: false,
|
||||||
|
extract_strategy: '' as ExtractStrategy | '',
|
||||||
|
validation_rule: '',
|
||||||
|
ask_back_prompt: '',
|
||||||
|
default_value: '',
|
||||||
|
linked_field_id: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRules: FormRules = {
|
||||||
|
slot_key: [
|
||||||
|
{ required: true, message: '请输入槽位标识', trigger: 'blur' },
|
||||||
|
{ pattern: /^[a-z][a-z0-9_]*$/, message: '以小写字母开头,仅允许小写字母、数字、下划线', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
type: [{ required: true, message: '请选择槽位类型', trigger: 'change' }],
|
||||||
|
required: [{ required: true, message: '请选择是否必填', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypeLabel = (type: SlotType) => {
|
||||||
|
return SLOT_TYPE_OPTIONS.find(o => o.value === type)?.label || type
|
||||||
|
}
|
||||||
|
|
||||||
|
const getExtractStrategyLabel = (strategy: ExtractStrategy) => {
|
||||||
|
return EXTRACT_STRATEGY_OPTIONS.find(o => o.value === strategy)?.label || strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSlots = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await slotDefinitionApi.list(filterRequired.value)
|
||||||
|
slots.value = res || []
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '获取槽位定义失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSlotFields = async () => {
|
||||||
|
try {
|
||||||
|
const res = await metadataSchemaApi.getByRole('slot', true)
|
||||||
|
slotFields.value = res.items || []
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取槽位角色字段失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
isEdit.value = false
|
||||||
|
Object.assign(formData, {
|
||||||
|
id: '',
|
||||||
|
slot_key: '',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
extract_strategy: '',
|
||||||
|
validation_rule: '',
|
||||||
|
ask_back_prompt: '',
|
||||||
|
default_value: '',
|
||||||
|
linked_field_id: ''
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (slot: SlotDefinition) => {
|
||||||
|
isEdit.value = true
|
||||||
|
Object.assign(formData, {
|
||||||
|
id: slot.id,
|
||||||
|
slot_key: slot.slot_key,
|
||||||
|
type: slot.type,
|
||||||
|
required: slot.required,
|
||||||
|
extract_strategy: slot.extract_strategy || '',
|
||||||
|
validation_rule: slot.validation_rule || '',
|
||||||
|
ask_back_prompt: slot.ask_back_prompt || '',
|
||||||
|
default_value: slot.default_value ?? '',
|
||||||
|
linked_field_id: slot.linked_field_id || ''
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (slot: SlotDefinition) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定要删除槽位「${slot.slot_key}」吗?[AC-MRS-16]`,
|
||||||
|
'删除确认',
|
||||||
|
{ type: 'warning' }
|
||||||
|
)
|
||||||
|
await slotDefinitionApi.delete(slot.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
fetchSlots()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error(error.response?.data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const data: SlotDefinitionCreateRequest | SlotDefinitionUpdateRequest = {
|
||||||
|
slot_key: formData.slot_key,
|
||||||
|
type: formData.type,
|
||||||
|
required: formData.required,
|
||||||
|
extract_strategy: formData.extract_strategy || undefined,
|
||||||
|
validation_rule: formData.validation_rule || undefined,
|
||||||
|
ask_back_prompt: formData.ask_back_prompt || undefined,
|
||||||
|
linked_field_id: formData.linked_field_id || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.default_value !== '') {
|
||||||
|
data.default_value = formData.default_value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await slotDefinitionApi.update(formData.id, data as SlotDefinitionUpdateRequest)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await slotDefinitionApi.create(data as SlotDefinitionCreateRequest)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = false
|
||||||
|
fetchSlots()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(filterRequired, () => {
|
||||||
|
fetchSlots()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchSlots()
|
||||||
|
fetchSlotFields()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.slot-definition-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot-key {
|
||||||
|
padding: 2px 6px;
|
||||||
|
background-color: var(--el-fill-color-light);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-value {
|
||||||
|
color: var(--el-text-color-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linked-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.linked-tag {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ask-back-prompt {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-hint {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extract-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
.extract-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-key-small {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
background-color: var(--el-fill-color-light);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue