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>
|
||||
<span>输出护栏</span>
|
||||
</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') }">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>元数据配置</span>
|
||||
</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>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
|
|
@ -90,7 +98,7 @@ import { ref, onMounted } from 'vue'
|
|||
import { useRoute } from 'vue-router'
|
||||
import { useTenantStore } from '@/stores/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'
|
||||
|
||||
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: '/',
|
||||
redirect: '/dashboard'
|
||||
},
|
||||
{
|
||||
path: '/share/:token',
|
||||
name: 'SharedSession',
|
||||
component: () => import('@/views/share/index.vue'),
|
||||
meta: { title: '共享对话', public: true }
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
|
|
@ -59,6 +65,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||
component: () => import('@/views/admin/metadata-schema/index.vue'),
|
||||
meta: { title: '元数据模式配置' }
|
||||
},
|
||||
{
|
||||
path: '/admin/slot-definitions',
|
||||
name: 'SlotDefinition',
|
||||
component: () => import('@/views/admin/slot-definition/index.vue'),
|
||||
meta: { title: '槽位定义管理' }
|
||||
},
|
||||
{
|
||||
path: '/admin/intent-rules',
|
||||
name: 'IntentRule',
|
||||
|
|
@ -77,6 +89,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||
component: () => import('@/views/admin/guardrail/index.vue'),
|
||||
meta: { title: '输出护栏管理' }
|
||||
},
|
||||
{
|
||||
path: '/admin/mid-platform-playground',
|
||||
name: 'MidPlatformPlayground',
|
||||
component: () => import('@/views/admin/mid-platform-playground/index.vue'),
|
||||
meta: { title: '中台联调工作台' }
|
||||
},
|
||||
{
|
||||
path: '/admin/decomposition-templates',
|
||||
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