Преглед на файлове

增加数据字典功能

lingmin_package@163.com преди 1 ден
родител
ревизия
fee8efac7c

+ 149 - 0
src/api/dict.ts

@@ -0,0 +1,149 @@
+import request from './request'
+import type { ApiResponse } from '@/types/auth'
+
+// 字典类型接口
+export interface DictCategory {
+  category_id: string
+  category_no?: string
+  category_name: string
+  category_desc?: string
+  parent_field: string
+  category_level?: string
+  del_flag: string
+  created_by?: string
+  created_time?: string
+  updated_by?: string
+  updated_time?: string
+  created_by_name?: string
+  updated_by_name?: string
+  children?: DictCategory[]
+}
+
+// 字典项接口
+export interface DictItem {
+  dict_id: number
+  dict_name: string
+  dict_value: string
+  dict_desc?: string
+  category_id: string
+  category_name?: string
+  enable_flag: string
+  del_flag: string
+  sort?: number
+  created_by?: string
+  created_time?: string
+  updated_by?: string
+  updated_time?: string
+  created_by_name?: string
+  updated_by_name?: string
+}
+
+// 字典类型创建/更新参数
+export interface DictCategoryForm {
+  category_no?: string
+  category_name: string
+  category_desc?: string
+  parent_field?: string
+  category_level?: string
+}
+
+// 字典项创建/更新参数
+export interface DictItemForm {
+  dict_name: string
+  dict_value: string
+  dict_desc?: string
+  category_id: string
+  enable_flag?: string
+  sort?: number
+}
+
+export const dictApi = {
+  // ==================== 字典类型管理 ====================
+  
+  // 获取字典类型树形结构
+  getCategoryTree(): Promise<ApiResponse<DictCategory[]>> {
+    return request.get('/api/v1/dict/category/tree')
+  },
+
+  // 获取字典类型列表(分页)
+  getCategoryList(params: {
+    keyword?: string
+    page: number
+    page_size: number
+  }): Promise<ApiResponse<{
+    list: DictCategory[]
+    total: number
+    page: number
+    page_size: number
+  }>> {
+    return request.post('/api/v1/dict/category/list', params)
+  },
+
+  // 获取字典类型详情
+  getCategoryDetail(categoryId: string): Promise<ApiResponse<DictCategory>> {
+    return request.get(`/api/v1/dict/category/${categoryId}`)
+  },
+
+  // 创建字典类型
+  createCategory(data: DictCategoryForm): Promise<ApiResponse<{ category_id: string }>> {
+    return request.post('/api/v1/dict/category', data)
+  },
+
+  // 更新字典类型
+  updateCategory(categoryId: string, data: Partial<DictCategoryForm>): Promise<ApiResponse> {
+    return request.put(`/api/v1/dict/category/${categoryId}`, data)
+  },
+
+  // 删除字典类型
+  deleteCategory(categoryId: string): Promise<ApiResponse> {
+    return request.delete(`/api/v1/dict/category/${categoryId}`)
+  },
+
+  // ==================== 字典项管理 ====================
+  
+  // 获取字典项列表(分页)
+  getItemList(params: {
+    category_id?: string
+    keyword?: string
+    enable_flag?: string
+    page: number
+    page_size: number
+  }): Promise<ApiResponse<{
+    list: DictItem[]
+    total: number
+    page: number
+    page_size: number
+  }>> {
+    return request.post('/api/v1/dict/item/list', params)
+  },
+
+  // 获取字典项详情
+  getItemDetail(dictId: number): Promise<ApiResponse<DictItem>> {
+    return request.get(`/api/v1/dict/item/${dictId}`)
+  },
+
+  // 创建字典项
+  createItem(data: DictItemForm): Promise<ApiResponse<{ dict_id: number }>> {
+    return request.post('/api/v1/dict/item', data)
+  },
+
+  // 更新字典项
+  updateItem(dictId: number, data: Partial<DictItemForm>): Promise<ApiResponse> {
+    return request.put(`/api/v1/dict/item/${dictId}`, data)
+  },
+
+  // 删除字典项
+  deleteItem(dictId: number): Promise<ApiResponse> {
+    return request.delete(`/api/v1/dict/item/${dictId}`)
+  },
+
+  // 批量删除字典项
+  batchDeleteItems(dictIds: number[]): Promise<ApiResponse> {
+    return request.post('/api/v1/dict/item/batch-delete', { dict_ids: dictIds })
+  },
+
+  // 切换字典项启用状态
+  toggleItemStatus(dictId: number, enableFlag: string): Promise<ApiResponse> {
+    return request.put(`/api/v1/dict/item/${dictId}/toggle-status?enable_flag=${enableFlag}`)
+  }
+}

+ 1 - 0
src/api/knowledge-base.ts

@@ -27,6 +27,7 @@ export interface MetadataField {
   field_zh_name: string
   field_en_name: string
   field_type: string
+  category_id?: string
   remark?: string
 }
 

+ 1 - 1
src/api/request-simple.ts

@@ -51,7 +51,7 @@ request.interceptors.response.use(
     }
     
     const { code, message } = response.data ?? {}
