chenkun 1 hónapja
szülő
commit
e367f7432d

+ 10 - 2
src/api/document.ts

@@ -4,9 +4,9 @@ import request from './request'
 
 export interface DocumentItem {
   id: string
-  source_id: string
   title: string
-  content: string
+  note?: string
+  content?: string // 兼容旧代码
   source_type: 'basis' | 'work' | 'job'
   table_type?: 'basis' | 'work' | 'job' // 兼容旧代码
   primary_category_id: string
@@ -24,6 +24,7 @@ export interface DocumentItem {
   conversion_error?: string
   error_message?: string // 兼容旧代码
   created_by?: string
+  updated_by?: string
   created_time: string
   updated_time: string
   created_at?: string // 兼容旧代码
@@ -37,6 +38,13 @@ export interface DocumentItem {
   validity?: string
   project_name?: string
   project_section?: string
+  compilation_basis?: string
+  plan_summary?: string
+  plan_category?: string
+  level_1_classification?: string
+  level_2_classification?: string
+  level_3_classification?: string
+  level_4_classification?: string
 }
 
 export interface DocumentQueryParams {

+ 1 - 0
src/api/image.ts

@@ -21,6 +21,7 @@ export interface ImageItem {
   category_name?: string
   creator_name?: string
   created_by: string
+  updated_by?: string
   created_time: string
   updated_time: string
 }

+ 10 - 1
src/layouts/MainLayout.vue

@@ -296,9 +296,18 @@ const getDefaultMenus = (): MenuItem[] => {
           path: '/admin/menus',
           icon: 'Menu',
           is_hidden: false
-        }
+        },
       ]
     })
+
+    defaultMenus.push({
+      id: 'admin-tasks',
+      name: 'admin-tasks',
+      title: '任务管理中心',
+      path: '/admin/tasks',
+      icon: 'List',
+      children: []
+    })
   }
   
   return defaultMenus

+ 6 - 0
src/router/index.ts

@@ -68,6 +68,12 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/views/admin/Menus.vue'),
         meta: { requiresAdmin: true }
       },
+      {
+        path: 'admin/tasks',
+        name: 'AdminTasks',
+        component: () => import('@/views/admin/TaskManagement.vue'),
+        meta: { requiresAdmin: true }
+      },
       {
         path: 'admin/permissions',
         name: 'AdminPermissions',

+ 1 - 1
src/stores/auth.ts

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

+ 127 - 0
src/views/admin/TaskManagement.vue

@@ -0,0 +1,127 @@
+
+<template>
+  <div class="task-management">
+    <div class="page-header">
+      <h2>任务管理中心</h2>
+      <p>管理数据处理和图片处理的异步任务</p>
+    </div>
+
+    <el-card class="task-card">
+      <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+        <el-tab-pane label="信息任务管理" name="data">
+          <el-table v-loading="loading" :data="taskList" border stripe style="width: 100%">
+            <el-table-column prop="id" label="基本信息ID" min-width="180" />
+            <el-table-column prop="name" label="名称" min-width="250" show-overflow-tooltip />
+            <el-table-column prop="task_id" label="任务ID (task_id)" min-width="200">
+              <template #default="{ row }">
+                <el-tag v-if="row.task_id" type="info">{{ row.task_id }}</el-tag>
+                <span v-else class="placeholder">暂无任务ID</span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+
+        <el-tab-pane label="图片任务管理" name="image">
+          <el-table v-loading="loading" :data="taskList" border stripe style="width: 100%">
+            <el-table-column prop="id" label="图片ID" min-width="180" />
+            <el-table-column prop="name" label="名称" min-width="250" show-overflow-tooltip />
+            <el-table-column prop="task_id" label="任务ID (task_id)" min-width="200">
+              <template #default="{ row }">
+                <el-tag v-if="row.task_id" type="info">{{ row.task_id }}</el-tag>
+                <span v-else class="placeholder">暂无任务ID</span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import request from '@/api/request'
+
+interface TaskItem {
+  id: string
+  task_id: string | null
+  type: string
+  name: string
+}
+
+interface ApiResponse<T = any> {
+  code: number
+  message: string
+  data: T
+  timestamp: string
+}
+
+const activeTab = ref('data')
+const loading = ref(false)
+const taskList = ref<TaskItem[]>([])
+
+const loadTasks = async () => {
+  loading.value = true
+  try {
+    const response = await request.get<any, ApiResponse<TaskItem[]>>('/api/v1/sample/tasks', {
+      params: { type: activeTab.value }
+    })
+    if (response.code === 0) {
+      taskList.value = response.data
+    } else {
+      ElMessage.error(response.message || '获取任务列表失败')
+    }
+  } catch (error) {
+    console.error('获取任务列表出错:', error)
+    ElMessage.error('获取任务列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleTabChange = () => {
+  taskList.value = []
+  loadTasks()
+}
+
+onMounted(() => {
+  loadTasks()
+})
+</script>
+
+<style scoped>
+.task-management {
+  padding: 20px;
+}
+
+.page-header {
+  margin-bottom: 20px;
+}
+
+.page-header h2 {
+  margin: 0 0 8px 0;
+  color: #333;
+}
+
+.page-header p {
+  margin: 0;
+  color: #666;
+  font-size: 14px;
+}
+
+.task-card {
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+}
+
+.placeholder {
+  color: #999;
+  font-style: italic;
+  font-size: 13px;
+}
+
+:deep(.el-tabs__header) {
+  margin-bottom: 20px;
+}
+</style>

+ 134 - 16
src/views/basic-info/Index.vue

@@ -52,6 +52,44 @@
             </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">
@@ -102,6 +140,11 @@
           </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 prop="validity" label="时效性" width="100" v-if="infoType === 'basis'">
           <template #default="scope">
@@ -110,12 +153,23 @@
             </el-tag>
           </template>
         </el-table-column>
+        <el-table-column label="备注" min-width="150" show-overflow-tooltip>
+          <template #default="scope">
+            {{ scope.row.note || '-' }}
+          </template>
+        </el-table-column>
         <el-table-column prop="created_by" label="创建人" width="100" />
         <el-table-column prop="created_at" label="创建时间" width="180">
           <template #default="scope">
             {{ formatDateTime(scope.row.created_at) }}
           </template>
         </el-table-column>
+        <el-table-column prop="updated_by" label="修改人" width="100" />
+        <el-table-column prop="updated_at" label="修改时间" width="180">
+          <template #default="scope">
+            {{ formatDateTime(scope.row.updated_at) }}
+          </template>
+        </el-table-column>
         <el-table-column label="操作" width="180" fixed="right" align="center">
           <template #default="scope">
             <div class="action-buttons">
@@ -270,14 +324,47 @@
                 <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="12" v-for="i in 9" :key="i">
-              <el-form-item :label="'编制依据 ' + i">
-                <el-input v-model="editForm['compilation_basis_' + i]" :placeholder="'请输入编制依据 ' + i" />
+            <el-col :span="24">
+              <el-form-item label="编制依据">
+                <el-input v-model="editForm.compilation_basis" type="textarea" :rows="3" placeholder="请输入编制依据,多个依据请用逗号分隔" />
               </el-form-item>
             </el-col>
           </template>
@@ -295,6 +382,12 @@
             </el-col>
           </template>
 
+          <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="文件链接">
               <el-input v-model="editForm.file_url" placeholder="请输入文件预览链接 (URL)" />
@@ -323,6 +416,13 @@
         <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="编制依据">{{ currentItem?.compilation_basis || '-' }}</el-descriptions-item>
         </template>
         <el-descriptions-item label="创建人">{{ currentItem?.created_by }}</el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ formatDateTime(currentItem?.created_at) }}</el-descriptions-item>
@@ -404,6 +504,7 @@ const editForm = reactive<any>({
   validity: '现行',
   project_name: '',
   project_section: '',
+  note: '',
   file_url: '',
   // 新增字段 - Basis
   english_name: '',
@@ -416,15 +517,12 @@ const editForm = reactive<any>({
   source_url: '',
   // 新增字段 - Work
   plan_summary: '',
-  compilation_basis_1: '',
-  compilation_basis_2: '',
-  compilation_basis_3: '',
-  compilation_basis_4: '',
-  compilation_basis_5: '',
-  compilation_basis_6: '',
-  compilation_basis_7: '',
-  compilation_basis_8: '',
-  compilation_basis_9: '',
+  compilation_basis: '',
+  plan_category: '',
+  level_1_classification: '施工方案',
+  level_2_classification: '',
+  level_3_classification: '',
+  level_4_classification: '',
   // 新增字段 - Job
   effective_start_date: '',
   effective_end_date: ''
@@ -442,6 +540,11 @@ interface SearchForm {
   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
 }
 
@@ -454,12 +557,21 @@ const searchForm = reactive<SearchForm>({
   validity: '',
   issuing_authority: '',
   release_date_start: '',
-  release_date_end: ''
+  release_date_end: '',
+  plan_category: '',
+  level_1_classification: '',
+  level_2_classification: '',
+  level_3_classification: '',
+  level_4_classification: ''
 })
 
 // 选项配置
 const documentTypeOptions = ['国家标准', '行业标准', '企业标准', '地方标准', '管理制度', '技术规范']
 const professionalFieldOptions = ['建筑工程', '市政工程', '机电安装', '路桥工程', '装饰装修', '其他']
+const planCategoryOptions = ['超危大方案', '超危大方案较大II级', '超危大方案特大IV级', '超危大方案一般I级', '超危大方案重大III级', '危大方案', '一般方案']
+const level2Options = ['临建工程', '路基工程', '其他', '桥梁工程', '隧道工程']
+const level3Options = ['/', 'TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '其他', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥']
+const level4Options = ['/', '挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '其他', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础']
 
 // 根据路由路径判断类型
 const infoType = computed(() => {
@@ -578,12 +690,12 @@ const handleCurrentChange = (val: number) => {
   loadData()
 }
 
-const formatDate = (date: string) => {
+const formatDate = (date: string | undefined | null) => {
   if (!date) return '-'
   return dayjs(date).format('YYYY-MM-DD')
 }
 
-const formatDateTime = (date: string) => {
+const formatDateTime = (date: string | undefined | null) => {
   if (!date) return '-'
   return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
 }
@@ -667,7 +779,13 @@ const handleAction = async (action: string, row: any) => {
 
 const handleAdd = () => {
   Object.keys(editForm).forEach(key => {
-    editForm[key] = key === 'validity' ? '现行' : ''
+    if (key === 'validity') {
+      editForm[key] = '现行'
+    } else if (key === 'level_1_classification') {
+      editForm[key] = '施工方案'
+    } else {
+      editForm[key] = ''
+    }
   })
   editForm.id = null
   formDialogVisible.value = true

+ 255 - 88
src/views/documents/Index.vue

@@ -37,7 +37,7 @@
       <div class="search-bar">
         <el-input
           v-model="searchQuery.keyword"
-          placeholder="搜索文档标题、正文内容或标准号..."
+          placeholder="搜索文档名称..."
           class="search-input"
           clearable
           @keyup.enter="handleSearch"
@@ -92,9 +92,14 @@
                 <el-icon v-else-if="getFileIcon(scope.row) === 'ppt'"><DataAnalysis /></el-icon>
                 <el-icon v-else><Document /></el-icon>
               </div>
-              <span class="file-name-link" @click="handleView(scope.row)">
-                {{ scope.row.title }}{{ getFileExtension(scope.row) }}
-              </span>
+              <div class="file-info-content">
+                <span class="file-name-link" @click="handleView(scope.row)">
+                  {{ scope.row.title }}{{ getFileExtension(scope.row) }}
+                </span>
+                <span v-if="scope.row.note" class="file-note">
+                  {{ scope.row.note }}
+                </span>
+              </div>
             </div>
           </template>
         </el-table-column>
@@ -127,20 +132,6 @@
           </template>
         </el-table-column>
 
-        <el-table-column prop="created_by" label="上传人" min-width="100" show-overflow-tooltip />
-
-        <el-table-column label="上传时间" min-width="150" prop="created_time">
-          <template #default="scope">
-            {{ formatDate(scope.row.created_time) }}
-          </template>
-        </el-table-column>
-
-        <el-table-column label="修改时间" min-width="150" prop="updated_time">
-          <template #default="scope">
-            {{ formatDate(scope.row.updated_time) }}
-          </template>
-        </el-table-column>
-
         <el-table-column label="知识库" min-width="120">
           <template #default="scope">
             <el-tag size="small" effect="plain" :type="getKBTagType(scope.row.source_type)">
@@ -160,7 +151,21 @@
           </template>
         </el-table-column>
 
+        <el-table-column prop="created_by" label="上传人" min-width="100" show-overflow-tooltip />
 
+        <el-table-column label="上传时间" min-width="150" prop="created_time">
+          <template #default="scope">
+            {{ formatDate(scope.row.created_time) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="updated_by" label="修改人" min-width="100" show-overflow-tooltip />
+
+        <el-table-column label="修改时间" min-width="150" prop="updated_time">
+          <template #default="scope">
+            {{ formatDate(scope.row.updated_time) }}
+          </template>
+        </el-table-column>
 
         <el-table-column label="操作" width="260" fixed="right" align="center">
           <template #default="scope">
@@ -230,7 +235,27 @@
         </div>
       </template>
       <div v-loading="previewLoading" class="preview-content">
-        <div v-if="isHtmlPage && !previewLoading" class="preview-tip">
+        <!-- Office 文档未转换提示 -->
+        <div v-if="isOfficeDoc && previewDocType === 'original' && !previewLoading" class="unsupported-preview">
+          <el-result
+            icon="warning"
+            title="该格式暂不支持直接预览"
+            sub-title="Word/Excel/PPT 等 Office 文档需要转换后才能在浏览器中直接预览。"
+          >
+            <template #extra>
+              <div class="unsupported-actions">
+                <el-button type="primary" @click="handleConvert(currentDoc!)">
+                  <el-icon><Switch /></el-icon> 立即转换
+                </el-button>
+                <el-button type="success" @click="handleDownload(currentDoc!)">
+                  <el-icon><Download /></el-icon> 下载原文档
+                </el-button>
+              </div>
+            </template>
+          </el-result>
+        </div>
+
+        <div v-if="isHtmlPage && !previewLoading && !isOfficeDoc" class="preview-tip">
           <el-alert
             title="网页预览提示"
             type="info"
@@ -240,7 +265,7 @@
           />
         </div>
         <iframe 
-          v-if="previewUrl" 
+          v-if="previewUrl && !(isOfficeDoc && previewDocType === 'original')" 
           :src="proxyPreviewUrl" 
           width="100%" 
           height="100%" 
@@ -297,8 +322,8 @@
             </template>
           </el-upload>
         </el-form-item>
-        <el-form-item label="文档内容">
-          <el-input v-model="uploadForm.content" type="textarea" :rows="4" placeholder="请输入文档内容" />
+        <el-form-item label="文档备注">
+          <el-input v-model="uploadForm.note" type="textarea" :rows="4" placeholder="请输入文档备注" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -308,8 +333,8 @@
     </el-dialog>
 
     <!-- 编辑文档对话框 -->
-    <el-dialog v-model="editDialogVisible" title="编辑文档" width="600px">
-      <el-form :model="editForm" label-width="110px">
+    <el-dialog v-model="editDialogVisible" title="编辑文档" width="700px">
+      <el-form :model="editForm" label-width="120px">
         <el-divider content-position="left">基础信息</el-divider>
         <el-form-item label="所属知识库" required>
           <el-select v-model="editForm.table_type" disabled placeholder="请选择知识库" style="width: 100%">
@@ -340,12 +365,12 @@
           </el-form-item>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="文类型">
+              <el-form-item label="文类型">
                 <el-input v-model="editForm.document_type" placeholder="如:国家标准" />
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="效性">
+              <el-form-item label="效性">
                 <el-select v-model="editForm.validity" placeholder="请选择" style="width: 100%">
                   <el-option label="现行" value="现行" />
                   <el-option label="已废止" value="已废止" />
@@ -361,17 +386,74 @@
 
         <!-- 施工方案特有字段 -->
         <template v-else-if="editForm.table_type === 'work'">
-          <el-form-item label="项目名称">
-            <el-input v-model="editForm.project_name" placeholder="请输入项目名称" />
-          </el-form-item>
-          <el-form-item label="项目标段">
-            <el-input v-model="editForm.project_section" placeholder="请输入项目标段" />
-          </el-form-item>
-          <el-form-item label="编制单位">
-            <el-input v-model="editForm.issuing_authority" placeholder="请输入编制单位" />
+          <el-row :gutter="20">
+            <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-row>
+          <el-row :gutter="20">
+            <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-row>
+          <el-row :gutter="20">
+            <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-row>
+          <el-row :gutter="20">
+            <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="12">
+              <el-form-item label="编制单位">
+                <el-input v-model="editForm.issuing_authority" placeholder="请输入编制单位" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <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-row>
+          <el-form-item label="方案摘要">
+            <el-input v-model="editForm.plan_summary" type="textarea" :rows="3" placeholder="请输入方案摘要" />
           </el-form-item>
-          <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 label="编制依据">
+            <el-input v-model="editForm.compilation_basis" type="textarea" :rows="3" placeholder="请输入编制依据,多个依据请用逗号分隔" />
           </el-form-item>
         </template>
 
@@ -388,9 +470,9 @@
           </el-form-item>
         </template>
 
-        <el-divider content-position="left">文档正文</el-divider>
-        <el-form-item label="文档内容">
-          <el-input v-model="editForm.content" type="textarea" :rows="6" placeholder="请输入文档内容" />
+        <el-divider content-position="left">文档备注</el-divider>
+        <el-form-item label="文档备注">
+          <el-input v-model="editForm.note" type="textarea" :rows="6" placeholder="请输入文档备注" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -405,15 +487,34 @@
         <el-descriptions-item label="名称">{{ currentDoc?.title }}</el-descriptions-item>
         <el-descriptions-item label="知识库">{{ getKnowledgeBaseName(currentDoc?.source_type) }}</el-descriptions-item>
         <el-descriptions-item label="上传人">{{ currentDoc?.created_by }}</el-descriptions-item>
-        <el-descriptions-item label="上传时间">{{ formatDate(currentDoc?.created_time || '') }}</el-descriptions-item>
+        <el-descriptions-item label="上传时间">{{ formatDate(currentDoc?.created_time) }}</el-descriptions-item>
         <el-descriptions-item label="入库状态">
           <el-tag :type="isEntered(currentDoc?.whether_to_enter) ? 'success' : 'info'">
             {{ isEntered(currentDoc?.whether_to_enter) ? '已入库' : '未入库' }}
           </el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="内容摘要">
-          <div class="content-preview">{{ currentDoc?.content || '暂无内容' }}</div>
+        <el-descriptions-item label="文档备注">
+          <div class="content-preview">{{ currentDoc?.note || '暂无备注' }}</div>
         </el-descriptions-item>
+        
+        <!-- 施工方案特有详情 -->
+        <template v-if="currentDoc?.source_type === 'work'">
+          <el-descriptions-item label="工程名称">{{ currentDoc?.project_name || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="工程标段">{{ currentDoc?.project_section || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="方案类别">{{ currentDoc?.plan_category || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="一级分类">{{ currentDoc?.level_1_classification || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="二级分类">{{ currentDoc?.level_2_classification || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="三级分类">{{ currentDoc?.level_3_classification || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="四级分类">{{ currentDoc?.level_4_classification || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="编制单位">{{ currentDoc?.issuing_authority || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="编制日期">{{ formatDate(currentDoc?.release_date) }}</el-descriptions-item>
+          <el-descriptions-item label="方案摘要">
+            <div class="content-preview">{{ currentDoc?.plan_summary || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="编制依据">
+            <div class="content-preview">{{ currentDoc?.compilation_basis || '-' }}</div>
+          </el-descriptions-item>
+        </template>
       </el-descriptions>
       <template #footer>
         <div class="detail-footer">
@@ -465,6 +566,8 @@ const previewVisible = ref(false)
 const previewLoading = ref(false)
 const previewTitle = ref('')
 const previewUrl = ref('')
+const previewDocType = ref('') // 'original' or 'md'
+const isOfficeDoc = ref(false)
 const total = ref(0)
 const statistics = ref({
   allTotal: 0,
@@ -476,10 +579,10 @@ const currentDoc = ref<DocumentItem | null>(null)
 
 const editForm = reactive({
   id: '',
-  source_id: '',
   title: '',
-  content: '',
+  note: '',
   table_type: 'basis' as 'basis' | 'work' | 'job',
+  year: new Date().getFullYear(),
   // 扩展字段 (子表特有属性)
   standard_no: '',
   issuing_authority: '',
@@ -489,9 +592,21 @@ const editForm = reactive({
   validity: '',
   project_name: '',
   project_section: '',
+  plan_category: '',
+  level_1_classification: '施工方案',
+  level_2_classification: '',
+  level_3_classification: '',
+  level_4_classification: '',
+  plan_summary: '',
+  compilation_basis: '',
   file_url: ''
 })
 
+const planCategoryOptions = ['超危大方案', '超危大方案较大II级', '超危大方案特大IV级', '超危大方案一般I级', '超危大方案重大III级', '危大方案', '一般方案']
+const level2Options = ['临建工程', '路基工程', '其他', '桥梁工程', '隧道工程']
+const level3Options = ['/', 'TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '其他', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥']
+const level4Options = ['/', '挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '其他', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础']
+
 const currentTitle = computed(() => {
   return '文档管理中心'
 })
@@ -507,7 +622,7 @@ const searchQuery = reactive({
 
 const uploadForm = reactive({
   title: '',
-  content: '',
+  note: '',
   file_url: '',
   table_type: 'basis' as 'basis' | 'work' | 'job',
   year: new Date().getFullYear()
@@ -533,7 +648,7 @@ const formatSimpleDate = (date: string | null) => {
   return dayjs(date).format('YYYY-MM-DD')
 }
 
-const formatDate = (date: string, formatStr: string = 'YYYY-MM-DD HH:mm:ss') => {
+const formatDate = (date: string | undefined | null, formatStr: string = 'YYYY-MM-DD HH:mm:ss') => {
   return date ? dayjs(date).format(formatStr) : '-'
 }
 
@@ -621,56 +736,49 @@ const handleSelectionChange = (selection: DocumentItem[]) => {
 const handleBatchEnter = async () => {
   if (selectedIds.value.length === 0) return
   
-  // 找出选中项中符合入库条件的:未入库 且 转换成功
-  const selectedDocs = documents.value.filter(doc => selectedIds.value.includes(doc.id))
-  const readyDocs = selectedDocs.filter(doc => !isEntered(doc.whether_to_enter) && doc.conversion_status === 2)
-  const readyIds = readyDocs.map(doc => doc.id)
-    
-  if (readyIds.length === 0) {
-    return ElMessage.warning('选中的文档中没有符合入库条件的(需转换成功且未入库)')
-  }
-
-  if (readyIds.length < selectedIds.value.length) {
-    try {
-      await ElMessageBox.confirm(
-        `选中的 ${selectedIds.value.length} 个文档中,仅有 ${readyIds.length} 个符合入库条件(已转换成功)。是否继续对这 ${readyIds.length} 个文档执行入库?`,
-        '入库提示',
-        {
-          confirmButtonText: '继续入库',
-          cancelButtonText: '取消',
-          type: 'info'
-        }
-      )
-    } catch {
-      return
-    }
-  }
-
+  const ids = [...selectedIds.value]
+  
   try {
-    const res = await documentApi.batchEnter(readyIds)
+    const res = await documentApi.batchEnter(ids)
     if (res.code === 0) {
-      ElMessage.success(res.message || '入库成功')
+      // 如果有详细详情(换行符标识),使用 MessageBox 显示
+      if (res.message && res.message.includes('\n')) {
+        ElMessageBox.alert(res.message, '入库结果', {
+          confirmButtonText: '确定',
+          customStyle: { 'white-space': 'pre-wrap' },
+          type: res.message.includes('失败') || res.message.includes('跳过') ? 'warning' : 'success'
+        })
+      } else {
+        ElMessage.success(res.message || '入库成功')
+      }
       fetchDocuments()
     } else {
-      ElMessage.error(res.message || '入库失败')
+      ElMessageBox.alert(res.message || '入库失败', '操作失败', {
+        confirmButtonText: '确定',
+        type: 'error'
+      })
     }
   } catch (error) {
     console.error('批量入库失败:', error)
-    ElMessage.error('网络错误')
+    ElMessage.error('网络连接异常,请稍后重试')
   }
 }
 
 const handleSingleEnter = async (doc: DocumentItem | null) => {
   if (!doc) return
   
-  if (doc.conversion_status !== 2) {
-    return ElMessage.warning('该文档尚未转化完成,暂不允许入库')
-  }
-
   try {
     const res = await documentApi.batchEnter([doc.id])
     if (res.code === 0) {
-      ElMessage.success('成功加入知识库')
+      if (res.message && res.message.includes('\n')) {
+        ElMessageBox.alert(res.message, '入库结果', {
+          confirmButtonText: '确定',
+          customStyle: { 'white-space': 'pre-wrap' },
+          type: res.message.includes('失败') || res.message.includes('跳过') ? 'warning' : 'success'
+        })
+      } else {
+        ElMessage.success(res.message || '入库成功')
+      }
       detailDialogVisible.value = false
       fetchDocuments()
     } else {
@@ -678,7 +786,7 @@ const handleSingleEnter = async (doc: DocumentItem | null) => {
     }
   } catch (error) {
     console.error('入库失败:', error)
-    ElMessage.error('入库失败')
+    ElMessage.error('入库异常')
   }
 }
 
@@ -891,7 +999,7 @@ const customUpload = async (options: any) => {
 
 const handleUpload = () => {
   uploadForm.title = ''
-  uploadForm.content = ''
+  uploadForm.note = ''
   uploadForm.file_url = ''
   uploadDialogVisible.value = true
 }
@@ -921,9 +1029,8 @@ const handleEdit = async (row: DocumentItem) => {
     if (res.code === 0 && res.data) {
       const data = res.data
       editForm.id = data.id
-      editForm.source_id = data.source_id
       editForm.title = data.title
-      editForm.content = data.content
+      editForm.note = data.note || ''
       editForm.table_type = data.source_type
       
       // 填充扩展字段
@@ -935,6 +1042,13 @@ const handleEdit = async (row: DocumentItem) => {
       editForm.validity = data.validity || ''
       editForm.project_name = data.project_name || ''
       editForm.project_section = data.project_section || ''
+      editForm.plan_category = data.plan_category || ''
+      editForm.level_1_classification = data.level_1_classification || '施工方案'
+      editForm.level_2_classification = data.level_2_classification || ''
+      editForm.level_3_classification = data.level_3_classification || ''
+      editForm.level_4_classification = data.level_4_classification || ''
+      editForm.plan_summary = data.plan_summary || ''
+      editForm.compilation_basis = data.compilation_basis || ''
       editForm.file_url = data.file_url || ''
       
       editDialogVisible.value = true
@@ -985,10 +1099,30 @@ const handleEditFromDetail = () => {
 
 const handlePreview = (row: DocumentItem | null) => {
   if (!row || !row.file_url) return
+  currentDoc.value = row
   previewTitle.value = row.title
-  previewUrl.value = row.file_url
+  
+  const ext = getFileExtension(row).toLowerCase()
+  const officeExtensions = ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
+  isOfficeDoc.value = officeExtensions.includes(ext)
+  
+  // 如果是 Office 文档且有转换后的 MD,优先预览 MD
+  if (isOfficeDoc.value && row.md_url) {
+    previewUrl.value = row.md_url
+    previewDocType.value = 'md'
+    previewTitle.value = `${row.title} (转换预览)`
+  } else {
+    previewUrl.value = row.file_url
+    previewDocType.value = 'original'
+  }
+  
   previewVisible.value = true
-  previewLoading.value = true
+  // 如果是无法直接预览的 Office 文档,不需要等待 iframe 加载
+  if (isOfficeDoc.value && previewDocType.value === 'original') {
+    previewLoading.value = false
+  } else {
+    previewLoading.value = true
+  }
 }
 
 const handleDownload = (row: DocumentItem) => {
@@ -1060,7 +1194,8 @@ const handleConvert = async (row: DocumentItem) => {
 
 const openInNewWindow = () => {
   if (previewUrl.value) {
-    window.open(previewUrl.value, '_blank')
+    // 优先尝试在新窗口打开代理后的链接,这有助于控制 Content-Disposition
+    window.open(proxyPreviewUrl.value, '_blank')
   }
 }
 
@@ -1209,6 +1344,22 @@ onUnmounted(() => {
 .file-icon-mini.excel { background-color: #f0f9eb; color: #67c23a; }
 .file-icon-mini.ppt { background-color: #fff7e6; color: #e6a23c; }
 
+.file-info-content {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  overflow: hidden;
+}
+
+.file-note {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  line-height: 1.2;
+}
+
 .file-name-link {
   color: #409eff;
   cursor: pointer;
@@ -1458,11 +1609,27 @@ onUnmounted(() => {
 }
 
 .preview-content {
-  height: 70vh;
+  height: 75vh;
   display: flex;
   flex-direction: column;
   border: 1px solid #dcdfe6;
   border-radius: 4px;
+  background-color: #f5f7fa;
+  position: relative;
+}
+
+.unsupported-preview {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fff;
+}
+
+.unsupported-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: center;
 }
 
 .preview-content iframe {

+ 0 - 5
src/views/images/Index.vue

@@ -80,11 +80,6 @@
                 {{ formatDateTime(scope.row.created_time) }}
               </template>
             </el-table-column>
-            <el-table-column prop="updated_time" label="修改时间" width="170">
-              <template #default="scope">
-                {{ formatDateTime(scope.row.updated_time) }}
-              </template>
-            </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>