Quellcode durchsuchen

feat(frontend): 补充 API 接口、全局样式调整、TaskCenter 组件更新

何文松 vor 1 Tag
Ursprung
Commit
7fb703d058

+ 1 - 171
frontend/vue-demo/src/App.vue

@@ -7,39 +7,6 @@
     
     <!-- 正常布局:无侧边栏,编辑器全宽 -->
     <div v-else class="app-container">
-      <!-- 顶部导航 -->
-      <header class="app-header">
-        <div class="header-left">
-          <div class="logo">
-            <div class="logo-icon">灵</div>
-            <span>灵越智报</span>
-          </div>
-        </div>
-        <div class="header-right">
-          <el-button class="header-icon-btn" circle>
-            <el-icon><Search /></el-icon>
-          </el-button>
-          <el-badge :value="3" class="notification-badge">
-            <el-button class="header-icon-btn" circle>
-              <el-icon><Bell /></el-icon>
-            </el-button>
-          </el-badge>
-          <el-dropdown trigger="click" @command="handleUserCommand">
-            <div class="user-menu">
-              <el-avatar :size="28" class="user-avatar">{{ userInitial }}</el-avatar>
-              <span class="user-name">{{ username }}</span>
-            </div>
-            <template #dropdown>
-              <el-dropdown-menu>
-                <el-dropdown-item command="profile">个人中心</el-dropdown-item>
-                <el-dropdown-item command="settings">系统设置</el-dropdown-item>
-                <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
-              </el-dropdown-menu>
-            </template>
-          </el-dropdown>
-        </div>
-      </header>
-
       <!-- 主体区域:全宽编辑器 -->
       <div class="app-body">
         <main class="app-main">
@@ -47,8 +14,7 @@
         </main>
       </div>
 
-      <!-- 任务中心悬浮按钮和面板 -->
-      <TaskCenterFab />
+      <!-- 任务中心面板 -->
       <TaskCenterPanel />
     </div>
   </el-config-provider>
@@ -57,48 +23,14 @@
 <script setup>
 import { computed } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
-import { Bell, Search } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
-import TaskCenterFab from '@/components/TaskCenter/TaskCenterFab.vue'
 import TaskCenterPanel from '@/components/TaskCenter/TaskCenterPanel.vue'
-import { authApi } from '@/api'
 
 const router = useRouter()
 const route = useRoute()
 
 const hideLayout = computed(() => route.meta.hideLayout === true)
-
-// 用户信息
-const username = computed(() => localStorage.getItem('username') || '用户')
-const userInitial = computed(() => username.value.charAt(0))
-
-// 用户菜单操作
-function handleUserCommand(command) {
-  switch (command) {
-    case 'profile':
-      ElMessage.info('个人中心开发中...')
-      break
-    case 'settings':
-      ElMessage.info('系统设置开发中...')
-      break
-    case 'logout':
-      ElMessageBox.confirm('确定要退出登录吗?', '退出确认', {
-        confirmButtonText: '退出',
-        cancelButtonText: '取消',
-        type: 'warning'
-      }).then(async () => {
-        try { await authApi.logout() } catch (e) { /* ignore */ }
-        localStorage.removeItem('accessToken')
-        localStorage.removeItem('refreshToken')
-        localStorage.removeItem('userId')
-        localStorage.removeItem('username')
-        ElMessage.success('已退出登录')
-        router.push('/login')
-      }).catch(() => {})
-      break
-  }
-}
 </script>
 
 <style lang="scss">
@@ -108,107 +40,6 @@ function handleUserCommand(command) {
   flex-direction: column;
 }
 
-// ==========================================
-// 全局头部 - 参考 V2 设计
-// ==========================================
-.app-header {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  height: 56px;
-  background: var(--white);
-  border-bottom: 1px solid var(--border);
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 0 18px;
-  z-index: 1000;
-}
-
-.header-left {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-
-.logo {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  font-size: 16px;
-  font-weight: 600;
-  color: var(--text-1);
-
-  .logo-icon {
-    width: 36px;
-    height: 36px;
-    background: linear-gradient(135deg, var(--primary) 0%, #69c0ff 100%);
-    border-radius: 8px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: white;
-    font-weight: 700;
-    font-size: 18px;
-  }
-}
-
-.header-right {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-
-.header-icon-btn {
-  width: 32px;
-  height: 32px;
-  border: none;
-  background: transparent;
-  font-size: 16px;
-  color: #8c8c8c;
-  
-  &:hover {
-    background: #f0f0f0;
-    color: #555;
-  }
-}
-
-.notification-badge {
-  :deep(.el-badge__content) {
-    top: 2px;
-    right: 4px;
-  }
-}
-
-.user-menu {
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  cursor: pointer;
-  padding: 4px 8px 4px 4px;
-  border-radius: 20px;
-  margin-left: 4px;
-  
-  &:hover {
-    background: var(--bg);
-  }
-}
-
-.user-avatar {
-  width: 28px;
-  height: 28px;
-  background: var(--primary);
-  font-weight: 700;
-  font-size: 12px;
-}
-
-.user-name {
-  font-size: 13px;
-  font-weight: 500;
-  color: var(--text-1);
-}
-
 // ==========================================
 // 主体区域:全宽
 // ==========================================
@@ -216,7 +47,6 @@ function handleUserCommand(command) {
   flex: 1;
   display: flex;
   overflow: hidden;
-  margin-top: 56px;
 }
 
 .app-main {

+ 70 - 24
frontend/vue-demo/src/api/index.js

@@ -32,11 +32,13 @@ api.interceptors.response.use(
     return Promise.reject(new Error(data.message || '请求失败'))
   },
   error => {
-    if (error.response?.status === 401) {
+    if (error.response?.status === 401 || error.response?.status === 403) {
       localStorage.removeItem('accessToken')
       localStorage.removeItem('refreshToken')
       localStorage.removeItem('username')
-      window.location.href = '/login'
+      if (!window.location.pathname.includes('/login')) {
+        window.location.href = '/login'
+      }
     }
     const msg = error.response?.data?.message || error.message || '网络错误'
     return Promise.reject(new Error(msg))
@@ -104,6 +106,10 @@ export const projectApi = {
 
   export(id) {
     return api.get(`/projects/${id}/export`, { responseType: 'blob' })
+  },
+
+  getDocContent(id) {
+    return api.get(`/projects/${id}/doc-content`)
   }
 }
 
@@ -177,36 +183,29 @@ export const attachmentApi = {
     return api.post(`/attachments/${attachmentId}/parse`)
   },
 
-  sort(projectId, orderedIds) {
-    return api.put(`/projects/${projectId}/attachments/sort`, orderedIds)
+  saveParsedContent(attachmentId, parsedText) {
+    return api.put(`/attachments/${attachmentId}/parsed-content`, { parsedText })
   },
 
-  getDocContent(attachmentId) {
-    return api.get(`/attachments/${attachmentId}/doc-content`)
-  }
-}
-
-// ==================== 实体 API ====================
-
-export const entityApi = {
-  listByAttachment(attachmentId) {
-    return api.get(`/attachments/${attachmentId}/entities`)
+  getParsedText(attachmentId) {
+    return api.get(`/attachments/${attachmentId}/parsed-text`)
   },
 
-  listByProject(projectId) {
-    return api.get(`/projects/${projectId}/entities`)
-  },
-
-  getById(entityId) {
-    return api.get(`/entities/${entityId}`)
+  parseDocx(file) {
+    const formData = new FormData()
+    formData.append('file', file)
+    return api.post('/tools/parse-docx', formData, {
+      headers: { 'Content-Type': 'multipart/form-data' },
+      timeout: 120000
+    })
   },
 
-  update(entityId, data) {
-    return api.put(`/entities/${entityId}`, data)
+  sort(projectId, orderedIds) {
+    return api.put(`/projects/${projectId}/attachments/sort`, orderedIds)
   },
 
-  merge(targetId, sourceIds) {
-    return api.post(`/entities/${targetId}/merge`, sourceIds)
+  getDocContent(attachmentId) {
+    return api.get(`/attachments/${attachmentId}/doc-content`)
   }
 }
 
@@ -326,4 +325,51 @@ export const aiApi = {
   }
 }
 
+// ==================== PDF 解析 API (外部服务) ====================
+
+const parseService = axios.create({
+  baseURL: 'http://47.108.80.98:4214',
+  timeout: 120000
+})
+
+export const parseApi = {
+  // 提交 PDF/图片解析任务
+  submit(file, options = {}) {
+    const formData = new FormData()
+    formData.append('file', file)
+    if (options.backend) formData.append('backend', options.backend)
+    if (options.remove_watermark) formData.append('remove_watermark', 'true')
+    if (options.crop_header_footer) formData.append('crop_header_footer', 'true')
+    if (options.return_images) formData.append('return_images', 'true')
+    return parseService.post('/pdf_to_markdown', formData, {
+      headers: { 'Content-Type': 'multipart/form-data' }
+    }).then(r => r.data)
+  },
+
+  // 查询任务状态
+  getStatus(taskId) {
+    return parseService.get(`/task/${taskId}`).then(r => r.data)
+  },
+
+  // 获取解析结果 JSON { markdown, filename }
+  getResult(taskId) {
+    return parseService.get(`/task/${taskId}/json`).then(r => r.data)
+  },
+
+  // 下载 markdown 文件
+  downloadMarkdown(taskId) {
+    return parseService.get(`/download/${taskId}/markdown`, { responseType: 'blob' }).then(r => r.data)
+  },
+
+  // 下载 zip 包(markdown + 图片)
+  downloadZip(taskId) {
+    return parseService.get(`/download/${taskId}/zip`, { responseType: 'blob' }).then(r => r.data)
+  },
+
+  // 获取解析任务的图片 URL 基础路径
+  getImageBaseUrl(taskId) {
+    return `http://47.108.80.98:4214/download/${taskId}/images`
+  }
+}
+
 export default api

+ 0 - 114
frontend/vue-demo/src/assets/main.scss

@@ -415,114 +415,6 @@ body {
   }
 }
 
-// ==========================================
-// 实体高亮样式
-// ==========================================
-.entity-highlight {
-  display: inline;
-  padding: 2px 8px;
-  border-radius: 4px;
-  cursor: pointer;
-  transition: all 0.2s;
-  font-weight: 500;
-  border: 1px solid var(--primary);
-  color: var(--primary);
-  background: rgba(24, 144, 255, 0.1);
-
-  &:hover {
-    background: var(--primary);
-    color: white;
-  }
-
-  // 核心实体
-  &.entity {
-    border-color: var(--primary);
-    color: var(--primary);
-    background: rgba(24, 144, 255, 0.1);
-    &:hover { background: var(--primary); color: white; }
-  }
-
-  // 概念/技术
-  &.concept {
-    border-color: #722ed1;
-    color: #722ed1;
-    background: rgba(114, 46, 209, 0.1);
-    &:hover { background: #722ed1; color: white; }
-  }
-
-  // 数据/指标
-  &.data {
-    border-color: #52c41a;
-    color: #52c41a;
-    background: rgba(82, 196, 26, 0.1);
-    &:hover { background: #52c41a; color: white; }
-  }
-
-  // 位置/地点
-  &.location {
-    border-color: #faad14;
-    color: #d48806;
-    background: rgba(250, 173, 20, 0.1);
-    &:hover { background: #faad14; color: white; }
-  }
-
-  // 资产/产品
-  &.asset {
-    border-color: #eb2f96;
-    color: #eb2f96;
-    background: rgba(235, 47, 150, 0.1);
-    &:hover { background: #eb2f96; color: white; }
-  }
-  
-  // 人物
-  &.person {
-    border-color: var(--primary);
-    color: var(--primary);
-    background: rgba(24, 144, 255, 0.1);
-    &:hover { background: var(--primary); color: white; }
-  }
-  
-  // 组织
-  &.org {
-    border-color: #722ed1;
-    color: #722ed1;
-    background: rgba(114, 46, 209, 0.1);
-    &:hover { background: #722ed1; color: white; }
-  }
-  
-  // 日期
-  &.date {
-    border-color: #13c2c2;
-    color: #13c2c2;
-    background: rgba(19, 194, 194, 0.1);
-    &:hover { background: #13c2c2; color: white; }
-  }
-  
-  // 产品
-  &.product {
-    border-color: #eb2f96;
-    color: #eb2f96;
-    background: rgba(235, 47, 150, 0.1);
-    &:hover { background: #eb2f96; color: white; }
-  }
-  
-  // 事件
-  &.event {
-    border-color: #fa8c16;
-    color: #fa8c16;
-    background: rgba(250, 140, 22, 0.1);
-    &:hover { background: #fa8c16; color: white; }
-  }
-  
-  // 法规
-  &.law {
-    border-color: #2f54eb;
-    color: #2f54eb;
-    background: rgba(47, 84, 235, 0.1);
-    &:hover { background: #2f54eb; color: white; }
-  }
-}
-
 // ==========================================
 // 变量/要素标签
 // ==========================================
@@ -549,12 +441,6 @@ body {
     cursor: grabbing;
   }
 
-  &.entity { border-left: 3px solid var(--primary); }
-  &.concept { border-left: 3px solid #722ed1; }
-  &.data { border-left: 3px solid var(--success); }
-  &.location { border-left: 3px solid var(--warning); }
-  &.asset { border-left: 3px solid #eb2f96; }
-
   .tag-icon { font-size: 12px; }
   .tag-name { font-weight: 500; }
 }

+ 1 - 1
frontend/vue-demo/src/components/TaskCenter/TaskCenterFab.vue

@@ -37,7 +37,7 @@ watch(() => store.runningTotal, (newVal, oldVal) => {
 <style scoped>
 .task-fab {
   position: fixed;
-  right: 24px;
+  left: 24px;
   bottom: 24px;
   width: 56px;
   height: 56px;

+ 1 - 1
frontend/vue-demo/src/components/TaskCenter/TaskCenterPanel.vue

@@ -355,7 +355,7 @@ async function handleDelete(item) {
 <style scoped>
 .task-panel {
   position: fixed;
-  right: 16px;
+  left: 16px;
   bottom: 16px;
   width: 480px;
   max-width: calc(100vw - 32px);

+ 1 - 17
frontend/vue-demo/src/stores/template.js

@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia'
 import { ref, computed } from 'vue'
-import { projectApi, elementApi, valueApi, attachmentApi, entityApi, ruleApi } from '@/api'
+import { projectApi, elementApi, valueApi, attachmentApi, ruleApi } from '@/api'
 
 export const useProjectStore = defineStore('project', () => {
   // 状态
@@ -10,7 +10,6 @@ export const useProjectStore = defineStore('project', () => {
   const elements = ref([])
   const values = ref([])
   const attachments = ref([])
-  const entities = ref([])
   const rules = ref([])
   const loading = ref(false)
 
@@ -136,17 +135,6 @@ export const useProjectStore = defineStore('project', () => {
     attachments.value = attachments.value.filter(a => a.id !== attachmentId)
   }
 
-  // ==================== 实体 ====================
-
-  async function fetchEntitiesByProject(projectId) {
-    entities.value = await entityApi.listByProject(projectId)
-    return entities.value
-  }
-
-  async function fetchEntitiesByAttachment(attachmentId) {
-    return await entityApi.listByAttachment(attachmentId)
-  }
-
   // ==================== 规则 ====================
 
   async function fetchRules(projectId) {
@@ -180,7 +168,6 @@ export const useProjectStore = defineStore('project', () => {
     elements.value = []
     values.value = []
     attachments.value = []
-    entities.value = []
     rules.value = []
   }
 
@@ -191,7 +178,6 @@ export const useProjectStore = defineStore('project', () => {
     elements,
     values,
     attachments,
-    entities,
     rules,
     loading,
 
@@ -215,8 +201,6 @@ export const useProjectStore = defineStore('project', () => {
     fetchAttachments,
     uploadAttachment,
     deleteAttachment,
-    fetchEntitiesByProject,
-    fetchEntitiesByAttachment,
     fetchRules,
     createRule,
     deleteRule,