-    if (code === 0 || code === 200 || typeof code === 'undefined') {
+    if ((code === '000000' || code === 0) || code === 200 || typeof code === 'undefined') {
       return response.data
     }
     

+ 5 - 5
src/api/request.ts

@@ -75,9 +75,9 @@ request.interceptors.response.use(
     
     const { code, message } = response.data ?? {}
 
-    // 检查业务错误码是否为401(无效的访问令牌)
-    if (code === 401) {
-      console.log('>>> 检测到业务错误码401(HTTP状态码为200)')
+    // 检查业务错误码是否为401或"200002"(无效的访问令牌)
+    if (code === 401 || code === '200002') {
+      console.log('>>> 检测到业务错误码401或200002(HTTP状态码为200)')
       console.log('>>> 立即跳转到登录页')
       
       // 清除所有存储
@@ -102,8 +102,8 @@ request.interceptors.response.use(
       return new Promise(() => {})
     }
 
-    // 成功响应(兼容 code=0 和 code=200,以及无 code 的纯数据/Blob 返回)
-    if (code === 0 || code === 200 || typeof code === 'undefined') {
+    // 成功响应(兼容字符串 code="000000"旧的整数 code=0/200,以及无 code 的纯数据/Blob 返回)
+    if (code === '000000' || (code === '000000' || code === 0) || code === 200 || typeof code === 'undefined') {
       return response.data
     }
     

+ 1 - 1
src/layouts/MainLayout.vue

@@ -188,7 +188,7 @@ const loadUserMenus = async () => {
   menuLoading.value = true
   try {
     const result = await request.get<any, ApiResponse<MenuItem[]>>('/api/v1/system/user/menus')
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       userMenus.value = result.data
       console.log('用户菜单加载成功:', result.data)
     } else {

+ 6 - 0
src/router/index.ts

@@ -100,6 +100,12 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/views/admin/Tag.vue'),
         meta: { requiresAdmin: true }
       },
+      {
+        path: 'admin/dictionary',
+        name: 'AdminDictionary',
+        component: () => import('@/views/admin/Dictionary.vue'),
+        meta: { requiresAdmin: true }
+      },
       {
         path: 'admin/documents',
         name: 'Documents',

+ 1 - 1
src/stores/auth.ts

@@ -101,7 +101,7 @@ export const useAuthStore = defineStore('auth', () => {
     
     try {
       const response: any = await request.get('/api/v1/system/user/menus')
-      if (response.code === 0) {
+      if (response.code === '000000' || response.code === 0) {
         userMenus.value = response.data
       }
     } catch (error) {

+ 7 - 7
src/views/admin/Apps.vue

@@ -651,7 +651,7 @@ const loadApps = async () => {
     
     const result = await request.get('/api/v1/system/apps', { params })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       apps.value = result.data.items || []
       total.value = result.data.total || 0
     } else {
@@ -717,7 +717,7 @@ const showSecret = async (app: any) => {
     // 获取包含密钥的完整应用信息
     const result = await request.get(`/api/v1/system/apps/${app.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       selectedApp.value = result.data
       showSecretValue.value = false
       showAppSecret.value = true
@@ -763,7 +763,7 @@ const toggleAppStatus = async (app: any) => {
       is_active: !app.is_active
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       app.is_active = !app.is_active
       ElMessage.success(`应用已${action}`)
     } else {
@@ -794,7 +794,7 @@ const resetAppSecret = async (app: any) => {
     // 调用API重置密钥
     const result = await request.post(`/api/v1/system/apps/${app.id}/reset-secret`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       // 更新应用的密钥
       app.app_secret = result.data.app_secret
       ElMessage.success('应用密钥已重置')
@@ -832,7 +832,7 @@ const deleteApp = async (app: any) => {
     // 调用API删除应用
     const result = await request.delete(`/api/v1/system/apps/${app.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用已删除')
       loadApps() // 重新加载列表
     } else {
@@ -945,7 +945,7 @@ const submitCreateForm = async () => {
     
     const result = await request.post('/api/v1/system/apps', createData)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用创建成功')
       showCreateDialog.value = false
       loadApps() // 重新加载列表
@@ -986,7 +986,7 @@ const submitEditForm = async () => {
     
     const result = await request.put(`/api/v1/system/apps/${selectedApp.value.id}`, updateData)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用更新成功')
       showEditApp.value = false
       

+ 897 - 0
src/views/admin/Dictionary.vue

@@ -0,0 +1,897 @@
+<template>
+  <div class="dictionary-management">
+    <div class="page-header">
+      <h2>字典管理</h2>
+      <p>管理系统字典类型和字典项</p>
+    </div>
+
+    <!-- 主容器:树 + 列表 -->
+    <div class="content-container">
+      <!-- 左侧:字典类型树 -->
+      <div class="tree-panel">
+        <div class="tree-header">
+          <h3>字典类型</h3>
+          <div class="tree-actions">
+            <el-button type="primary" size="small" @click="openCreateCategoryDialog">
+              <el-icon><Plus /></el-icon>
+              新增
+            </el-button>
+            <el-button 
+              type="warning" 
+              size="small" 
+              @click="openEditCategoryDialog" 
+              :disabled="!selectedCategory"
+            >
+              <el-icon><Edit /></el-icon>
+              修改
+            </el-button>
+            <el-button 
+              type="danger" 
+              size="small" 
+              @click="deleteCategoryConfirm" 
+              :disabled="!selectedCategory"
+            >
+              <el-icon><Delete /></el-icon>
+              删除
+            </el-button>
+          </div>
+        </div>
+        <el-tree
+          ref="treeRef"
+          :data="categoryTree"
+          node-key="category_id"
+          :props="{ children: 'children', label: 'category_name' }"
+          default-expand-all
+          highlight-current
+          @node-click="handleTreeNodeClick"
+          v-loading="treeLoading"
+        />
+      </div>
+
+      <!-- 右侧:字典项列表 -->
+      <div class="list-panel">
+        <div class="list-header">
+          <div v-if="selectedCategory">
+            <h3>{{ selectedCategory.category_name }} 中的字典项</h3>
+          </div>
+          <div v-else>
+            <h3>请在左侧选择字典类型</h3>
+          </div>
+          <div class="list-actions" v-if="selectedCategory">
+            <el-input
+              v-model="searchKeyword"
+              placeholder="搜索字典项"
+              style="width: 200px; margin-right: 10px"
+              clearable
+              @clear="loadItems"
+            >
+              <template #prefix>
+                <el-icon><Search /></el-icon>
+              </template>
+            </el-input>
+            <el-select
+              v-model="searchEnableFlag"
+              placeholder="状态"
+              style="width: 120px; margin-right: 10px"
+              clearable
+              @change="loadItems"
+            >
+              <el-option label="全部" value="" />
+              <el-option label="启用" value="1" />
+              <el-option label="禁用" value="0" />
+            </el-select>
+            <el-button type="primary" @click="loadItems">
+              <el-icon><Search /></el-icon>
+              查询
+            </el-button>
+            <el-button type="primary" @click="openCreateItemDialog">
+              <el-icon><Plus /></el-icon>
+              新增
+            </el-button>
+            <el-button 
+              type="danger" 
+              @click="batchDeleteItems"
+              :disabled="selectedItems.length === 0"
+            >
+              <el-icon><Delete /></el-icon>
+              批量删除
+            </el-button>
+          </div>
+        </div>
+
+        <!-- 字典项列表 -->
+        <el-table
+          v-if="selectedCategory"
+          v-loading="loading"
+          :data="items"
+          style="width: 100%"
+          @selection-change="handleSelectionChange"
+        >
+          <el-table-column type="selection" width="55" />
+          <el-table-column prop="dict_name" label="字典名称" min-width="150" show-overflow-tooltip />
+          <el-table-column prop="dict_value" label="字典值" width="120" show-overflow-tooltip />
+          <el-table-column prop="dict_desc" label="字典备注" min-width="150" show-overflow-tooltip>
+            <template #default="{ row }">
+              <el-tooltip v-if="row.dict_desc && row.dict_desc.length > 100" :content="row.dict_desc" placement="top">
+                <span>{{ row.dict_desc.substring(0, 100) }}...</span>
+              </el-tooltip>
+              <span v-else>{{ row.dict_desc }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="sort" label="排序" width="80" align="center" />
+          <el-table-column prop="enable_flag" label="状态" width="100" align="center">
+            <template #default="{ row }">
+              <el-switch
+                v-model="row.enable_flag"
+                active-value="1"
+                inactive-value="0"
+                @change="handleStatusChange(row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column prop="created_by_name" label="创建人" width="100" />
+          <el-table-column prop="created_time" label="创建时间" width="160">
+            <template #default="{ row }">
+              {{ formatDate(row.created_time) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="updated_by_name" label="修改人" width="100" />
+          <el-table-column prop="updated_time" label="修改时间" width="160">
+            <template #default="{ row }">
+              {{ formatDate(row.updated_time) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="150" fixed="right">
+            <template #default="{ row }">
+              <el-button type="warning" size="small" @click="editItem(row)">
+                修改
+              </el-button>
+              <el-button type="danger" size="small" @click="deleteItem(row)">
+                删除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div v-else class="empty-state">
+          <el-empty description="请在左侧选择一个字典类型" />
+        </div>
+
+        <!-- 分页 -->
+        <div v-if="selectedCategory" class="pagination">
+          <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[10, 20, 50, 100]"
+            :total="total"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          />
+        </div>
+      </div>
+    </div>
+
+    <!-- 创建/编辑字典类型对话框 -->
+    <el-dialog
+      v-model="showCategoryDialog"
+      :title="editingCategory ? '编辑字典类型' : '创建字典类型'"
+      width="600px"
+      @close="resetCategoryForm"
+    >
+      <el-form
+        ref="categoryFormRef"
+        :model="categoryForm"
+        :rules="categoryRules"
+        label-width="120px"
+      >
+        <el-form-item label="字典类型ID" prop="category_id" v-if="editingCategory">
+          <el-input v-model="categoryForm.category_id" disabled />
+        </el-form-item>
+        <el-form-item label="字典类型编号" prop="category_no">
+          <el-input v-model="categoryForm.category_no" placeholder="请输入字典类型编号" maxlength="512" />
+        </el-form-item>
+        <el-form-item label="字典类型名称" prop="category_name">
+          <el-input v-model="categoryForm.category_name" placeholder="请输入字典类型名称" maxlength="512" />
+        </el-form-item>
+        <el-form-item label="字典类型备注" prop="category_desc">
+          <el-input 
+            v-model="categoryForm.category_desc" 
+            type="textarea" 
+            :rows="3"
+            placeholder="请输入字典类型备注" 
+            maxlength="512"
+            show-word-limit
+          />
+        </el-form-item>
+        <el-form-item label="父节点" prop="parent_field">
+          <el-tree-select
+            v-model="categoryForm.parent_field"
+            :data="categoryTreeForSelect"
+            node-key="category_id"
+            :props="{ children: 'children', label: 'category_name' }"
+            placeholder="请选择父节点(默认为根节点)"
+            check-strictly
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="字典层级" prop="category_level">
+          <el-input v-model="categoryForm.category_level" placeholder="请输入字典层级" maxlength="1" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="showCategoryDialog = false">取消</el-button>
+        <el-button type="primary" @click="submitCategoryForm" :loading="submitting">
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 创建/编辑字典项对话框 -->
+    <el-dialog
+      v-model="showItemDialog"
+      :title="editingItem ? '编辑字典项' : '创建字典项'"
+      width="600px"
+      @close="resetItemForm"
+    >
+      <el-form
+        ref="itemFormRef"
+        :model="itemForm"
+        :rules="itemRules"
+        label-width="120px"
+      >
+        <el-form-item label="字典名称" prop="dict_name">
+          <el-input v-model="itemForm.dict_name" placeholder="请输入字典名称" maxlength="512" />
+        </el-form-item>
+        <el-form-item label="字典值" prop="dict_value">
+          <el-input v-model="itemForm.dict_value" placeholder="请输入字典值" maxlength="512" />
+        </el-form-item>
+        <el-form-item label="字典备注" prop="dict_desc">
+          <el-input 
+            v-model="itemForm.dict_desc" 
+            type="textarea" 
+            :rows="3"
+            placeholder="请输入字典备注" 
+            maxlength="512"
+            show-word-limit
+          />
+        </el-form-item>
+        <el-form-item label="字典类型" prop="category_id">
+          <el-tree-select
+            v-model="itemForm.category_id"
+            :data="categoryTree"
+            node-key="category_id"
+            :props="{ children: 'children', label: 'category_name' }"
+            placeholder="请选择字典类型"
+            check-strictly
+          />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="itemForm.sort" :min="0" controls-position="right" />
+        </el-form-item>
+        <el-form-item label="启用状态" prop="enable_flag">
+          <el-radio-group v-model="itemForm.enable_flag">
+            <el-radio label="1">启用</el-radio>
+            <el-radio label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="showItemDialog = false">取消</el-button>
+        <el-button type="primary" @click="submitItemForm" :loading="submitting">
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, computed } from 'vue'
+import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
+import { Plus, Edit, Delete, Search } from '@element-plus/icons-vue'
+import { dictApi, type DictCategory, type DictItem, type DictCategoryForm, type DictItemForm } from '@/api/dict'
+
+// 树相关
+const treeRef = ref()
+const treeLoading = ref(false)
+const categoryTree = ref<DictCategory[]>([])
+const selectedCategory = ref<DictCategory | null>(null)
+
+// 列表相关
+const loading = ref(false)
+const items = ref<DictItem[]>([])
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const selectedItems = ref<DictItem[]>([])
+
+// 搜索相关
+const searchKeyword = ref('')
+const searchEnableFlag = ref('')
+
+// 字典类型对话框
+const showCategoryDialog = ref(false)
+const editingCategory = ref(false)
+const categoryFormRef = ref<FormInstance>()
+const categoryForm = reactive<DictCategoryForm & { category_id?: string }>({
+  category_id: '',
+  category_no: '',
+  category_name: '',
+  category_desc: '',
+  parent_field: '0',
+  category_level: ''
+})
+
+const categoryRules: FormRules = {
+  category_name: [
+    { required: true, message: '请输入字典类型名称', trigger: 'blur' },
+    { max: 512, message: '字典类型名称不能超过512个字符', trigger: 'blur' }
+  ],
+  category_no: [
+    { max: 512, message: '字典类型编号不能超过512个字符', trigger: 'blur' }
+  ],
+  category_desc: [
+    { max: 512, message: '字典类型备注不能超过512个字符', trigger: 'blur' }
+  ]
+}
+
+// 字典项对话框
+const showItemDialog = ref(false)
+const editingItem = ref(false)
+const itemFormRef = ref<FormInstance>()
+const itemForm = reactive<DictItemForm & { dict_id?: number }>({
+  dict_id: undefined,
+  dict_name: '',
+  dict_value: '',
+  dict_desc: '',
+  category_id: '',
+  enable_flag: '1',
+  sort: 0
+})
+
+const itemRules: FormRules = {
+  dict_name: [
+    { required: true, message: '请输入字典名称', trigger: 'blur' },
+    { max: 512, message: '字典名称不能超过512个字符', trigger: 'blur' }
+  ],
+  dict_value: [
+    { required: true, message: '请输入字典值', trigger: 'blur' },
+    { max: 512, message: '字典值不能超过512个字符', trigger: 'blur' }
+  ],
+  dict_desc: [
+    { max: 512, message: '字典备注不能超过512个字符', trigger: 'blur' }
+  ],
+  category_id: [
+    { required: true, message: '请选择字典类型', trigger: 'change' }
+  ]
+}
+
+const submitting = ref(false)
+
+// 计算属性:用于选择父节点的树(排除当前编辑的节点)
+const categoryTreeForSelect = computed(() => {
+  if (!editingCategory.value) {
+    return [{ category_id: '0', category_name: '根节点', children: categoryTree.value }]
+  }
+  // 编辑时需要排除自己和子节点
+  const filterNode = (nodes: DictCategory[], excludeId: string): DictCategory[] => {
+    return nodes.filter(node => node.category_id !== excludeId).map(node => ({
+      ...node,
+      children: node.children ? filterNode(node.children, excludeId) : []
+    }))
+  }
+  return [{ 
+    category_id: '0', 
+    category_name: '根节点', 
+    children: filterNode(categoryTree.value, categoryForm.category_id || '')
+  }]
+})
+
+// 加载字典类型树
+const loadCategoryTree = async () => {
+  treeLoading.value = true
+  try {
+    const res = await dictApi.getCategoryTree()
+    if (res.code === '000000') {
+      categoryTree.value = res.data || []
+    } else {
+      ElMessage.error(res.message || '加载字典类型树失败')
+    }
+  } catch (error) {
+    console.error('加载字典类型树失败:', error)
+    ElMessage.error('加载字典类型树失败')
+  } finally {
+    treeLoading.value = false
+  }
+}
+
+// 树节点点击
+const handleTreeNodeClick = (data: DictCategory) => {
+  selectedCategory.value = data
+  currentPage.value = 1
+  searchKeyword.value = ''
+  searchEnableFlag.value = ''
+  loadItems()
+}
+
+// 加载字典项列表
+const loadItems = async () => {
+  if (!selectedCategory.value) return
+  
+  loading.value = true
+  try {
+    const res = await dictApi.getItemList({
+      category_id: selectedCategory.value.category_id,
+      keyword: searchKeyword.value || undefined,
+      enable_flag: searchEnableFlag.value || undefined,
+      page: currentPage.value,
+      page_size: pageSize.value
+    })
+    if (res.code === '000000') {
+      items.value = res.data.list || []
+      total.value = res.data.total || 0
+    } else {
+      ElMessage.error(res.message || '加载字典项列表失败')
+    }
+  } catch (error) {
+    console.error('加载字典项列表失败:', error)
+    ElMessage.error('加载字典项列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 分页相关
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  currentPage.value = 1
+  loadItems()
+}
+
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  loadItems()
+}
+
+// 选择变化
+const handleSelectionChange = (val: DictItem[]) => {
+  selectedItems.value = val
+}
+
+// ==================== 字典类型操作 ====================
+
+// 打开创建字典类型对话框
+const openCreateCategoryDialog = () => {
+  editingCategory.value = false
+  resetCategoryForm()
+  showCategoryDialog.value = true
+}
+
+// 打开编辑字典类型对话框
+const openEditCategoryDialog = () => {
+  if (!selectedCategory.value) return
+  
+  editingCategory.value = true
+  Object.assign(categoryForm, {
+    category_id: selectedCategory.value.category_id,
+    category_no: selectedCategory.value.category_no || '',
+    category_name: selectedCategory.value.category_name,
+    category_desc: selectedCategory.value.category_desc || '',
+    parent_field: selectedCategory.value.parent_field || '0',
+    category_level: selectedCategory.value.category_level || ''
+  })
+  showCategoryDialog.value = true
+}
+
+// 重置字典类型表单
+const resetCategoryForm = () => {
+  categoryFormRef.value?.resetFields()
+  Object.assign(categoryForm, {
+    category_id: '',
+    category_no: '',
+    category_name: '',
+    category_desc: '',
+    parent_field: '0',
+    category_level: ''
+  })
+}
+
+// 提交字典类型表单
+const submitCategoryForm = async () => {
+  if (!categoryFormRef.value) return
+  
+  await categoryFormRef.value.validate(async (valid) => {
+    if (!valid) return
+    
+    submitting.value = true
+    try {
+      const data: DictCategoryForm = {
+        category_no: categoryForm.category_no || undefined,
+        category_name: categoryForm.category_name,
+        category_desc: categoryForm.category_desc || undefined,
+        parent_field: categoryForm.parent_field || '0',
+        category_level: categoryForm.category_level || undefined
+      }
+      
+      if (editingCategory.value && categoryForm.category_id) {
+        // 更新
+        const res = await dictApi.updateCategory(categoryForm.category_id, data)
+        if (res.code === '000000') {
+          ElMessage.success('字典类型更新成功')
+          showCategoryDialog.value = false
+          await loadCategoryTree()
+        } else {
+          ElMessage.error(res.message || '字典类型更新失败')
+        }
+      } else {
+        // 创建
+        const res = await dictApi.createCategory(data)
+        if (res.code === '000000') {
+          ElMessage.success('字典类型创建成功')
+          showCategoryDialog.value = false
+          await loadCategoryTree()
+        } else {
+          ElMessage.error(res.message || '字典类型创建失败')
+        }
+      }
+    } catch (error) {
+      console.error('提交字典类型失败:', error)
+      ElMessage.error('操作失败')
+    } finally {
+      submitting.value = false
+    }
+  })
+}
+
+// 删除字典类型确认
+const deleteCategoryConfirm = () => {
+  if (!selectedCategory.value) return
+  
+  ElMessageBox.confirm(
+    `确定要删除字典类型"${selectedCategory.value.category_name}"吗?`,
+    '删除确认',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }
+  ).then(async () => {
+    await deleteCategory()
+  }).catch(() => {
+    // 取消删除
+  })
+}
+
+// 删除字典类型
+const deleteCategory = async () => {
+  if (!selectedCategory.value) return
+  
+  try {
+    const res = await dictApi.deleteCategory(selectedCategory.value.category_id)
+    if (res.code === '000000') {
+      ElMessage.success('字典类型删除成功')
+      selectedCategory.value = null
+      items.value = []
+      await loadCategoryTree()
+    } else {
+      ElMessage.error(res.message || '字典类型删除失败')
+    }
+  } catch (error) {
+    console.error('删除字典类型失败:', error)
+    ElMessage.error('删除失败')
+  }
+}
+
+// ==================== 字典项操作 ====================
+
+// 打开创建字典项对话框
+const openCreateItemDialog = () => {
+  if (!selectedCategory.value) {
+    ElMessage.warning('请先选择字典类型')
+    return
+  }
+  
+  editingItem.value = false
+  resetItemForm()
+  itemForm.category_id = selectedCategory.value.category_id
+  showItemDialog.value = true
+}
+
+// 编辑字典项
+const editItem = (row: DictItem) => {
+  editingItem.value = true
+  Object.assign(itemForm, {
+    dict_id: row.dict_id,
+    dict_name: row.dict_name,
+    dict_value: row.dict_value,
+    dict_desc: row.dict_desc || '',
+    category_id: row.category_id,
+    enable_flag: row.enable_flag,
+    sort: row.sort || 0
+  })
+  showItemDialog.value = true
+}
+
+// 重置字典项表单
+const resetItemForm = () => {
+  itemFormRef.value?.resetFields()
+  Object.assign(itemForm, {
+    dict_id: undefined,
+    dict_name: '',
+    dict_value: '',
+    dict_desc: '',
+    category_id: '',
+    enable_flag: '1',
+    sort: 0
+  })
+}
+
+// 提交字典项表单
+const submitItemForm = async () => {
+  if (!itemFormRef.value) return
+  
+  await itemFormRef.value.validate(async (valid) => {
+    if (!valid) return
+    
+    submitting.value = true
+    try {
+      const data: DictItemForm = {
+        dict_name: itemForm.dict_name,
+        dict_value: itemForm.dict_value,
+        dict_desc: itemForm.dict_desc || undefined,
+        category_id: itemForm.category_id,
+        enable_flag: itemForm.enable_flag,
+        sort: itemForm.sort
+      }
+      
+      if (editingItem.value && itemForm.dict_id) {
+        // 更新
+        const res = await dictApi.updateItem(itemForm.dict_id, data)
+        if (res.code === '000000') {
+          ElMessage.success('字典项更新成功')
+          showItemDialog.value = false
+          await loadItems()
+        } else {
+          ElMessage.error(res.message || '字典项更新失败')
+        }
+      } else {
+        // 创建
+        const res = await dictApi.createItem(data)
+        if (res.code === '000000') {
+          ElMessage.success('字典项创建成功')
+          showItemDialog.value = false
+          await loadItems()
+        } else {
+          ElMessage.error(res.message || '字典项创建失败')
+        }
+      }
+    } catch (error) {
+      console.error('提交字典项失败:', error)
+      ElMessage.error('操作失败')
+    } finally {
+      submitting.value = false
+    }
+  })
+}
+
+// 删除字典项
+const deleteItem = (row: DictItem) => {
+  ElMessageBox.confirm(
+    `确定要删除字典项"${row.dict_name}"吗?`,
+    '删除确认',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }
+  ).then(async () => {
+    try {
+      const res = await dictApi.deleteItem(row.dict_id)
+      if (res.code === '000000') {
+        ElMessage.success('字典项删除成功')
+        await loadItems()
+      } else {
+        ElMessage.error(res.message || '字典项删除失败')
+      }
+    } catch (error) {
+      console.error('删除字典项失败:', error)
+      ElMessage.error('删除失败')
+    }
+  }).catch(() => {
+    // 取消删除
+  })
+}
+
+// 批量删除字典项
+const batchDeleteItems = () => {
+  if (selectedItems.value.length === 0) {
+    ElMessage.warning('请先选择要删除的字典项')
+    return
+  }
+  
+  ElMessageBox.confirm(
+    `确定要删除选中的 ${selectedItems.value.length} 个字典项吗?`,
+    '批量删除确认',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }
+  ).then(async () => {
+    try {
+      const dictIds = selectedItems.value.map(item => item.dict_id)
+      const res = await dictApi.batchDeleteItems(dictIds)
+      if (res.code === '000000') {
+        ElMessage.success(res.message || '批量删除成功')
+        selectedItems.value = []
+        await loadItems()
+      } else {
+        ElMessage.error(res.message || '批量删除失败')
+      }
+    } catch (error) {
+      console.error('批量删除字典项失败:', error)
+      ElMessage.error('批量删除失败')
+    }
+  }).catch(() => {
+    // 取消删除
+  })
+}
+
+// 切换字典项状态
+const handleStatusChange = async (row: DictItem) => {
+  try {
+    const res = await dictApi.toggleItemStatus(row.dict_id, row.enable_flag)
+    if (res.code === '000000') {
+      ElMessage.success(res.message || '状态更新成功')
+    } else {
+      ElMessage.error(res.message || '状态更新失败')
+      // 恢复原状态
+      row.enable_flag = row.enable_flag === '1' ? '0' : '1'
+    }
+  } catch (error) {
+    console.error('切换字典项状态失败:', error)
+    ElMessage.error('状态更新失败')
+    // 恢复原状态
+    row.enable_flag = row.enable_flag === '1' ? '0' : '1'
+  }
+}
+
+// 格式化日期
+const formatDate = (dateStr: string | undefined) => {
+  if (!dateStr) return '-'
+  const date = new Date(dateStr)
+  return date.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit',
+    second: '2-digit'
+  })
+}
+
+// 初始化
+onMounted(() => {
+  loadCategoryTree()
+})
+</script>
+
+<style scoped>
+.dictionary-management {
+  padding: 20px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  margin-bottom: 20px;
+}
+
+.page-header h2 {
+  margin: 0 0 8px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.page-header p {
+  margin: 0;
+  font-size: 14px;
+  color: #909399;
+}
+
+.content-container {
+  flex: 1;
+  display: flex;
+  gap: 20px;
+  overflow: hidden;
+}
+
+.tree-panel {
+  width: 300px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.tree-header {
+  padding: 16px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.tree-header h3 {
+  margin: 0 0 12px 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.tree-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.tree-actions .el-button {
+  flex: 1;
+}
+
+.el-tree {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+}
+
+.list-panel {
+  flex: 1;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.list-header {
+  padding: 16px;
+  border-bottom: 1px solid #ebeef5;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.list-header h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.list-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.el-table {
+  flex: 1;
+}
+
+.empty-state {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.pagination {
+  padding: 16px;
+  border-top: 1px solid #ebeef5;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 1 - 1
src/views/admin/Menus.vue

@@ -586,7 +586,7 @@ const loadMenus = async () => {
       }
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       menus.value = result.data.items
       total.value = result.data.total
       

+ 3 - 3
src/views/admin/Roles.vue

@@ -344,7 +344,7 @@ const loadRoles = async () => {
       }
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       roles.value = result.data.items
       total.value = result.data.total
     } else {
@@ -457,7 +457,7 @@ const loadAllMenus = async () => {
       }
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       allMenus.value = result.data.items
     } else {
       throw new Error(result.message)
@@ -476,7 +476,7 @@ const loadRolePermissions = async (roleId: string) => {
     
     console.log('📡 Role permissions API response:', result)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       const menuIds = result.data.menu_ids || []
       console.log('✅ Received menu_ids:', menuIds)
       console.log('📊 Menu IDs count:', menuIds.length)

+ 4 - 4
src/views/admin/TaskManagement.vue

@@ -244,7 +244,7 @@ const loadTasks = async () => {
     const response = await request.get<any, ApiResponse<TaskItem[]>>('/api/v1/sample/tasks', {
       params: { type: activeTab.value }
     })
-    if (response.code === 0) {
+    if (response.code === '000000' || response.code === 0) {
       taskList.value = response.data
     } else {
       ElMessage.error(response.message || '获取任务列表失败')
@@ -269,7 +269,7 @@ const handleViewDetails = async (row: TaskItem) => {
         type: activeTab.value
       }
     })
-    if (response.code === 0) {
+    if (response.code === '000000' || response.code === 0) {
       currentProjectFiles.value = response.data
     } else {
       ElMessage.error(response.message || '获取项目详情失败')
@@ -294,7 +294,7 @@ const handleCheckProgress = async (row: TaskItem) => {
       params: { project_id: row.project_id }
     })
     
-    if (response.code === 0) {
+    if (response.code === '000000' || response.code === 0) {
       const data = response.data
       const message = `
         项目名称: ${data.project_name}
@@ -331,7 +331,7 @@ const handleExportData = async (row: TaskItem) => {
       format: 'json'
     })
     
-    if (response.code === 0) {
+    if (response.code === '000000' || response.code === 0) {
       const fileUrl = response.data.file_url
       const fileName = response.data.file_name || `export_${row.project_name || row.project_id}.json`
       

+ 4 - 4
src/views/admin/Users.vue

@@ -410,7 +410,7 @@ const loadUsers = async () => {
       }
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       users.value = result.data.items
       total.value = result.data.total
     } else {
@@ -429,7 +429,7 @@ const loadRoles = async () => {
   try {
     const result = await request.get('/api/v1/system/roles/all')
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       allRoles.value = result.data
     }
   } catch (error) {
@@ -483,7 +483,7 @@ const editUser = async (user: any) => {
     // 获取用户详情(包含角色信息)
     const result = await request.get(`/api/v1/system/admin/users/${user.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       const userDetail = result.data
       Object.assign(userForm, {
         username: userDetail.username,
@@ -581,7 +581,7 @@ const assignRoles = async (user: any) => {
     // 获取用户详情(包含角色信息)
     const result = await request.get(`/api/v1/system/admin/users/${user.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       const userDetail = result.data
       selectedRoleIds.value = userDetail.role_ids || []
     } else {

+ 2 - 2
src/views/apps/Create.vue

@@ -358,7 +358,7 @@ const removeRedirectUri = (index: number) => {
 
 // 图标上传成功
 const handleIconSuccess = (response: any) => {
-  if (response.code === 0) {
+  if (response.code === '000000' || response.code === 0) {
     appForm.icon_url = response.data.url
     ElMessage.success('图标上传成功')
   } else {
@@ -415,7 +415,7 @@ const createApp = async () => {
     const result = await response.json()
     console.log('创建应用响应:', result)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用创建成功')
       
       // 跳转到应用列表

+ 6 - 6
src/views/apps/Index.vue

@@ -508,7 +508,7 @@ const loadApps = async () => {
     const result = await request.get('/api/v1/system/apps', { params })
     console.log('API响应:', result)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       apps.value = result.data.items || []
       total.value = result.data.total || 0
       console.log(`加载了 ${apps.value.length} 个应用,总计 ${total.value} 个`)
@@ -575,7 +575,7 @@ const showSecret = async (app: any) => {
     // 获取包含密钥的完整应用信息
     const result = await request.get(`/api/v1/system/apps/${app.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       selectedApp.value = result.data
       showSecretValue.value = false
       showAppSecret.value = true
@@ -621,7 +621,7 @@ const toggleAppStatus = async (app: any) => {
       is_active: !app.is_active
     })
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       app.is_active = !app.is_active
       ElMessage.success(`应用已${action}`)
     } else {
@@ -652,7 +652,7 @@ const resetAppSecret = async (app: any) => {
     // 调用API重置密钥
     const result = await request.post(`/api/v1/system/apps/${app.id}/reset-secret`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       // 更新应用的密钥
       app.app_secret = result.data.app_secret
       ElMessage.success('应用密钥已重置')
@@ -690,7 +690,7 @@ const deleteApp = async (app: any) => {
     // 调用API删除应用
     const result = await request.delete(`/api/v1/system/apps/${app.id}`)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用已删除')
       loadApps() // 重新加载列表
     } else {
@@ -774,7 +774,7 @@ const submitEditForm = async () => {
     
     const result = await request.put(`/api/v1/system/apps/${selectedApp.value.id}`, updateData)
     
-    if (result.code === 0) {
+    if (result.code === '000000' || result.code === 0) {
       ElMessage.success('应用更新成功')
       showEditApp.value = false
       

+ 135 - 26
src/views/basic-info/ConstructionPlan.vue

@@ -17,21 +17,23 @@
           <el-col :span="6">
             <el-form-item label="方案类别">
               <el-select v-model="searchForm.plan_category" placeholder="请选择方案类别" clearable style="width: 100%">
-                <el-option v-for="item in planCategoryOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in planCategoryOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
 
           <el-col :span="6">
             <el-form-item label="一级分类">
-              <el-input v-model="searchForm.level_1_classification" placeholder="请输入一级分类" clearable @keyup.enter="handleSearch" />
+              <el-select v-model="searchForm.level_1_classification" placeholder="请选择一级分类" clearable style="width: 100%">
+                <el-option v-for="item in level1Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
+              </el-select>
             </el-form-item>
           </el-col>
 
           <el-col :span="6">
             <el-form-item label="二级分类">
               <el-select v-model="searchForm.level_2_classification" placeholder="请选择二级分类" clearable style="width: 100%">
-                <el-option v-for="item in level2Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level2Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -39,7 +41,7 @@
           <el-col :span="6">
             <el-form-item label="三级分类">
               <el-select v-model="searchForm.level_3_classification" placeholder="请选择三级分类" clearable style="width: 100%">
-                <el-option v-for="item in level3Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level3Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -47,7 +49,7 @@
           <el-col :span="6">
             <el-form-item label="四级分类">
               <el-select v-model="searchForm.level_4_classification" placeholder="请选择四级分类" clearable style="width: 100%">
-                <el-option v-for="item in level4Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level4Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -106,11 +108,31 @@
             {{ formatDate(scope.row.release_date) }}
           </template>
         </el-table-column>
-        <el-table-column prop="plan_category" label="方案类别" width="120" />
-        <el-table-column prop="level_1_classification" label="一级分类" width="120" />
-        <el-table-column prop="level_2_classification" label="二级分类" width="120" />
-        <el-table-column prop="level_3_classification" label="三级分类" width="120" />
-        <el-table-column prop="level_4_classification" label="四级分类" width="120" />
+        <el-table-column prop="plan_category" label="方案类别" width="150">
+          <template #default="scope">
+            {{ getDictName('construction_plan_type', scope.row.plan_category) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="level_1_classification" label="一级分类" width="120">
+          <template #default="scope">
+            {{ getDictName('construction_plan_first_type', scope.row.level_1_classification) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="level_2_classification" label="二级分类" width="120">
+          <template #default="scope">
+            {{ getDictName('construction_plan_second_type', scope.row.level_2_classification) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="level_3_classification" label="三级分类" width="150">
+          <template #default="scope">
+            {{ getDictName('construction_plan_third_type', scope.row.level_3_classification) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="level_4_classification" label="四级分类" width="150">
+          <template #default="scope">
+            {{ getDictName('construction_plan_fourth_type', scope.row.level_4_classification) }}
+          </template>
+        </el-table-column>
         <el-table-column label="备注" min-width="150" show-overflow-tooltip>
           <template #default="scope">
             {{ scope.row.note || '-' }}
@@ -217,33 +239,35 @@
           <el-col :span="12">
             <el-form-item label="方案类别">
               <el-select v-model="editForm.plan_category" placeholder="请选择方案类别" style="width: 100%">
-                <el-option v-for="item in planCategoryOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in planCategoryOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="一级分类">
-              <el-input v-model="editForm.level_1_classification" disabled />
+              <el-select v-model="editForm.level_1_classification" placeholder="请选择一级分类" style="width: 100%">
+                <el-option v-for="item in level1Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
+              </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="二级分类">
               <el-select v-model="editForm.level_2_classification" placeholder="请选择二级分类" style="width: 100%">
-                <el-option v-for="item in level2Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level2Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="三级分类">
               <el-select v-model="editForm.level_3_classification" placeholder="请选择三级分类" style="width: 100%">
-                <el-option v-for="item in level3Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level3Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="四级分类">
               <el-select v-model="editForm.level_4_classification" placeholder="请选择四级分类" style="width: 100%">
-                <el-option v-for="item in level4Options" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in level4Options" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -383,6 +407,7 @@ import dayjs from 'dayjs'
 import { downloadFile } from '@/utils/download'
 import { getFileExtension } from '@/utils/file'
 import { useAuthStore } from '@/stores/auth'
+import { dictApi, type DictItem } from '@/api/dict'
 
 const authStore = useAuthStore()
 const loading = ref(false)
@@ -479,11 +504,92 @@ const searchForm = reactive<any>({
   level_4_classification: ''
 })
 
-// 选项配置
-const planCategoryOptions = ['超危大方案', '超危大方案较大Ⅱ级', '超危大方案特大Ⅳ级', '超危大方案一般Ⅰ级', '超危大方案重大Ⅲ级', '危大方案', '一般方案']
-const level2Options = ['临建工程', '路基工程', '桥梁工程', '隧道工程', '其他']
-const level3Options = ['TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥', '其他']
-const level4Options = ['挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础', '其他']
+// 选项配置 - 从字典动态加载
+const planCategoryOptions = ref<DictItem[]>([])
+const level1Options = ref<DictItem[]>([])
+const level2Options = ref<DictItem[]>([])
+const level3Options = ref<DictItem[]>([])
+const level4Options = ref<DictItem[]>([])
+
+// 加载字典选项
+const loadDictOptions = async () => {
+    try {
+        // 加载方案类别
+        const planCategoryRes = await dictApi.getItemList({
+            category_id: 'construction_plan_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (planCategoryRes.code === '000000' || planCategoryRes.code === 0) {
+            planCategoryOptions.value = planCategoryRes.data.list || []
+        }
+        
+        // 加载一级分类
+        const level1Res = await dictApi.getItemList({
+            category_id: 'construction_plan_first_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (level1Res.code === '000000' || level1Res.code === 0) {
+            level1Options.value = level1Res.data.list || []
+        }
+        
+        // 加载二级分类
+        const level2Res = await dictApi.getItemList({
+            category_id: 'construction_plan_second_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (level2Res.code === '000000' || level2Res.code === 0) {
+            level2Options.value = level2Res.data.list || []
+        }
+        
+        // 加载三级分类
+        const level3Res = await dictApi.getItemList({
+            category_id: 'construction_plan_third_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (level3Res.code === '000000' || level3Res.code === 0) {
+            level3Options.value = level3Res.data.list || []
+        }
+        
+        // 加载四级分类
+        const level4Res = await dictApi.getItemList({
+            category_id: 'construction_plan_fourth_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (level4Res.code === '000000' || level4Res.code === 0) {
+            level4Options.value = level4Res.data.list || []
+        }
+    } catch (error) {
+        console.error('加载字典选项失败', error)
+    }
+}
+
+// 字典值转换为名称
+const getDictName = (categoryId: string, dictValue: string) => {
+    let items: DictItem[] = []
+    if (categoryId === 'construction_plan_type') {
+        items = planCategoryOptions.value
+    } else if (categoryId === 'construction_plan_first_type') {
+        items = level1Options.value
+    } else if (categoryId === 'construction_plan_second_type') {
+        items = level2Options.value
+    } else if (categoryId === 'construction_plan_third_type') {
+        items = level3Options.value
+    } else if (categoryId === 'construction_plan_fourth_type') {
+        items = level4Options.value
+    }
+    const item = items.find(i => i.dict_value === dictValue)
+    return item ? item.dict_name : dictValue
+}
 
 // 获取列表数据
 const loadData = async () => {
@@ -510,7 +616,7 @@ const loadData = async () => {
       size: number
     }>
 
-    if (resData.code === 0) {
+    if (resData.code === '000000' || resData.code === 0) {
       tableData.value = resData.data.items as any
       total.value = resData.data.total
     } else {
@@ -617,7 +723,7 @@ const handleAction = async (action: string, row: any) => {
       }).then(async () => {
         try {
           const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=construction_plan&id=${row.id}`)
-          if (res.code === 0) {
+          if (res.code === '000000' || res.code === 0) {
             ElMessage.success('删除成功')
             loadData()
           } else {
@@ -640,7 +746,7 @@ const loadKnowledgeBases = async () => {
         page_size: 1000
       }
     })
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       knowledgeBases.value = res.data
     }
   } catch (error) {
@@ -654,7 +760,9 @@ const handleAdd = () => {
     editForm[key] = ''
   })
   editForm.id = null
-  editForm.level_1_classification = '施工方案'
+  // 设置一级分类默认值为"施工方案"对应的字典值
+  const defaultLevel1 = level1Options.value.find(item => item.dict_name === '施工方案')
+  editForm.level_1_classification = defaultLevel1 ? defaultLevel1.dict_value : 'SC'
   compilationBasisList.value = ['']
   fileList.value = []
   formDialogVisible.value = true
@@ -682,7 +790,7 @@ const submitForm = async () => {
         content_type: file.type,
         prefix: 'basic-info/construction_plan'
       })
-      if (urlRes.code === 0) {
+      if (urlRes.code === '000000' || urlRes.code === 0) {
         const { upload_url, file_url } = urlRes.data
         await fetch(upload_url, {
           method: 'PUT',
@@ -708,7 +816,7 @@ const submitForm = async () => {
       : '/api/v1/sample/basic-info/add?type=construction_plan'
     
     const res = await request.post<ApiResponse>(url, editForm)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(editForm.id ? '修改成功' : '新增成功')
       formDialogVisible.value = false
       fileList.value = []
@@ -731,6 +839,7 @@ const openInNewWindow = () => {
 }
 
 onMounted(() => {
+  loadDictOptions()
   loadData()
 })
 </script>

+ 5 - 5
src/views/basic-info/Regulation.vue

@@ -396,7 +396,7 @@ const loadData = async () => {
       size: number
     }>
 
-    if (resData.code === 0) {
+    if (resData.code === '000000' || resData.code === 0) {
       tableData.value = resData.data.items as any
       total.value = resData.data.total
     } else {
@@ -502,7 +502,7 @@ const handleAction = async (action: string, row: any) => {
       }).then(async () => {
         try {
           const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=regulation&id=${row.id}`)
-          if (res.code === 0) {
+          if (res.code === '000000' || res.code === 0) {
             ElMessage.success('删除成功')
             loadData()
           } else {
@@ -525,7 +525,7 @@ const loadKnowledgeBases = async () => {
         page_size: 1000
       }
     })
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       knowledgeBases.value = res.data
     }
   } catch (error) {
@@ -565,7 +565,7 @@ const submitForm = async () => {
         content_type: file.type,
         prefix: 'basic-info/regulation'
       })
-      if (urlRes.code === 0) {
+      if (urlRes.code === '000000' || urlRes.code === 0) {
         const { upload_url, file_url } = urlRes.data
         await fetch(upload_url, {
           method: 'PUT',
@@ -589,7 +589,7 @@ const submitForm = async () => {
       : '/api/v1/sample/basic-info/add?type=regulation'
     
     const res = await request.post<ApiResponse>(url, editForm)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(editForm.id ? '修改成功' : '新增成功')
       formDialogVisible.value = false
       fileList.value = []

+ 82 - 21
src/views/basic-info/Standard.vue

@@ -26,7 +26,7 @@
           <el-col :span="6">
             <el-form-item label="文件类型">
               <el-select v-model="searchForm.document_type" placeholder="请选择文件类型" clearable style="width: 100%">
-                <el-option v-for="item in documentTypeOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in documentTypeOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -35,7 +35,7 @@
           <el-col :span="6">
             <el-form-item label="专业领域">
               <el-select v-model="searchForm.professional_field" placeholder="请选择专业领域" clearable style="width: 100%">
-                <el-option v-for="item in professionalFieldOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in professionalFieldOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -45,9 +45,7 @@
           <el-col :span="6">
             <el-form-item label="时效性">
               <el-select v-model="searchForm.validity" placeholder="请选择时效性" clearable style="width: 100%">
-                <el-option label="现行" value="XH" />
-                <el-option label="废止" value="FZ" />
-                <el-option label="试行" value="SX" />
+                <el-option v-for="item in validityOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -109,8 +107,16 @@
             {{ formatDate(scope.row.release_date) }}
           </template>
         </el-table-column>
-        <el-table-column prop="document_type" label="文档类型" width="120" />
-        <el-table-column prop="professional_field" label="专业领域" width="120" />
+        <el-table-column prop="document_type" label="文档类型" width="120">
+          <template #default="scope">
+            {{ getDictName('file_type', scope.row.document_type) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="professional_field" label="专业领域" width="120">
+          <template #default="scope">
+            {{ getDictName('professional_type', scope.row.professional_field) }}
+          </template>
+        </el-table-column>
         <el-table-column label="参编单位" min-width="150">
           <template #default="scope">
             <template v-if="scope.row.participating_units">
@@ -194,7 +200,7 @@
         <el-table-column prop="validity" label="时效性" width="100">
           <template #default="scope">
             <el-tag :type="getValidityType(scope.row.validity)">
-              {{ formatValidity(scope.row.validity) }}
+              {{ getDictName('time_effect', scope.row.validity) }}
             </el-tag>
           </template>
         </el-table-column>
@@ -311,7 +317,7 @@
           <el-col :span="12">
             <el-form-item label="文件类型">
               <el-select v-model="editForm.document_type" placeholder="请选择文件类型" style="width: 100%">
-                <el-option v-for="item in documentTypeOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in documentTypeOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -319,7 +325,7 @@
           <el-col :span="12">
             <el-form-item label="专业领域">
               <el-select v-model="editForm.professional_field" placeholder="请选择专业领域" style="width: 100%">
-                <el-option v-for="item in professionalFieldOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in professionalFieldOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -327,9 +333,7 @@
           <el-col :span="12">
             <el-form-item label="时效性">
               <el-select v-model="editForm.validity" placeholder="请选择时效性" style="width: 100%">
-                <el-option label="现行" value="XH" />
-                <el-option label="废止" value="FZ" />
-                <el-option label="试行" value="SX" />
+                <el-option v-for="item in validityOptions" :key="item.dict_value" :label="item.dict_name" :value="item.dict_value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -498,6 +502,7 @@
 import { ref, onMounted, computed, reactive } from 'vue'
 import { Search, View, Monitor, Download, Edit, Delete, Refresh, Plus, Minus, Document, Tickets } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import { dictApi, type DictItem } from '@/api/dict'
 import request from '@/api/request'
 import type { ApiResponse } from '@/types/auth'
 import dayjs from 'dayjs'
@@ -604,9 +609,64 @@ const searchForm = reactive<any>({
   release_date_end: ''
 })
 
-// 选项配置
-const documentTypeOptions = ['国家标准', '行业标准', '部门规章', '地方标准', '企业标准', '管理制度', '技术规范', '团体标准', '国际标准', '国家法律', '地方法规', '其他']
-const professionalFieldOptions = ['法律法规', '通用标准', '勘察钻探', '地基基础', '路基路面', '桥梁工程', '隧道工程', '交通工程', '建筑工程', '市政工程', '机电安装', '路桥工程', '装饰装修', '港口航道', '铁路工程', '房建工程', '水利电力', '信息化', '试验检测', '安全环保', '其他']
+// 选项配置 - 从字典动态加载
+const documentTypeOptions = ref<DictItem[]>([])
+const professionalFieldOptions = ref<DictItem[]>([])
+const validityOptions = ref<DictItem[]>([])
+
+// 加载字典选项
+const loadDictOptions = async () => {
+    try {
+        // 加载文件类型
+        const fileTypeRes = await dictApi.getItemList({
+            category_id: 'file_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (fileTypeRes.code === '000000' || fileTypeRes.code === 0) {
+            documentTypeOptions.value = fileTypeRes.data.list || []
+        }
+        
+        // 加载专业类型
+        const professionalTypeRes = await dictApi.getItemList({
+            category_id: 'professional_type',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (professionalTypeRes.code === '000000' || professionalTypeRes.code === 0) {
+            professionalFieldOptions.value = professionalTypeRes.data.list || []
+        }
+        
+        // 加载时效性
+        const validityRes = await dictApi.getItemList({
+            category_id: 'time_effect',
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (validityRes.code === '000000' || validityRes.code === 0) {
+            validityOptions.value = validityRes.data.list || []
+        }
+    } catch (error) {
+        console.error('加载字典选项失败', error)
+    }
+}
+
+// 字典值转换为名称
+const getDictName = (categoryId: string, dictValue: string) => {
+    let items: DictItem[] = []
+    if (categoryId === 'file_type') {
+        items = documentTypeOptions.value
+    } else if (categoryId === 'professional_type') {
+        items = professionalFieldOptions.value
+    } else if (categoryId === 'time_effect') {
+        items = validityOptions.value
+    }
+    const item = items.find(i => i.dict_value === dictValue)
+    return item ? item.dict_name : dictValue
+}
 
 // 获取列表数据
 const loadData = async () => {
@@ -633,7 +693,7 @@ const loadData = async () => {
       size: number
     }>
 
-    if (resData.code === 0) {
+    if (resData.code === '000000' || resData.code === 0) {
       tableData.value = resData.data.items as any
       total.value = resData.data.total
     } else {
@@ -758,7 +818,7 @@ const handleAction = async (action: string, row: any) => {
       }).then(async () => {
         try {
           const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=standard&id=${row.id}`)
-          if (res.code === 0) {
+          if (res.code === '000000' || res.code === 0) {
             ElMessage.success('删除成功')
             loadData()
           } else {
@@ -781,7 +841,7 @@ const loadKnowledgeBases = async () => {
         page_size: 1000
       }
     })
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       knowledgeBases.value = res.data
       if (knowledgeBases.value.length > 0 && !editForm.kb_id) {
         editForm.kb_id = knowledgeBases.value[0].id
@@ -826,7 +886,7 @@ const submitForm = async () => {
         content_type: file.type,
         prefix: 'basic-info/standard'
       })
-      if (urlRes.code === 0) {
+      if (urlRes.code === '000000' || urlRes.code === 0) {
         const { upload_url, file_url } = urlRes.data
         await fetch(upload_url, {
           method: 'PUT',
@@ -853,7 +913,7 @@ const submitForm = async () => {
       : '/api/v1/sample/basic-info/add?type=standard'
     
     const res = await request.post<ApiResponse>(url, editForm)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(editForm.id ? '修改成功' : '新增成功')
       formDialogVisible.value = false
       fileList.value = []
@@ -876,6 +936,7 @@ const openInNewWindow = () => {
 }
 
 onMounted(() => {
+  loadDictOptions()
   loadData()
 })
 </script>

+ 153 - 29
src/views/documents/Index.vue

@@ -530,7 +530,7 @@
           <el-col :span="12" v-if="editForm.table_type === 'standard' || editForm.table_type === 'regulation'">
             <el-form-item :label="editForm.table_type === 'standard' ? '* 文件类型' : '文件类型'">
               <el-select v-model="editForm.document_type" placeholder="请选择文件类型" style="width: 100%">
-                <el-option v-for="item in documentTypeOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in documentTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -538,7 +538,7 @@
           <el-col :span="12" v-if="editForm.table_type === 'standard'">
             <el-form-item label="* 专业领域">
               <el-select v-model="editForm.professional_field" placeholder="请选择专业领域" style="width: 100%">
-                <el-option v-for="item in professionalFieldOptions" :key="item" :label="item" :value="item" />
+                <el-option v-for="item in professionalFieldOptions" :key="item.value" :label="item.label" :value="item.value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -546,9 +546,7 @@
           <el-col :span="12" v-if="editForm.table_type === 'standard'">
             <el-form-item label="* 时效性">
               <el-select v-model="editForm.validity" placeholder="请选择时效性" style="width: 100%">
-                <el-option label="现行" value="XH" />
-                <el-option label="废止" value="FZ" />
-                <el-option label="试行" value="SX" />
+                <el-option v-for="item in validityOptions" :key="item.value" :label="item.label" :value="item.value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -615,33 +613,35 @@
             <el-col :span="12">
               <el-form-item label="* 方案类别">
                 <el-select v-model="editForm.plan_category" placeholder="请选择方案类别" style="width: 100%">
-                  <el-option v-for="item in planCategoryOptions" :key="item" :label="item" :value="item" />
+                  <el-option v-for="item in planCategoryOptions" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="* 一级分类">
-                <el-input v-model="editForm.level_1_classification" disabled />
+                <el-select v-model="editForm.level_1_classification" placeholder="请选择一级分类" style="width: 100%">
+                  <el-option v-for="item in level1Options" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="* 二级分类">
                 <el-select v-model="editForm.level_2_classification" placeholder="请选择二级分类" style="width: 100%">
-                  <el-option v-for="item in level2Options" :key="item" :label="item" :value="item" />
+                  <el-option v-for="item in level2Options" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="* 三级分类">
                 <el-select v-model="editForm.level_3_classification" placeholder="请选择三级分类" style="width: 100%">
-                  <el-option v-for="item in level3Options" :key="item" :label="item" :value="item" />
+                  <el-option v-for="item in level3Options" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="* 四级分类">
                 <el-select v-model="editForm.level_4_classification" placeholder="请选择四级分类" style="width: 100%">
-                  <el-option v-for="item in level4Options" :key="item" :label="item" :value="item" />
+                  <el-option v-for="item in level4Options" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -826,6 +826,7 @@ import dayjs from 'dayjs'
 import type { ApiResponse } from '@/types/auth'
 import { documentApi, type DocumentItem } from '@/api/document'
 import { getKnowledgeBases } from '@/api/knowledge-base'
+import { dictApi, type DictItem } from '@/api/dict'
 
 // 接口定义已移至 @/api/document
 
@@ -987,12 +988,134 @@ const participatingUnitsList = ref<string[]>([''])
 const referenceBasisList = ref<string[]>([''])
 const compilationBasisList = ref<string[]>([''])
 
-const planCategoryOptions = ['超危大方案', '超危大方案较大Ⅱ级', '超危大方案特大Ⅳ级', '超危大方案一般Ⅰ级', '超危大方案重大Ⅲ级', '危大方案', '一般方案']
-const level2Options = ['临建工程', '路基工程', '桥梁工程', '隧道工程', '其他']
-const level3Options = ['TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥', '其他']
-const level4Options = ['挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础', '其他']
-const documentTypeOptions = ['国家标准', '行业标准', '部门规章', '地方标准', '企业标准', '管理制度', '技术规范', '团体标准', '国际标准', '国家法律', '地方法规', '其他']
-const professionalFieldOptions = ['法律法规', '通用标准', '勘察钻探', '地基基础', '路基路面', '桥梁工程', '隧道工程', '交通工程', '建筑工程', '市政工程', '机电安装', '路桥工程', '装饰装修', '港口航道', '铁路工程', '房建工程', '水利电力', '信息化', '试验检测', '安全环保', '其他']
+// 字典选项(从字典表动态加载)
+const documentTypeOptions = ref<Array<{ label: string, value: string }>>([])
+const professionalFieldOptions = ref<Array<{ label: string, value: string }>>([])
+const validityOptions = ref<Array<{ label: string, value: string }>>([])
+const planCategoryOptions = ref<Array<{ label: string, value: string }>>([])
+const level1Options = ref<Array<{ label: string, value: string }>>([])
+const level2Options = ref<Array<{ label: string, value: string }>>([])
+const level3Options = ref<Array<{ label: string, value: string }>>([])
+const level4Options = ref<Array<{ label: string, value: string }>>([])
+
+// 加载字典选项
+const loadDictOptions = async () => {
+  try {
+    // 加载文件类型字典
+    const fileTypeRes = await dictApi.getItemList({
+      category_id: 'file_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (fileTypeRes.code === '000000' || fileTypeRes.code === 0) {
+      documentTypeOptions.value = fileTypeRes.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载专业类型字典
+    const professionalTypeRes = await dictApi.getItemList({
+      category_id: 'professional_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (professionalTypeRes.code === '000000' || professionalTypeRes.code === 0) {
+      professionalFieldOptions.value = professionalTypeRes.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载时效性字典
+    const validityRes = await dictApi.getItemList({
+      category_id: 'time_effect',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (validityRes.code === '000000' || validityRes.code === 0) {
+      validityOptions.value = validityRes.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载施工方案类别字典
+    const planTypeRes = await dictApi.getItemList({
+      category_id: 'construction_plan_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (planTypeRes.code === '000000' || planTypeRes.code === 0) {
+      planCategoryOptions.value = planTypeRes.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载一级分类字典
+    const level1Res = await dictApi.getItemList({
+      category_id: 'construction_plan_first_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (level1Res.code === '000000' || level1Res.code === 0) {
+      level1Options.value = level1Res.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载二级分类字典
+    const level2Res = await dictApi.getItemList({
+      category_id: 'construction_plan_second_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (level2Res.code === '000000' || level2Res.code === 0) {
+      level2Options.value = level2Res.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载三级分类字典
+    const level3Res = await dictApi.getItemList({
+      category_id: 'construction_plan_third_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (level3Res.code === '000000' || level3Res.code === 0) {
+      level3Options.value = level3Res.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+
+    // 加载四级分类字典
+    const level4Res = await dictApi.getItemList({
+      category_id: 'construction_plan_fourth_type',
+      enable_flag: '1',
+      page: 1,
+      page_size: 100
+    })
+    if (level4Res.code === '000000' || level4Res.code === 0) {
+      level4Options.value = level4Res.data.list.map((item: DictItem) => ({
+        label: item.dict_name,
+        value: item.dict_value
+      }))
+    }
+  } catch (error) {
+    console.error('加载字典选项失败:', error)
+  }
+}
 
 // 列表项管理
 const addListItem = (list: string[]) => {
@@ -1067,7 +1190,7 @@ const kbOptions = ref<{ label: string, value: string }[]>([])
 const loadKbOptions = async () => {
   try {
     const res = await getKnowledgeBases({ page_size: 100 })
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       kbOptions.value = res.data.map((item: any) => ({
         label: item.name,
         value: item.id
@@ -1198,7 +1321,7 @@ const confirmIngest = async () => {
       table_type: searchQuery.table_type
     })
     
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(res.message || '已加入入库队列')
       selectedIds.value = []
       multipleTableRef.value?.clearSelection()
@@ -1364,7 +1487,7 @@ const confirmAddTask = async () => {
     const tags = taskForm.selectedTags.map(t => t.name)
     const res = await documentApi.batchAddToTask(selectedIds.value, taskForm.project_name, tags)
 
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(res.message || '操作成功')
       selectedIds.value = []
       multipleTableRef.value?.clearSelection()
@@ -1418,7 +1541,7 @@ const handleDelete = async (row: DocumentItem) => {
     
     const res = await documentApi.batchDelete([row.id])
     
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success('删除成功')
       fetchDocuments()
     } else {
@@ -1455,7 +1578,7 @@ const handleBatchDelete = async () => {
     
     const res = await documentApi.batchDelete(selectedIds.value)
     
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(res.message || '批量删除成功')
       selectedIds.value = []
       multipleTableRef.value?.clearSelection()
@@ -1501,7 +1624,7 @@ const handleBatchClear = async () => {
     loading.value = true
     const res = await documentApi.batchClear(enteredIds)
     
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(res.message || '数据清空成功')
       selectedIds.value = []
       multipleTableRef.value?.clearSelection()
@@ -1545,7 +1668,7 @@ const fetchDocuments = async () => {
   loading.value = true
   try {
     const res = await documentApi.getList(searchQuery)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       documents.value = res.data.items
       total.value = res.data.total
       statistics.value.allTotal = res.data.all_total || 0
@@ -1587,7 +1710,7 @@ const refreshDocumentsSilently = async () => {
   isRefreshing.value = true
   try {
     const res = await documentApi.getList(searchQuery, true)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       documents.value = res.data.items
       total.value = res.data.total
       statistics.value.allTotal = res.data.all_total || 0
@@ -1652,7 +1775,7 @@ const customUpload = async (options: any) => {
     // 1. 获取预签名 URL
     const res = await documentApi.getUploadUrl(file.name, file.type || 'application/octet-stream', uploadForm.table_type)
 
-    if (res.code !== 0) {
+    if (res.code !== '000000' && res.code !== 0) {
       throw new Error(res.message || '获取上传链接失败')
     }
 
@@ -1708,7 +1831,7 @@ const submitUpload = async () => {
   submitting.value = true
   try {
     const res = await documentApi.add(uploadForm)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success('上传成功')
       uploadDialogVisible.value = false
       fetchDocuments()
@@ -1726,7 +1849,7 @@ const handleEdit = async (row: DocumentItem) => {
   loadKbOptions()
   try {
     const res = await documentApi.getDetail(row.id)
-    if (res.code === 0 && res.data) {
+    if ((res.code === '000000' || res.code === 0) && res.data) {
       const data = res.data
       editForm.id = data.id
       editForm.title = data.title
@@ -1821,7 +1944,7 @@ const submitEdit = async () => {
     }
     
     const res = await documentApi.edit(payload)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success('更新成功')
       editDialogVisible.value = false
       fetchDocuments()
@@ -1932,7 +2055,7 @@ const handleConvert = async (row: DocumentItem) => {
     
     // 启动转换任务(静默刷新,不触发全局 loading)
     const res = await documentApi.convert(row.id)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(res.message || '转换任务已启动')
       // 启动轮询以监控转换进度
       startPolling()
@@ -1959,6 +2082,7 @@ const openInNewWindow = () => {
 onMounted(() => {
   fetchDocuments()
   loadKbOptions()
+  loadDictOptions()
 })
 
 onUnmounted(() => {

+ 55 - 6
src/views/documents/KnowledgeBase.vue

@@ -260,17 +260,31 @@
                         </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-select v-model="field.field_type" placeholder="类型" @change="handleFieldTypeChange(index)">
                                     <el-option label="文本" value="text" />
                                     <el-option label="数字" value="num" />
+                                    <el-option label="字典" value="dict" />
                                 </el-select>
                             </el-form-item>
                         </el-col>
-                        <el-col :span="7">
+                        <el-col :span="7" v-if="field.field_type !== 'dict'">
                             <el-form-item label-width="0" style="margin-bottom: 12px;">
                                 <el-input v-model="field.remark" placeholder="备注说明" />
                             </el-form-item>
                         </el-col>
+                        <el-col :span="7" v-else>
+                            <el-form-item label-width="0" style="margin-bottom: 12px;">
+                                <el-tree-select
+                                    v-model="field.category_id"
+                                    :data="dictCategoryTree"
+                                    placeholder="选择字典类别"
+                                    :props="{ label: 'category_name', value: 'category_id' }"
+                                    check-strictly
+                                    :render-after-expand="false"
+                                    style="width: 100%"
+                                />
+                            </el-form-item>
+                        </el-col>
                     </el-row>
                 </div>
                 
@@ -329,10 +343,16 @@
             <el-table-column prop="field_en_name" label="英文标识" width="150" />
             <el-table-column prop="field_type" label="类型" width="100">
                 <template #default="{ row }">
-                    <el-tag size="small" type="info">{{ row.field_type === 'text' ? '文本' : '数字' }}</el-tag>
+                    <el-tag size="small" type="info">
+                        {{ row.field_type === 'text' ? '文本' : row.field_type === 'num' ? '数字' : row.field_type === 'dict' ? '字典' : row.field_type }}
+                    </el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="remark" label="备注/字典类别" min-width="150">
+                <template #default="{ row }">
+                    {{ row.field_type === 'dict' ? (row.category_name || row.category_id || '-') : (row.remark || '-') }}
                 </template>
             </el-table-column>
-            <el-table-column prop="remark" label="备注" />
         </el-table>
         <div v-else class="empty-text">暂无元数据字段定义</div>
       </div>
@@ -355,6 +375,7 @@ import {
     syncKnowledgeBase,
     type KnowledgeBase 
 } from '@/api/knowledge-base'
+import { dictApi, type DictCategory } from '@/api/dict'
 import dayjs from 'dayjs'
 
 // Query Parameters
@@ -398,6 +419,33 @@ const viewData = ref<KnowledgeBase>({} as KnowledgeBase)
 const viewMetadata = ref<any[]>([])
 const viewSchema = ref<any[]>([])
 
+// 字典类别树
+const dictCategoryTree = ref<DictCategory[]>([])
+
+// 加载字典类别树
+const loadDictCategoryTree = async () => {
+    try {
+        const res = await dictApi.getCategoryTree()
+        if (res.code === '000000' || res.code === 0) {
+            dictCategoryTree.value = res.data || []
+        }
+    } catch (error) {
+        console.error('加载字典类别树失败', error)
+    }
+}
+
+// 字段类型变化处理
+const handleFieldTypeChange = (index: number) => {
+    const field = formData.metadata_fields[index]
+    if (field.field_type !== 'dict') {
+        // 如果不是字典类型,清空 category_id
+        field.category_id = undefined
+    } else {
+        // 如果是字典类型,清空 remark
+        field.remark = ''
+    }
+}
+
 // 可选字段列表
 const availableFields = [
     { label: "文件名称", value: "文件名称" },
@@ -546,7 +594,7 @@ const handleEdit = async (row: KnowledgeBase) => {
   // 加载元数据字段,允许编辑
   try {
       const res = await getKnowledgeBaseMetadata(row.id)
-      if (res.code === 0 && res.data) {
+      if ((res.code === '000000' || 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
@@ -600,7 +648,7 @@ const handleView = async (row: KnowledgeBase) => {
 
     try {
         const res = await getKnowledgeBaseMetadata(row.id)
-        if (res.code === 0 && res.data) {
+        if ((res.code === '000000' || res.code === 0) && res.data) {
             if (res.data.metadata_fields && res.data.metadata_fields.length > 0) {
                 viewMetadata.value = res.data.metadata_fields
             }
@@ -741,6 +789,7 @@ const getIconColor = (name: string) => {
 
 onMounted(() => {
   loadData()
+  loadDictCategoryTree()
 })
 </script>
 

+ 113 - 21
src/views/documents/KnowledgeSnippet.vue

@@ -247,32 +247,35 @@
             <el-row :gutter="20">
                 <el-col :span="12" v-for="field in currentKbSchema" :key="field.field_en_name">
                     <el-form-item :label="field.field_zh_name || field.field_en_name">
-                        <!-- 时效性、文档类型等枚举字段使用下拉框 -->
+                        <!-- 字典类型:显示下拉列表 -->
                         <el-select
-                            v-if="METADATA_MAPS[field.field_en_name]"
+                            v-if="field.field_type === 'dict' && field.category_id"
                             v-model="formData.custom_fields[field.field_en_name]"
                             :placeholder="'请选择' + (field.field_zh_name || field.field_en_name)"
                             style="width: 100%"
                             clearable
                         >
                             <el-option
-                                v-for="(label, value) in METADATA_MAPS[field.field_en_name]"
-                                :key="value"
-                                :label="label"
-                                :value="value"
+                                v-for="item in getDictItems(field.category_id)"
+                                :key="item.dict_value"
+                                :label="item.dict_name"
+                                :value="item.dict_value"
                             />
                         </el-select>
+                        <!-- 文本类型 -->
                         <el-input 
                             v-else-if="field.field_type === 'text'"
                             v-model="formData.custom_fields[field.field_en_name]" 
                             :placeholder="'请输入' + (field.field_zh_name || field.field_en_name)"
                         />
+                        <!-- 数字类型 -->
                         <el-input-number 
                             v-else-if="field.field_type === 'num'"
                             v-model="formData.custom_fields[field.field_en_name]" 
                             :placeholder="'请输入' + (field.field_zh_name || field.field_en_name)"
                             style="width: 100%"
                         />
+                        <!-- 默认文本输入 -->
                         <el-input 
                             v-else
                             v-model="formData.custom_fields[field.field_en_name]" 
@@ -382,6 +385,7 @@ import {
 } from '@/api/snippet'
 import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from '@/api/knowledge-base'
 import { tagApi } from '@/api/tag'
+import { dictApi, type DictItem } from '@/api/dict'
 
 // Table Data
 const tableData = ref<Snippet[]>([])
@@ -480,7 +484,7 @@ const loadDocOptions = async (query: string) => {
             // whether_to_enter: 1 // 移除此过滤,搜索所有文档
         }, true)
 
-        if (res.code === 0) {
+        if (res.code === '000000' || res.code === 0) {
             // 映射为 label (title) 和 value (id)
             docOptions.value = res.data.items.map(item => ({
                 label: item.title,
@@ -502,6 +506,31 @@ const activeParentTab = ref('0')
 const parentLoading = ref(false)
 
 const currentKbSchema = ref<any[]>([]) // 当前选中知识库的自定义Schema
+const dictItemsCache = ref<Record<string, DictItem[]>>({}) // 缓存字典项数据
+
+// 获取字典项列表
+const getDictItems = (categoryId: string) => {
+    return dictItemsCache.value[categoryId] || []
+}
+
+// 加载字典项
+const loadDictItems = async (categoryId: string) => {
+    if (dictItemsCache.value[categoryId]) return
+    
+    try {
+        const res = await dictApi.getItemList({
+            category_id: categoryId,
+            enable_flag: '1',
+            page: 1,
+            page_size: 100
+        })
+        if (res.code === '000000' || res.code === 0) {
+            dictItemsCache.value[categoryId] = res.data.list || []
+        }
+    } catch (error) {
+        console.error(`加载字典项失败: ${categoryId}`, error)
+    }
+}
 
 // 当选择知识库变化时,加载对应的 Schema 字段
 const handleKbChange = async (collection_name: string) => {
@@ -524,7 +553,7 @@ const handleKbChange = async (collection_name: string) => {
     // 加载知识库的元数据定义
     try {
         const res = await getKnowledgeBaseMetadata(kb.id)
-        if (res.code === 0) {
+        if (res.code === '000000' || res.code === 0) {
              if (Array.isArray(res.data)) {
                  currentKbSchema.value = res.data
              } else if (res.data && Array.isArray(res.data.metadata_fields)) {
@@ -532,6 +561,12 @@ const handleKbChange = async (collection_name: string) => {
              } else {
                  currentKbSchema.value = []
              }
+             
+             // 加载字典类型字段的字典项
+             const dictFields = currentKbSchema.value.filter(f => f.field_type === 'dict' && f.category_id)
+             for (const field of dictFields) {
+                 await loadDictItems(field.category_id)
+             }
         }
     } catch (error) {
         console.error("加载元数据定义失败", error)
@@ -560,7 +595,7 @@ const loadData = async () => {
             kb: queryParams.kb, // 知识库集合名称
             status: queryParams.status // 传递状态参数
         })
-        if (res.code === 0) {
+        if (res.code === '000000' || res.code === 0) {
             tableData.value = res.data
             pagination.total = res.meta?.total || 0
         } else {
@@ -584,7 +619,7 @@ const kbOptions = ref<{label: string, value: string, id: string}[]>([])
 const loadKbOptions = async () => {
     try {
         const res = await getKnowledgeBases({ page_size: 100 })
-        if (res.code === 0) {
+        if (res.code === '000000' || res.code === 0) {
             kbOptions.value = []
             res.data.forEach((item: any) => {
                 // 根据需求:只选择知识库名称,且对应的是子表
@@ -616,11 +651,11 @@ const loadKbOptions = async () => {
                 }
             })
             
-            // 默认选中第一个
-            if (kbOptions.value.length > 0 && !queryParams.kb) {
-                queryParams.kb = kbOptions.value[0].value
-                loadData()
-            }
+            // 默认不选中任何知识库,让用户手动选择
+            // if (kbOptions.value.length > 0 && !queryParams.kb) {
+            //     queryParams.kb = kbOptions.value[0].value
+            //     loadData()
+            // }
         }
     } catch (error) {
         console.error("加载知识库选项失败", error)
@@ -660,12 +695,55 @@ const loadTagTree = async () => {
 onMounted(() => {
     loadKbOptions()
     loadTagTree()
+    // 如果已选择知识库,加载元数据
+    if (queryParams.kb) {
+        loadCurrentKbMetadata()
+    }
 })
 
 // Methods
 const handleSearch = () => {
     pagination.page = 1
     loadData()
+    // 加载当前知识库的元数据字段信息
+    loadCurrentKbMetadata()
+}
+
+// 加载当前选中知识库的元数据字段信息
+const loadCurrentKbMetadata = async () => {
+    if (!queryParams.kb) {
+        currentKbSchema.value = []
+        dictItemsCache.value = {}
+        return
+    }
+    
+    const kb = kbOptions.value.find(k => k.value === queryParams.kb)
+    if (!kb) {
+        currentKbSchema.value = []
+        return
+    }
+    
+    try {
+        const res = await getKnowledgeBaseMetadata(kb.id)
+        if (res.code === '000000' || res.code === 0) {
+            if (Array.isArray(res.data)) {
+                currentKbSchema.value = res.data
+            } else if (res.data && Array.isArray(res.data.metadata_fields)) {
+                currentKbSchema.value = res.data.metadata_fields
+            } else {
+                currentKbSchema.value = []
+            }
+            
+            // 加载字典类型字段的字典项
+            const dictFields = currentKbSchema.value.filter(f => f.field_type === 'dict' && f.category_id)
+            for (const field of dictFields) {
+                await loadDictItems(field.category_id)
+            }
+        }
+    } catch (error) {
+        console.error("加载元数据定义失败", error)
+        currentKbSchema.value = []
+    }
 }
 
 const handleAdd = async () => {
@@ -732,7 +810,7 @@ const handleDocChange = async (val: string) => {
             try {
                 // 获取文档详情
                 const res = await documentApi.getDetail(val)
-                if (res.code === 0 && res.data) {
+                if ((res.code === '000000' || res.code === 0) && res.data) {
                     const doc = res.data
                     
                     // 定义元数据字段名与文档属性的映射关系
@@ -884,7 +962,7 @@ const handleView = async (row: Snippet) => {
         parentLoading.value = true
         try {
             const res = await getSnippetDetail(row.collection_name, row.parent_id)
-            if (res.code === 0 && res.data) {
+            if ((res.code === '000000' || res.code === 0) && res.data) {
                 if (Array.isArray(res.data.parent_segments) && res.data.parent_segments.length > 0) {
                     parentSegments.value = res.data.parent_segments
                 } else if (res.data.parent_content) {
@@ -1046,9 +1124,17 @@ const METADATA_MAPS: Record<string, Record<string, string>> = {
 const getMetadataValueName = (key: string, val: any) => {
     if (val === null || val === undefined) return '-'
     const valStr = String(val)
-    if (METADATA_MAPS[key]) {
-        return METADATA_MAPS[key][valStr] || valStr
+    
+    // 查找对应的元数据字段定义
+    const field = currentKbSchema.value.find(f => f.field_en_name === key)
+    
+    // 如果是字典类型,从缓存中查找字典名称
+    if (field && field.field_type === 'dict' && field.category_id) {
+        const items = dictItemsCache.value[field.category_id] || []
+        const item = items.find(i => i.dict_value === valStr)
+        return item ? item.dict_name : valStr
     }
+    
     return valStr
 }
 
@@ -1059,8 +1145,11 @@ const formatMetaInfo = (row: Snippet) => {
         if (typeof row.metadata === 'object') {
              const displayParts = []
              for (const [key, value] of Object.entries(row.metadata)) {
+                 // 查找对应的元数据字段定义
+                 const field = currentKbSchema.value.find(f => f.field_en_name === key)
                  const valStr = getMetadataValueName(key, value)
-                 displayParts.push(`${key}: ${valStr}`)
+                 const fieldLabel = field?.field_zh_name || key
+                 displayParts.push(`${fieldLabel}: ${valStr}`)
              }
              if (displayParts.length > 0) return displayParts.join(' | ')
         } else if (typeof row.metadata === 'string') {
@@ -1069,8 +1158,11 @@ const formatMetaInfo = (row: Snippet) => {
                 const metaObj = JSON.parse(row.metadata)
                 const displayParts = []
                 for (const [key, value] of Object.entries(metaObj)) {
+                    // 查找对应的元数据字段定义
+                    const field = currentKbSchema.value.find(f => f.field_en_name === key)
                     const valStr = getMetadataValueName(key, value)
-                    displayParts.push(`${key}: ${valStr}`)
+                    const fieldLabel = field?.field_zh_name || key
+                    displayParts.push(`${fieldLabel}: ${valStr}`)
                 }
                 if (displayParts.length > 0) return displayParts.join(' | ')
             } catch (e) {

+ 89 - 141
src/views/documents/SearchEngine.vue

@@ -113,6 +113,7 @@
                                 style="width: 100%"
                                 clearable
                                 :disabled="!searchForm.kb_id"
+                                @change="handleFilterFieldChange(index)"
                             >
                                 <el-option 
                                     v-for="field in metadataFields" 
@@ -124,18 +125,23 @@
                         </el-col>
                         <el-col :span="1" style="text-align: center; color: #909399;">=</el-col>
                         <el-col :span="11">
+                            <!-- 字典类型:显示下拉列表 -->
                             <el-select 
-                                v-if="filter.field === 'validity'"
+                                v-if="getFieldType(filter.field) === 'dict'"
                                 v-model="filter.value"
-                                placeholder="选择时效性"
+                                placeholder="选择"
                                 style="width: 100%"
                                 clearable
-                                @change="handleSearch"
+                                :disabled="!filter.field"
                             >
-                                <el-option label="现行" value="XH" />
-                                <el-option label="废止" value="FZ" />
-                                <el-option label="试行" value="SX" />
+                                <el-option 
+                                    v-for="item in getDictItems(filter.field)" 
+                                    :key="item.dict_value" 
+                                    :label="item.dict_name" 
+                                    :value="item.dict_value" 
+                                />
                             </el-select>
+                            <!-- 文本或数字类型:显示输入框 -->
                             <el-input 
                                 v-else
                                 v-model="filter.value" 
@@ -283,10 +289,12 @@ import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from
 import { searchKnowledgeBase, type KBSearchResultItem } from '@/api/search-engine'
 import { getSnippetDetail } from '@/api/snippet'
 import { documentApi } from '@/api/document'
+import { dictApi, type DictItem } from '@/api/dict'
 
 // Data
 const kbList = ref<KnowledgeBase[]>([])
 const metadataFields = ref<any[]>([]) // Store available metadata fields for selected KB
+const dictItemsCache = ref<Record<string, DictItem[]>>({}) // 缓存字典项数据
 const loading = ref(false)
 const hasSearched = ref(false)
 const tableData = ref<KBSearchResultItem[]>([])
@@ -314,7 +322,7 @@ const loadDocOptions = async (query: string) => {
             keyword: query,
         }, true)
 
-        if (res.code === 0) {
+        if (res.code === '000000' || res.code === 0) {
             // 映射为 label (title) 和 value (title) - 这里我们用 title 作为过滤值
             // 因为 Milvus 中存的是 doc_name/title,而不是 ID
             docOptions.value = res.data.items.map(item => ({
@@ -369,6 +377,7 @@ const handleKbChange = async () => {
     // Reset metadata selection
     searchForm.filters = [{ field: '', value: '' }]
     metadataFields.value = []
+    dictItemsCache.value = {}
     
     if (searchForm.kb_id) {
         // Find selected KB object to get ID (kb_id in form is collection_name)
@@ -381,7 +390,7 @@ const handleKbChange = async () => {
             try {
                 const res = await getKnowledgeBaseMetadata(selectedKb.id)
                 // 确保后端返回的数据格式正确
-                if (res.code === 0) {
+                if (res.code === '000000' || res.code === 0) {
                     if (Array.isArray(res.data)) {
                         metadataFields.value = res.data
                     } else if (res.data && Array.isArray(res.data.metadata_fields)) {
@@ -389,6 +398,9 @@ const handleKbChange = async () => {
                     } else {
                         metadataFields.value = []
                     }
+                    
+                    // 预加载所有字典类型字段的字典项
+                    await loadDictItemsForFields()
                 } else {
                     console.warn("Invalid metadata response:", res)
                     metadataFields.value = []
@@ -401,135 +413,57 @@ const handleKbChange = async () => {
     }
 }
 
-const docNamesMap = ref<Record<string, string>>({})
+// 获取字段类型
+const getFieldType = (fieldEnName: string) => {
+    const field = metadataFields.value.find(f => f.field_en_name === fieldEnName)
+    return field?.field_type || 'text'
+}
 
-const formatMetaInfo = (row: any) => {
-    // 元数据字段简写转中文映射
-    const METADATA_MAPS: Record<string, Record<string, string>> = {
-        plan_category: {
-            CH: "超危大方案",
-            CH2: "超危大方案较大Ⅱ级",
-            CH4: "超危大方案特大Ⅳ级",
-            CH1: "超危大方案一般Ⅰ级",
-            CH3: "超危大方案重大Ⅲ级",
-            WD: "危大方案",
-            YB: "一般方案"
-        },
-        level_1_classification: {
-            SC: "施工方案"
-        },
-        level_2_classification: {
-            LZ: "临建工程",
-            LJ: "路基工程",
-            QL: "桥梁工程",
-            SD: "隧道工程",
-            QT: "其他"
-        },
-        level_3_classification: {
-            TM: "TBM施工",
-            BH: "拌和站安、拆施工",
-            BL: "不良地质隧道施工",
-            CG: "常规桥梁",
-            DT: "挡土墙工程类",
-            FB: "辅助坑道施工",
-            FD: "复杂洞口工程施工",
-            GG: "钢筋加工场安、拆",
-            GZ: "钢栈桥施工",
-            GH: "拱桥",
-            HD: "涵洞工程类",
-            HP: "滑坡体处理类",
-            LT: "路堤",
-            LQ: "路堑",
-            QT: "其他",
-            JK: "深基坑",
-            ZT: "隧道总体施工",
-            TS: "特殊结构隧道",
-            XL: "斜拉桥",
-            XS: "悬索桥"
-        },
-        level_4_classification: {
-            DT: "挡土墙",
-            DG: "顶管",
-            DL: "断层破碎带及软弱围岩",
-            GX: "钢筋砼箱涵",
-            GT: "高填路堤",
-            KH: "抗滑桩",
-            RY: "软岩大变形隧道",
-            SB: "上部结构",
-            JK: "深基坑开挖与支护",
-            LC: "深挖路堑",
-            TM: "隧道TBM",
-            JD: "隧道进洞",
-            SJ: "隧道竖井",
-            XJ: "隧道斜井",
-            TZ: "特种设备",
-            WS: "瓦斯隧道",
-            XB: "下部结构",
-            NJ: "小净距隧道",
-            YB: "岩爆隧道",
-            YR: "岩溶隧道",
-            YN: "涌水突泥隧道",
-            ZJ: "桩基础",
-            QT: "其他"
-        },
-        document_type: {
-            GB: "国家标准",
-            HY: "行业标准",
-            BM: "部门规章",
-            DB: "地方标准",
-            QY: "企业标准",
-            GL: "管理制度",
-            GF: "技术规范",
-            TT: "团体标准",
-            GJ: "国际标准",
-            FL: "国家法律",
-            LR: "地方法规",
-            QT: "其他",
-            HB: "行业标准",
-            QB: "企业标准",
-            GLZD: "管理制度",
-            JSGF: "技术规范",
-            TB: "团体标准",
-            IB: "国际标准",
-            OTHER: "其他"
-        },
-        professional_field: {
-            FL: "法律法规",
-            TY: "通用标准",
-            KC: "勘察钻探",
-            DJ: "地基基础",
-            LJ: "路基面",
-            QL: "桥梁工程",
-            SD: "隧道工程",
-            JT: "交通工程",
-            JZ: "建筑工程",
-            SZ: "市政工程",
-            JD: "机电安装",
-            LB: "路桥工程",
-            ZS: "装饰装修",
-            GK: "港口航道",
-            TL: "铁路工程",
-            FJ: "房建工程",
-            SL: "水利电力",
-            XX: "信息化",
-            SY: "试验检测",
-            AQ: "安全环保",
-            QT: "其他"
-        },
-        validity: {
-            XH: "现行",
-            FZ: "废止",
-            SX: "试行"
+// 获取字段的字典项列表
+const getDictItems = (fieldEnName: string) => {
+    const field = metadataFields.value.find(f => f.field_en_name === fieldEnName)
+    if (!field || !field.category_id) return []
+    return dictItemsCache.value[field.category_id] || []
+}
+
+// 预加载所有字典类型字段的字典项
+const loadDictItemsForFields = async () => {
+    const dictFields = metadataFields.value.filter(f => f.field_type === 'dict' && f.category_id)
+    const categoryIds = [...new Set(dictFields.map(f => f.category_id))]
+    
+    for (const categoryId of categoryIds) {
+        if (!dictItemsCache.value[categoryId]) {
+            try {
+                const res = await dictApi.getItemList({
+                    category_id: categoryId,
+                    enable_flag: '1', // 只获取启用的字典项
+                    page: 1,
+                    page_size: 100
+                })
+                if (res.code === '000000' || res.code === 0) {
+                    dictItemsCache.value[categoryId] = res.data.list || []
+                }
+            } catch (error) {
+                console.error(`加载字典项失败: ${categoryId}`, error)
+            }
         }
     }
+}
 
-    const getMetadataValueName = (key: string, val: any) => {
-        if (val === null || val === undefined) return '-'
-        const valStr = String(val)
-        if (METADATA_MAPS[key]) {
-            return METADATA_MAPS[key][valStr] || valStr
-        }
-        return valStr
+// 字段变化时清空值
+const handleFilterFieldChange = (index: number) => {
+    searchForm.filters[index].value = ''
+}
+
+const docNamesMap = ref<Record<string, string>>({})
+
+const formatMetaInfo = (row: any) => {
+    // 获取字典值到名称的映射函数
+    const getDictName = (categoryId: string, dictValue: any) => {
+        if (!categoryId || !dictValue) return dictValue
+        const items = dictItemsCache.value[categoryId] || []
+        const item = items.find(i => i.dict_value === String(dictValue))
+        return item ? item.dict_name : dictValue
     }
 
     // 优先展示 metadata 字段中的信息
@@ -538,8 +472,16 @@ const formatMetaInfo = (row: any) => {
         if (typeof row.metadata === 'object') {
              const displayParts = []
              for (const [key, value] of Object.entries(row.metadata)) {
-                 const valStr = getMetadataValueName(key, value)
-                 displayParts.push(`${key}: ${valStr}`)
+                 // 查找对应的元数据字段定义
+                 const field = metadataFields.value.find(f => f.field_en_name === key)
+                 let displayValue = value
+                 
+                 // 如果是字典类型,转换为字典名称
+                 if (field && field.field_type === 'dict' && field.category_id) {
+                     displayValue = getDictName(field.category_id, value)
+                 }
+                 
+                 displayParts.push(`${field?.field_zh_name || key}: ${displayValue}`)
              }
              if (displayParts.length > 0) return displayParts.join(' | ')
         } else if (typeof row.metadata === 'string') {
@@ -547,8 +489,16 @@ const formatMetaInfo = (row: any) => {
                 const metaObj = JSON.parse(row.metadata)
                 const displayParts = []
                 for (const [key, value] of Object.entries(metaObj)) {
-                    const valStr = getMetadataValueName(key, value)
-                    displayParts.push(`${key}: ${valStr}`)
+                    // 查找对应的元数据字段定义
+                    const field = metadataFields.value.find(f => f.field_en_name === key)
+                    let displayValue = value
+                    
+                    // 如果是字典类型,转换为字典名称
+                    if (field && field.field_type === 'dict' && field.category_id) {
+                        displayValue = getDictName(field.category_id, value)
+                    }
+                    
+                    displayParts.push(`${field?.field_zh_name || key}: ${displayValue}`)
                 }
                 if (displayParts.length > 0) return displayParts.join(' | ')
             } catch (e) {
@@ -556,8 +506,6 @@ const formatMetaInfo = (row: any) => {
             }
         }
     }
-    // 如果 row 本身有 vector 属性,也不显示在 meta_info 中
-    // 这里只处理了 metadata 字段内的
     return row.meta_info || '-'
 }
 
@@ -581,7 +529,7 @@ const loadDocNames = async (docIds: string[]) => {
         try {
             // 这里用 getDetail 查
             const res = await documentApi.getDetail(id)
-            if (res.code === 0 && res.data) {
+            if ((res.code === '000000' || res.code === 0) && res.data) {
                 docNamesMap.value[id] = res.data.title
             } else {
                  docNamesMap.value[id] = '未知文档'
@@ -667,7 +615,7 @@ const handleDetail = (row: KBSearchResultItem) => {
         parentLoading.value = true
         getSnippetDetail(row.kb_name, row.parent_id)
             .then((res: any) => {
-                if (res.code === 0 && res.data) {
+                if ((res.code === '000000' || res.code === 0) && res.data) {
                     if (Array.isArray(res.data.parent_segments) && res.data.parent_segments.length > 0) {
                         parentSegments.value = res.data.parent_segments
                     } else if (res.data.parent_content) {

+ 6 - 6
src/views/images/Index.vue

@@ -344,7 +344,7 @@ const previewSrcList = computed(() => {
 const fetchCategories = async () => {
   try {
     const res = await imageApi.getCategories()
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       categories.value = [
         { id: '0', type_name: '全部分类', parent_id: '-1', children: res.data, created_time: '', updated_time: '' }
       ]
@@ -362,7 +362,7 @@ const fetchImages = async () => {
   }
   try {
     const res = await imageApi.getList(queryParams)
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       images.value = res.data.list
       total.value = res.data.total
     }
@@ -472,7 +472,7 @@ const confirmAddTask = async () => {
     }
 
     const { code, message, data } = res
-    if (code === 0) {
+    if ((code === '000000' || code === 0)) {
       let successMsg = message || '批量加入任务成功'
       if (isCategoryBatch.value && data) {
         if (data.image_count === 0) {
@@ -541,7 +541,7 @@ const handleDeleteCategory = (data: ImageCategory) => {
   }).then(async () => {
     try {
       const res = await imageApi.deleteCategory(data.id)
-      if (res.code === 0) {
+      if (res.code === '000000' || res.code === 0) {
         ElMessage.success('删除成功')
         fetchCategories()
       } else {
@@ -564,7 +564,7 @@ const submitCategory = async () => {
     } else {
       res = await imageApi.addCategory(categoryForm)
     }
-    if (res.code === 0) {
+    if (res.code === '000000' || res.code === 0) {
       ElMessage.success(categoryForm.id ? '更新成功' : '新增成功')
       categoryDialogVisible.value = false
       fetchCategories()
@@ -659,7 +659,7 @@ const handleDeleteImage = (row: ImageItem) => {
   }).then(async () => {
     try {
       const res = await imageApi.delete(row.id)
-      if (res.code === 0) {
+      if (res.code === '000000' || res.code === 0) {
         ElMessage.success('删除成功')
         fetchImages()
       } else {

+ 1 - 1
src/views/user/Profile.vue

@@ -394,7 +394,7 @@ const changePassword = async () => {
 
 // 头像上传成功回调
 const handleAvatarSuccess = (response: any) => {
-  if (response.code === 0) {
+  if (response.code === '000000' || response.code === 0) {
     profileForm.avatar_url = response.data.url
     ElMessage.success('头像上传成功')
   } else {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
tsconfig.tsbuildinfo


Някои файлове не бяха показани, защото твърде много файлове са промени