chenkun 3 週間 前
コミット
d170ce129c

+ 35 - 15
src/api/document.ts

@@ -7,8 +7,8 @@ export interface DocumentItem {
   title: string
   note?: string
   content?: string // 兼容旧代码
-  source_type: 'basis' | 'work' | 'job'
-  table_type?: 'basis' | 'work' | 'job' // 兼容旧代码
+  source_type: 'standard' | 'construction_plan' | 'regulation' | 'other'
+  table_type?: 'standard' | 'construction_plan' | 'regulation' | 'other' // 兼容旧代码
   primary_category_id: string
   secondary_category_id: string
   year: number
@@ -23,6 +23,8 @@ export interface DocumentItem {
   json_display_name?: string
   conversion_error?: string
   error_message?: string // 兼容旧代码
+  kb_id?: string // 目标知识库ID
+  kb_name?: string // 知识库名称
   created_by?: string
   updated_by?: string
   creator_name?: string
@@ -47,6 +49,17 @@ export interface DocumentItem {
   level_2_classification?: string
   level_3_classification?: string
   level_4_classification?: string
+  // 补全缺失字段
+  english_name?: string
+  implementation_date?: string
+  drafting_unit?: string
+  approving_department?: string
+  engineering_phase?: string
+  participating_units?: string
+  reference_basis?: string
+  source_url?: string
+  effective_start_date?: string
+  effective_end_date?: string
 }
 
 export interface DocumentQueryParams {
@@ -55,6 +68,7 @@ export interface DocumentQueryParams {
   keyword?: string
   table_type?: string | null
   whether_to_enter?: number | null
+  conversion_status?: number | null
 }
 
 export interface ApiResponse<T = any> {
@@ -75,7 +89,7 @@ export interface PageResult<T> {
 
 // --- API 方法 ---
 
-const prefix = '/api/v1/sample'
+const API_PREFIX = '/api/v1/sample'
 
 export const documentApi = {
   // 获取文档列表
@@ -84,56 +98,62 @@ export const documentApi = {
     if (silent) {
       config.headers = { 'Skip-Error-Message': 'true' }
     }
-    return request.get(`${prefix}/documents/list`, config)
+    return request.get(`${API_PREFIX}/documents/list`, config)
   },
 
   // 获取文档详情
   getDetail(id: string): Promise<ApiResponse<DocumentItem>> {
-    return request.get(`${prefix}/documents/detail/${id}`)
+    return request.get(`${API_PREFIX}/documents/detail/${id}`)
   },
 
   // 添加文档
   add(data: Partial<DocumentItem>): Promise<ApiResponse<{ id: string }>> {
-    return request.post(`${prefix}/documents/add`, data)
+    return request.post(`${API_PREFIX}/documents/add`, data)
   },
 
   // 编辑文档
   edit(data: Partial<DocumentItem>): Promise<ApiResponse<null>> {
-    return request.post(`${prefix}/documents/edit`, data)
+    return request.post(`${API_PREFIX}/documents/edit`, data)
   },
 
   // 文档入库
   enter(id: string): Promise<ApiResponse<null>> {
-    return request.post(`${prefix}/documents/enter`, { id })
+    return request.post(`${API_PREFIX}/documents/enter`, { id })
   },
 
   // 批量入库
   batchEnter(ids: string[]): Promise<ApiResponse<null>> {
-    return request.post(`${prefix}/documents/batch-enter`, { ids })
+    return request.post(`${API_PREFIX}/documents/batch-enter`, { ids })
   },
 
   // 批量删除
   batchDelete(ids: string[]): Promise<ApiResponse<null>> {
-    return request.post(`${prefix}/documents/batch-delete`, { ids })
+    return request.post(`${API_PREFIX}/documents/batch-delete`, { ids })
+  },
+
+  // 批量加入任务中心
+  batchAddToTask(ids: string[]): Promise<ApiResponse<any>> {
+    return request.post(`${API_PREFIX}/documents/batch-add-to-task`, { ids })
   },
 
   // 启动转换
   convert(id: string, tableType?: string): Promise<ApiResponse<null>> {
-    return request.post(`${prefix}/documents/convert`, { id, table_type: tableType })
+    return request.post(`${API_PREFIX}/documents/convert`, { id, table_type: tableType })
   },
 
   // 代理查看内容
   proxyView(url: string, token?: string): Promise<any> {
-    return request.get(`${prefix}/documents/proxy-view`, {
+    return request.get(`${API_PREFIX}/documents/proxy-view`, {
       params: { url, token }
     })
   },
 
   // 获取上传预签名 URL
-  getUploadUrl(filename: string, contentType: string): Promise<ApiResponse<{ upload_url: string, file_url: string }>> {
-    return request.post(`${prefix}/documents/upload-url`, {
+  getUploadUrl(filename: string, contentType: string, prefix?: string): Promise<ApiResponse<{ upload_url: string, file_url: string }>> {
+    return request.post(`${API_PREFIX}/documents/upload-url`, {
       filename,
-      content_type: contentType
+      content_type: contentType,
+      prefix
     })
   }
 }

+ 15 - 10
src/api/image.ts

@@ -48,50 +48,55 @@ export interface PageResult<T> {
 
 // --- API 方法 ---
 
-const prefix = '/api/v1/images'
+const API_PREFIX = '/api/v1/images'
 
 export const imageApi = {
   // --- 分类管理 ---
   
   // 获取全部分类树
   getCategories(): Promise<ApiResponse<ImageCategory[]>> {
-    return request.get(`${prefix}/categories`)
+    return request.get(`${API_PREFIX}/categories`)
   },
 
   // 新增分类
   addCategory(data: Partial<ImageCategory>): Promise<ApiResponse<{ id: string }>> {
-    return request.post(`${prefix}/categories`, data)
+    return request.post(`${API_PREFIX}/categories`, data)
   },
 
   // 更新分类
   updateCategory(id: string, data: Partial<ImageCategory>): Promise<ApiResponse<null>> {
-    return request.put(`${prefix}/categories/${id}`, data)
+    return request.put(`${API_PREFIX}/categories/${id}`, data)
   },
 
   // 删除分类
   deleteCategory(id: string): Promise<ApiResponse<null>> {
-    return request.delete(`${prefix}/categories/${id}`)
+    return request.delete(`${API_PREFIX}/categories/${id}`)
   },
 
   // --- 图片管理 ---
 
   // 获取图片列表
   getList(params: ImageQueryParams): Promise<ApiResponse<PageResult<ImageItem>>> {
-    return request.get(prefix, { params })
+    return request.get(API_PREFIX, { params })
   },
 
   // 保存图片信息
   add(data: Partial<ImageItem>): Promise<ApiResponse<null>> {
-    return request.post(prefix, data)
+    return request.post(API_PREFIX, data)
   },
 
   // 删除图片
   delete(id: string): Promise<ApiResponse<null>> {
-    return request.delete(`${prefix}/${id}`)
+    return request.delete(`${API_PREFIX}/${id}`)
   },
 
   // 获取上传链接
-  getUploadUrl(filename: string, contentType: string): Promise<ApiResponse<{ upload_url: string, file_url: string, object_name: string }>> {
-    return request.post(`${prefix}/upload-url`, { filename, content_type: contentType })
+  getUploadUrl(filename: string, contentType: string, prefix?: string): Promise<ApiResponse<{ upload_url: string, file_url: string, object_name: string }>> {
+    return request.post(`${API_PREFIX}/upload-url`, { filename, content_type: contentType, prefix })
+  },
+
+  // 批量加入任务中心
+  batchAddToTask(ids: string[]): Promise<ApiResponse<null>> {
+    return request.post(`${API_PREFIX}/batch-add-to-task`, { ids })
   }
 }

+ 2 - 2
src/api/request.ts

@@ -1,5 +1,5 @@
 import axios from 'axios'
-import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
+import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig } from 'axios'
 import { ElMessage } from 'element-plus'
 import { useAuthStore } from '@/stores/auth'
 import { getToken, getRefreshToken } from '@/utils/auth'
@@ -16,7 +16,7 @@ const getBaseURL = () => {
 }
 
 // 创建axios实例
-const request: AxiosInstance = axios.create({
+const request = axios.create({
   baseURL: getBaseURL(),
   timeout: 30000, // 调整为 30 秒,避免长时间挂起
   headers: {

+ 15 - 0
src/env.d.ts

@@ -13,4 +13,19 @@ interface ImportMetaEnv {
 
 interface ImportMeta {
   readonly env: ImportMetaEnv
+}
+
+// Axios 响应类型增强
+import 'axios'
+declare module 'axios' {
+  export interface AxiosInstance {
+    <T = any, R = T>(config: AxiosRequestConfig): Promise<R>;
+    get<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
+    delete<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
+    head<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
+    options<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
+    post<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
+    put<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
+    patch<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
+  }
 }

+ 42 - 71
src/layouts/MainLayout.vue

@@ -44,11 +44,10 @@
             router
             v-loading="menuLoading"
           >
-            <!-- 动态渲染菜单 -->
             <template v-for="menu in userMenus" :key="menu.id">
-              <!-- 父菜单类型:只做层级展示,点击展开子菜单 -->
-              <template v-if="menu.menu_type === 'parent'">
-                <el-sub-menu :index="menu.name" v-show="!menu.is_hidden">
+              <template v-if="!menu.is_hidden">
+                <!-- 有子菜单的情况 -->
+                <el-sub-menu v-if="hasMenuChildren(menu)" :index="menu.id.toString()">
                   <template #title>
                     <el-icon v-if="menu.icon">
                       <component :is="getIconComponent(menu.icon)" />
@@ -56,74 +55,50 @@
                     <span>{{ menu.title }}</span>
                   </template>
                   
-                  <!-- 渲染子菜单 -->
+                  <!-- 二级菜单渲染 -->
                   <template v-for="child in getMenuChildren(menu)" :key="child.id">
-                    <!-- 子菜单也是父菜单类型 -->
-                    <el-sub-menu v-if="child.menu_type === 'parent'" :index="child.name" v-show="!child.is_hidden">
-                      <template #title>
-                        <el-icon v-if="child.icon">
-                          <component :is="getIconComponent(child.icon)" />
-                        </el-icon>
-                        <span>{{ child.title }}</span>
-                      </template>
+                    <template v-if="!child.is_hidden">
+                      <!-- 二级菜单还有子项 (三级菜单) -->
+                      <el-sub-menu v-if="hasMenuChildren(child)" :index="child.id.toString()">
+                        <template #title>
+                          <el-icon v-if="child.icon">
+                            <component :is="getIconComponent(child.icon)" />
+                          </el-icon>
+                          <span>{{ child.title }}</span>
+                        </template>
+                        
+                        <!-- 三级菜单项 -->
+                        <el-menu-item 
+                          v-for="grandChild in getMenuChildren(child)" 
+                          :key="grandChild.id"
+                          :index="grandChild.path || grandChild.name"
+                          v-show="!grandChild.is_hidden"
+                        >
+                          <el-icon v-if="grandChild.icon">
+                            <component :is="getIconComponent(grandChild.icon)" />
+                          </el-icon>
+                          <span>{{ grandChild.title }}</span>
+                        </el-menu-item>
+                      </el-sub-menu>
                       
-                      <!-- 渲染三级菜单项 -->
+                      <!-- 二级菜单没有子项 -->
                       <el-menu-item 
-                        v-for="grandChild in getMenuChildren(child)" 
-                        :key="grandChild.id"
-                        :index="grandChild.path || grandChild.name"
-                        v-show="!grandChild.is_hidden && grandChild.menu_type === 'menu'"
+                        v-else 
+                        :index="child.path || child.name"
                       >
-                        <el-icon v-if="grandChild.icon">
-                          <component :is="getIconComponent(grandChild.icon)" />
+                        <el-icon v-if="child.icon">
+                          <component :is="getIconComponent(child.icon)" />
                         </el-icon>
-                        <span>{{ grandChild.title }}</span>
+                        <span>{{ child.title }}</span>
                       </el-menu-item>
-                    </el-sub-menu>
-                    
-                    <!-- 子菜单是菜单项类型 -->
-                    <el-menu-item 
-                      v-else-if="child.menu_type === 'menu'"
-                      :index="child.path || child.name"
-                      v-show="!child.is_hidden"
-                    >
-                      <el-icon v-if="child.icon">
-                        <component :is="getIconComponent(child.icon)" />
-                      </el-icon>
-                      <span>{{ child.title }}</span>
-                    </el-menu-item>
-                  </template>
-                </el-sub-menu>
-              </template>
-              
-              <!-- 菜单项类型:直接显示为菜单项 -->
-              <template v-else-if="menu.menu_type === 'menu'">
-                <!-- 有子菜单的菜单项 -->
-                <el-sub-menu v-if="hasMenuChildren(menu)" :index="menu.name" v-show="!menu.is_hidden">
-                  <template #title>
-                    <el-icon v-if="menu.icon">
-                      <component :is="getIconComponent(menu.icon)" />
-                    </el-icon>
-                    <span>{{ menu.title }}</span>
+                    </template>
                   </template>
-                  <el-menu-item 
-                    v-for="child in getMenuChildren(menu)" 
-                    :key="child.id"
-                    :index="child.path || child.name"
-                    v-show="!child.is_hidden && child.menu_type === 'menu'"
-                  >
-                    <el-icon v-if="child.icon">
-                      <component :is="getIconComponent(child.icon)" />
-                    </el-icon>
-                    <span>{{ child.title }}</span>
-                  </el-menu-item>
                 </el-sub-menu>
                 
-                <!-- 没有子菜单的菜单项 -->
+                <!-- 没有子菜单的情况 -->
                 <el-menu-item 
                   v-else 
                   :index="menu.path || menu.name"
-                  v-show="!menu.is_hidden"
                 >
                   <el-icon v-if="menu.icon">
                     <component :is="getIconComponent(menu.icon)" />
@@ -157,7 +132,7 @@ const route = useRoute()
 const authStore = useAuthStore()
 
 // 接口定义
-interface MenuItem {
+export interface MenuItem {
   id: string | number
   name: string
   title: string
@@ -198,18 +173,14 @@ const getIconComponent = (iconName: string) => {
   return (ElementPlusIcons as any)[iconName] || ElementPlusIcons.Menu
 }
 
-// 检查是否有菜单类型的子项(包括parent和menu类型)
-const hasMenuChildren = (menu: MenuItem) => {
-  return menu.children && menu.children.some((child: MenuItem) => 
-    child.menu_type === 'menu' || child.menu_type === 'parent'
-  )
+// 检查是否有子项需要显示
+const hasMenuChildren = (menu: MenuItem): boolean => {
+  return !!(menu.children && menu.children.length > 0 && menu.children.some((child: MenuItem) => !child.is_hidden))
 }
 
-// 获取菜单类型的子项(包括parent和menu类型,排除button类型)
-const getMenuChildren = (menu: MenuItem) => {
-  return menu.children ? menu.children.filter((child: MenuItem) => 
-    child.menu_type === 'menu' || child.menu_type === 'parent'
-  ) : []
+// 获取需要显示的子项
+const getMenuChildren = (menu: MenuItem): MenuItem[] => {
+  return menu.children ? menu.children.filter((child: MenuItem) => !child.is_hidden) : []
 }
 
 // 获取用户菜单

+ 19 - 23
src/router/index.ts

@@ -4,10 +4,6 @@ import { useAuthStore } from '@/stores/auth'
 import MainLayout from '@/layouts/MainLayout.vue'
 
 const routes: RouteRecordRaw[] = [
-  {
-    path: '/',
-    redirect: '/dashboard'
-  },
   {
     path: '/login',
     name: 'Login',
@@ -30,6 +26,10 @@ const routes: RouteRecordRaw[] = [
     component: MainLayout,
     meta: { requiresAuth: true },
     children: [
+      {
+        path: '',
+        redirect: '/dashboard'
+      },
       {
         path: 'dashboard',
         name: 'Dashboard',
@@ -40,10 +40,6 @@ const routes: RouteRecordRaw[] = [
         name: 'Profile',
         component: () => import('@/views/user/Profile.vue')
       },
-      {
-        path: 'admin',
-        redirect: '/admin/dashboard'
-      },
       {
         path: 'admin/dashboard',
         name: 'AdminDashboard',
@@ -110,12 +106,6 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/views/documents/Index.vue'),
         meta: { requiresAdmin: true }
       },
-      {
-        path: 'admin/images',
-        name: 'Images',
-        component: () => import('@/views/images/Index.vue'),
-        meta: { requiresAdmin: true }
-      },
       {
         path: 'admin/documents/kb',
         name: 'KnowledgeBase',
@@ -135,21 +125,27 @@ const routes: RouteRecordRaw[] = [
         meta: { requiresAdmin: true }
       },
       {
-        path: 'admin/basic-info/basis',
-        name: 'BasicInfoBasis',
-        component: () => import('@/views/basic-info/Index.vue'),
+        path: 'admin/images',
+        name: 'Images',
+        component: () => import('@/views/images/Index.vue'),
+        meta: { requiresAdmin: true }
+      },
+      {
+        path: 'admin/basic-info/standard',
+        name: 'BasicInfoStandard',
+        component: () => import('@/views/basic-info/Standard.vue'),
         meta: { requiresAdmin: true }
       },
       {
-        path: 'admin/basic-info/work',
-        name: 'BasicInfoWork',
-        component: () => import('@/views/basic-info/Index.vue'),
+        path: 'admin/basic-info/construction_plan',
+        name: 'BasicInfoConstructionPlan',
+        component: () => import('@/views/basic-info/ConstructionPlan.vue'),
         meta: { requiresAdmin: true }
       },
       {
-        path: 'admin/basic-info/job',
-        name: 'BasicInfoJob',
-        component: () => import('@/views/basic-info/Index.vue'),
+        path: 'admin/basic-info/regulation',
+        name: 'BasicInfoRegulation',
+        component: () => import('@/views/basic-info/Regulation.vue'),
         meta: { requiresAdmin: true }
       }
     ]

+ 1 - 1
src/stores/auth.ts

@@ -19,7 +19,7 @@ export const useAuthStore = defineStore('auth', () => {
     // Check if user is superuser OR has admin/super_admin roles
     return user.value.is_superuser || 
            (user.value.roles && user.value.roles.some((role: string) => 
-             role === 'admin' || role === 'super_admin'
+             ['admin', 'super_admin', '管理员', '超级管理员'].includes(role.toLowerCase())
            ))
   })
 

+ 22 - 57
src/views/admin/Menus.vue

@@ -499,92 +499,57 @@ const buildMenuTree = (menuList: any[]) => {
     return []
   }
   
-  // 创建菜单映射
+  // 1. 初始化映射和节点
   const menuMap = new Map()
-  const tree: any[] = []
-  
-  // 先创建所有菜单节点
   menuList.forEach(menu => {
     menuMap.set(menu.id, { 
       ...menu, 
       children: [],
       hasChildren: false,
-      level: 0  // 添加层级信息
+      level: 0
     })
   })
   
-  console.log('📋 菜单映射创建完成,共', menuMap.size, '个节点')
-  
-  // 按层级和sort_order排序
-  const sortedMenus = [...menuList].sort((a, b) => {
-    // 先按层级排序(主菜单优先)
-    const aLevel = a.parent_id ? 1 : 0
-    const bLevel = b.parent_id ? 1 : 0
-    if (aLevel !== bLevel) {
-      return aLevel - bLevel
-    }
-    // 同层级按sort_order排序
-    return (a.sort_order || 0) - (b.sort_order || 0)
-  })
-  
-  // 构建树结构并设置层级
-  let rootCount = 0
-  let childCount = 0
+  const tree: any[] = []
   
-  sortedMenus.forEach(menu => {
-    const menuNode = menuMap.get(menu.id)
-    if (menu.parent_id && menuMap.has(menu.parent_id)) {
-      const parentNode = menuMap.get(menu.parent_id)
-      menuNode.level = parentNode.level + 1  // 设置子节点层级
-      parentNode.children.push(menuNode)
-      parentNode.hasChildren = true  // 确保设置hasChildren为true
-      childCount++
+  // 2. 构建结构 (不依赖顺序)
+  menuList.forEach(menu => {
+    const node = menuMap.get(menu.id)
+    const parentId = menu.parent_id
+    
+    if (parentId && menuMap.has(parentId)) {
+      const parentNode = menuMap.get(parentId)
+      parentNode.children.push(node)
+      parentNode.hasChildren = true
     } else {
-      menuNode.level = 0  // 根节点层级为0
-      tree.push(menuNode)
-      rootCount++
+      tree.push(node)
     }
   })
   
-  console.log('🌲 树结构构建完成:', rootCount, '个根节点,', childCount, '个子节点')
-  
-  // 对每个节点的children进行排序,并确保hasChildren正确设置
-  const sortChildren = (node: any, level = 0) => {
+  // 3. 递归排序并计算深度
+  const processNode = (node: any, level: number) => {
+    node.level = level
     if (node.children && node.children.length > 0) {
-      // 确保hasChildren为true
       node.hasChildren = true
-      
-      // 先按菜单类型排序(menu在前,button在后),再按sort_order排序
+      // 排序:menu > button, 然后按 sort_order
       node.children.sort((a: any, b: any) => {
         if (a.menu_type !== b.menu_type) {
+          if (a.menu_type === 'parent') return -1
+          if (b.menu_type === 'parent') return 1
           return a.menu_type === 'menu' ? -1 : 1
         }
         return (a.sort_order || 0) - (b.sort_order || 0)
       })
-      
-      console.log(`📂 层级 ${level}: ${node.title} 有 ${node.children.length} 个子节点`)
-      
-      // 递归排序子节点并设置层级
-      node.children.forEach((child, index) => {
-        child.level = level + 1
-        sortChildren(child, level + 1)
-      })
+      node.children.forEach((child: any) => processNode(child, level + 1))
     } else {
-      // 确保没有子节点的节点hasChildren为false
       node.hasChildren = false
     }
   }
   
-  tree.forEach(node => sortChildren(node, 0))
+  tree.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0))
+  tree.forEach(node => processNode(node, 0))
   
   console.log('✅ 菜单树构建完成,返回', tree.length, '个根节点')
-  console.log('🔍 根节点详情:', tree.map(t => ({ 
-    title: t.title, 
-    children: t.children.length, 
-    hasChildren: t.hasChildren,
-    level: t.level
-  })))
-  
   return tree
 }
 

+ 902 - 0
src/views/basic-info/ConstructionPlan.vue

@@ -0,0 +1,902 @@
+<template>
+  <div class="basic-info-container">
+    <el-card class="box-card search-card">
+      <div class="search-header">
+        <span class="title">施工方案管理</span>
+        <el-button type="primary" :icon="Plus" @click="handleAdd">新增施工方案</el-button>
+      </div>
+      
+      <el-form :model="searchForm" label-width="80px" class="search-form" label-position="top">
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <el-form-item label="方案名称">
+              <el-input v-model="searchForm.title" placeholder="请输入方案名称" clearable @keyup.enter="handleSearch" />
+            </el-form-item>
+          </el-col>
+          
+          <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-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-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-select>
+            </el-form-item>
+          </el-col>
+
+          <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-select>
+            </el-form-item>
+          </el-col>
+
+          <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-select>
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="6">
+            <el-form-item label="编制单位">
+              <el-input v-model="searchForm.issuing_authority" placeholder="请输入编制单位" clearable @keyup.enter="handleSearch" />
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="6">
+            <el-form-item label="编制开始日期">
+              <el-date-picker
+                v-model="searchForm.release_date_start"
+                type="date"
+                placeholder="选择开始日期"
+                style="width: 100%"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="编制结束日期">
+              <el-date-picker
+                v-model="searchForm.release_date_end"
+                type="date"
+                placeholder="选择结束日期"
+                style="width: 100%"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <div class="search-buttons">
+          <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
+          <el-button :icon="Refresh" @click="resetSearch">重置</el-button>
+        </div>
+      </el-form>
+    </el-card>
+
+    <el-card class="box-card table-card">
+      <el-table :data="tableData" v-loading="loading" style="width: 100%" border stripe>
+        <el-table-column prop="title" label="方案名称" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="kb_name" label="所属知识库" width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <span v-if="scope.row.kb_name" class="kb-name-tag">
+              <el-icon><Tickets /></el-icon> {{ scope.row.kb_name }}
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="issuing_authority" label="编制单位" width="180" show-overflow-tooltip />
+        <el-table-column prop="release_date" label="编制日期" width="120">
+          <template #default="scope">
+            {{ 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 label="备注" min-width="150" show-overflow-tooltip>
+          <template #default="scope">
+            {{ scope.row.note || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="creator_name" label="创建人" width="100" />
+        <el-table-column prop="created_time" label="创建时间" width="180">
+          <template #default="scope">
+            {{ formatDateTime(scope.row.created_time) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="updater_name" label="修改人" width="100" />
+        <el-table-column prop="updated_time" label="修改时间" width="180">
+          <template #default="scope">
+            {{ formatDateTime(scope.row.updated_time) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="180" fixed="right" align="center">
+          <template #default="scope">
+            <div class="action-buttons">
+              <el-tooltip content="详情" placement="top">
+                <el-button link type="primary" @click="handleAction('view', scope.row)">
+                  <el-icon><View /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="下载" placement="top" v-if="scope.row.file_url">
+                <el-button link type="success" @click="handleAction('download', scope.row)">
+                  <el-icon><Download /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="编辑" placement="top">
+                <el-button link type="primary" @click="handleAction('edit', scope.row)">
+                  <el-icon><Edit /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="danger" @click="handleAction('delete', scope.row)">
+                  <el-icon><Delete /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="入库" placement="top">
+                <el-button link type="warning" @click="handleAction('ingest', scope.row)">
+                  <el-icon><Document /></el-icon>
+                </el-button>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog v-model="formDialogVisible" :title="formTitle" width="800px">
+      <el-form :model="editForm" label-width="110px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="目标知识库">
+              <el-select v-model="editForm.kb_id" placeholder="请选择目标知识库" clearable style="width: 100%">
+                <el-option
+                  v-for="item in knowledgeBases"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="方案名称" required>
+              <el-input v-model="editForm.title" placeholder="请输入方案名称" />
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="12">
+            <el-form-item label="项目名称">
+              <el-input v-model="editForm.project_name" placeholder="请输入项目名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="项目标段">
+              <el-input v-model="editForm.project_section" placeholder="请输入项目标段" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="编制单位">
+              <el-input v-model="editForm.issuing_authority" placeholder="请输入编制单位" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="编制日期">
+              <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+
+          <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-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="一级分类">
+              <el-input v-model="editForm.level_1_classification" disabled />
+            </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-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-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-select>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="方案摘要">
+              <el-input v-model="editForm.plan_summary" type="textarea" :rows="3" placeholder="请输入方案摘要" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="编制依据">
+              <div v-for="(planStd, index) in compilationBasisList" :key="index" class="dynamic-input-row">
+                <el-input v-model="compilationBasisList[index]" placeholder="请输入编制依据" />
+                <div class="row-actions">
+                  <el-button :icon="Plus" circle size="small" @click="addListItem(compilationBasisList)" />
+                  <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(compilationBasisList, index)" v-if="compilationBasisList.length > 1" />
+                </div>
+              </div>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="备注">
+              <el-input v-model="editForm.note" type="textarea" :rows="2" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="上传文件" :required="!editForm.id">
+              <el-upload
+                v-if="!editForm.id"
+                class="upload-demo"
+                action="#"
+                :auto-upload="false"
+                :on-change="handleFileChange"
+                :on-remove="handleFileRemove"
+                :file-list="fileList"
+                :limit="1"
+                accept=".pdf,.doc,.docx,.ppt,.pptx,.png,.jpg,.jpeg,.txt"
+              >
+                <el-button type="primary">选择文件</el-button>
+                <template #tip>
+                  <div class="el-upload__tip">
+                    支持 PDF, Word, PPT, 图片, TXT 等格式
+                  </div>
+                </template>
+              </el-upload>
+              <div v-else class="file-info-edit">
+                <el-tag type="info" size="large" class="file-tag">
+                  <el-icon><Document /></el-icon>
+                  <span>文件已锁定(编辑模式不可更改)</span>
+                </el-tag>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button @click="formDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 查看详情对话框 -->
+    <el-dialog v-model="detailDialogVisible" title="详情信息" width="600px">
+      <el-descriptions :column="1" border>
+        <el-descriptions-item label="方案名称">{{ currentItem?.title }}</el-descriptions-item>
+        <el-descriptions-item label="所属知识库" v-if="currentItem?.kb_name">
+          <span class="kb-name-tag">
+            <el-icon><Tickets /></el-icon> {{ currentItem.kb_name }}
+          </span>
+        </el-descriptions-item>
+        <el-descriptions-item label="编制单位">{{ currentItem?.issuing_authority || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="编制日期">{{ formatDate(currentItem?.release_date) }}</el-descriptions-item>
+        <el-descriptions-item label="项目名称">{{ currentItem?.project_name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="项目标段">{{ currentItem?.project_section || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="方案类别">{{ currentItem?.plan_category || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="一级分类">{{ currentItem?.level_1_classification || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="二级分类">{{ currentItem?.level_2_classification || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="三级分类">{{ currentItem?.level_3_classification || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="四级分类">{{ currentItem?.level_4_classification || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="方案摘要">{{ currentItem?.plan_summary || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="编制依据">
+          <div v-if="currentItem?.compilation_basis">
+            <div v-for="(planStd, idx) in currentItem.compilation_basis.split(';')" :key="idx">{{ planStd }}</div>
+          </div>
+          <span v-else>-</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ currentItem?.creator_name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ formatDateTime(currentItem?.created_time) }}</el-descriptions-item>
+        <el-descriptions-item label="修改人">{{ currentItem?.updater_name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="修改时间">{{ formatDateTime(currentItem?.updated_time) }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+        <el-button type="primary" @click="handleAction('edit', currentItem)">编辑</el-button>
+        <el-button type="success" @click="handleAction('preview', currentItem)" v-if="currentItem?.file_url">预览文件</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 预览对话框 -->
+    <el-dialog v-model="previewVisible" :title="previewTitle" width="85%" top="5vh" custom-class="preview-dialog" @closed="previewUrl = ''">
+      <template #header>
+        <div class="dialog-header-custom">
+          <span>{{ previewTitle }}</span>
+          <div class="header-actions">
+            <el-button type="primary" link @click="openInNewWindow">
+              <el-icon><Monitor /></el-icon> 在新窗口打开
+            </el-button>
+          </div>
+        </div>
+      </template>
+      <div v-loading="previewLoading" class="preview-content" style="height: 70vh;">
+        <iframe 
+          v-if="previewUrl" 
+          :src="proxyPreviewUrl" 
+          width="100%" 
+          height="100%" 
+          frameborder="0"
+          @load="previewLoading = false"
+        ></iframe>
+        <div v-else-if="!previewLoading" class="no-preview">
+          <el-empty description="无法预览该文件" />
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 入库确认对话框 -->
+    <el-dialog v-model="ingestDialogVisible" title="文档入库" width="450px">
+      <el-form :model="ingestForm" label-width="100px">
+        <el-form-item label="选择知识库" required>
+          <el-select v-model="ingestForm.kb_id" placeholder="请选择知识库" style="width: 100%">
+            <el-option
+              v-for="item in knowledgeBases"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="切分方式" required>
+          <el-radio-group v-model="ingestForm.kb_method">
+            <el-radio label="length">按长度切分</el-radio>
+            <el-radio label="symbol">按符号切分</el-radio>
+            <el-radio label="parent_child">父子段切分</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="ingestDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmIngest" :loading="ingesting">确认入库</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+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 request from '@/api/request'
+import type { ApiResponse } from '@/types/auth'
+import dayjs from 'dayjs'
+import { downloadFile } from '@/utils/download'
+import { getFileExtension } from '@/utils/file'
+import { useAuthStore } from '@/stores/auth'
+
+const authStore = useAuthStore()
+const loading = ref(false)
+const submitting = ref(false)
+const tableData = ref([])
+const total = ref(0)
+const currentPage = ref(1)
+const pageSize = ref(20)
+
+// 对话框状态
+const formDialogVisible = ref(false)
+const detailDialogVisible = ref(false)
+const previewVisible = ref(false)
+const previewLoading = ref(false)
+const previewUrl = ref('')
+const previewTitle = ref('')
+
+// 入库相关状态
+const ingestDialogVisible = ref(false)
+const ingesting = ref(false)
+const knowledgeBases = ref<any[]>([])
+const ingestForm = reactive({
+  kb_id: '',
+  kb_method: 'length'
+})
+
+// 动态列表字段存储
+const compilationBasisList = ref<string[]>([''])
+const fileList = ref<any[]>([])
+
+const handleFileChange = (file: any, files: any[]) => {
+  fileList.value = files
+  if (file.name && !editForm.title) {
+    const nameParts = file.name.split('.')
+    if (nameParts.length > 1) {
+      nameParts.pop()
+    }
+    editForm.title = nameParts.join('.')
+  }
+}
+
+const handleFileRemove = () => {
+  fileList.value = []
+}
+
+const addListItem = (list: string[]) => {
+  list.push('')
+}
+
+const removeListItem = (list: string[], index: number) => {
+  if (list.length > 1) {
+    list.splice(index, 1)
+  } else {
+    list[0] = ''
+  }
+}
+
+// 当前操作的数据
+const currentItem = ref<any>(null)
+const editForm = reactive<any>({
+  id: null,
+  title: '',
+  issuing_authority: '',
+  release_date: '',
+  project_name: '',
+  project_section: '',
+  note: '',
+  file_url: '',
+  plan_summary: '',
+  compilation_basis: '',
+  plan_category: '',
+  level_1_classification: '施工方案',
+  level_2_classification: '',
+  level_3_classification: '',
+  level_4_classification: '',
+  kb_id: ''
+})
+
+const formTitle = computed(() => editForm.id ? '编辑施工方案' : '新增施工方案')
+
+// 检索表单
+const searchForm = reactive<any>({
+  title: '',
+  issuing_authority: '',
+  release_date_start: '',
+  release_date_end: '',
+  plan_category: '',
+  level_1_classification: '',
+  level_2_classification: '',
+  level_3_classification: '',
+  level_4_classification: ''
+})
+
+// 选项配置
+const planCategoryOptions = ['超危大方案', '超危大方案较大II级', '超危大方案特大IV级', '超危大方案一般I级', '超危大方案重大III级', '危大方案', '一般方案']
+const level2Options = ['临建工程', '路基工程', '其他', '桥梁工程', '隧道工程']
+const level3Options = ['/', 'TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '其他', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥']
+const level4Options = ['/', '挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '其他', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础']
+
+// 获取列表数据
+const loadData = async () => {
+  loading.value = true
+  try {
+    const response = await request.get<ApiResponse<{
+      items: any[]
+      total: number
+      page: number
+      size: number
+    }>>('/api/v1/sample/basic-info/list', {
+      params: {
+        type: 'construction_plan',
+        page: currentPage.value,
+        size: pageSize.value,
+        ...searchForm
+      }
+    })
+    
+    const resData = (response as unknown) as ApiResponse<{
+      items: any[]
+      total: number
+      page: number
+      size: number
+    }>
+
+    if (resData.code === 0) {
+      tableData.value = resData.data.items as any
+      total.value = resData.data.total
+    } else {
+      ElMessage.error(resData.message || '获取数据失败')
+    }
+  } catch (error) {
+    console.error('获取数据失败:', error)
+    ElMessage.error('网络错误,请稍后重试')
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleSearch = () => {
+  currentPage.value = 1
+  loadData()
+}
+
+const resetSearch = () => {
+  Object.keys(searchForm).forEach(key => {
+    searchForm[key] = ''
+  })
+  handleSearch()
+}
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  loadData()
+}
+
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  loadData()
+}
+
+const formatDate = (date: string | undefined | null) => {
+  if (!date) return '-'
+  return dayjs(date).format('YYYY-MM-DD')
+}
+
+const formatDateTime = (date: string | undefined | null) => {
+  if (!date) return '-'
+  return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
+}
+
+const proxyPreviewUrl = computed(() => {
+  if (!previewUrl.value) return ''
+  let baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
+  if (baseUrl.includes('localhost') && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+    baseUrl = baseUrl.replace('localhost', window.location.hostname)
+  }
+  return `${baseUrl}/api/v1/sample/documents/proxy-view?url=${encodeURIComponent(previewUrl.value)}&token=${authStore.token}`
+})
+
+const handleAction = async (action: string, row: any) => {
+  currentItem.value = row
+  switch (action) {
+    case 'view':
+      if (row.file_url) {
+        previewTitle.value = row.title
+        previewUrl.value = row.file_url
+        previewVisible.value = true
+        previewLoading.value = true
+      } else {
+        detailDialogVisible.value = true
+      }
+      break
+    case 'preview':
+      if (row.file_url) {
+        previewTitle.value = row.title
+        previewUrl.value = row.file_url
+        previewVisible.value = true
+        previewLoading.value = true
+      } else {
+        ElMessage.warning('该文档暂无预览链接')
+      }
+      break
+    case 'download':
+      if (row.file_url) {
+        const ext = getFileExtension(row)
+        const filename = row.title.endsWith(ext) ? row.title : `${row.title}${ext}`
+        downloadFile(row.file_url, filename)
+      } else {
+        ElMessage.warning('该文档暂无下载链接')
+      }
+      break
+    case 'edit':
+      loadKnowledgeBases()
+      Object.keys(editForm).forEach(key => {
+        editForm[key] = row[key] || ''
+      })
+      compilationBasisList.value = row.compilation_basis ? row.compilation_basis.split(';') : ['']
+      editForm.id = row.id
+      fileList.value = []
+      formDialogVisible.value = true
+      break
+    case 'delete':
+      ElMessageBox.confirm('确定要删除该条信息吗?', '提示', {
+        type: 'warning'
+      }).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) {
+            ElMessage.success('删除成功')
+            loadData()
+          } else {
+            ElMessage.error(res.message || '删除失败')
+          }
+        } catch (error) {
+          console.error('删除失败:', error)
+          ElMessage.error('网络错误,请稍后重试')
+        }
+      }).catch(() => {})
+      break
+    case 'ingest':
+      currentItem.value = row
+      loadKnowledgeBases()
+      ingestDialogVisible.value = true
+      break
+  }
+}
+
+const loadKnowledgeBases = async () => {
+  try {
+    const res = await request.get<ApiResponse<any[]>>('/api/v1/sample/knowledge-base/list', {
+      params: {
+        page: 1,
+        page_size: 1000
+      }
+    })
+    if (res.code === 0) {
+      knowledgeBases.value = res.data
+      if (knowledgeBases.value.length > 0 && !ingestForm.kb_id) {
+        ingestForm.kb_id = knowledgeBases.value[0].id
+      }
+    }
+  } catch (error) {
+    console.error('获取知识库失败:', error)
+  }
+}
+
+const confirmIngest = async () => {
+  if (!ingestForm.kb_id) {
+    ElMessage.warning('请选择知识库')
+    return
+  }
+  if (!currentItem.value || !currentItem.value.id) {
+    ElMessage.warning('未选中有效文档')
+    return
+  }
+  ingesting.value = true
+  try {
+    const res = await request.post<ApiResponse>('/api/v1/sample/documents/batch-enter', {
+      ids: [currentItem.value.id],
+      table_type: 'construction_plan',
+      kb_id: ingestForm.kb_id,
+      kb_method: ingestForm.kb_method
+    })
+    if (res.code === 0) {
+      ElMessage.success(res.message || '已加入入库队列')
+      ingestDialogVisible.value = false
+      loadData()
+    } else {
+      ElMessage.error(res.message || '入库失败')
+    }
+  } catch (error) {
+    console.error('入库失败:', error)
+    ElMessage.error('网络错误,请稍后重试')
+  } finally {
+    ingesting.value = false
+  }
+}
+
+const handleAdd = () => {
+  loadKnowledgeBases()
+  Object.keys(editForm).forEach(key => {
+    editForm[key] = ''
+  })
+  editForm.id = null
+  editForm.level_1_classification = '施工方案'
+  compilationBasisList.value = ['']
+  fileList.value = []
+  formDialogVisible.value = true
+}
+
+const submitForm = async () => {
+  if (!editForm.title) {
+    ElMessage.warning('请输入标题')
+    return
+  }
+  submitting.value = true
+  try {
+    if (fileList.value.length > 0 && fileList.value[0].raw) {
+      const file = fileList.value[0].raw
+      const urlRes = await request.post<ApiResponse<any>>('/api/v1/sample/documents/upload-url', {
+        filename: file.name,
+        content_type: file.type,
+        prefix: 'basic-info/construction_plan'
+      })
+      if (urlRes.code === 0) {
+        const { upload_url, file_url } = urlRes.data
+        await fetch(upload_url, {
+          method: 'PUT',
+          body: file,
+          headers: {
+            'Content-Type': file.type
+          }
+        })
+        editForm.file_url = file_url
+      } else {
+        throw new Error(urlRes.message || '获取上传链接失败')
+      }
+    } else if (!editForm.id && fileList.value.length === 0) {
+      ElMessage.warning('请选择要上传的文件')
+      submitting.value = false
+      return
+    }
+
+    editForm.compilation_basis = compilationBasisList.value.filter(item => item.trim() !== '').join(';')
+    
+    const url = editForm.id 
+      ? `/api/v1/sample/basic-info/edit?type=construction_plan&id=${editForm.id}`
+      : '/api/v1/sample/basic-info/add?type=construction_plan'
+    
+    const res = await request.post<ApiResponse>(url, editForm)
+    if (res.code === 0) {
+      ElMessage.success(editForm.id ? '修改成功' : '新增成功')
+      formDialogVisible.value = false
+      fileList.value = []
+      loadData()
+    } else {
+      ElMessage.error(res.message || '提交失败')
+    }
+  } catch (error: any) {
+    console.error('提交失败:', error)
+    ElMessage.error(error.message || '网络错误,请稍后重试')
+  } finally {
+    submitting.value = false
+  }
+}
+
+const openInNewWindow = () => {
+  if (previewUrl.value) {
+    window.open(proxyPreviewUrl.value, '_blank')
+  }
+}
+
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style scoped>
+.basic-info-container {
+  padding: 20px;
+}
+.search-card {
+  margin-bottom: 20px;
+}
+.search-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+.search-header .title {
+  font-size: 18px;
+  font-weight: bold;
+}
+.search-form {
+  margin-bottom: 0;
+}
+.search-buttons {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+.table-card {
+  margin-bottom: 20px;
+}
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+.action-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 5px;
+}
+.file-info-edit {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+}
+.file-tag {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  width: fit-content;
+}
+.dynamic-input-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 8px;
+  width: 100%;
+}
+.dynamic-input-row :deep(.el-input) {
+  flex: 1;
+}
+.dynamic-input-row:last-child {
+  margin-bottom: 0;
+}
+.row-actions {
+  display: flex;
+  gap: 5px;
+  margin-left: 10px;
+  flex-shrink: 0;
+}
+.tag-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+.info-tag {
+  max-width: 120px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.popover-tag-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  padding: 4px;
+}
+.popover-info-tag {
+  margin-bottom: 4px;
+}
+.dialog-header-custom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-right: 30px;
+}
+.header-actions {
+  display: flex;
+  align-items: center;
+}
+.no-preview {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+}
+:deep(.preview-dialog .el-dialog__body) {
+  padding: 0;
+}
+</style>

+ 745 - 0
src/views/basic-info/Regulation.vue

@@ -0,0 +1,745 @@
+<template>
+  <div class="basic-info-container">
+    <el-card class="box-card search-card">
+      <div class="search-header">
+        <span class="title">办公制度管理</span>
+        <el-button type="primary" :icon="Plus" @click="handleAdd">新增办公制度</el-button>
+      </div>
+      
+      <el-form :model="searchForm" label-width="80px" class="search-form" label-position="top">
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <el-form-item label="文件名称">
+              <el-input v-model="searchForm.title" placeholder="请输入文件名称" clearable @keyup.enter="handleSearch" />
+            </el-form-item>
+          </el-col>
+          
+          <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-select>
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="6">
+            <el-form-item label="发布部门">
+              <el-input v-model="searchForm.issuing_authority" placeholder="请输入发布部门" clearable @keyup.enter="handleSearch" />
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="6">
+            <el-form-item label="发布开始日期">
+              <el-date-picker
+                v-model="searchForm.release_date_start"
+                type="date"
+                placeholder="选择开始日期"
+                style="width: 100%"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="发布结束日期">
+              <el-date-picker
+                v-model="searchForm.release_date_end"
+                type="date"
+                placeholder="选择结束日期"
+                style="width: 100%"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <div class="search-buttons">
+          <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
+          <el-button :icon="Refresh" @click="resetSearch">重置</el-button>
+        </div>
+      </el-form>
+    </el-card>
+
+    <el-card class="box-card table-card">
+      <el-table :data="tableData" v-loading="loading" style="width: 100%" border stripe>
+        <el-table-column prop="title" label="文件名称" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="kb_name" label="所属知识库" width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <span v-if="scope.row.kb_name" class="kb-name-tag">
+              <el-icon><Tickets /></el-icon> {{ scope.row.kb_name }}
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="issuing_authority" label="发布部门" width="180" show-overflow-tooltip />
+        <el-table-column prop="release_date" label="发布日期" width="120">
+          <template #default="scope">
+            {{ formatDate(scope.row.release_date) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="document_type" label="文档类型" width="120" />
+        <el-table-column label="备注" min-width="150" show-overflow-tooltip>
+          <template #default="scope">
+            {{ scope.row.note || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="creator_name" label="创建人" width="100" />
+        <el-table-column prop="created_time" label="创建时间" width="180">
+          <template #default="scope">
+            {{ formatDateTime(scope.row.created_time) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="updater_name" label="修改人" width="100" />
+        <el-table-column prop="updated_time" label="修改时间" width="180">
+          <template #default="scope">
+            {{ formatDateTime(scope.row.updated_time) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="180" fixed="right" align="center">
+          <template #default="scope">
+            <div class="action-buttons">
+              <el-tooltip content="详情" placement="top">
+                <el-button link type="primary" @click="handleAction('view', scope.row)">
+                  <el-icon><View /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="下载" placement="top" v-if="scope.row.file_url">
+                <el-button link type="success" @click="handleAction('download', scope.row)">
+                  <el-icon><Download /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="编辑" placement="top">
+                <el-button link type="primary" @click="handleAction('edit', scope.row)">
+                  <el-icon><Edit /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="danger" @click="handleAction('delete', scope.row)">
+                  <el-icon><Delete /></el-icon>
+                </el-button>
+              </el-tooltip>
+              <el-tooltip content="入库" placement="top">
+                <el-button link type="warning" @click="handleAction('ingest', scope.row)">
+                  <el-icon><Document /></el-icon>
+                </el-button>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog v-model="formDialogVisible" :title="formTitle" width="800px">
+      <el-form :model="editForm" label-width="110px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="目标知识库">
+              <el-select v-model="editForm.kb_id" placeholder="请选择目标知识库" clearable style="width: 100%">
+                <el-option
+                  v-for="item in knowledgeBases"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="文件名称" required>
+              <el-input v-model="editForm.title" placeholder="请输入文件名称" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="发布部门">
+              <el-input v-model="editForm.issuing_authority" placeholder="请输入发布部门" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="发布日期">
+              <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+
+          <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-select>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="生效日期">
+              <el-date-picker v-model="editForm.effective_start_date" type="date" placeholder="选择生效日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="失效日期">
+              <el-date-picker v-model="editForm.effective_end_date" type="date" placeholder="选择失效日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="备注">
+              <el-input v-model="editForm.note" type="textarea" :rows="2" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="上传文件" :required="!editForm.id">
+              <el-upload
+                v-if="!editForm.id"
+                class="upload-demo"
+                action="#"
+                :auto-upload="false"
+                :on-change="handleFileChange"
+                :on-remove="handleFileRemove"
+                :file-list="fileList"
+                :limit="1"
+                accept=".pdf,.doc,.docx,.ppt,.pptx,.png,.jpg,.jpeg,.txt"
+              >
+                <el-button type="primary">选择文件</el-button>
+                <template #tip>
+                  <div class="el-upload__tip">
+                    支持 PDF, Word, PPT, 图片, TXT 等格式
+                  </div>
+                </template>
+              </el-upload>
+              <div v-else class="file-info-edit">
+                <el-tag type="info" size="large" class="file-tag">
+                  <el-icon><Document /></el-icon>
+                  <span>文件已锁定(编辑模式不可更改)</span>
+                </el-tag>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button @click="formDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 查看详情对话框 -->
+    <el-dialog v-model="detailDialogVisible" title="详情信息" width="600px">
+      <el-descriptions :column="1" border>
+        <el-descriptions-item label="文件名称">{{ currentItem?.title }}</el-descriptions-item>
+        <el-descriptions-item label="所属知识库" v-if="currentItem?.kb_name">
+          <span class="kb-name-tag">
+            <el-icon><Tickets /></el-icon> {{ currentItem.kb_name }}
+          </span>
+        </el-descriptions-item>
+        <el-descriptions-item label="发布部门">{{ currentItem?.issuing_authority || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="发布日期">{{ formatDate(currentItem?.release_date) }}</el-descriptions-item>
+        <el-descriptions-item label="文档类型">{{ currentItem?.document_type || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="生效日期">{{ formatDate(currentItem?.effective_start_date) }}</el-descriptions-item>
+        <el-descriptions-item label="失效日期">{{ formatDate(currentItem?.effective_end_date) }}</el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ currentItem?.creator_name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ formatDateTime(currentItem?.created_time) }}</el-descriptions-item>
+        <el-descriptions-item label="修改人">{{ currentItem?.updater_name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="修改时间">{{ formatDateTime(currentItem?.updated_time) }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+        <el-button type="primary" @click="handleAction('edit', currentItem)">编辑</el-button>
+        <el-button type="success" @click="handleAction('preview', currentItem)" v-if="currentItem?.file_url">预览文件</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 预览对话框 -->
+    <el-dialog v-model="previewVisible" :title="previewTitle" width="85%" top="5vh" custom-class="preview-dialog" @closed="previewUrl = ''">
+      <template #header>
+        <div class="dialog-header-custom">
+          <span>{{ previewTitle }}</span>
+          <div class="header-actions">
+            <el-button type="primary" link @click="openInNewWindow">
+              <el-icon><Monitor /></el-icon> 在新窗口打开
+            </el-button>
+          </div>
+        </div>
+      </template>
+      <div v-loading="previewLoading" class="preview-content" style="height: 70vh;">
+        <iframe 
+          v-if="previewUrl" 
+          :src="proxyPreviewUrl" 
+          width="100%" 
+          height="100%" 
+          frameborder="0"
+          @load="previewLoading = false"
+        ></iframe>
+        <div v-else-if="!previewLoading" class="no-preview">
+          <el-empty description="无法预览该文件" />
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 入库确认对话框 -->
+    <el-dialog v-model="ingestDialogVisible" title="文档入库" width="450px">
+      <el-form :model="ingestForm" label-width="100px">
+        <el-form-item label="选择知识库" required>
+          <el-select v-model="ingestForm.kb_id" placeholder="请选择知识库" style="width: 100%">
+            <el-option
+              v-for="item in knowledgeBases"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="切分方式" required>
+          <el-radio-group v-model="ingestForm.kb_method">
+            <el-radio label="length">按长度切分</el-radio>
+            <el-radio label="symbol">按符号切分</el-radio>
+            <el-radio label="parent_child">父子段切分</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="ingestDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmIngest" :loading="ingesting">确认入库</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, computed, reactive } from 'vue'
+import { Search, View, Monitor, Download, Edit, Delete, Refresh, Plus, Document, Tickets } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import request from '@/api/request'
+import type { ApiResponse } from '@/types/auth'
+import dayjs from 'dayjs'
+import { downloadFile } from '@/utils/download'
+import { getFileExtension } from '@/utils/file'
+import { useAuthStore } from '@/stores/auth'
+
+const authStore = useAuthStore()
+const loading = ref(false)
+const submitting = ref(false)
+const tableData = ref([])
+const total = ref(0)
+const currentPage = ref(1)
+const pageSize = ref(20)
+
+// 对话框状态
+const formDialogVisible = ref(false)
+const detailDialogVisible = ref(false)
+const previewVisible = ref(false)
+const previewLoading = ref(false)
+const previewUrl = ref('')
+const previewTitle = ref('')
+
+// 入库相关状态
+const ingestDialogVisible = ref(false)
+const ingesting = ref(false)
+const knowledgeBases = ref<any[]>([])
+const ingestForm = reactive({
+  kb_id: '',
+  kb_method: 'length'
+})
+
+// 动态列表字段存储
+const fileList = ref<any[]>([])
+
+const handleFileChange = (file: any, files: any[]) => {
+  fileList.value = files
+  if (file.name && !editForm.title) {
+    const nameParts = file.name.split('.')
+    if (nameParts.length > 1) {
+      nameParts.pop()
+    }
+    editForm.title = nameParts.join('.')
+  }
+}
+
+const handleFileRemove = () => {
+  fileList.value = []
+}
+
+// 当前操作的数据
+const currentItem = ref<any>(null)
+const editForm = reactive<any>({
+  id: null,
+  title: '',
+  issuing_authority: '',
+  release_date: '',
+  document_type: '',
+  note: '',
+  file_url: '',
+  effective_start_date: '',
+  effective_end_date: '',
+  kb_id: ''
+})
+
+const formTitle = computed(() => editForm.id ? '编辑办公制度' : '新增办公制度')
+
+// 检索表单
+const searchForm = reactive<any>({
+  title: '',
+  document_type: '',
+  issuing_authority: '',
+  release_date_start: '',
+  release_date_end: ''
+})
+
+// 选项配置
+const documentTypeOptions = ['国家标准', '行业标准', '企业标准', '地方标准', '管理制度', '技术规范']
+
+// 获取列表数据
+const loadData = async () => {
+  loading.value = true
+  try {
+    const response = await request.get<ApiResponse<{
+      items: any[]
+      total: number
+      page: number
+      size: number
+    }>>('/api/v1/sample/basic-info/list', {
+      params: {
+        type: 'regulation',
+        page: currentPage.value,
+        size: pageSize.value,
+        ...searchForm
+      }
+    })
+    
+    const resData = (response as unknown) as ApiResponse<{
+      items: any[]
+      total: number
+      page: number
+      size: number
+    }>
+
+    if (resData.code === 0) {
+      tableData.value = resData.data.items as any
+      total.value = resData.data.total
+    } else {
+      ElMessage.error(resData.message || '获取数据失败')
+    }
+  } catch (error) {
+    console.error('获取数据失败:', error)
+    ElMessage.error('网络错误,请稍后重试')
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleSearch = () => {
+  currentPage.value = 1
+  loadData()
+}
+
+const resetSearch = () => {
+  Object.keys(searchForm).forEach(key => {
+    searchForm[key] = ''
+  })
+  handleSearch()
+}
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  loadData()
+}
+
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  loadData()
+}
+
+const formatDate = (date: string | undefined | null) => {
+  if (!date) return '-'
+  return dayjs(date).format('YYYY-MM-DD')
+}
+
+const formatDateTime = (date: string | undefined | null) => {
+  if (!date) return '-'
+  return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
+}
+
+const proxyPreviewUrl = computed(() => {
+  if (!previewUrl.value) return ''
+  let baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
+  if (baseUrl.includes('localhost') && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+    baseUrl = baseUrl.replace('localhost', window.location.hostname)
+  }
+  return `${baseUrl}/api/v1/sample/documents/proxy-view?url=${encodeURIComponent(previewUrl.value)}&token=${authStore.token}`
+})
+
+const handleAction = async (action: string, row: any) => {
+  currentItem.value = row
+  switch (action) {
+    case 'view':
+      if (row.file_url) {
+        previewTitle.value = row.title
+        previewUrl.value = row.file_url
+        previewVisible.value = true
+        previewLoading.value = true
+      } else {
+        detailDialogVisible.value = true
+      }
+      break
+    case 'preview':
+      if (row.file_url) {
+        previewTitle.value = row.title
+        previewUrl.value = row.file_url
+        previewVisible.value = true
+        previewLoading.value = true
+      } else {
+        ElMessage.warning('该文档暂无预览链接')
+      }
+      break
+    case 'download':
+      if (row.file_url) {
+        const ext = getFileExtension(row)
+        const filename = row.title.endsWith(ext) ? row.title : `${row.title}${ext}`
+        downloadFile(row.file_url, filename)
+      } else {
+        ElMessage.warning('该文档暂无下载链接')
+      }
+      break
+    case 'edit':
+      loadKnowledgeBases()
+      Object.keys(editForm).forEach(key => {
+        editForm[key] = row[key] || ''
+      })
+      editForm.id = row.id
+      fileList.value = []
+      formDialogVisible.value = true
+      break
+    case 'delete':
+      ElMessageBox.confirm('确定要删除该条信息吗?', '提示', {
+        type: 'warning'
+      }).then(async () => {
+        try {
+          const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=regulation&id=${row.id}`)
+          if (res.code === 0) {
+            ElMessage.success('删除成功')
+            loadData()
+          } else {
+            ElMessage.error(res.message || '删除失败')
+          }
+        } catch (error) {
+          console.error('删除失败:', error)
+          ElMessage.error('网络错误,请稍后重试')
+        }
+      }).catch(() => {})
+      break
+    case 'ingest':
+      currentItem.value = row
+      loadKnowledgeBases()
+      ingestDialogVisible.value = true
+      break
+  }
+}
+
+const loadKnowledgeBases = async () => {
+  try {
+    const res = await request.get<ApiResponse<any[]>>('/api/v1/sample/knowledge-base/list', {
+      params: {
+        page: 1,
+        page_size: 1000
+      }
+    })
+    if (res.code === 0) {
+      knowledgeBases.value = res.data
+      if (knowledgeBases.value.length > 0 && !ingestForm.kb_id) {
+        ingestForm.kb_id = knowledgeBases.value[0].id
+      }
+    }
+  } catch (error) {
+    console.error('获取知识库失败:', error)
+  }
+}
+
+const confirmIngest = async () => {
+  if (!ingestForm.kb_id) {
+    ElMessage.warning('请选择知识库')
+    return
+  }
+  if (!currentItem.value || !currentItem.value.id) {
+    ElMessage.warning('未选中有效文档')
+    return
+  }
+  ingesting.value = true
+  try {
+    const res = await request.post<ApiResponse>('/api/v1/sample/documents/batch-enter', {
+      ids: [currentItem.value.id],
+      table_type: 'regulation',
+      kb_id: ingestForm.kb_id,
+      kb_method: ingestForm.kb_method
+    })
+    if (res.code === 0) {
+      ElMessage.success(res.message || '已加入入库队列')
+      ingestDialogVisible.value = false
+      loadData()
+    } else {
+      ElMessage.error(res.message || '入库失败')
+    }
+  } catch (error) {
+    console.error('入库失败:', error)
+    ElMessage.error('网络错误,请稍后重试')
+  } finally {
+    ingesting.value = false
+  }
+}
+
+const handleAdd = () => {
+  loadKnowledgeBases()
+  Object.keys(editForm).forEach(key => {
+    editForm[key] = ''
+  })
+  editForm.id = null
+  fileList.value = []
+  formDialogVisible.value = true
+}
+
+const submitForm = async () => {
+  if (!editForm.title) {
+    ElMessage.warning('请输入标题')
+    return
+  }
+  submitting.value = true
+  try {
+    if (fileList.value.length > 0 && fileList.value[0].raw) {
+      const file = fileList.value[0].raw
+      const urlRes = await request.post<ApiResponse<any>>('/api/v1/sample/documents/upload-url', {
+        filename: file.name,
+        content_type: file.type,
+        prefix: 'basic-info/regulation'
+      })
+      if (urlRes.code === 0) {
+        const { upload_url, file_url } = urlRes.data
+        await fetch(upload_url, {
+          method: 'PUT',
+          body: file,
+          headers: {
+            'Content-Type': file.type
+          }
+        })
+        editForm.file_url = file_url
+      } else {
+        throw new Error(urlRes.message || '获取上传链接失败')
+      }
+    } else if (!editForm.id && fileList.value.length === 0) {
+      ElMessage.warning('请选择要上传的文件')
+      submitting.value = false
+      return
+    }
+
+    const url = editForm.id 
+      ? `/api/v1/sample/basic-info/edit?type=regulation&id=${editForm.id}`
+      : '/api/v1/sample/basic-info/add?type=regulation'
+    
+    const res = await request.post<ApiResponse>(url, editForm)
+    if (res.code === 0) {
+      ElMessage.success(editForm.id ? '修改成功' : '新增成功')
+      formDialogVisible.value = false
+      fileList.value = []
+      loadData()
+    } else {
+      ElMessage.error(res.message || '提交失败')
+    }
+  } catch (error: any) {
+    console.error('提交失败:', error)
+    ElMessage.error(error.message || '网络错误,请稍后重试')
+  } finally {
+    submitting.value = false
+  }
+}
+
+const openInNewWindow = () => {
+  if (previewUrl.value) {
+    window.open(proxyPreviewUrl.value, '_blank')
+  }
+}
+
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style scoped>
+.basic-info-container {
+  padding: 20px;
+}
+.search-card {
+  margin-bottom: 20px;
+}
+.search-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+.search-header .title {
+  font-size: 18px;
+  font-weight: bold;
+}
+.search-form {
+  margin-bottom: 0;
+}
+.search-buttons {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+.table-card {
+  margin-bottom: 20px;
+}
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+.action-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 5px;
+}
+.file-info-edit {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+}
+.file-tag {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  width: fit-content;
+}
+.dialog-header-custom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-right: 30px;
+}
+.header-actions {
+  display: flex;
+  align-items: center;
+}
+.no-preview {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+}
+:deep(.preview-dialog .el-dialog__body) {
+  padding: 0;
+}
+</style>

+ 329 - 427
src/views/basic-info/Index.vue → src/views/basic-info/Standard.vue

@@ -2,28 +2,28 @@
   <div class="basic-info-container">
     <el-card class="box-card search-card">
       <div class="search-header">
-        <span class="title">{{ pageTitle }}</span>
-        <el-button type="primary" :icon="Plus" @click="handleAdd">新增{{ moduleName }}</el-button>
+        <span class="title">施工标准规范管理</span>
+        <el-button type="primary" :icon="Plus" @click="handleAdd">新增施工标准规范</el-button>
       </div>
       
       <el-form :model="searchForm" label-width="80px" class="search-form" label-position="top">
         <el-row :gutter="20">
           <!-- 共通字段: 名称 -->
           <el-col :span="6">
-            <el-form-item :label="titleLabel">
-              <el-input v-model="searchForm.title" :placeholder="'请输入' + titleLabel" clearable />
+            <el-form-item label="文档名称">
+              <el-input v-model="searchForm.title" placeholder="请输入文档名称" clearable @keyup.enter="handleSearch" />
             </el-form-item>
           </el-col>
           
-          <!-- Basis 专用: 标准编号 -->
-          <el-col :span="6" v-if="infoType === 'basis'">
+          <!-- Standard 专用: 标准编号 -->
+          <el-col :span="6">
             <el-form-item label="标准编号">
-              <el-input v-model="searchForm.standard_no" placeholder="请输入标准编号" clearable />
+              <el-input v-model="searchForm.standard_no" placeholder="请输入标准编号" clearable @keyup.enter="handleSearch" />
             </el-form-item>
           </el-col>
           
-          <!-- Basis/Job 专用: 文件类型 -->
-          <el-col :span="6" v-if="infoType === 'basis' || infoType === 'job'">
+          <!-- Standard 专用: 文件类型 -->
+          <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" />
@@ -31,8 +31,8 @@
             </el-form-item>
           </el-col>
           
-          <!-- Basis 专用: 专业领域 -->
-          <el-col :span="6" v-if="infoType === 'basis'">
+          <!-- Standard 专用: 专业领域 -->
+          <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" />
@@ -41,8 +41,8 @@
           </el-col>
 
           <!-- Row 2 -->
-          <!-- Basis 专用: 时效性 -->
-          <el-col :span="6" v-if="infoType === 'basis'">
+          <!-- Standard 专用: 时效性 -->
+          <el-col :span="6">
             <el-form-item label="时效性">
               <el-select v-model="searchForm.validity" placeholder="请选择时效性" clearable style="width: 100%">
                 <el-option label="现行" value="现行" />
@@ -52,48 +52,10 @@
             </el-form-item>
           </el-col>
           
-          <el-col :span="6" v-if="infoType === 'work'">
-            <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-select>
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="6" v-if="infoType === 'work'">
-            <el-form-item label="一级分类">
-              <el-input v-model="searchForm.level_1_classification" placeholder="请输入一级分类" clearable />
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="6" v-if="infoType === 'work'">
-            <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-select>
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="6" v-if="infoType === 'work'">
-            <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-select>
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="6" v-if="infoType === 'work'">
-            <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-select>
-            </el-form-item>
-          </el-col>
-          
-          <!-- 共通字段: 发布单位/编制单位/发布部门 -->
+          <!-- 共通字段: 发布单位 -->
           <el-col :span="6">
-            <el-form-item :label="authorityLabel">
-              <el-input v-model="searchForm.issuing_authority" :placeholder="'请输入' + authorityLabel" clearable />
+            <el-form-item label="发布单位">
+              <el-input v-model="searchForm.issuing_authority" placeholder="请输入发布单位" clearable @keyup.enter="handleSearch" />
             </el-form-item>
           </el-col>
           
@@ -131,22 +93,25 @@
 
     <el-card class="box-card table-card">
       <el-table :data="tableData" v-loading="loading" style="width: 100%" border stripe>
-        <el-table-column prop="title" :label="titleLabel" min-width="200" show-overflow-tooltip />
-        <el-table-column prop="standard_no" label="标准编号" width="150" show-overflow-tooltip v-if="infoType === 'basis'" />
-        <el-table-column prop="issuing_authority" :label="authorityLabel" width="180" show-overflow-tooltip />
-        <el-table-column prop="release_date" :label="dateLabel" width="120">
+        <el-table-column prop="title" label="文档名称" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="kb_name" label="所属知识库" width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <span v-if="scope.row.kb_name" class="kb-name-tag">
+              <el-icon><Tickets /></el-icon> {{ scope.row.kb_name }}
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="standard_no" label="标准编号" width="150" show-overflow-tooltip />
+        <el-table-column prop="issuing_authority" label="发布单位" width="180" show-overflow-tooltip />
+        <el-table-column prop="release_date" label="发布日期" width="120">
           <template #default="scope">
             {{ formatDate(scope.row.release_date) }}
           </template>
         </el-table-column>
-        <el-table-column prop="document_type" label="文档类型" width="120" v-if="infoType === 'basis' || infoType === 'job'" />
-        <el-table-column prop="plan_category" label="方案类别" width="120" v-if="infoType === 'work'" />
-        <el-table-column prop="level_1_classification" label="一级分类" width="120" v-if="infoType === 'work'" />
-        <el-table-column prop="level_2_classification" label="二级分类" width="120" v-if="infoType === 'work'" />
-        <el-table-column prop="level_3_classification" label="三级分类" width="120" v-if="infoType === 'work'" />
-        <el-table-column prop="level_4_classification" label="四级分类" width="120" v-if="infoType === 'work'" />
-        <el-table-column prop="professional_field" label="专业领域" width="120" v-if="infoType === 'basis'" />
-        <el-table-column label="参编单位" min-width="150" v-if="infoType === 'basis'">
+        <el-table-column prop="document_type" label="文档类型" width="120" />
+        <el-table-column prop="professional_field" label="专业领域" width="120" />
+        <el-table-column label="参编单位" min-width="150">
           <template #default="scope">
             <template v-if="scope.row.participating_units">
               <el-popover
@@ -185,7 +150,7 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="参考依据" min-width="150" v-if="infoType === 'basis'">
+        <el-table-column label="引用标准" min-width="150">
           <template #default="scope">
             <template v-if="scope.row.reference_basis">
               <el-popover
@@ -197,13 +162,13 @@
                 <template #reference>
                   <div class="tag-group">
                     <el-tag 
-                      v-for="(basis, idx) in scope.row.reference_basis.split(';').slice(0, 2)" 
+                      v-for="(std, idx) in scope.row.reference_basis.split(';').slice(0, 2)" 
                       :key="idx" 
                       size="small" 
                       type="info"
                       class="info-tag"
                     >
-                      {{ basis }}
+                      {{ std }}
                     </el-tag>
                     <el-tag v-if="scope.row.reference_basis.split(';').length > 2" size="small" type="info" class="info-tag">
                       +{{ scope.row.reference_basis.split(';').length - 2 }}
@@ -212,13 +177,13 @@
                 </template>
                 <div class="popover-tag-list">
                   <el-tag 
-                    v-for="(basis, idx) in scope.row.reference_basis.split(';')" 
+                    v-for="(std, idx) in scope.row.reference_basis.split(';')" 
                     :key="idx" 
                     size="small" 
                     type="info"
                     class="popover-info-tag"
                   >
-                    {{ basis }}
+                    {{ std }}
                   </el-tag>
                 </div>
               </el-popover>
@@ -226,7 +191,7 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column prop="validity" label="时效性" width="100" v-if="infoType === 'basis'">
+        <el-table-column prop="validity" label="时效性" width="100">
           <template #default="scope">
             <el-tag :type="getValidityType(scope.row.validity)">
               {{ scope.row.validity || '现行' }}
@@ -273,6 +238,11 @@
                   <el-icon><Delete /></el-icon>
                 </el-button>
               </el-tooltip>
+              <el-tooltip content="入库" placement="top">
+                <el-button link type="warning" @click="handleAction('ingest', scope.row)">
+                  <el-icon><Document /></el-icon>
+                </el-button>
+              </el-tooltip>
             </div>
           </template>
         </el-table-column>
@@ -296,42 +266,54 @@
       <el-form :model="editForm" label-width="110px">
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item :label="titleLabel" required>
-              <el-input v-model="editForm.title" :placeholder="'请输入' + titleLabel" />
+            <el-form-item label="目标知识库">
+              <el-select v-model="editForm.kb_id" placeholder="请选择目标知识库" clearable style="width: 100%">
+                <el-option
+                  v-for="item in knowledgeBases"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="文档名称" required>
+              <el-input v-model="editForm.title" placeholder="请输入文档名称" />
             </el-form-item>
           </el-col>
           
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="英文名称">
               <el-input v-model="editForm.english_name" placeholder="请输入英文名称" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="标准编号">
               <el-input v-model="editForm.standard_no" placeholder="请输入标准编号" />
             </el-form-item>
           </el-col>
 
           <el-col :span="12">
-            <el-form-item :label="authorityLabel">
-              <el-input v-model="editForm.issuing_authority" :placeholder="'请输入' + authorityLabel" />
+            <el-form-item label="发布单位">
+              <el-input v-model="editForm.issuing_authority" placeholder="请输入发布单位" />
             </el-form-item>
           </el-col>
 
           <el-col :span="12">
-            <el-form-item :label="dateLabel">
+            <el-form-item label="发布日期">
               <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="实施日期">
               <el-date-picker v-model="editForm.implementation_date" type="date" placeholder="选择实施日期" value-format="YYYY-MM-DD" style="width: 100%" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis' || infoType === 'job'">
+          <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" />
@@ -339,7 +321,7 @@
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <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" />
@@ -347,7 +329,7 @@
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="时效性">
               <el-select v-model="editForm.validity" placeholder="请选择时效性" style="width: 100%">
                 <el-option label="现行" value="现行" />
@@ -357,129 +339,54 @@
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="起草单位">
               <el-input v-model="editForm.drafting_unit" placeholder="请输入起草单位" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="批准部门">
               <el-input v-model="editForm.approving_department" placeholder="请输入批准部门" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="24" v-if="infoType === 'basis'">
+          <el-col :span="24">
             <el-form-item label="参编单位">
               <div v-for="(unit, index) in participatingUnitsList" :key="index" class="dynamic-input-row">
                 <el-input v-model="participatingUnitsList[index]" placeholder="请输入参编单位" />
                 <div class="row-actions">
                   <el-button :icon="Plus" circle size="small" @click="addListItem(participatingUnitsList)" />
-                  <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(participatingUnitsList, index)" />
+                  <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(participatingUnitsList, index)" v-if="participatingUnitsList.length > 1" />
                 </div>
               </div>
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="工程阶段">
               <el-input v-model="editForm.engineering_phase" placeholder="请输入工程阶段" />
             </el-form-item>
           </el-col>
 
-          <el-col :span="24" v-if="infoType === 'basis'">
+          <el-col :span="24">
             <el-form-item label="引用依据">
-              <div v-for="(basis, index) in referenceBasisList" :key="index" class="dynamic-input-row">
+              <div v-for="(std, index) in referenceBasisList" :key="index" class="dynamic-input-row">
                 <el-input v-model="referenceBasisList[index]" placeholder="请输入引用依据" />
                 <div class="row-actions">
                   <el-button :icon="Plus" circle size="small" @click="addListItem(referenceBasisList)" />
-                  <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(referenceBasisList, index)" />
+                  <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(referenceBasisList, index)" v-if="referenceBasisList.length > 1" />
                 </div>
               </div>
             </el-form-item>
           </el-col>
 
-          <el-col :span="12" v-if="infoType === 'basis'">
+          <el-col :span="12">
             <el-form-item label="来源链接">
               <el-input v-model="editForm.source_url" placeholder="请输入来源链接" />
             </el-form-item>
           </el-col>
 
-          <template v-if="infoType === 'work'">
-            <el-col :span="12">
-              <el-form-item label="项目名称">
-                <el-input v-model="editForm.project_name" placeholder="请输入项目名称" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="项目标段">
-                <el-input v-model="editForm.project_section" placeholder="请输入项目标段" />
-              </el-form-item>
-            </el-col>
-            <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-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="一级分类">
-                <el-input v-model="editForm.level_1_classification" disabled />
-              </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-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-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-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="24">
-              <el-form-item label="方案摘要">
-                <el-input v-model="editForm.plan_summary" type="textarea" :rows="3" placeholder="请输入方案摘要" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="24">
-              <el-form-item label="施工标准规范">
-                <div v-for="(basis, index) in compilationBasisList" :key="index" class="dynamic-input-row">
-                  <el-input v-model="compilationBasisList[index]" placeholder="请输入施工标准规范" />
-                  <div class="row-actions">
-                    <el-button :icon="Plus" circle size="small" @click="addListItem(compilationBasisList)" />
-                    <el-button :icon="Minus" circle size="small" type="danger" @click="removeListItem(compilationBasisList, index)" />
-                  </div>
-                </div>
-              </el-form-item>
-            </el-col>
-          </template>
-
-          <template v-if="infoType === 'job'">
-            <el-col :span="12">
-              <el-form-item label="生效日期">
-                <el-date-picker v-model="editForm.effective_start_date" type="date" placeholder="选择生效日期" value-format="YYYY-MM-DD" style="width: 100%" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="失效日期">
-                <el-date-picker v-model="editForm.effective_end_date" type="date" placeholder="选择失效日期" value-format="YYYY-MM-DD" style="width: 100%" />
-              </el-form-item>
-            </el-col>
-          </template>
-
           <el-col :span="24">
             <el-form-item label="备注">
               <el-input v-model="editForm.note" type="textarea" :rows="2" placeholder="请输入备注" />
@@ -487,8 +394,31 @@
           </el-col>
 
           <el-col :span="24">
-            <el-form-item label="文件预览链接" v-if="false">
-              <el-input v-model="editForm.file_url" placeholder="请输入文件预览链接 (URL)" />
+            <el-form-item label="上传文件" :required="!editForm.id">
+              <el-upload
+                v-if="!editForm.id"
+                class="upload-demo"
+                action="#"
+                :auto-upload="false"
+                :on-change="handleFileChange"
+                :on-remove="handleFileRemove"
+                :file-list="fileList"
+                :limit="1"
+                accept=".pdf,.doc,.docx,.ppt,.pptx,.png,.jpg,.jpeg,.txt"
+              >
+                <el-button type="primary">选择文件</el-button>
+                <template #tip>
+                  <div class="el-upload__tip">
+                    支持 PDF, Word, PPT, 图片, TXT 等格式
+                  </div>
+                </template>
+              </el-upload>
+              <div v-else class="file-info-edit">
+                <el-tag type="info" size="large" class="file-tag">
+                  <el-icon><Document /></el-icon>
+                  <span>文件已锁定(编辑模式不可更改)</span>
+                </el-tag>
+              </div>
             </el-form-item>
           </el-col>
         </el-row>
@@ -502,43 +432,32 @@
     <!-- 查看详情对话框 -->
     <el-dialog v-model="detailDialogVisible" title="详情信息" width="600px">
       <el-descriptions :column="1" border>
-        <el-descriptions-item :label="titleLabel">{{ currentItem?.title }}</el-descriptions-item>
-        <el-descriptions-item label="标准编号" v-if="infoType === 'basis'">{{ currentItem?.standard_no || '-' }}</el-descriptions-item>
-        <el-descriptions-item :label="authorityLabel">{{ currentItem?.issuing_authority || '-' }}</el-descriptions-item>
-        <el-descriptions-item :label="dateLabel">{{ formatDate(currentItem?.release_date) }}</el-descriptions-item>
-        <el-descriptions-item label="文档类型" v-if="infoType === 'basis' || infoType === 'job'">{{ currentItem?.document_type || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="专业领域" v-if="infoType === 'basis'">{{ currentItem?.professional_field || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="参编单位" v-if="infoType === 'basis'">
+        <el-descriptions-item label="文档名称">{{ currentItem?.title }}</el-descriptions-item>
+        <el-descriptions-item label="所属知识库" v-if="currentItem?.kb_name">
+          <span class="kb-name-tag">
+            <el-icon><Tickets /></el-icon> {{ currentItem.kb_name }}
+          </span>
+        </el-descriptions-item>
+        <el-descriptions-item label="标准编号">{{ currentItem?.standard_no || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="发布单位">{{ currentItem?.issuing_authority || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="发布日期">{{ formatDate(currentItem?.release_date) }}</el-descriptions-item>
+        <el-descriptions-item label="文档类型">{{ currentItem?.document_type || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="专业领域">{{ currentItem?.professional_field || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="参编单位">
           <div v-if="currentItem?.participating_units">
             <div v-for="(unit, idx) in currentItem.participating_units.split(';')" :key="idx">{{ unit }}</div>
           </div>
           <span v-else>-</span>
         </el-descriptions-item>
-        <el-descriptions-item label="引用依据" v-if="infoType === 'basis'">
+        <el-descriptions-item label="引用依据">
           <div v-if="currentItem?.reference_basis">
-            <div v-for="(basis, idx) in currentItem.reference_basis.split(';')" :key="idx">{{ basis }}</div>
+            <div v-for="(std, idx) in currentItem.reference_basis.split(';')" :key="idx">{{ std }}</div>
           </div>
           <span v-else>-</span>
         </el-descriptions-item>
-        <el-descriptions-item label="时效性" v-if="infoType === 'basis'">
+        <el-descriptions-item label="时效性">
           <el-tag :type="getValidityType(currentItem?.validity)">{{ currentItem?.validity || '现行' }}</el-tag>
         </el-descriptions-item>
-        <template v-if="infoType === 'work'">
-          <el-descriptions-item label="项目名称">{{ currentItem?.project_name || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="项目标段">{{ currentItem?.project_section || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="方案类别">{{ currentItem?.plan_category || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="一级分类">{{ currentItem?.level_1_classification || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="二级分类">{{ currentItem?.level_2_classification || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="三级分类">{{ currentItem?.level_3_classification || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="四级分类">{{ currentItem?.level_4_classification || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="方案摘要">{{ currentItem?.plan_summary || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="施工标准规范">
-            <div v-if="currentItem?.compilation_basis">
-              <div v-for="(basis, idx) in currentItem.compilation_basis.split(';')" :key="idx">{{ basis }}</div>
-            </div>
-            <span v-else>-</span>
-          </el-descriptions-item>
-        </template>
         <el-descriptions-item label="创建人">{{ currentItem?.creator_name || '-' }}</el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ formatDateTime(currentItem?.created_time) }}</el-descriptions-item>
         <el-descriptions-item label="修改人">{{ currentItem?.updater_name || '-' }}</el-descriptions-item>
@@ -577,13 +496,39 @@
         </div>
       </div>
     </el-dialog>
+
+    <!-- 入库确认对话框 -->
+    <el-dialog v-model="ingestDialogVisible" title="文档入库" width="450px">
+      <el-form :model="ingestForm" label-width="100px">
+        <el-form-item label="选择知识库" required>
+          <el-select v-model="ingestForm.kb_id" placeholder="请选择知识库" style="width: 100%">
+            <el-option
+              v-for="item in knowledgeBases"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="切分方式" required>
+          <el-radio-group v-model="ingestForm.kb_method">
+            <el-radio label="length">按长度切分</el-radio>
+            <el-radio label="symbol">按符号切分</el-radio>
+            <el-radio label="parent_child">父子段切分</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="ingestDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmIngest" :loading="ingesting">确认入库</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, computed, watch, reactive } from 'vue'
-import { useRoute } from 'vue-router'
-import { Search, View, Monitor, Download, Edit, Delete, Refresh, Plus, Minus, Document } from '@element-plus/icons-vue'
+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 request from '@/api/request'
 import type { ApiResponse } from '@/types/auth'
@@ -592,7 +537,6 @@ import { downloadFile } from '@/utils/download'
 import { getFileExtension } from '@/utils/file'
 import { useAuthStore } from '@/stores/auth'
 
-const route = useRoute()
 const authStore = useAuthStore()
 const loading = ref(false)
 const submitting = ref(false)
@@ -609,10 +553,34 @@ const previewLoading = ref(false)
 const previewUrl = ref('')
 const previewTitle = ref('')
 
+// 入库相关状态
+const ingestDialogVisible = ref(false)
+const ingesting = ref(false)
+const knowledgeBases = ref<any[]>([])
+const ingestForm = reactive({
+  kb_id: '',
+  kb_method: 'length'
+})
+
 // 动态列表字段存储
 const participatingUnitsList = ref<string[]>([''])
 const referenceBasisList = ref<string[]>([''])
-const compilationBasisList = ref<string[]>([''])
+const fileList = ref<any[]>([])
+
+const handleFileChange = (file: any, files: any[]) => {
+  fileList.value = files
+  if (file.name && !editForm.title) {
+    const nameParts = file.name.split('.')
+    if (nameParts.length > 1) {
+      nameParts.pop()
+    }
+    editForm.title = nameParts.join('.')
+  }
+}
+
+const handleFileRemove = () => {
+  fileList.value = []
+}
 
 const addListItem = (list: string[]) => {
   list.push('')
@@ -637,11 +605,8 @@ const editForm = reactive<any>({
   document_type: '',
   professional_field: '',
   validity: '现行',
-  project_name: '',
-  project_section: '',
   note: '',
   file_url: '',
-  // 新增字段 - Basis
   english_name: '',
   implementation_date: '',
   drafting_unit: '',
@@ -650,41 +615,13 @@ const editForm = reactive<any>({
   engineering_phase: '',
   reference_basis: '',
   source_url: '',
-  // 新增字段 - Work
-  plan_summary: '',
-  compilation_basis: '',
-  plan_category: '',
-  level_1_classification: '施工方案',
-  level_2_classification: '',
-  level_3_classification: '',
-  level_4_classification: '',
-  // 新增字段 - Job
-  effective_start_date: '',
-  effective_end_date: ''
+  kb_id: ''
 })
 
-const formTitle = computed(() => editForm.id ? `编辑${moduleName.value}` : `新增${moduleName.value}`)
-
-// 检索表单接口
-interface SearchForm {
-  title: string
-  standard_no: string
-  document_type: string
-  professional_field: string
-  validity: string
-  issuing_authority: string
-  release_date_start: string
-  release_date_end: string
-  plan_category: string
-  level_1_classification: string
-  level_2_classification: string
-  level_3_classification: string
-  level_4_classification: string
-  [key: string]: string
-}
+const formTitle = computed(() => editForm.id ? '编辑施工标准规范' : '新增施工标准规范')
 
 // 检索表单
-const searchForm = reactive<SearchForm>({
+const searchForm = reactive<any>({
   title: '',
   standard_no: '',
   document_type: '',
@@ -692,75 +629,12 @@ const searchForm = reactive<SearchForm>({
   validity: '',
   issuing_authority: '',
   release_date_start: '',
-  release_date_end: '',
-  plan_category: '',
-  level_1_classification: '',
-  level_2_classification: '',
-  level_3_classification: '',
-  level_4_classification: ''
+  release_date_end: ''
 })
 
 // 选项配置
 const documentTypeOptions = ['国家标准', '行业标准', '企业标准', '地方标准', '管理制度', '技术规范']
 const professionalFieldOptions = ['建筑工程', '市政工程', '机电安装', '路桥工程', '装饰装修', '其他']
-const planCategoryOptions = ['超危大方案', '超危大方案较大II级', '超危大方案特大IV级', '超危大方案一般I级', '超危大方案重大III级', '危大方案', '一般方案']
-const level2Options = ['临建工程', '路基工程', '其他', '桥梁工程', '隧道工程']
-const level3Options = ['/', 'TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '其他', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥']
-const level4Options = ['/', '挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '其他', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础']
-
-// 根据路由路径判断类型
-const infoType = computed(() => {
-  const path = route.path
-  if (path.includes('/basis')) return 'basis'
-  if (path.includes('/work')) return 'work'
-  if (path.includes('/job')) return 'job'
-  return 'basis'
-})
-
-const pageTitle = computed(() => {
-  switch (infoType.value) {
-    case 'basis': return '施工标准规范管理'
-    case 'work': return '施工方案管理'
-    case 'job': return '办公制度管理'
-    default: return '基本信息管理'
-  }
-})
-
-const moduleName = computed(() => {
-  switch (infoType.value) {
-    case 'basis': return '施工标准规范'
-    case 'work': return '施工方案'
-    case 'job': return '办公制度'
-    default: return ''
-  }
-})
-
-const titleLabel = computed(() => {
-  switch (infoType.value) {
-    case 'basis': return '文档名称'
-    case 'work': return '方案名称'
-    case 'job': return '文件名称'
-    default: return '名称'
-  }
-})
-
-const authorityLabel = computed(() => {
-  switch (infoType.value) {
-    case 'basis': return '发布单位'
-    case 'work': return '编制单位'
-    case 'job': return '发布部门'
-    default: return '单位'
-  }
-})
-
-const dateLabel = computed(() => {
-  switch (infoType.value) {
-    case 'basis': return '发布日期'
-    case 'work': return '编制日期'
-    case 'job': return '发布日期'
-    default: return '日期'
-  }
-})
 
 // 获取列表数据
 const loadData = async () => {
@@ -773,15 +647,13 @@ const loadData = async () => {
       size: number
     }>>('/api/v1/sample/basic-info/list', {
       params: {
-        type: infoType.value,
+        type: 'standard',
         page: currentPage.value,
         size: pageSize.value,
         ...searchForm
       }
     })
     
-    // axios 返回的是 AxiosResponse,但在拦截器中我们返回了 response.data
-    // 如果 TS 还是报错,可以显式转换
     const resData = (response as unknown) as ApiResponse<{
       items: any[]
       total: number
@@ -846,7 +718,6 @@ const getValidityType = (validity: string) => {
 
 const proxyPreviewUrl = computed(() => {
   if (!previewUrl.value) return ''
-  // 使用后端代理接口查看外部网页,附加 token 以通过认证
   let baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
   if (baseUrl.includes('localhost') && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
     baseUrl = baseUrl.replace('localhost', window.location.hostname)
@@ -859,13 +730,11 @@ const handleAction = async (action: string, row: any) => {
   switch (action) {
     case 'view':
       if (row.file_url) {
-        // 如果有文件链接,直接进入预览
         previewTitle.value = row.title
         previewUrl.value = row.file_url
         previewVisible.value = true
         previewLoading.value = true
       } else {
-        // 否则进入详情对话框
         detailDialogVisible.value = true
       }
       break
@@ -889,15 +758,14 @@ const handleAction = async (action: string, row: any) => {
       }
       break
     case 'edit':
+      loadKnowledgeBases()
       Object.keys(editForm).forEach(key => {
         editForm[key] = row[key] || ''
       })
-      // 处理动态列表字段
       participatingUnitsList.value = row.participating_units ? row.participating_units.split(';') : ['']
       referenceBasisList.value = row.reference_basis ? row.reference_basis.split(';') : ['']
-      compilationBasisList.value = row.compilation_basis ? row.compilation_basis.split(';') : ['']
-      
       editForm.id = row.id
+      fileList.value = []
       formDialogVisible.value = true
       break
     case 'delete':
@@ -905,87 +773,154 @@ const handleAction = async (action: string, row: any) => {
         type: 'warning'
       }).then(async () => {
         try {
-          const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=${infoType.value}&id=${row.id}`)
-          const resData = (res as unknown) as ApiResponse
-          if (resData.code === 0) {
+          const res = await request.post<ApiResponse>(`/api/v1/sample/basic-info/delete?type=standard&id=${row.id}`)
+          if (res.code === 0) {
             ElMessage.success('删除成功')
             loadData()
           } else {
-            ElMessage.error(resData.message || '删除失败')
+            ElMessage.error(res.message || '删除失败')
           }
         } catch (error) {
           console.error('删除失败:', error)
-          ElMessage.error('网络错误')
+          ElMessage.error('网络错误,请稍后重试')
         }
       }).catch(() => {})
       break
+    case 'ingest':
+      currentItem.value = row
+      loadKnowledgeBases()
+      ingestDialogVisible.value = true
+      break
   }
 }
 
-const handleAdd = () => {
-  Object.keys(editForm).forEach(key => {
-    if (key === 'validity') {
-      editForm[key] = '现行'
-    } else if (key === 'level_1_classification') {
-      editForm[key] = '施工方案'
+const loadKnowledgeBases = async () => {
+  try {
+    const res = await request.get<ApiResponse<any[]>>('/api/v1/sample/knowledge-base/list', {
+      params: {
+        page: 1,
+        page_size: 1000
+      }
+    })
+    if (res.code === 0) {
+      knowledgeBases.value = res.data
+      if (knowledgeBases.value.length > 0 && !ingestForm.kb_id) {
+        ingestForm.kb_id = knowledgeBases.value[0].id
+      }
+    }
+  } catch (error) {
+    console.error('获取知识库失败:', error)
+  }
+}
+
+const confirmIngest = async () => {
+  if (!ingestForm.kb_id) {
+    ElMessage.warning('请选择知识库')
+    return
+  }
+  if (!currentItem.value || !currentItem.value.id) {
+    ElMessage.warning('未选中有效文档')
+    return
+  }
+  ingesting.value = true
+  try {
+    const res = await request.post<ApiResponse>('/api/v1/sample/documents/batch-enter', {
+      ids: [currentItem.value.id],
+      table_type: 'standard',
+      kb_id: ingestForm.kb_id,
+      kb_method: ingestForm.kb_method
+    })
+    if (res.code === 0) {
+      ElMessage.success(res.message || '已加入入库队列')
+      ingestDialogVisible.value = false
+      loadData()
     } else {
-      editForm[key] = ''
+      ElMessage.error(res.message || '入库失败')
     }
+  } catch (error) {
+    console.error('入库失败:', error)
+    ElMessage.error('网络错误,请稍后重试')
+  } finally {
+    ingesting.value = false
+  }
+}
+
+const handleAdd = () => {
+  loadKnowledgeBases()
+  Object.keys(editForm).forEach(key => {
+    editForm[key] = ''
   })
-  // 初始化动态列表字段
-   participatingUnitsList.value = ['']
-   referenceBasisList.value = ['']
-   compilationBasisList.value = ['']
-  
   editForm.id = null
+  editForm.validity = '现行'
+  participatingUnitsList.value = ['']
+  referenceBasisList.value = ['']
+  fileList.value = []
   formDialogVisible.value = true
 }
 
 const submitForm = async () => {
   if (!editForm.title) {
-    return ElMessage.warning('请输入名称')
+    ElMessage.warning('请输入标题')
+    return
   }
-  
-  // 提交前合并动态列表字段,过滤掉空行并用分号连接
-  editForm.participating_units = participatingUnitsList.value.filter(item => item.trim() !== '').join(';')
-  editForm.reference_basis = referenceBasisList.value.filter(item => item.trim() !== '').join(';')
-  editForm.compilation_basis = compilationBasisList.value.filter(item => item.trim() !== '').join(';')
-  
   submitting.value = true
   try {
-    const isEdit = !!editForm.id
-    const url = isEdit ? `/api/v1/sample/basic-info/edit?type=${infoType.value}&id=${editForm.id}` : `/api/v1/sample/basic-info/add?type=${infoType.value}`
+    if (fileList.value.length > 0 && fileList.value[0].raw) {
+      const file = fileList.value[0].raw
+      const urlRes = await request.post<ApiResponse<any>>('/api/v1/sample/documents/upload-url', {
+        filename: file.name,
+        content_type: file.type,
+        prefix: 'basic-info/standard'
+      })
+      if (urlRes.code === 0) {
+        const { upload_url, file_url } = urlRes.data
+        await fetch(upload_url, {
+          method: 'PUT',
+          body: file,
+          headers: {
+            'Content-Type': file.type
+          }
+        })
+        editForm.file_url = file_url
+      } else {
+        throw new Error(urlRes.message || '获取上传链接失败')
+      }
+    } else if (!editForm.id && fileList.value.length === 0) {
+      ElMessage.warning('请选择要上传的文件')
+      submitting.value = false
+      return
+    }
+
+    editForm.participating_units = participatingUnitsList.value.filter(item => item.trim() !== '').join(';')
+    editForm.reference_basis = referenceBasisList.value.filter(item => item.trim() !== '').join(';')
     
-    const res = await request.post<ApiResponse>(url, editForm)
-    const resData = (res as unknown) as ApiResponse
+    const url = editForm.id 
+      ? `/api/v1/sample/basic-info/edit?type=standard&id=${editForm.id}`
+      : '/api/v1/sample/basic-info/add?type=standard'
     
-    if (resData.code === 0) {
-      ElMessage.success(isEdit ? '更新成功' : '新增成功')
+    const res = await request.post<ApiResponse>(url, editForm)
+    if (res.code === 0) {
+      ElMessage.success(editForm.id ? '修改成功' : '新增成功')
       formDialogVisible.value = false
+      fileList.value = []
       loadData()
     } else {
-      ElMessage.error(resData.message || '操作失败')
+      ElMessage.error(res.message || '提交失败')
     }
-  } catch (error) {
-    console.error('操作失败:', error)
-    ElMessage.error('网络错误')
+  } catch (error: any) {
+    console.error('提交失败:', error)
+    ElMessage.error(error.message || '网络错误,请稍后重试')
   } finally {
     submitting.value = false
   }
 }
 
 const openInNewWindow = () => {
-  if (proxyPreviewUrl.value) {
+  if (previewUrl.value) {
     window.open(proxyPreviewUrl.value, '_blank')
   }
 }
 
-// 监听路由变化,切换类型时重新加载数据
-watch(() => route.path, () => {
-  currentPage.value = 1
-  resetSearch()
-})
-
 onMounted(() => {
   loadData()
 })
@@ -994,141 +929,108 @@ onMounted(() => {
 <style scoped>
 .basic-info-container {
   padding: 20px;
-  background-color: #f5f7fa;
-  min-height: calc(100vh - 84px);
 }
-
 .search-card {
   margin-bottom: 20px;
 }
-
 .search-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 20px;
 }
-
 .search-header .title {
   font-size: 18px;
   font-weight: bold;
 }
-
 .search-form {
-  padding: 0 10px;
-}
-
-:deep(.el-form-item__label) {
-  font-weight: normal;
-  color: #606266;
-  padding-bottom: 4px;
+  margin-bottom: 0;
 }
-
 .search-buttons {
   display: flex;
   justify-content: flex-end;
-  gap: 10px;
   margin-top: 10px;
 }
-
+.table-card {
+  margin-bottom: 20px;
+}
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+.action-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 5px;
+}
+.file-info-edit {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+}
+.file-tag {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  width: fit-content;
+}
 .dynamic-input-row {
   display: flex;
   align-items: center;
   margin-bottom: 8px;
   width: 100%;
 }
-
+.dynamic-input-row :deep(.el-input) {
+  flex: 1;
+}
 .dynamic-input-row:last-child {
   margin-bottom: 0;
 }
-
 .row-actions {
   display: flex;
   gap: 5px;
   margin-left: 10px;
+  flex-shrink: 0;
 }
-
-.table-card {
-  padding: 10px;
-}
-
-.pagination-container {
-  margin-top: 20px;
+.tag-group {
   display: flex;
-  justify-content: flex-end;
+  flex-wrap: wrap;
+  gap: 4px;
 }
-
 .info-tag {
-  margin-right: 4px;
-  margin-bottom: 2px;
   max-width: 120px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
-
-.tag-group {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 4px;
-}
-
-.action-buttons {
-  display: flex;
-  justify-content: center;
-  gap: 4px;
-}
-
-.action-buttons .el-button {
-  padding: 4px;
-  margin: 0;
-}
-
 .popover-tag-list {
   display: flex;
-  flex-direction: column;
+  flex-wrap: wrap;
   gap: 8px;
-  padding: 4px 0;
+  padding: 4px;
 }
-
 .popover-info-tag {
-  width: 100%;
-  justify-content: flex-start;
-  height: auto;
-  padding: 4px 8px;
-  white-space: normal;
-  word-break: break-all;
-  line-height: 1.4;
-}
-
-:deep(.el-table .cell) {
-  white-space: nowrap;
-}
-
-.preview-content {
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  overflow: hidden;
+  margin-bottom: 4px;
 }
-
-.no-preview {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 100%;
-  background-color: #f5f7fa;
-}
-
 .dialog-header-custom {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  width: 100%;
   padding-right: 30px;
 }
-
 .header-actions {
   display: flex;
-  gap: 10px;
+  align-items: center;
+}
+.no-preview {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+}
+:deep(.preview-dialog .el-dialog__body) {
+  padding: 0;
 }
 </style>

ファイルの差分が大きいため隠しています
+ 542 - 173
src/views/documents/Index.vue


+ 64 - 8
src/views/images/Index.vue

@@ -38,6 +38,14 @@
             <span class="current-category">{{ currentCategoryName }}</span>
           </div>
           <div class="header-right">
+            <el-button 
+              type="primary" 
+              :icon="Tickets" 
+              @click="handleBatchAddToTask"
+              :disabled="selectedIds.length === 0"
+            >
+              批量加入任务
+            </el-button>
             <el-upload
               ref="uploadRef"
               class="upload-btn"
@@ -48,19 +56,27 @@
               accept="image/*"
               :on-change="handleFileChange"
             >
-              <el-button type="primary" :icon="Upload">新增图片</el-button>
+              <el-button type="success" :icon="Upload">新增图片</el-button>
             </el-upload>
           </div>
         </div>
 
         <div class="image-list" v-loading="loading">
-          <el-table :data="images" style="width: 100%" height="calc(100vh - 240px)">
+          <el-table 
+            :data="images" 
+            style="width: 100%" 
+            height="calc(100vh - 240px)"
+            @selection-change="handleSelectionChange"
+          >
+            <el-table-column type="selection" width="55" />
             <el-table-column label="图片预览" width="120">
               <template #default="scope">
                 <el-image 
+                  :ref="(el: any) => imageRefs[scope.$index] = el"
                   class="table-image"
                   :src="scope.row.image_url" 
-                  :preview-src-list="[scope.row.image_url]"
+                  :preview-src-list="previewSrcList"
+                  :initial-index="scope.$index"
                   fit="cover"
                   preview-teleported
                 >
@@ -82,7 +98,7 @@
             </el-table-column>
             <el-table-column label="操作" width="120" fixed="right">
               <template #default="scope">
-                <el-button type="primary" link @click="handlePreview(scope.row)">预览</el-button>
+                <el-button type="primary" link @click="handlePreview(scope.$index)">预览</el-button>
                 <el-button type="danger" link @click="handleDeleteImage(scope.row)">删除</el-button>
               </template>
             </el-table-column>
@@ -158,7 +174,7 @@
 
 <script setup lang="ts">
 import { ref, onMounted, reactive, computed } from 'vue'
-import { Plus, Edit, Delete, Upload, Picture, Folder, FolderOpened } from '@element-plus/icons-vue'
+import { Plus, Edit, Delete, Upload, Picture, Folder, FolderOpened, Tickets } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { imageApi, type ImageCategory, type ImageItem } from '@/api/image'
 import axios from 'axios'
@@ -187,6 +203,8 @@ const uploadDialogVisible = ref(false)
 const uploading = ref(false)
 const uploadFileList = ref<any[]>([])
 const uploadRef = ref()
+const imageRefs = reactive<any>({})
+const selectedIds = ref<string[]>([])
 
 const categoryForm = reactive({
   id: '',
@@ -199,6 +217,10 @@ const currentCategoryName = computed(() => {
   return currentCategory.value ? currentCategory.value.type_name : '全部分类'
 })
 
+const previewSrcList = computed(() => {
+  return images.value.map(img => img.image_url).filter(url => !!url)
+})
+
 // --- 方法定义 ---
 
 const fetchCategories = async () => {
@@ -216,6 +238,10 @@ const fetchCategories = async () => {
 
 const fetchImages = async () => {
   loading.value = true
+  // 清空引用
+  for (const key in imageRefs) {
+    delete imageRefs[key]
+  }
   try {
     const res = await imageApi.getList(queryParams)
     if (res.code === 0) {
@@ -229,6 +255,27 @@ const fetchImages = async () => {
   }
 }
 
+const handleSelectionChange = (selection: ImageItem[]) => {
+  selectedIds.value = selection.map(item => item.id)
+}
+
+const handleBatchAddToTask = async () => {
+  if (selectedIds.value.length === 0) return
+  
+  try {
+    const { code, message } = await imageApi.batchAddToTask(selectedIds.value)
+    if (code === 0) {
+      ElMessage.success(message || '批量加入任务成功')
+      // 可以在此处刷新列表或清除选择
+    } else {
+      ElMessage.error(message || '操作失败')
+    }
+  } catch (error) {
+    console.error('批量加入任务失败:', error)
+    ElMessage.error('操作异常')
+  }
+}
+
 const handleCategoryClick = (data: ImageCategory) => {
   currentCategory.value = data
   queryParams.category_id = data.id === '0' ? '' : data.id
@@ -400,9 +447,12 @@ const handleDeleteImage = (row: ImageItem) => {
   })
 }
 
-const handlePreview = (row: ImageItem) => {
-  // el-image 的预览功能已经集成在组件中了
-  // 这里可以手动触发大图预览,如果需要的话
+const handlePreview = (index: number) => {
+  if (imageRefs[index]) {
+    // 触发 el-image 的预览
+    const el = imageRefs[index].$el.querySelector('img')
+    if (el) el.click()
+  }
 }
 
 const handleSizeChange = (val: number) => {
@@ -507,6 +557,12 @@ onMounted(() => {
   margin-bottom: 20px;
 }
 
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
 .current-category {
   font-size: 18px;
   font-weight: bold;

+ 6 - 1
tsconfig.json

@@ -1,8 +1,13 @@
 {
   "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "include": ["src/env.d.ts", "src/**/*", "src/**/*.vue"],
   "exclude": ["src/**/__tests__/*"],
   "compilerOptions": {
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
     "composite": true,
     "baseUrl": ".",
     "paths": {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません