| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- <template>
- <div class="kb-container">
- <div class="header-section">
- <div class="title-info">
- <h2>知识库管理</h2>
- <p class="subtitle">管理所有知识库和相关内容</p>
- </div>
- <div class="action-buttons">
- <el-select v-model="queryParams.status" placeholder="所有状态" clearable style="width: 120px; margin-right: 12px" @change="handleSearch">
- <el-option label="正常" value="normal" />
- <el-option label="已禁用" value="disabled" />
- </el-select>
- <el-input
- v-model="queryParams.keyword"
- placeholder="搜索知识库名称..."
- style="width: 240px; margin-right: 12px"
- clearable
- @keyup.enter="handleSearch"
- @clear="handleSearch"
- />
- <el-button type="primary" @click="handleSearch">
- <el-icon><Search /></el-icon> 查询
- </el-button>
- <el-button type="primary" @click="handleAdd">
- <el-icon><Plus /></el-icon> 新建知识库
- </el-button>
- </div>
- </div>
- <el-card class="table-card" shadow="never">
- <el-table :data="tableData" v-loading="loading" style="width: 100%" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55" />
-
- <el-table-column label="知识库名称" min-width="200">
- <template #default="{ row }">
- <div class="kb-name-cell">
- <el-icon class="kb-icon" :size="24" :style="{ color: getIconColor(row.name) }"><Collection /></el-icon>
- <div class="kb-info">
- <div class="name">{{ row.name }}</div>
- <div class="desc">{{ row.description || '暂无描述' }}</div>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="collection_name" label="知识库表名" min-width="150" />
-
- <el-table-column prop="document_count" label="文档数量" width="100" />
- <el-table-column prop="status" label="状态" width="100">
- <template #default="{ row }">
- <el-tag :type="getStatusType(row.status)" effect="light" round>{{ getStatusText(row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="created_by" label="创建人" width="100" />
-
- <el-table-column prop="created_at" label="创建时间" width="160">
- <template #default="{ row }">
- {{ formatTime(row.created_at) }}
- </template>
- </el-table-column>
- <el-table-column prop="updated_by" label="修改人" width="100" />
- <el-table-column prop="updated_at" label="修改时间" width="160">
- <template #default="{ row }">
- {{ formatTime(row.updated_at) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="180" fixed="right">
- <template #default="{ row }">
- <el-tooltip content="查看" placement="top">
- <el-button link type="primary" @click="handleView(row)">
- <el-icon><View /></el-icon>
- </el-button>
- </el-tooltip>
- <el-tooltip content="编辑" placement="top">
- <el-button link type="primary" @click="handleEdit(row)">
- <el-icon><Edit /></el-icon>
- </el-button>
- </el-tooltip>
- <el-tooltip :content="row.is_synced ? '已同步' : '同步到Milvus'" placement="top">
- <el-button link type="warning" @click="handleSync(row)" :disabled="row.is_synced">
- <el-icon><Refresh /></el-icon>
- </el-button>
- </el-tooltip>
- <el-tooltip content="删除" placement="top">
- <el-button link type="danger" @click="handleDelete(row)">
- <el-icon><Delete /></el-icon>
- </el-button>
- </el-tooltip>
- <el-dropdown @command="(cmd) => handleCommand(cmd, row)" trigger="click">
- <el-button link type="info">
- <el-icon><MoreFilled /></el-icon>
- </el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="toggleStatus">
- {{ row.status === 'disabled' ? '启用' : '禁用' }}
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table>
- <div class="pagination-container">
- <div class="pagination-info">
- 显示 {{ (pagination.page - 1) * pagination.pageSize + 1 }} 到
- {{ Math.min(pagination.page * pagination.pageSize, pagination.total) }} 条,
- 共 {{ pagination.total }} 条记录
- </div>
- <el-pagination
- v-model:current-page="pagination.page"
- v-model:page-size="pagination.pageSize"
- :total="pagination.total"
- :page-sizes="[10, 20, 50, 100]"
- layout="prev, pager, next, sizes"
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- />
- </div>
- </el-card>
- <!-- Create/Edit Dialog -->
- <el-dialog
- v-model="dialogVisible"
- :title="dialogType === 'create' ? '新建知识库' : '编辑知识库'"
- width="700px"
- @close="resetForm"
- class="kb-dialog"
- >
- <el-form ref="formRef" :model="formData" :rules="rules" label-width="110px" class="kb-form">
- <div class="form-section">
- <div class="section-title">基本信息</div>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="知识库名称" prop="name">
- <el-input v-model="formData.name" placeholder="请输入知识库名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="集合名称" prop="collection_name" v-if="dialogType === 'create'">
- <el-input v-model="formData.collection_name" placeholder="Milvus集合名(英文)" />
- </el-form-item>
- <el-form-item label="集合名称" v-else>
- <el-input v-model="formData.collection_name" disabled />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-form-item label="描述" prop="description">
- <el-input
- v-model="formData.description"
- type="textarea"
- rows="3"
- placeholder="请输入关于此知识库的详细描述..."
- />
- </el-form-item>
-
- <el-row :gutter="20">
- <el-col :span="12" v-if="dialogType === 'create'">
- <el-form-item label="向量维度" prop="dimension">
- <el-input-number v-model="formData.dimension" :min="1" :step="1" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="状态" prop="status">
- <el-radio-group v-model="formData.status">
- <el-radio-button label="normal">正常</el-radio-button>
- <el-radio-button label="disabled">禁用</el-radio-button>
- </el-radio-group>
- </el-form-item>
- </el-col>
- </el-row>
- </div>
- <div class="form-section">
- <div class="section-header">
- <div class="section-title">元数据字段定义</div>
- <div class="section-desc">定义文档的额外属性,用于精确检索过滤</div>
- </div>
-
- <div class="metadata-fields-container">
- <div v-for="(field, index) in formData.metadata_fields" :key="index" class="metadata-field-card">
- <div class="field-header">
- <span class="field-index">字段 {{ index + 1 }}</span>
- <el-button
- v-if="formData.metadata_fields.length > 1"
- type="danger"
- link
- :icon="Delete"
- @click="removeMetadataField(index)"
- size="small"
- >删除</el-button>
- </div>
- <el-row :gutter="15">
- <el-col :span="6">
- <el-form-item label-width="0" style="margin-bottom: 12px;">
- <el-select
- v-model="field.field_zh_name"
- placeholder="中文名称"
- filterable
- allow-create
- default-first-option
- @change="(val) => handleFieldChange(val, index)"
- >
- <el-option
- v-for="opt in availableFields"
- :key="opt.value"
- :label="opt.label"
- :value="opt.value"
- :disabled="getDisabledOptions(index).includes(opt.value)"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label-width="0" style="margin-bottom: 12px;">
- <el-input v-model="field.field_en_name" placeholder="英文标识 (如: year)" />
- </el-form-item>
- </el-col>
- <el-col :span="5">
- <el-form-item label-width="0" style="margin-bottom: 12px;">
- <el-select v-model="field.field_type" placeholder="类型">
- <el-option label="文本" value="text" />
- <el-option label="数字" value="num" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="7">
- <el-form-item label-width="0" style="margin-bottom: 12px;">
- <el-input v-model="field.remark" placeholder="备注说明" />
- </el-form-item>
- </el-col>
- </el-row>
- </div>
-
- <div class="add-field-btn">
- <el-button type="primary" plain :icon="Plus" @click="addMetadataField" style="width: 100%">添加元数据字段</el-button>
- </div>
- </div>
- </div>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, computed } from 'vue'
- import { Search, Plus, Collection, View, Edit, Delete, MoreFilled, Refresh } from '@element-plus/icons-vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import type { FormInstance, FormRules } from 'element-plus'
- import {
- getKnowledgeBases,
- getKnowledgeBaseMetadata,
- createKnowledgeBase,
- updateKnowledgeBase,
- updateKnowledgeBaseStatus,
- deleteKnowledgeBase,
- syncKnowledgeBase,
- type KnowledgeBase
- } from '@/api/knowledge-base'
- import dayjs from 'dayjs'
- // Query Parameters
- const queryParams = reactive({
- page: 1,
- pageSize: 10,
- keyword: '',
- status: ''
- })
- // Table Data
- const loading = ref(false)
- const tableData = ref<KnowledgeBase[]>([])
- const pagination = reactive({
- page: 1,
- pageSize: 10,
- total: 0
- })
- // Dialog
- const dialogVisible = ref(false)
- const dialogType = ref<'create' | 'edit'>('create')
- const submitLoading = ref(false)
- const formRef = ref<FormInstance>()
- const formData = reactive({
- id: '',
- name: '',
- collection_name: '',
- description: '',
- dimension: 768,
- status: 'normal',
- metadata_fields: [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
- })
- // 可选字段列表
- const availableFields = [
- { label: "文件名称", value: "文件名称" },
- { label: "标准编号", value: "标准编号" },
- { label: "发布单位", value: "发布单位" },
- { label: "文件类型", value: "文件类型" },
- { label: "专业领域", value: "专业领域" },
- { label: "时效性", value: "时效性" },
- { label: "文档层级信息", value: "文档层级信息" },
- { label: "文件URL", value: "文件URL" },
- { label: "施工方案类型适配", value: "施工方案类型适配" }
- ]
- // 计算每个字段下拉框的禁用选项
- const getDisabledOptions = (currentIndex: number) => {
- // 获取所有已被选中的字段名
- const selectedValues = formData.metadata_fields
- .map((f, idx) => idx !== currentIndex ? f.field_zh_name : null)
- .filter(v => v !== null && v !== '')
-
- return selectedValues
- }
- const rules: FormRules = {
- name: [{ required: true, message: '请输入知识库名称', trigger: 'blur' }],
- collection_name: [
- { required: true, message: '请输入集合名称', trigger: 'blur' },
- { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '必须以字母或下划线开头,只能包含字母、数字和下划线', trigger: 'blur' }
- ],
- dimension: [{ required: true, message: '请输入维度', trigger: 'blur' }]
- }
- // Methods
- const loadData = async () => {
- loading.value = true
- try {
- const res = await getKnowledgeBases({
- page: pagination.page,
- page_size: pagination.pageSize,
- keyword: queryParams.keyword,
- status: queryParams.status
- })
- tableData.value = res.data
- pagination.total = res.meta?.total || 0
- } catch (error) {
- console.error(error)
- } finally {
- loading.value = false
- }
- }
- const handleSearch = () => {
- pagination.page = 1
- loadData()
- }
- const handleSizeChange = (val: number) => {
- pagination.pageSize = val
- loadData()
- }
- const handleCurrentChange = (val: number) => {
- pagination.page = val
- loadData()
- }
- const handleSelectionChange = (val: KnowledgeBase[]) => {
- // console.log(val)
- }
- const handleFieldChange = (val: string, index: number) => {
- // 自动映射中英文名称和类型
- const map: Record<string, any> = {
- '文件名称': { en: 'file_name', type: 'text' },
- '标准编号': { en: 'standard_number', type: 'text' },
- '发布单位': { en: 'issuing_authority', type: 'text' },
- '文件类型': { en: 'document_type', type: 'text' },
- '专业领域': { en: 'professional_field', type: 'text' },
- '时效性': { en: 'validity', type: 'text' },
- '文档层级信息': { en: 'hierarchy', type: 'text' },
- '文件URL': { en: 'file_url', type: 'text' },
- '施工方案类型适配': { en: 'plan_type_list', type: 'text' }
- }
-
- if (map[val]) {
- formData.metadata_fields[index].field_en_name = map[val].en
- formData.metadata_fields[index].field_type = map[val].type
- }
- }
- const handleAdd = () => {
- dialogType.value = 'create'
- formData.id = ''
- formData.name = ''
- formData.collection_name = ''
- formData.description = ''
- formData.dimension = 768
- formData.status = 'normal'
- formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
- dialogVisible.value = true
- }
- const handleEdit = async (row: KnowledgeBase) => {
- dialogType.value = 'edit'
- formData.id = row.id
- formData.name = row.name
- formData.collection_name = row.collection_name
- formData.description = row.description || ''
- formData.status = row.status
-
- // 加载元数据字段,允许编辑
- try {
- const res = await getKnowledgeBaseMetadata(row.id)
- if (res.code === 0 && res.data) {
- // res.data.metadata_fields 是从 t_samp_metadata 获取的
- if (res.data.metadata_fields && res.data.metadata_fields.length > 0) {
- formData.metadata_fields = res.data.metadata_fields
- } else {
- formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
- }
- }
- } catch (error) {
- console.error("加载元数据失败", error)
- formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
- }
-
- dialogVisible.value = true
- }
- const handleSync = async (row: KnowledgeBase) => {
- try {
- await syncKnowledgeBase(row.id)
- ElMessage.success('同步成功,Milvus集合已创建')
- loadData()
- } catch (error) {
- // error handled by request interceptor
- }
- }
- const handleView = async (row: KnowledgeBase) => {
- // 复用编辑时的逻辑加载元数据,但不打开编辑弹窗
- try {
- const res = await getKnowledgeBaseMetadata(row.id)
- let metadataInfo = ''
- let schemaInfo = ''
-
- if (res.code === 0 && res.data) {
- if (res.data.metadata_fields && res.data.metadata_fields.length > 0) {
- metadataInfo = res.data.metadata_fields.map((f: any) =>
- `<li>${f.field_zh_name} (${f.field_en_name}): ${f.field_type === 'text' ? '文本' : '数字'} - ${f.remark || '无备注'}</li>`
- ).join('')
- }
-
- if (res.data.custom_schemas && res.data.custom_schemas.length > 0) {
- schemaInfo = res.data.custom_schemas.map((s: any) =>
- `<li>${s.field_name}: ${s.field_type} ${s.max_length ? `(Max: ${s.max_length})` : ''} - ${s.description || '无描述'}</li>`
- ).join('')
- }
- }
-
- const content = `
- <div style="text-align: left;">
- <p><strong>名称:</strong> ${row.name}</p>
- <p><strong>集合:</strong> ${row.collection_name}</p>
- <p><strong>描述:</strong> ${row.description || '暂无'}</p>
- <p><strong>状态:</strong> ${getStatusText(row.status)}</p>
- <p><strong>文档数:</strong> ${row.document_count}</p>
- <p><strong>创建时间:</strong> ${formatTime(row.created_at)}</p>
- <br>
- <p><strong>自定义 Schema (Milvus):</strong></p>
- <ul>
- <li>pk: INT64 (主键)</li>
- <li>text: VARCHAR (65535) - 内容</li>
- <li>vector: FLOAT_VECTOR (768) - 向量列</li>
- <li>sparse: BM25 - 关键字检索</li>
- <li>document_id: VARCHAR (128) - 文档ID</li>
- <li>parent_id: VARCHAR (128) - 父段ID</li>
- <li>index: INT64 - 索引序号</li>
- <li>tag_list: VARCHAR (2048) - 标签</li>
- <li>permission: JSON - 权限</li>
- <li>metadata: JSON - 元数据</li>
- <li>is_deleted: BOOL - 删除标志</li>
- <li>created_by: VARCHAR (128) - 创建人</li>
- <li>created_time: INT64 - 创建时间</li>
- <li>updated_by: VARCHAR (128) - 修改人</li>
- <li>updated_time: INT64 - 修改时间</li>
- </ul>
- <br>
- <p><strong>元数据字段 (Metadata):</strong></p>
- <ul>${metadataInfo || '<li>无元数据字段</li>'}</ul>
- </div>
- `
-
- ElMessageBox.alert(content, '知识库详情', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '关闭',
- customStyle: { maxWidth: '600px' }
- })
- } catch (error) {
- console.error("加载详情失败", error)
- ElMessage.error("获取详情失败")
- }
- }
- const handleDelete = (row: KnowledgeBase) => {
- // 预先检查,提升用户体验
- if (row.document_count > 0) {
- ElMessage.warning(`知识库 "${row.name}" 中仍有 ${row.document_count} 条文档,请先清空文档后再删除`)
- return
- }
- ElMessageBox.confirm(
- `确定要删除知识库 "${row.name}" 吗?此操作不可恢复,且会删除对应的Milvus集合。`,
- '警告',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(async () => {
- try {
- await deleteKnowledgeBase(row.id)
- ElMessage.success('删除成功')
- loadData()
- } catch (error) {
- // error handled by request interceptor
- }
- })
- .catch(() => {})
- }
- const handleCommand = async (command: string, row: KnowledgeBase) => {
- if (command === 'toggleStatus') {
- const newStatus = row.status === 'disabled' ? 'normal' : 'disabled'
- try {
- await updateKnowledgeBaseStatus(row.id, newStatus)
- ElMessage.success('状态更新成功')
- loadData()
- } catch (error) {
- console.error(error)
- }
- }
- }
- const addMetadataField = () => {
- formData.metadata_fields.push({ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' })
- }
- const removeMetadataField = (index: number) => {
- formData.metadata_fields.splice(index, 1)
- }
- const handleSubmit = async () => {
- if (!formRef.value) return
- await formRef.value.validate(async (valid) => {
- if (valid) {
- submitLoading.value = true
- try {
- if (dialogType.value === 'create') {
- await createKnowledgeBase({
- name: formData.name,
- collection_name: formData.collection_name,
- description: formData.description,
- dimension: formData.dimension,
- metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
- })
- ElMessage.success('创建成功')
- } else {
- await updateKnowledgeBase(formData.id, {
- name: formData.name,
- description: formData.description,
- status: formData.status,
- metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
- })
- ElMessage.success('更新成功')
- }
- dialogVisible.value = false
- loadData()
- } catch (error) {
- console.error(error)
- } finally {
- submitLoading.value = false
- }
- }
- })
- }
- const resetForm = () => {
- if (formRef.value) {
- formRef.value.resetFields()
- }
- }
- // Helpers
- const getStatusType = (status: string) => {
- const map: Record<string, string> = {
- normal: 'success',
- test: 'info',
- disabled: 'danger'
- }
- return map[status] || 'info'
- }
- const getStatusText = (status: string) => {
- const map: Record<string, string> = {
- normal: '正常',
- test: '测试',
- disabled: '已禁用'
- }
- return map[status] || status
- }
- const formatTime = (time: string) => {
- if (!time) return '-'
- return dayjs(time).format('YYYY-MM-DD HH:mm')
- }
- // 为图标生成随机颜色或基于名称的固定颜色
- const getIconColor = (name: string) => {
- const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc']
- let hash = 0
- for (let i = 0; i < name.length; i++) {
- hash = name.charCodeAt(i) + ((hash << 5) - hash)
- }
- const index = Math.abs(hash) % colors.length
- return colors[index]
- }
- onMounted(() => {
- loadData()
- })
- </script>
- <style scoped>
- .kb-container {
- padding: 20px;
- }
- .header-section {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- }
- .title-info h2 {
- margin: 0;
- font-size: 20px;
- font-weight: 600;
- color: #303133;
- }
- .subtitle {
- margin: 8px 0 0;
- color: #909399;
- font-size: 14px;
- }
- .table-card {
- border-radius: 8px;
- }
- .kb-name-cell {
- display: flex;
- align-items: flex-start;
- padding: 8px 0;
- }
- .kb-icon {
- margin-right: 12px;
- margin-top: 4px;
- }
- .kb-info {
- flex: 1;
- }
- .kb-info .name {
- font-weight: 600;
- font-size: 14px;
- color: #303133;
- margin-bottom: 4px;
- }
- .kb-info .desc {
- font-size: 12px;
- color: #909399;
- line-height: 1.4;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- overflow: hidden;
- }
- .pagination-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 20px;
- }
- .pagination-info {
- color: #909399;
- font-size: 13px;
- }
- .kb-dialog :deep(.el-dialog__body) {
- padding: 20px 30px;
- }
- .kb-form {
- padding-right: 10px;
- }
- .form-section {
- margin-bottom: 24px;
- }
- .section-title {
- font-size: 15px;
- font-weight: 600;
- color: #303133;
- margin-bottom: 16px;
- padding-left: 10px;
- border-left: 3px solid #409eff;
- }
- .section-header {
- display: flex;
- align-items: baseline;
- margin-bottom: 16px;
- }
- .section-desc {
- font-size: 12px;
- color: #909399;
- margin-left: 10px;
- }
- .metadata-field-card {
- background-color: #f8f9fa;
- border-radius: 4px;
- border: 1px solid #e4e7ed;
- padding: 12px 16px;
- margin-bottom: 12px;
- }
- .field-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 10px;
- }
- .field-index {
- font-size: 12px;
- font-weight: 600;
- color: #606266;
- background-color: #e6e8eb;
- padding: 2px 6px;
- border-radius: 2px;
- }
- .add-field-btn {
- margin-top: 10px;
- }
- .metadata-fields-container {
- padding: 0 5px;
- }
- .delete-btn {
- margin-left: 10px;
- }
- </style>
|