浏览代码

更新知识图谱构建的新代码

yangdr 4 周之前
父节点
当前提交
fdf065e934

+ 101 - 1
src/api/AgentApi.ts

@@ -3,6 +3,7 @@ import { getRequestId } from '@/utils/misc'
 export interface StandardResponse {
     code: number
     message: string
+    data: any,
     meta: any
     records: any
 }
@@ -12,6 +13,7 @@ const URL_GET_QUEUE = '/api/agent/queue'
 const URL_GET_FILE = '/api/file/browse'
 const URL_GET_USER = '/api/user/session'
 const URL_PUT_USER = '/api/user/signin'
+const URL_GET_CONFIG = '/api/config/do'
 const SUCCESS = 200
 const FAILED = 500
 const PROCESSING = 202
@@ -19,6 +21,26 @@ interface RequestActionParams { // 定义Job接口
     name: string
     value: any
 }
+export interface SearchData {
+    job_name: string,
+    start_category: string,
+    job_category: string,
+    status: number,
+}
+export interface JobData {
+    id: number
+    start_category: string
+    job_category: string
+    job_name: string
+    job_details: string
+    job_creator: string
+    job_status: string
+    job_logs: string
+    status: number
+    executor: string
+    created: Date
+    updated: Date
+}
 export interface RequestBody { // 定义Job接口
     id: string
     action: string
@@ -36,6 +58,18 @@ export interface JobData {
     created: Date
     updated: Date
 }
+export const JobCategory = {
+    "SYSTEM_WORD": "WORD文档文字提取",
+    "SYSTEM_OCR": "PDF文档文字提取",
+    "SYSTEM_PDF": "PDF文档文字提取",
+    "SYSTEM_IMAGE": "图片文字提取",
+    "SYSTEM_CHUNKS": "文本分块",
+    "SYSTEM_EMBEDDINGS": "文本向量化",
+    "SYSTEM_SUMMARIZE": "文本摘要",
+    "SYSTEM_QA": "文本问答",
+    "SYSTEM_KB_BUILD": "知识图谱构建",
+    "SYSTEM_KB_EXTRACT": "抽取知识",
+}
 export const JobSatus = [
     //primary success info warning danger
     { "label": "无效", "value": -1, "tag": "danger", "could_be_set": false },
@@ -51,7 +85,40 @@ export const JobSatus = [
     { "label": "跳过", "value": 9, "tag": "info", "could_be_set": true },
     { "label": "重试", "value": 10, "tag": "warning", "could_be_set": true },
 ]
-
+export function getJobCategory(category: string): string {
+    if (category in JobCategory) {
+        return JobCategory[category as keyof typeof JobCategory]
+    }
+    return category
+}
+export function searchJobs(params: SearchData, page: number = 1, page_size: number = 10): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'search_jobs',
+        params: [
+            { name: 'job_status', value: params.status },
+            { name: 'job_category', value: params.job_category },
+            { name: 'start_category', value: params.start_category },
+            { name: 'job_name', value: params.job_name },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+        ]
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
+export function getStartQueueJobs(categroy: string, name: string, page: number = 1, page_size: number = 10): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_jobs_start_from_queue',
+        params: [
+            { name: 'queue_category', value: categroy },
+            { name: 'queue_name', value: name },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+        ]
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
 export function getJobStatus(status: number): string {
     var result = JobSatus.find(item => item.value == status)
     if (result) {
@@ -270,4 +337,37 @@ export function getKnowledgeBase(params: any = { name: "", pageNo: 1, pageSize:
 
 export function getKnowledgeBaseFilesList(params: any): Promise<StandardResponse> {
     return serverGetRequest(`/open-platform/knowledge-base/${params.kbId}/files/?file_name=${params.file_name}&pageNo=${params.pageNo}&pageSize=${params.pageSize}`)
+}
+
+export function getConfig(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_config',
+        params: [
+            { name: 'code', value: params['code'] },
+        ]
+    }
+    return serverRequest(URL_GET_CONFIG, data)
+}
+export function getQueues(): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_queues',
+        params: []
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
+export function setConfig(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'set_config',
+        params: [
+            { name: 'code', value: params['code'] },
+            { name: 'name', value: params['name'] },
+            { name: 'content', value: params['content'] },
+        ]
+    }
+    return serverRequest(URL_GET_CONFIG, data)
 }

+ 265 - 31
src/api/GraphApi.ts

@@ -1,67 +1,301 @@
-import r from '@/utils/request'
+import { Request, AsyncRequest } from '@/utils/request'
 import { getRequestId } from '@/utils/misc'
-import { serverRequest,type RequestBody, type StandardResponse } from '@/api/AgentApi'
+import { serverRequest, type RequestBody, type StandardResponse } from '@/api/AgentApi'
 
 const URL_GET_NODE = '/api/kb/nodes'
+const URL_GET_EDGE = '/api/kb/edges'
 const URL_GET_SUMMARY = '/api/kb/summary'
 const URL_GET_SCHEMA = '/api/kb/schemas'
-export function searchNodes(keyword:string, graph_id:number): Promise<StandardResponse> {
-    var data:RequestBody = {
+const URL_GET_GRAPHS = '/api/kb/graphs'
+const URL_GET_STD_SCHEMA = '/api/kb/std-schemas'
+const URL_GET_REPORT = '/api/kb/report'
+
+export const GraphStatus = {
+    GRAPH_STATUS_NORMAL: 0,
+    GRAPH_STATUS_DELETED: -1,
+    GRAPH_STATUS_NEED_CHECK: 1,
+    GRAPH_STATUS_CHECKING: 2,
+    GRAPH_STATUS_CHECKED: 3,
+}
+export function getGraphCheckReport(graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_latest_report',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckReportEvents(graph_id: number, page: number, page_size: number, event_name: string): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_report_events',
+        params: [
+            { name: 'graph_id', value: graph_id },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+            { name: 'event_name', value: event_name },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckProgressValue(progressData: any): number {
+    for (var i = 0; i < progressData.length; i++) {
+        if (progressData[i].name == "progress") {
+            return Math.ceil(progressData[i].value * 1000) / 10
+        }
+    }
+    return 0
+}
+
+export function getGraphCheckProgressData(progressData: any): any {
+    for (var i = 0; i < progressData.length; i++) {
+        if (progressData[i].name == "progress") {
+            return progressData[i].data
+        }
+    }
+    return 0
+}
+
+export function getGraph(graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_graph',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_GRAPHS, data)
+}
+export function getGraphs(params: any): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_graphs',
+        params: [
+            { name: 'page', value: params['page'] },
+            { name: 'page_size', value: params['page_size'] },
+        ]
+    }
+    return serverRequest(URL_GET_GRAPHS, data)
+}
+
+export function searchNodes(keyword: string, graph_id: number, category: string = "any"): Promise<StandardResponse> {
+    var data: RequestBody = {
         id: getRequestId(),
-        action: 'search_nodes', 
-        params:[
-            {name: 'name', value: keyword} ,
-            {name: 'category', value: "any"} ,
-            {name: 'graph_id', value: graph_id},
+        action: 'search_nodes',
+        params: [
+            { name: 'name', value: keyword },
+            { name: 'category', value: category },
+            { name: 'graph_id', value: graph_id },
         ]
     }
     return serverRequest(URL_GET_NODE, data)
 }
 
-export function getNodeNeighbors(node_id:number, graph_id:number): Promise<StandardResponse> {
-    var data:RequestBody = {
+export function getNode(node_id: number, graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
         id: getRequestId(),
-        action:'neighbors',
-        params:[
-            {name: 'node_id', value: node_id},
-            {name: 'graph_id', value: graph_id},
+        action: 'get_node',
+        params: [
+            { name: 'node_id', value: node_id },
+            { name: 'graph_id', value: graph_id },
         ]
-    } 
+    }
     return serverRequest(URL_GET_NODE, data)
 }
-export function getGraphSummary(params:any): Promise<StandardResponse> {
+
+// export function getIndividualNodes(graph_id:number): Promise<StandardResponse> {
+//     var data:RequestBody = {
+//         id: getRequestId(),
+//         action:'get_individual_nodes',
+//         params:[
+//             {name: 'graph_id', value: graph_id},
+//         ]
+//     } 
+//     return serverRequest(URL_GET_NODE, data)
+// }
+
+// export function getContradictions(graph_id:number): Promise<StandardResponse> {
+//     var data:RequestBody = {
+//         id: getRequestId(),
+//         action:'get_contradictions',
+//         params:[
+//             {name: 'graph_id', value: graph_id},
+//         ]
+//     } 
+//     return serverRequest(URL_GET_NODE, data)
+// }
+export function updateNode(params: any): Promise<StandardResponse> {
     console.log(params)
-    var data:RequestBody = {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'update_node',
+        params: [
+            { name: 'node_id', value: params['node_id'] },
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'name', value: params['name'] },
+            { name: 'category', value: params['category'] },
+        ]
+    }
+    return serverRequest(URL_GET_NODE, data)
+}
+export function searchEdges(keyword: string, graph_id: number, category: string = "any"): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'search_edges',
+        params: [
+            { name: 'name', value: keyword },
+            { name: 'category', value: category },
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_EDGE, data)
+}
+export function getNodeNeighbors(node_id: number, graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'neighbors',
+        params: [
+            { name: 'node_id', value: node_id },
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_NODE, data)
+}
+export function getGraphSummary(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_summary',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},      
-        ] 
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+        ]
     }
     return serverRequest(URL_GET_SUMMARY, data)
 }
 
-export function getGraphNodeSchema(params:any): Promise<StandardResponse> {
+export function getGraphNodeSchema(params: any): Promise<StandardResponse> {
     console.log(params)
-    var data:RequestBody = {
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_nodes_schemas',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
         ]
-    } 
+    }
     return serverRequest(URL_GET_SCHEMA, data)
 }
 
 
-export function getGraphEdgeSchema(params:any): Promise<StandardResponse> {
+export function getGraphEdgeSchema(params: any): Promise<StandardResponse> {
     console.log(params)
-    var data:RequestBody = {
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_edges_schemas',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
         ]
-    } 
+    }
     return serverRequest(URL_GET_SCHEMA, data)
+}
+
+export function getGraphStdSchemas(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_std_schemas',
+        params: [
+            { name: 'page', value: params['page'] },
+            { name: 'page_size', value: params['page_size'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+
+export function reindexSchema(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'index_std_schema',
+        params: [
+            { name: 'schema_id', value: params['schema_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+
+export function searchStdSchema(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'std_schema_search',
+        params: [
+            { name: 'query_word', value: params['query_word'] },
+            { name: 'type', value: params['type'] },
+            { name: 'schema_id', value: params['schema_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+export function updateStdSchemaContent(params: any): Promise<StandardResponse> {
+    console.log("updateStdSchemaContent", params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'std_schema_update_content',
+        params: [
+            { name: 'schema_id', value: params['schema_id'] },
+            { name: 'content', value: params['content'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+export function updateGraphNodeSchema(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'update_node_schema',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'category', value: params['category'] },
+            { name: 'category_new', value: params['category_new'] },
+        ]
+    }
+    return serverRequest(URL_GET_SCHEMA, data)
+}
+export function updateGraphEdgeSchema(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'update_edge_schema',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'category', value: params['category'] },
+            { name: 'category_new', value: params['category_new'] },
+        ]
+    }
+    return serverRequest(URL_GET_SCHEMA, data)
+}
+
+export function createGraphCheckReport(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'create_graph_check_report',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckProgress(graph_id: number): Promise<StandardResponse> {
+
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'graph_report_progress',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
 }

+ 57 - 130
src/components/SideMenu.vue

@@ -1,114 +1,40 @@
 <template>
   <div class="left-nav">
-    <el-menu default-active="1-1" class="el-menu-vertical-demo">
-      <el-sub-menu index="1">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作台</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item @click="router.push({ name: 'home' })" index="1-1">工作台概览</el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="2">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作队列({{ queues.length }})</span>
-        </template>
-        <el-menu-item-group>
-          <template v-for="(queue, index) in queues" :key="queue.id">
-            <el-menu-item :index="'2' + '-' + index" @click="queueClicked(queue)">
-              <!-- <template #title> -->
-              <span>{{ queue.name }}</span>
-              <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
-              <!-- </template> -->
-            </el-menu-item>
-          </template>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="3">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>知识图谱</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item index="3-1" @click="handleMenuClick('graph')">全部图谱</el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="4">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作人员({{ workers.length }})</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item v-for="worker in workers" :key="worker.id" @click="selectWorker(worker)"
-            :index="'4-' + worker.id">
-            <el-icon>
-              <User />
-            </el-icon>
-            <span>{{ worker.name }}</span>
-            <el-tag :type="worker.status === '空闲' ? 'success' : 'warning'" size="small">
-              {{ worker.status }}
-            </el-tag>
-          </el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-    </el-menu>
-    <!-- <div class="nav-section">
+    <div class="nav-section">
+
       <h3>工作台</h3>
       <el-menu :default-active="$route.path" router>
-        <el-menu-item index="/home" :route="{ path: '/home' }">
+        <el-menu-item index="/" :route="{ name: 'home' }">
           <template #title>
             <el-icon></el-icon>
             <span>工作台概览</span>
           </template>
         </el-menu-item>
-      </el-menu>
-    </div>
+        <el-menu-item index="/workspace/config" :route="{ name: 'config' }">
+          <template #title>
+            <el-icon></el-icon>
+            <span>配置</span>
+          </template>
+        </el-menu-item>
 
-    <div class="nav-section">
-      <h3>工作队列({{ queues.length }})</h3>
-      <el-menu>
-        <template v-for="queue in queues" :key="queue.id">
-          <el-menu-item :index="'queue' + '_' + queue.id" @click="queueClicked(queue)">
-            <template #title>
-              <span>{{ queue.name }}</span>
-              <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
-            </template>
-          </el-menu-item>
-        </template>
-      </el-menu>
-    </div>
-    <div class="nav-section">
-      <h3>知识图谱</h3>
-      <el-menu>
+        <h3>文本抽取</h3>
+        <el-menu-item v-for="queue in queues" :key="queue.id" :index="'queue' + '_' + queue.id"
+          @click="queueClicked(queue)">
+          <template #title>
+            <span>{{ queue.name }}</span>
+            <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
+          </template>
+        </el-menu-item>
+
+        <h3>知识图谱</h3>
         <el-menu-item @click="handleMenuClick('graph')">
           <span>全部图谱</span>
         </el-menu-item>
-      </el-menu>
-    </div>
-    <div class="nav-section">
-      <h3>工作人员({{ workers.length }})</h3>
-      <el-menu>
-        <el-menu-item v-for="worker in workers" :key="worker.id" @click="selectWorker(worker)">
-          <el-icon>
-            <User />
-          </el-icon>
-          <span>{{ worker.name }}</span>
-          <el-tag :type="worker.status === '空闲' ? 'success' : 'warning'" size="small">
-            {{ worker.status }}
-          </el-tag>
-        </el-menu-item>
-      </el-menu>
-    </div> -->
-    <!-- <div class="nav-section">
-      <h3>知识库管理平台</h3>
-      <el-menu>
-        <el-menu-item @click="router.push({ name: 'kmplatform' })">
-          <span>知识库管理</span>
+        <el-menu-item @click="handleMenuClick('graph-std-schemas')">
+          <span>图谱标准</span>
         </el-menu-item>
       </el-menu>
-    </div> -->
+    </div>
   </div>
 </template>
 
@@ -128,46 +54,47 @@ const queues = [
     id: 0,
     name: "全部工作",
     taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 6,
-    name: "WORD文档抽取",
-    taskCount: 1,
     tasks: [],
-    type: "input"
-  },
-  {
-    id: 1,
-    name: "PDF文档抽取",
-    taskCount: 1,
-    tasks: [],
-    type: "input"
+    type: ""
   },
   // {
+  //   id: 6,
+  //   name: "WORD文档抽取",
+  //   taskCount: 1,
+  //   tasks: [],
+  //   type: "input"
+  // },
+  // {
+  //   id: 1,
+  //   name: "PDF文档抽取",
+  //   taskCount: 1,
+  //   tasks: [],
+  //   type: "input"
+  // },
+  // {
   //   id: 2,
   //   name: "文本校对",
   //   taskCount: 1,
   //   tasks: []
   // },
-  {
-    id: 3,
-    name: "文本切片队列",
-    taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 4,
-    name: "知识抽取",
-    taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 5,
-    name: "知识图谱构建",
-    taskCount: 1,
-    tasks: []
-  },
+  // {
+  //   id: 3,
+  //   name: "文本切片队列",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
+  // {
+  //   id: 4,
+  //   name: "知识抽取",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
+  // {
+  //   id: 5,
+  //   name: "知识图谱构建",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
 ]
 
 
@@ -201,7 +128,7 @@ const selectWorker = (worker: Worker) => {
 <style scoped lang="less">
 .left-nav {
   // width: 280px;
-  height: 100vh;
+  // height: 100vh;
 
   // border-right: 0px solid #e4e7ed;
   // padding: 20px;
@@ -222,4 +149,4 @@ const selectWorker = (worker: Worker) => {
 .el-menu-vertical-demo {
   height: 100%;
 }
-</style>
+</style>

+ 471 - 0
src/dialogs/GraphSchemaDialog.vue

@@ -0,0 +1,471 @@
+<template>
+
+    <el-dialog title="标准详情" v-model="dialogFormVisible" style="width: 800px; height: 700px;">
+        <el-tabs v-model="dialogCurrentTab" style="width: 100%;">
+            <el-tab-pane label="实体类型" name="tab1">
+                <el-row>
+                    <!-- <el-col :span="12" style=" border-right:1px solid #CDCDCD;"> -->
+
+                    <el-button @click="handleAddSchema('entity')">增加</el-button>
+
+                    <el-table :data="currentData?.entities" style="width: 100%; height: 500px;"
+                        :tree-props="{ children: 'props' }" row-key="id"
+                        :default-sort="{ props: 'id', order: 'descending' }">
+                        <el-table-column prop="name" label="名称" width="200">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.name" placeholder="请输入名称" style="width: 100px">{{ scope.name
+                                    }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.name
+                                        }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{ scope.row.name
+                                    }}</span>
+                                <span v-else>{{ scope.row.name }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="category" label="类型" width="250">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.category" placeholder="请输入类型" style="width: 100px">{{
+                                        scope.category }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.category
+                                        }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{
+                                    scope.row.category }}</span>
+                                <span v-else>{{ scope.row.category }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="操作" width="200">
+                            <template v-slot="scope">
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleEditSchema('entity', scope.row.id, true)">编辑</el-link> &nbsp;
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleDeleteSchema('entity', scope.row.id)">删除</el-link>
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="success"
+                                    @click="handleSaveSchema('entity', scope.row.id)">保存</el-link>&nbsp;
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="warning"
+                                    @click="handleCancelEditSchema('entity', scope.row.id)">取消</el-link>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+
+                </el-row>
+            </el-tab-pane>
+            <el-tab-pane label="关系类型" name="tab2">
+                <el-row>
+
+                    <el-button @click="handleAddSchema('relation')">增加</el-button>
+
+                    <el-table :data="currentData?.relations" style="width: 100%; height: 500px;"
+                        :tree-props="{ children: 'props' }" row-key="id"
+                        :default-sort="{ props: 'id', order: 'descending' }">
+                        <el-table-column prop="name" label="名称" width="200">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.name" placeholder="请输入名称" style="width: 100px">{{ scope.name
+                                    }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.name
+                                        }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{ scope.row.name
+                                    }}</span>
+                                <span v-else>{{ scope.row.name }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="category" label="类型" width="250">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.category" placeholder="请输入类型" style="width: 100px">{{
+                                        scope.category }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.category
+                                        }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{
+                                    scope.row.category }}</span>
+                                <span v-else>{{ scope.row.category }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="操作" width="200">
+                            <template v-slot="scope">
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleEditSchema('relation', scope.row.id, true)">编辑</el-link> &nbsp;
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleDeleteSchema('relation', scope.row.id)">删除</el-link>
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="success"
+                                    @click="handleSaveSchema('relation', scope.row.id)">保存</el-link>&nbsp;
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="warning"
+                                    @click="handleCancelEditSchema('relation', scope.row.id)">取消</el-link>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+
+                </el-row>
+            </el-tab-pane>
+            <el-tab-pane label="调试" name="debug">
+                <el-row>
+                    <div style="width: 100%; height: 500px; overflow: auto;">{{ JSON.stringify(currentData) }}</div>
+                </el-row>
+
+            </el-tab-pane>
+        </el-tabs>
+        <template #footer>
+            <el-button type="primary" @click="handleUpdateStdSchema">更新图谱标准</el-button>
+            <el-button type="success" @click="handleApply">应用</el-button>
+            <el-button @click="dialogFormVisible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+<script setup lang="ts">
+import { ElMessage } from 'element-plus'
+import { ref, defineEmits } from 'vue'
+import { ElMessageBox, ElNotification } from 'element-plus'
+import { updateStdSchemaContent, reindexSchema } from '@/api/GraphApi'
+import { type SchemaData } from '@/types/dataset'
+
+
+const currentSchemaId = ref(0)
+const currentData = ref<SchemaData>()
+const currentDialogRelation = ref({})
+// const entitiesEditable = ref({})
+// const propsEditable = ref({})
+
+const dialogFormVisible = ref(false)
+const dialogCurrentTab = ref('tab1')
+const currentId = ref(0)
+
+const emits = defineEmits(['stdUpdated']);
+
+const handleAddSchema = (schemaType: string) => {
+    currentId.value++
+    if (currentData.value == null) {
+        return
+    }
+
+    if (schemaType == "entity") {
+        currentData.value.entities.unshift({ id: `${currentId.value}`, name: "", category: "", old_name: "", old_category: "", action: "create", editable: false, is_new: true, props: [] })
+        handleEditSchema(schemaType, `${currentId.value}`, true)
+    }
+    if (schemaType == "relation") {
+        currentData.value.relations.unshift({ id: `${currentId.value}`, name: "", category: "", old_name: "", old_category: "", action: "create", editable: false, is_new: true, props: [] })
+        handleEditSchema(schemaType, `${currentId.value}`, true)
+    }
+
+}
+const handleApplyDataChange = () => {
+    if (currentData.value == null) {
+        return
+    }
+
+    var targetData = currentData.value.entities
+    for (var i = 0; i < targetData.length;) {
+
+        if (targetData[i].action == "delete") {
+            targetData.splice(i, 1)
+            continue
+        }
+        i++
+
+    }
+    targetData = currentData.value.relations
+    for (var i = 0; i < targetData.length;) {
+
+        if (targetData[i].action == "delete") {
+            targetData.splice(i, 1)
+            continue
+        }
+        i++
+    }
+}
+const handleApply = (need_confirm: boolean = false) => {
+    if (currentData.value == null) {
+        return
+    }
+
+    if (need_confirm) {
+        ElMessageBox.confirm('接受数据变更会更新当前的对话框数据,但是不会对原始数据产生影响。若需保存变更,请点击‘更新图谱标准’按钮', '提示', {}).then(() => {
+            handleApplyDataChange()
+        }).catch(() => { })
+    } else {
+        handleApplyDataChange()
+    }
+
+}
+const handleSaveSchema = (schemaType: string, id: any) => {
+    console.log("handleSaveEntity")
+    if (currentData.value == null) {
+        return
+    }
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id) {
+
+            if (targetData[i].name == "") {
+                ElMessage({ type: 'warning', message: '名称不能为空' })
+                return
+            }
+            if (targetData[i].category == "") {
+                ElMessage({ type: 'warning', message: '类型不能为空' })
+                return
+            }
+            if (targetData[i].action == "create") {
+                targetData[i].action = "create"
+                targetData[i].props = []
+            } else {
+                targetData[i].action = "update"
+            }
+            targetData[i].is_new = false
+
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    if (targetData[i].props[j].name == "") {
+                        ElMessage({ type: 'warning', message: '名称不能为空' })
+                        return
+                    }
+                    if (targetData[i].props[j].category == "") {
+                        ElMessage({ type: 'warning', message: '类型不能为空' })
+                        return
+                    }
+                    if (targetData[i].props[j].action == "create") {
+                        targetData[i].props[j].action = "create"
+                    } else {
+                        targetData[i].props[j].action = "update"
+                    }
+                    targetData[i].is_new = false
+                }
+            }
+        }
+    }
+    handleEditSchema(schemaType, id, false)
+}
+const handleCancelEditSchema = (schemaType: string, id: any) => {
+    console.log("handleCancelEditEntity")
+    if (currentData.value == null) {
+        return
+    }
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id && targetData[i].is_new == true) {
+            targetData.splice(i, 1)
+            break
+        }
+        if (targetData[i].id == id) {
+            targetData[i].name = targetData[i].old_name
+            targetData[i].category = targetData[i].old_category
+            handleEditSchema(schemaType, id, false)
+            break
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    targetData[i].props[j].name = targetData[i].props[j].old_name
+                    targetData[i].props[j].category = targetData[i].props[j].old_category
+                    handleEditSchema(schemaType, id, false)
+                    break
+                }
+            }
+        }
+    }
+}
+const handleCancelEditSchemaAll = (schemaType: string) => {
+    if (currentData.value == null) {
+        return
+    }
+
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].editable == true) {
+            targetData[i].editable = false
+            if (targetData[i].is_new) {
+                targetData.splice(i, 1)
+            }
+            break
+        }
+
+        for (var j = 0; j < targetData[i].props.length; j++) {
+            if (targetData[i].props[j].editable == true) {
+                targetData[i].props[j].editable = false
+                if (targetData[i].props[j].is_new) {
+                    targetData[i].props.splice(j, 1)
+                }
+                break
+            }
+        }
+    }
+}
+const handleEditSchema = (schemaType: string, id: any, newValue: boolean) => {
+    console.log("handleEditEntity")
+    if (currentData.value == null) {
+        return
+    }
+    handleCancelEditSchemaAll(schemaType)
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id) {
+            targetData[i].editable = newValue
+            if (newValue == true) {
+                targetData[i].old_name = targetData[i].name
+                targetData[i].old_category = targetData[i].category
+            }
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    targetData[i].props[j].editable = newValue
+                    if (newValue == true) {
+                        targetData[i].props[j].old_name = targetData[i].props[j].name
+                        targetData[i].props[j].old_category = targetData[i].props[j].category
+                    }
+                } else {
+                    targetData[i].props[j].editable = false
+                }
+            }
+
+            targetData[i].editable = false
+        }
+    }
+}
+const handleDeleteSchema = (schemaType: string, id: any, confirm: boolean = true) => {
+    if (currentData.value == null) return
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    if (confirm == false) {
+        for (var i = 0; i < targetData.length; i++) {
+            if (targetData[i].id == id) {
+                targetData[i].action = "delete"
+                break
+            }
+        }
+        return
+    }
+    ElMessageBox.confirm('是否删除该实体类型?', '提示', {}).then(() => {
+        for (var i = 0; i < targetData.length; i++) {
+            if (targetData[i].id == id) {
+                targetData[i].action = "delete"
+                for (var j = 0; j < targetData[i].props.length; j++) {
+                    targetData[i].props[j].action = "delete"
+                }
+                break
+            } else {
+                for (var j = 0; j < targetData[i].props.length; j++) {
+                    if (targetData[i].props[j].id == id) {
+                        targetData[i].props[j].action = "delete"
+                        break
+                    }
+                }
+            }
+        }
+        ElMessage({ type: 'success', message: '删除成功' })
+
+    })
+}
+
+const handleUpdateStdSchema = () => {
+    if (currentData.value == null) return
+
+
+    ElMessageBox.confirm('请注意,此操作将更新图谱标准数据,引用该标准的图谱数据不会被自动更新', '提示', {}).then(() => {
+        var content = JSON.stringify(currentData.value);
+        updateStdSchemaContent({ schema_id: currentSchemaId.value, content: content }).then(response => {
+            ElNotification({
+                title: 'Success',
+                message: '更新图谱标准成功',
+                type: 'success',
+            })
+            emits('stdUpdated')
+            // reindexSchema({schema_id: currentData.value.data.id}).then(response => {
+            //     ElNotification({
+            //     title: 'Success',
+            //     message: '更新图谱索引成功',
+            //     type:'success', 
+            //     }) 
+            // })
+        })
+
+        dialogFormVisible.value = false
+    })
+}
+// function handleCurrentEntityChange(data:any) {
+//     if (data == null) {
+//         return
+//     }
+//     currentDialogEntity.value = data
+// }
+
+// function generateIdForData(){
+//     var id = currentId.value
+//     for (var i=0;i<currentData.value.data.entities.length;i++) {
+//         currentData.value.data.entities[i].id=`${id}`
+//         currentData.value.data.entities[i].new_name= ""
+//         currentData.value.data.entities[i].new_category= ""
+//         currentData.value.data.entities[i].editable = false
+//         var subId =0
+//         for (var j=0;j<currentData.value.data.entities[i].props.length;j++) {
+//             currentData.value.data.entities[i].props[j].id=`${id}-${subId}`
+//             currentData.value.data.entities[i].props[j].new_name=""
+//             currentData.value.data.entities[i].props[j].new_category=""
+//             currentData.value.data.entities[i].props[j].editable=false
+//             subId++ 
+//         }
+//         id ++ 
+//     }
+//     for (var i=0;i<currentData.value.data.relations.length;i++) {
+//         currentData.value.data.relations[i].id=id 
+//         id++
+//     }
+//     currentId.value = id
+// }
+function handleCurrentRelationChange(data: any) {
+    if (data == null) {
+        return
+    }
+    currentDialogRelation.value = data
+}
+function showDialog(visible: boolean = true, schemaData: any, id: number) {
+    var data = schemaData
+    console.log("schemaData", schemaData)
+    currentSchemaId.value = id
+    console.log("showDialog", data)
+    dialogFormVisible.value = visible
+    currentData.value = { entities: [], relations: [] }
+
+
+    for (var i = 0; i < data.entities.length; i++) {
+        var entity = { ...data.entities[i], editable: false, is_new: false, action: "", props: [] }
+        if ("props" in data.entities[i] == false) {
+            data.entities[i].props = []
+        }
+        for (var j = 0; j < data.entities[i].props.length; j++) {
+            entity.props.push({ ...data.entities[i].props[j], editable: false, is_new: false, action: "", })
+        }
+        currentData.value.entities.push(entity)
+    }
+    for (var i = 0; i < data.relations.length; i++) {
+        var entity = { ...data.relations[i], editable: false, is_new: false, action: "", props: [] }
+        if ("props" in data.relations[i] == false) {
+            data.relations[i].props = []
+        }
+        for (var j = 0; j < data.relations[i].props.length; j++) {
+            entity.props.push({ ...data.relations[i].props[j], editable: false, is_new: false, action: "", })
+        }
+        currentData.value.relations.push(entity)
+    }
+    //generateIdForData()
+
+}
+defineExpose({ showDialog })
+</script>

+ 0 - 1
src/dialogs/OCRDialog.vue

@@ -307,7 +307,6 @@ async function handleAllImport() {
   } catch (e) {
     console.log(e)
   }
-
 }
 
 function fetchFile(fileName: string, fileUrl: string) {

+ 21 - 0
src/router/index.ts

@@ -129,6 +129,14 @@ const router = createRouter({
                     auth: true,
                   },
                 },
+                {
+                  path: 'job/:id',
+                  name: 'job-view',
+                  component: () => import('@/views/JobView.vue'),
+                  meta: {
+                    auth: true,
+                  },
+                },
                 {
                   path: "worker",
                   name: "worker",
@@ -145,6 +153,11 @@ const router = createRouter({
                     auth: true,
                   },
                 },
+                {
+                  path: 'graph-std-schemas',
+                  name: 'graph-std-schemas',
+                  component: () => import('@/views/GraphStdSchemas.vue'),
+                },
                 {
                   path: "graph-mgr/:id",
                   name: "graph-mgr",
@@ -163,6 +176,14 @@ const router = createRouter({
                 auth: true,
               },
             },
+            {
+              path: 'config',
+              name: 'config',
+              component: () => import('@/views/ConfigurationView.vue'),
+              meta: {
+                auth: true,
+              },
+            },
           ]
         },
         {

+ 63 - 0
src/types/dataset.ts

@@ -0,0 +1,63 @@
+import type { ListCache } from "element-plus";
+
+type SchemaDataItem = {
+  id: string;
+  name: string;
+  category: string; 
+  old_name: string;
+  old_category: string;
+  action: string;
+  editable: boolean;
+  is_new: boolean;
+  props: SchemaDataItem[];
+}
+type SchemaData = {
+  entities: SchemaDataItem[];
+  relations: SchemaDataItem[];
+
+}
+
+type ViewSchemaData = {
+  id: number
+  name: string
+  category: string
+  content: string
+  data: SchemaData
+}
+
+type ViewCategoryData = {
+ name: string
+ std_name:string
+ type:string 
+}
+
+type NodesData = {
+  id: number
+  name: string
+  category: string
+}
+
+type EdgeData = {
+  id: number
+  name: string
+  category: string
+  src_id: number
+  dest_id:number
+}
+
+type ContradictionData = {
+  count: number
+  edges: EdgeData[]
+  src_id: number
+  src_name: string
+  src_category: string
+  dest_id: number
+  dest_name: string
+  dest_category: string
+}
+export {type SchemaData, 
+  type ViewSchemaData, 
+  type ViewCategoryData,
+  type NodesData,
+  type EdgeData,
+  type ContradictionData,}

+ 14 - 8
src/utils/misc.ts

@@ -1,13 +1,19 @@
 
 import { format } from 'date-fns';
-export function formatDate (dateString:string) :string {
-    if (!dateString) {
-      return ''; // 或者你可以返回一个默认值,如 'N/A' 
-    }
-    return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); // 可以根据需要调整格式字符串
-  };
+export function formatDate(dateString: string): string {
+  if (!dateString) {
+    return ''; // 或者你可以返回一个默认值,如 'N/A' 
+  }
+  return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); // 可以根据需要调整格式字符串
+};
 
+export function formatOverflowText(text: string, len: number): string {
+  if (text.length <= len) {
+    return text;
+  }
+  return text.slice(0, len - 1) + "...";
+}
 
-export function getRequestId():string {
-    return Math.floor(Date.now()).toString(); // 除以 1000 以获取秒级时间戳 
+export function getRequestId(): string {
+  return Math.floor(Date.now()).toString(); // 除以 1000 以获取秒级时间戳 
 }

+ 69 - 63
src/utils/request.ts

@@ -1,13 +1,13 @@
 import axios from "axios";
 import { toFormData } from '@/utils/app'
-import type { AxiosInstance, AxiosRequestConfig, AxiosResponse,InternalAxiosRequestConfig } from "axios";
-import { getSessionVar,deleteSessionVar } from "@/utils/session";
+import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
+import { getSessionVar, deleteSessionVar } from "@/utils/session";
 import { useRouter } from 'vue-router'
-export const baseURL:string = import.meta.env.VITE_BASE_URL
 
+export const baseURL: string = import.meta.env.VITE_BASE_URL
 export const timeout = 80000
 
-export const statusDesc:{[value: number]:string} = {
+export const statusDesc: { [value: number]: string } = {
   0: "SUCCESS",
   400: "请求错误(400)",
   401: "未授权,请重新登录(401)",
@@ -31,85 +31,91 @@ export type ResponseResult<T> = {
 };
 
 declare module 'axios' {
-  export interface AxiosRequestConfig{
-    closeLoading?:boolean,//默认所有请求Loading,可关闭
-    token?:string,//默认获取本地token,可针对某个请求写死或置空
-    isFormRequest?:boolean,//将请求自动转换为表单请求
-    closeInstance?:boolean
+  export interface AxiosRequestConfig {
+    closeLoading?: boolean,//默认所有请求Loading,可关闭
+    token?: string,//默认获取本地token,可针对某个请求写死或置空
+    isFormRequest?: boolean,//将请求自动转换为表单请求
+    closeInstance?: boolean
   }
 }
 
 export type RequestConfig = Omit<AxiosRequestConfig, 'closeInstance' | 'transformRequest'>
 
+export class AsyncRequest {
+  instance: AxiosInstance;
+  constructor() {
+    this.instance = axios.create({ baseURL, timeout })
+  }
+}
 export class Request {
   instance: AxiosInstance;
   constructor(config: AxiosRequestConfig) {
     this.instance = axios.create(config);
     this.instance.interceptors.request.use(
-        (config:InternalAxiosRequestConfig) => {          
-          if ("token" in config){
-            config.headers.Authorization = config.token
-          }else {
-            if (getSessionVar('session_id') != null && getSessionVar('username') != null){
-              config.headers.Authorization = 'Beaver ' + getSessionVar('username') + ' ' + getSessionVar('session_id');
-            }
-            
-            //config.headers.Authorization = getToken()
+      (config: InternalAxiosRequestConfig) => {
+        if ("token" in config) {
+          config.headers.Authorization = config.token
+        } else {
+          if (getSessionVar('session_id') != null && getSessionVar('username') != null) {
+            config.headers.Authorization = 'Beaver ' + getSessionVar('username') + ' ' + getSessionVar('session_id');
           }
-          if (config.isFormRequest){config.transformRequest = toFormData}
-          if(!config.closeLoading){
-            //Loading
-          }
-          return config;
-        },
-        (err: any) => {
-          // 请求错误,这里可以用全局提示框进行提示
-          return Promise.reject(err);
+
+          //config.headers.Authorization = getToken()
+        }
+        if (config.isFormRequest) { config.transformRequest = toFormData }
+        if (!config.closeLoading) {
+          //Loading
         }
+        return config;
+      },
+      (err: any) => {
+        // 请求错误,这里可以用全局提示框进行提示
+        return Promise.reject(err);
+      }
     );
     this.instance.interceptors.response.use(
-        (res: AxiosResponse) => {
-          // 直接返回res,当然你也可以只返回res.data
-          // 系统如果有自定义code也可以在这里处理
-          return res.data;
-        },
-        (err: any) => {
-          
-          let message = "";
-          if (statusDesc[err.response.status]){
-            message = statusDesc[err.response.status]
-          }else {
-            message = `连接出错(${err.response.status})!`
-          }
-          // 这里错误消息可以使用全局弹框展示出来
-          // 比如element plus 可以使用 ElMessage
-          // ElMessage({
-          //   showClose: true,
-          //   message: `${message},请检查网络或联系管理员!`,
-          //   type: "error",
-          // });
-          // 这里是AxiosError类型,所以一般我们只reject我们需要的响应即可
-          if (err.response.status == 401){
-            const router = useRouter();
-           
-            deleteSessionVar("session_id")
-            deleteSessionVar("user_id")
-            deleteSessionVar("username")
-            deleteSessionVar("full_name")
-            router.replace({path:"/"});
-           
-            return [];
-          }
-          console.log(message)
-          return Promise.reject(err.response);
+      (res: AxiosResponse) => {
+        // 直接返回res,当然你也可以只返回res.data
+        // 系统如果有自定义code也可以在这里处理
+        return res.data;
+      },
+      (err: any) => {
+
+        let message = "";
+        if (statusDesc[err.response.status]) {
+          message = statusDesc[err.response.status]
+        } else {
+          message = `连接出错(${err.response.status})!`
+        }
+        // 这里错误消息可以使用全局弹框展示出来
+        // 比如element plus 可以使用 ElMessage
+        // ElMessage({
+        //   showClose: true,
+        //   message: `${message},请检查网络或联系管理员!`,
+        //   type: "error",
+        // });
+        // 这里是AxiosError类型,所以一般我们只reject我们需要的响应即可
+        if (err.response.status == 401) {
+          const router = useRouter();
+
+          deleteSessionVar("session_id")
+          deleteSessionVar("user_id")
+          deleteSessionVar("username")
+          deleteSessionVar("full_name")
+          document.location.href = "/"
+
+          return [];
         }
+        console.log(message)
+        return Promise.reject(err.response);
+      }
     );
   }
 
   //未拦截请求,响应原封不动返回
 
   unhandledRequest<T>(config: RequestConfig): Promise<AxiosResponse<ResponseResult<T>>> {
-    return this.instance.request({...config,closeInstance:true});
+    return this.instance.request({ ...config, closeInstance: true });
   }
   //做了拦截处理,自动报错,只返回关心的数据
   request<T>(config: RequestConfig): Promise<T> {
@@ -117,7 +123,7 @@ export class Request {
   }
 }
 
-export  default new Request({baseURL,timeout})
+export default new Request({ baseURL, timeout })
 
 
 

+ 17 - 44
src/views/AppCopy.vue

@@ -1,6 +1,6 @@
 <template>
 
-  <el-container class="main-container">
+  <div class="main-container">
     <el-container>
       <el-aside class="main-aside">
         <SideMenu @selectQueue="onSelectQueue" @selectMenu="onSelectMenu"></SideMenu>
@@ -12,7 +12,7 @@
         </router-view>
       </el-main>
     </el-container>
-  </el-container>
+  </div>
 </template>
 <script setup lang="ts">
 import { onMounted, ref, watch, watchEffect } from "vue";
@@ -33,59 +33,32 @@ const onSelectQueue = (idData: any) => {
   router.push({ name: 'queue', params: { id: idData.id } });
 };
 const onSelectMenu = (menuData: any) => {
-  if (menuData.name == "graph") {
-    router.push({ name: 'graph' });
-  }
+  router.push({ name: menuData.name });
+  // if (menuData.name == "graph") {
+  //   router.push({ name: 'graph' });
+  // }
 };
 
 </script>
-<style scoped>
-#app {
-  width: 100vw;
-  height: 100vh;
-  overflow: auto;
-}
-
+<style scoped lang="less">
 .main-container {
   width: 100%;
-  height: 100%;
-  overflow: hidden;
-  box-sizing: margin-box;
-  padding: 0px;
-  margin: 0px;
-  min-width: 0px;
-}
+  // height: calc(100vh - 150px);
 
-.login-box {
-  width: 100%;
-  height: 100%;
-  background-image: url("@/assets/images/loginBanar.png");
-  background-repeat: no-repeat;
-  background-size: cover;
-  box-sizing: margin-box;
+  &>.el-container {
+    // width: 100%;
+    // height: 100%;
+    padding: 0px;
+    margin: 0px;
+    min-width: 0px;
+  }
 }
 
-.login-container {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  margin: auto;
-  width: 500px;
-  /* width: 100vw; */
-  /* height: 100vh; */
-  padding: 50px;
-  background-color: #eef6ff;
-  border-radius: 12px 12px 12px 12px;
-}
 
-.login-header {
-  font-size: 26px;
-  font-weight: bold;
-  margin-bottom: 20px;
-}
+
 
 .content-area {
+  // height: 100px;
   padding: 20px;
   width: calc(100% - 300px);
   overflow: auto;

+ 245 - 0
src/views/ConfigurationView.vue

@@ -0,0 +1,245 @@
+<template>
+    <el-row>
+        <h2>配置</h2>
+    </el-row>
+    <el-row style="margin:15px; ">
+        <el-tabs v-model="activeName" style="width: 100%;">
+            <el-tab-pane label="基本配置" name="basic"
+                style="width: 100%;height: 800px; overflow-y:auto; display: flex; flex-direction: row;">
+                <el-form :model="basicConfig" label-width="120px" style="margin: 5px;">
+                    <el-form-item label="名称" prop="name" style="height: 25px;">
+                        <el-input v-model="basicConfig.name"></el-input>
+                    </el-form-item>
+                    <el-form-item label="联系人" prop="contactor" style="height: 25px;">
+                        <el-input v-model="basicConfig.contactor"></el-input>
+                    </el-form-item>
+                    <el-form-item label="联系电话" prop="contact_phone" style="height: 25px;">
+                        <el-input v-model="basicConfig.contact_phone"></el-input>
+                    </el-form-item>
+                    <el-form-item label="启用日期" prop="system_uptime" style="height: 25px;">
+                        <el-date-picker v-model="basicConfig.system_uptime" type="date" placeholder="选择日期">
+                        </el-date-picker>
+                    </el-form-item>
+                    <el-form-item label="结束日期" prop="system_downtime" style="height: 25px;">
+                        <el-date-picker v-model="basicConfig.system_downtime" type="date" placeholder="选择日期">
+                        </el-date-picker>
+                    </el-form-item>
+                </el-form>
+            </el-tab-pane>
+            <el-tab-pane label="队列配置" name="first"
+                style="width: 100%;height: 800px; overflow-y:auto; display: flex; flex-direction: row;">
+                <div v-for="(queue) in queueData" :key="queue.id" style="margin-right:8px;width:350px; ">
+                    <span style="font-weight: bold;"> {{ queue.queue_label }}</span> <span style="font-size: 8pt;">({{
+                        queue.queue_category }}_{{ queue.queue_name }})</span><br />
+                    <br />
+                    <div style="border: solid 1px #CDCDCD;padding:8px;">
+                        当成功时,转到 {{ queue.queue_config?.success.queue_category }}_{{
+                            queue.queue_config?.success.queue_name }}<br />
+                        当失败时,转到 {{ queue.queue_config?.failed.queue_category }}_{{ queue.queue_config?.failed.queue_name
+                        }}<br />
+                        当错误时,转到 {{ queue.queue_config?.error.queue_category }}_{{ queue.queue_config?.error.queue_name
+                        }}<br />
+                        最大实例数:<el-select v-model="queue.queue_config.max_instance" style="width:50px;margin-right:5px;">
+                            <el-option v-for="item in [1, 2, 3, 4, 5]" :key="item" :label="item"
+                                :value="item"></el-option>
+                        </el-select>
+                        <div style="border-top: solid 1px #CDCDCD; margin-top:5px; display:grid;justify-items: right;">
+                            <el-button @click="handleSaveConfig(queue)"
+                                style=" width:80px; margin-top:3px;">保存</el-button>
+                        </div>
+                    </div>
+
+
+                </div>
+            </el-tab-pane>
+            <el-tab-pane label="大模型配置" name="second">
+
+                <el-button type="text" @click="handleSaveLLMConfig" style="">保存</el-button>
+                <div style="width: 100%;display: flex; flex-direction: row; flex-wrap:wrap;">
+                    <div
+                        style="width: 300px;height:210px;border: 1px solid #CDCDCD; display: flex; flex-direction: column;justify-content:center;margin-right: 8px; margin-bottom: 8px;">
+                        <el-button type="text" @click="handleAddLLMConfig">添加</el-button>
+                    </div>
+                    <template v-for="(item) in llmConfig.items" :key="item.id">
+                        <div
+                            style="width: 300px;height:210px;border: 1px solid #CDCDCD; display: flex; flex-direction: column;justify-content:center;margin-right: 8px; margin-bottom: 8px;">
+                            <div v-if="item.is_default"
+                                style="position: relative; width: 15px; height:15px;left: 0px; top:15px; background-color: darkgreen;">
+                                <span style="height:15px;">&nbsp;</span>
+                            </div>
+                            <el-form :model="item" label-width="80px" style="margin: 5px;">
+                                <el-form-item label="类型" prop="api_name" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">{{ item.api_name }}</span>
+                                    <el-select v-else v-model="item.api_name" style="width: 150px;">
+                                        <el-option v-for="option in ['openai', 'deepseek v3', 'deepseek r1']"
+                                            :key="option" :label="option" :value="option"></el-option>
+                                    </el-select>
+                                </el-form-item>
+                                <el-form-item lable="模型" prop="model_name" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">{{ item.model_name }}</span>
+                                    <el-input v-else v-model="item.model_name"></el-input>
+                                </el-form-item>
+                                <el-form-item label="API 地址" prop="api_host" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false" style="overflow: hidden;">{{
+                                        formatOverflowText(item.api_host, 32) }}</span>
+                                    <el-input v-else v-model="item.api_host"></el-input>
+                                </el-form-item>
+                                <el-form-item label="API Key" prop="api_key" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">********</span>
+                                    <el-input v-else v-model="item.api_key"></el-input>
+                                </el-form-item>
+                                <el-form-item style="display:grid;justify-items: right; height:25px;">
+                                    <el-button v-if="!item.is_default" type="text"
+                                        @click="handleSetLLMConfigDefault(item)" style=" ">设为默认</el-button>
+                                    <el-button type="text" @click="handleDeleteLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == false" style=" ">删除</el-button>
+                                    <el-button type="text" @click="handleEditLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == false" style=" ">编辑</el-button>
+                                    <el-button type="text" @click="handleCancelEditLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == true" style=" ">取消</el-button>
+
+                                </el-form-item>
+                            </el-form>
+                        </div>
+                    </template>
+                </div>
+            </el-tab-pane>
+            <!-- <el-tab-pane label="许可证" name="third">
+        
+        第一步:通过hash数值查询许可证信息<br/>
+        第二步:导入许可证文件
+        <el-image style="width: 300px; height: 300px" :src="'/api/agent/qrcode/somm'" wid></el-image>
+    </el-tab-pane> -->
+        </el-tabs>
+    </el-row>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, watch } from 'vue'
+import { getConfig, setConfig, getQueues } from '@/api/AgentApi'
+import { ElMessage } from 'element-plus'
+import { formatOverflowText } from '@/utils/misc'
+import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults.mjs'
+interface ViewBasicInfo {
+    name: string,
+    contactor: string,
+    contact_phone: string,
+    system_uptime: Date,
+    system_downtime: Date
+}
+interface ActionQueue {
+    queue_category: string
+    queue_name: string
+}
+interface QueueConfig {
+    command: string
+    script: string
+    args: string[]
+    success: ActionQueue
+    failed: ActionQueue
+    error: ActionQueue
+    max_instance: number
+}
+interface QueueData {
+    id: number
+    queue_category: string
+    queue_name: string
+    queue_label: string
+    queue_config: QueueConfig
+}
+interface ConfigData {
+    id: number
+    code: string
+    name: string
+    content: string
+    status: number
+}
+interface LLMConfigData {
+    current_api_name: string
+    items: LLMConfigItemData[]
+}
+interface LLMConfigItemData {
+    api_key: string
+    api_host: string
+    api_name: string
+    model_name: string
+    is_default: boolean
+}
+const basicConfig = ref<ViewBasicInfo>({} as ViewBasicInfo)
+const activeName = ref('first')
+const queueData = ref<QueueData[]>([])
+const llmConfig = ref<LLMConfigData>({ current_api_name: "", items: [] })
+const llmConfigEditFlag = ref<Map<LLMConfigItemData, boolean>>(new Map())
+function loadQueueConfigData() {
+    queueData.value.forEach((queue) => {
+        var queue_code = queue.queue_category + "_" + queue.queue_name
+        getConfig({ code: queue_code }).then((res) => {
+            var config: ConfigData = res.records[0] as ConfigData
+            var config_data: QueueConfig = JSON.parse(config.content) as QueueConfig
+            queue.queue_config = config_data
+            if (queue.queue_config != null) {
+                if (queue.queue_config.max_instance === undefined) {
+                    queue.queue_config.max_instance = 1
+                }
+            }
+        })
+    })
+}
+function loadLLMConfigData() {
+    getConfig({ code: "LLM_CONFIG" }).then((res) => {
+        var config: ConfigData = res.records[0] as ConfigData
+        var config_data: LLMConfigData = JSON.parse(config.content) as LLMConfigData
+        llmConfig.value = config_data
+    })
+}
+function handleSaveLLMConfig() {
+    llmConfig.value.items.forEach((item) => {
+        if (item.is_default) {
+            llmConfig.value.current_api_name = item.api_name
+        }
+    })
+    var config: ConfigData = { id: 0, code: "LLM_CONFIG", name: "大模型配置", content: JSON.stringify(llmConfig.value), status: 1 }
+    setConfig(config).then((res) => {
+        if (res.code == 200) {
+            ElMessage.success("保存成功")
+        }
+    })
+
+}
+function handleSetLLMConfigDefault(item: LLMConfigItemData) {
+    llmConfig.value.items.forEach((item) => {
+        item.is_default = false
+    })
+    item.is_default = true
+}
+function handleDeleteLLMConfig(item: LLMConfigItemData) {
+    var index = llmConfig.value.items.indexOf(item)
+    llmConfig.value.items.splice(index, 1)
+}
+function getLLMConfigEditFlag(item: LLMConfigItemData) {
+    return llmConfigEditFlag.value.get(item) == true ? true : false
+}
+function handleEditLLMConfig(item: LLMConfigItemData) {
+    llmConfigEditFlag.value.set(item, true)
+}
+function handleCancelEditLLMConfig(item: LLMConfigItemData) {
+    llmConfigEditFlag.value.set(item, false)
+}
+function handleAddLLMConfig() {
+    var item: LLMConfigItemData = { api_key: "", api_host: "", api_name: "", is_default: false }
+    item.api_name = "openai"
+
+    llmConfig.value.items.push(item)
+    llmConfigEditFlag.value.set(item, true)
+}
+function loadQueues() {
+    getQueues().then((res) => {
+        queueData.value = res.records as QueueData[]
+        loadQueueConfigData()
+    })
+}
+onMounted(() => {
+    loadQueues()
+    loadLLMConfigData()
+})
+</script>
+<style lang="css" scoped></style>

+ 120 - 0
src/views/GraphStdSchemas.vue

@@ -0,0 +1,120 @@
+<template>
+
+    <GraphSchemaDialog ref="graphSchemaDialog" @std-updated="handleStdSchemaUpdated" />
+    <el-row>
+        <span style="text-align: right;">
+            <h2>图谱标准</h2>
+        </span>
+    </el-row>
+    <el-row style="margin:15px;">
+        <el-button @click="handleRefreshTable">刷新</el-button>
+    </el-row>
+
+    <el-row>
+        <el-table :data="pageData.records" highlight-current-row>
+            <el-table-column prop="schema_system" label="标准机构" width="100"></el-table-column>
+            <el-table-column prop="schema_type" label="标准类型" width="100"></el-table-column>
+            <el-table-column prop="category" label="分类" width="100"></el-table-column>
+            <el-table-column prop="name" label="名称" width="200"></el-table-column>
+
+            <el-table-column label="操作" width="500">
+                <template v-slot="scope">
+                    <el-button type="primary" @click="handleShowStdSchema(scope.row)">查看</el-button>
+                    <el-button type="primary" @click="handleIndexSchema(scope.row)">索引</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+
+    </el-row>
+    <el-row style="margin-top:30px;">
+        <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
+            @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
+        </el-pagination>
+    </el-row>
+
+</template>
+
+
+<script setup lang="ts">
+
+import { ref, onMounted, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+import GraphSchemaDialog from '@/dialogs/GraphSchemaDialog.vue'
+import { ElNotification, ElMessageBox } from 'element-plus'
+import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
+import { formatDate } from '@/utils/misc'
+import { getSessionVar, saveSessionVar } from '@/utils/session'
+import { getGraphStdSchemas, reindexSchema } from '@/api/GraphApi'
+//从路由参数中获取队列ID
+
+const sessionId = ref("")
+//dialog related
+
+const graphSchemaDialog = ref()
+const currentData = ref({})
+const currentDialogEntity = ref({})
+const entitiesEditable = ref({})
+
+const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
+
+const route = useRoute()
+const router = useRouter()
+
+const loadTableData = () => {
+    getGraphStdSchemas({ page: pageData.value.page, page_size: pageData.value.page_size }).then((res: any) => {
+        pageData.value.records = res.records
+    })
+}
+
+function handleStdSchemaUpdated() {
+    console.log("handleStdSchemaUpdated")
+
+}
+function handleShowStdSchema(data: any) {
+    // console.log("handleShowStdSchema", data)
+    data['data'] = JSON.parse(data["content"])
+    graphSchemaDialog.value.showDialog(true, data['data'], data.id)
+}
+
+
+const handleIndexSchema = (data: any) => {
+    console.log(data)
+    ElMessageBox.confirm('是否确定索引该标准?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+    }).then(() => {
+        // 用户点击了确定按钮,执行相应的操作
+        console.log('用户点击了确定按钮');
+        reindexSchema({ schema_id: data.id }).then((res: any) => {
+            var result = `总计${res.records[0]["entities_count"]}个实体,${res.records[0]["relations_count"]}个关系`
+            ElNotification({
+                title: 'Success',
+                message: '索引成功,' + result,
+                type: 'success',
+                duration: 3000, // 显示时间(单位:毫秒),0 表示不自动关闭  }) 
+            })
+        })
+    })
+}
+const handleCurrentPageChange = (val: number) => {
+    pageData.value.page = val
+    loadTableData()
+}
+const handleRefreshTable = () => {
+    loadTableData()
+}
+onMounted(() => {
+
+    loadTableData()
+})
+</script>
+
+<style lang="css" scoped>
+.prop_header {
+    font-weight: bold;
+    margin-top: 10px;
+    margin-bottom: 5px;
+}
+</style>

+ 269 - 0
src/views/JobView.vue

@@ -0,0 +1,269 @@
+<template>
+
+    <TextViewDialog ref="textViewDialog" :title="'查看文件'" :content="textViewData"></TextViewDialog>        
+
+    <el-row>
+        <el-page-header @back="handleGoBack" :content="jobData?.job_name" title="返回">
+        </el-page-header>
+        <el-divider></el-divider>
+    </el-row>    
+    <el-row>
+        <el-col :span="20">
+        <el-tabs type="border-card" v-model="activeTab">
+            <el-tab-pane label="基本信息" name="basic_info">
+                <el-row>
+                    <el-col :span="6">
+                        <div class="prop_header">任务ID</div>
+                    </el-col>
+                    <el-col :span="6"><div>{{ jobData?.id }}</div></el-col>
+                    <el-col :span="6"><div class="prop_header">任务类型</div></el-col>
+                    <el-col :span="6">
+                        <div>{{ getJobCategory(jobData?.start_category) }}</div>
+                    </el-col>
+                </el-row>
+                <el-row>
+                    <el-col :span="6">
+                        <div class="prop_header">当前工序</div>
+                    </el-col>
+                    <el-col :span="6"><div>{{ getJobCategory(jobData?.job_category) }}</div></el-col>
+                    <el-col :span="6"><div class="prop_header">任务名称</div></el-col>
+                    <el-col :span="6">
+                        <el-tooltip class="item" effect="dark" :content="jobData?.job_name" placement="top-end">                            
+                            <div>{{ formatFilename(jobData?.job_name,20) }}</div>
+                        </el-tooltip>
+                    </el-col>
+                </el-row>
+                <el-row>
+                    <el-col :span="6"><div class="prop_header">任务状态</div></el-col>
+                    <el-col :span="6">
+                        <div>{{ getJobStatus(jobData?.status) }}</div>
+                    </el-col>
+                    <el-col :span="6"><div class="prop_header">创建人</div></el-col>
+                    <el-col :span="6">
+                        <div>{{ jobData?.job_creator }}</div>
+                    </el-col>
+                </el-row>
+                <el-row>
+                    <el-col :span="6">
+                        <div class="prop_header">创建时间</div>
+                    </el-col>
+                    <el-col :span="6"><div>{{ jobData?.created }}</div></el-col>
+                    <el-col :span="6"><div class="prop_header">更新时间</div></el-col>
+                    <el-col :span="6"><div>{{ jobData?.updated }}</div>
+                    </el-col>
+                </el-row>
+                <el-row>
+                        <div class="prop_header" style="background-color: #EFEFEF; width: 100%;">日志</div><br/>
+                </el-row>
+                <el-row>
+                        <div style="white-space: pre-wrap; height:500px; width:100%; overflow-y: auto;">{{ jobData?.job_logs }}</div>
+
+                </el-row>
+
+
+            </el-tab-pane>
+            <el-tab-pane label="文件夹" name="file_browse">                
+                <el-row >
+                        <el-col :span="4" style="border-right: 1px solid #CDCDCD;">
+                            <el-tree :data="jobFileData" :props="jobFileTree" @node-click="handleTreeNodeClick"></el-tree>             
+                        </el-col>
+                        <el-col :span="20" style="display:flex;flex-direction: row; flex-wrap: wrap; height:500px; overflow: auto;">  
+                            <template v-for="item in jobFileInDir">
+                                <el-card shadow="hover" style="width: 200px; height: 120px; margin: 10px;">
+                                    <div class="card-header">
+                                    <span style="font-size:10px;">
+                                        <span v-if="item.type=='file'">文件</span>
+                                        <span v-if="item.type=='dir'">文件夹</span>
+                                    </span>
+                                    </div>
+                                    <div class="card-content">
+                                     <el-tooltip class="item" effect="dark" :content="item.name" placement="top-end">
+                                        <span style="font-size:12px;display: flex; flex-wrap: nowrap; height:25px;">{{ formatFilename(item.name, 20) }}</span>
+                                    </el-tooltip>
+                                    </div>
+                                
+                                    <div class="bottom clearfix">
+                                        
+                                        <a :href="'/api/file/download/'+jobData?.id+'?path='+encodeURIComponent(item.path)">下载</a>
+                                        <a  @click="handleViewFile(jobData, item.path)">查看</a>
+                                    </div>
+                                </el-card>
+
+                            </template>                  
+                            <!-- <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row style="width: 100%;">
+                                <el-table-column prop="type" label="类型" width="50">
+     
+                                </el-table-column>
+                                <el-table-column prop="name" label="名称" width="250">
+     
+                                </el-table-column>
+                                <el-table-column prop="size" label="操作" width="100">
+                                    <template v-slot="scope">
+                                        <a :href="'/api/file/download/'+jobData?.id+'?path='+encodeURIComponent(scope.row.path)">下载</a>
+                                        <a  @click="handleViewFile(jobData, scope.row.path)">查看</a>
+                                    </template>
+                                </el-table-column>
+                            </el-table> -->
+                        </el-col>
+                </el-row>
+            </el-tab-pane>
+        </el-tabs>
+        </el-col>
+        <el-col :span="4">
+        <el-menu @select="handleMenuSelect" :default-active="activeTab" mode="vertical" class="el-menu-vertical-demo" menu-trigger="click" >
+            <el-menu-item-group index="CHANGE_CATEGORY">
+                <template #title>工序变更</template>
+                <el-menu-item :index="jobData.start_category">开始文字提取</el-menu-item>
+                <el-menu-item index="SYSTEM_CHUNKS">开始文本切片</el-menu-item>
+                <el-menu-item index="SYSTEM_KB_EXTRACT">开始抽取知识</el-menu-item>
+                <el-menu-item index="SYSTEM_KB_BUILD">开始构建图谱</el-menu-item>
+            </el-menu-item-group>
+        </el-menu>
+        </el-col>
+        </el-row>
+</template>
+<script lang="ts" setup>
+import {  onMounted, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { getJob,browseJobFile,getJobStatus,getFileContent,putQueueJob,getJobCategory } from '@/api/AgentApi'
+import { formatDate } from '@/utils/misc'
+import TextViewDialog from '@/dialogs/TextViewDialog.vue'
+import type { JobData  } from '@/api/AgentApi'
+import { ElMessageBox, ElNotification } from 'element-plus'
+
+interface JobFile {
+    type:string
+    name:string
+    path:string
+}
+const router = useRouter()
+
+const jobId = ref<number>(0)
+const jobData = ref<JobData>({} as JobData)
+
+var textViewContent = "hello"
+const textViewData = ref(textViewContent)
+const textViewDialog = ref()
+const jobFileTree = ref({
+          children: 'children',
+          label: 'display_name'
+        })
+const jobFileData = ref([])
+const jobFileInDir = ref<JobFile[]>([])
+
+const activeTab = ref('basic_info')
+function formatFolderName(name: string){
+    let translate = new Map([
+        ["logs", "日志"], 
+        ["results", "结果"], 
+        ["chunks", "切片"], 
+        ["files", "文件"], 
+        ["ocr_output", "文本转换结果"], 
+        ["kb_extract", "知识抽取"], 
+        ["kb_build", "知识构建"],
+        ["upload", "文件上传"],
+        ["output", "输出"]]
+    ) 
+    if (translate.get(name) != undefined) {
+        return translate.get(name)
+    }
+    return name
+}
+function handleTreeNodeClick(data: any, node: any, event: any) {
+        if (data.type=='dir'){
+        browseJobFile({ job_id:jobData.value.id, path:data.name}).then((res) => {
+            jobFileInDir.value = res.records
+        })
+    }
+}
+function handleViewFile(job:JobData, path:string) {
+    const url = '/api/file/view/'+job?.id+'?path='+encodeURIComponent(path)
+    console.log(url)
+    getFileContent(url).then((res) => {
+        console.log(res)
+        textViewData.value = res.records[0].content
+        textViewDialog.value.showDialog(true) 
+    })
+}
+function handleGoBack() {
+    router.push("/workspace/queue/0")
+}
+function loadJobData() {
+    console.log(jobId.value)
+    getJob(jobId.value).then((res) => {
+        jobData.value = res.records[0] as JobData
+        
+        browseJobFile({ job_id:jobId.value, path:""}).then((res) => {        
+            for (let i = 0; i < res.records.length; i++) {
+                res.records[i]['label'] = res.records[i].name 
+                res.records[i]['children'] = []
+                res.records[i]['display_name'] = formatFolderName(res.records[i].name)
+            }
+            jobFileData.value = res.records
+
+        })
+    })
+}
+function formatFilename(name:string, length:number) {
+    if (name == undefined){
+        return ""
+    }
+    if (name.length > length) {
+        return name.substring(0, length/2) + "..." + name.substring(name.length - length/2)
+    } 
+    return name
+}
+function handleMenuSelect(key:string, keyPath:string){
+    var category = getJobCategory(keyPath)
+    ElMessageBox.confirm(
+        `确定要转移任务到队列 "${category}" 吗?`,
+        '提示' , 
+    ).then(() => {
+        let category = key.split("_")[0]
+        let name = key.split("_")[2] ?  key.split("_")[1] +'_' + key.split("_")[2] : key.split("_")[1]
+        handleJobChangeCategory(category, name)
+    })
+}
+function handleJobChangeCategory(category:string, name:string) {
+
+    var params = { job_id: jobId.value, queue_category: category, queue_name: name }
+    
+    putQueueJob(params).then((res:any) => {
+        ElNotification({
+            title: '提示',
+            message: '任务已转移到队列',
+            type: 'success',            
+            duration: 3000
+          }) 
+        loadJobData()
+        })
+
+}
+onMounted(() => {
+    var route = useRoute()
+    console.log(route.params)
+    var id = Number.parseInt(route.params.id  as string)
+    jobId.value = id
+    loadJobData()
+})
+</script>
+<style lang="css" scoped>
+    .prop_header {
+        font-weight: bold;
+        margin-bottom: 5px;
+    }
+    
+  .bottom {
+    margin-top: 15px;
+    line-height: 12px;
+  }
+  .clearfix:before,
+  .clearfix:after {
+      display: table;
+      content: "";
+  }
+  
+  .clearfix:after {
+      clear: both
+  }
+</style>

+ 251 - 98
src/views/QueueView.vue

@@ -1,25 +1,75 @@
 <template>
-    <OCRDialog ref="ocrDialog" :title="'OCR任务'" :queue_category="queueData.queue_category"
+    <OCRDialog ref="ocrDialog" :title="queueData.title" :queue_category="queueData.queue_category"
         :queue_name="queueData.queue_name" @success="jobCreated"></OCRDialog>
     <QueueSelectDialog ref="queueSelectDialog" :title="'选择队列'" @success="jobMoved" :queue_data="queueIdNameDict">
     </QueueSelectDialog>
     <TextViewDialog ref="textViewDialog" :title="'查看文件'" :content="textViewData"></TextViewDialog>
     <el-row>
         <span style="text-align: right;">
-            <h2>工作队列- {{ queueData.title }} </h2>
+            <h2>工作队列- {{ pageTitle }} </h2>
         </span>
     </el-row>
+
     <el-row style="margin:15px;">
-        <el-button v-if="allowCreateJob && operationPermissions['b-1']" type="primary"
-            @click="handleCreateJob">新建工作</el-button>
-        <el-button @click="handleRefreshTable">刷新</el-button>
-    </el-row>
+        <el-form :model="searchForm" :inline="true" label-position="right" :size="'medium'">
+            <el-form-item label="任务名称" prop="job_name" :label-width="70" style="margin-left:0px;margin-right:0px;">
+                <el-input v-model="searchForm.job_name" :maxlength="32" :size="30"></el-input>
+            </el-form-item>
+            <el-form-item label="任务类型" prop="start_category" :label-width="70"
+                style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.start_category" placeholder="请选择任务类型" style="width:100px;">
+                    <el-option v-for="item in jobStartCategoryList" :key="item.id" :label="item.label"
+                        :value="item.id"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="工序" prop="job_category" :label-width="50" style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.job_category" placeholder="请选择任务类型" style="width:120px;margin:0px;">
+                    <el-option v-for="item in jobCategoryList" :key="item.id" :label="item.label"
+                        :value="item.id"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="状态" prop="status" :label-width="50" style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.status" placeholder="请选择状态" style="width:120px;">
+                    <el-option v-for="item in jobStatusList" :key="item.value" :label="item.label"
+                        :value="item.value"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item style="justify-items: right;margin-left:10px;">
+                <el-button type="primary" @click="handleSearch">搜索</el-button>
+                <el-button type="primary" @click="handleSearchReset" style="margin-left:5px;">重置</el-button>
+                <el-dropdown style="margin-left:5px;">
+                    <el-button type="primary">
+                        新建<i class="el-icon-arrow-down el-icon--right"></i>
+                    </el-button>
+                    <template #dropdown>
+                        <el-dropdown-menu>
+                            <el-dropdown-item @click="handleCreateJob('SYSTEM', 'OCR', 'PDF 任务')">PDF
+                                任务</el-dropdown-item>
+                            <el-dropdown-item @click="handleCreateJob('SYSTEM', 'WORD', 'Word 任务')">Word
+                                任务</el-dropdown-item>
+                        </el-dropdown-menu>
+                    </template>
 
+                </el-dropdown>
+                <el-button @click="handleRefreshTable" style="margin-left:5px;" type="success">刷新</el-button>
+            </el-form-item>
+        </el-form>
+    </el-row>
     <el-row>
-        <el-table :data="pageData.records" height="650" highlight-current-row @current-change="handleCurrentChange">
+        <el-table :data="pageData.records" height="650" style="width: 100%;" highlight-current-row
+            @current-change="handleCurrentChange">
             <el-table-column prop="id" label="ID" width="50"></el-table-column>
-            <el-table-column prop="job_category" label="工作类型" width="200"></el-table-column>
-            <el-table-column prop="job_name" label="工作名称" width="200"></el-table-column>
+            <el-table-column prop="job_name" label="文件名称" width="200"></el-table-column>
+            <el-table-column prop="start_category" label="任务类型" width="200">
+                <template v-slot="scope">
+                    {{ formatJobCategory(scope.row.start_category) }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="job_category" label="当前工序" width="200">
+                <template v-slot="scope">
+                    {{ formatJobCategory(scope.row.job_category) }}
+                </template>
+            </el-table-column>
             <el-table-column prop="job_creator" label="创建人" width="100"></el-table-column>
             <el-table-column prop="status" label="状态" width="100">
                 <template v-slot="scope">
@@ -29,20 +79,12 @@
             <el-table-column label="操作" width="500">
                 <template v-slot="scope">
                     <el-menu mode="horizontal">
-
                         <el-sub-menu index="1">
                             <template #title>操作</template>
                             <el-menu-item index="1-1" @click="handleViewJob(scope.row)">查看</el-menu-item>
-                            <el-menu-item index="1-2" @click="handlePutJobIntoQueue(scope.row)">移入队列</el-menu-item>
+                            <!-- <el-menu-item index="1-2" @click="handlePutJobIntoQueue(scope.row)">移入队列</el-menu-item> -->
                             <el-menu-item index="1-3" @click="handleDeleteJob(scope.row)">删除</el-menu-item>
                         </el-sub-menu>
-                        <el-sub-menu index="4">
-                            <template #title>变更状态</template>
-                            <template v-for="item in jobStatusList">
-                                <el-menu-item v-if="item.could_be_set" index="4-{{item.id}}"
-                                    @click="handleJobSetStatus(scope.row, item.value)">{{ item.label }}</el-menu-item>
-                            </template>
-                        </el-sub-menu>
                     </el-menu>
                 </template>
             </el-table-column>
@@ -53,22 +95,23 @@
         <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
             @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
         </el-pagination>
+        <span style="margin-left: 20px;">{{ pageData.total }}条记录</span>
     </el-row>
     <el-drawer title="工作详情" v-model="showDrawer" :direction="showDrawerDirection" size="45%">
         <el-tabs type="border-card" v-model="jobDetailsActivateTab">
             <el-tab-pane label="基本信息" name="basic_info">
                 <div class="prop_header">任务类型</div>
-                <div>{{ currentJob.job_category }}</div>
+                <div>{{ currentJob?.start_category }}</div>
                 <div class="prop_header">任务名称</div>
-                <div>{{ currentJob.job_name }}</div>
+                <div>{{ currentJob?.job_name }}</div>
                 <div class="prop_header">创建人</div>
-                <div>{{ currentJob.job_creator }}</div>
+                <div>{{ currentJob?.job_creator }}</div>
                 <div class="prop_header">创建时间</div>
-                <div>{{ formatDate(currentJob.created) }}</div>
+                <div>{{ formatDate(currentJob?.created) }}</div>
                 <div class="prop_header">更新时间</div>
-                <div>{{ formatDate(currentJob.updated) }}</div>
+                <div>{{ formatDate(currentJob?.updated) }}</div>
                 <div class="prop_header">日志</div>
-                <div><el-input type="textarea" v-model="currentJob.job_logs" :rows="20"></el-input></div>
+                <div><el-input type="textarea" :rows="20">{{ currentJob?.job_logs }}</el-input></div>
             </el-tab-pane>
             <el-tab-pane label="文件夹" name="file_browse">
                 <el-row>
@@ -78,6 +121,9 @@
                     <el-col :span="20">
                         <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row
                             style="width: 100%;">
+                            <el-table-column prop="type" label="类型" width="50">
+
+                            </el-table-column>
                             <el-table-column prop="name" label="名称" width="250">
 
                             </el-table-column>
@@ -102,11 +148,12 @@
 
 <script setup lang="ts">
 
-import { ref, onMounted, watch } from 'vue'
-import { useRoute } from 'vue-router'
+import { ref, onMounted, watch, watchEffect } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
 import {
     getJob,
     getJobs,
+    getStartQueueJobs,
     getQueue,
     browseJobFile,
     getJobStatus,
@@ -115,9 +162,11 @@ import {
     deleteJob,
     updateJob,
     updateJobStatus,
+    searchJobs,
     getFileContent
 } from '@/api/AgentApi'
-import type { JobData, } from '@/api/AgentApi'
+import type { JobData, SearchData } from '@/api/AgentApi'
+
 import OCRDialog from '@/dialogs/OCRDialog.vue'
 import QueueSelectDialog from '@/dialogs/QueueSelectDialog.vue'
 import TextViewDialog from '@/dialogs/TextViewDialog.vue'
@@ -125,30 +174,42 @@ import { ElNotification, ElMessageBox } from 'element-plus'
 import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
 import { formatDate } from '@/utils/misc'
 import { getSessionVar, saveSessionVar } from '@/utils/session'
-import { useMenuStore } from "@/stores/menu.js"
-const { operationPermissions } = useMenuStore();
 
+const router = useRouter()
 //从路由参数中获取队列ID
-
-const sessionId = ref<any>("")
+interface ViewJobData {
+    id: number,
+    job_category: string,
+    job_name: string,
+    job_creator: string,
+    created: string,
+    updated: string,
+    job_logs: string,
+    start_category: string,
+}
+const sessionId = ref("")
 const ocrDialog = ref()
+const pageTitle = ref("全部工作")
 const allowCreateJob = ref(false)
 const queueSelectDialog = ref()
 const textViewDialog = ref()
-const queueId = ref<any>("")
+//const queueId = ref("")
 const queueData = ref({ id: "0", queue_category: "", queue_name: "", title: "" })
 const showDrawer = ref(false)
 const showDrawerDirection = ref('rtl')
 const jobDetailsActivateTab = ref('basic_info')
 const queueIdNameDict = ref([
-    { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "全部工作", dialog: null },
-    { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog },
-    { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null },
-    { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null },
-    { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null },
-    { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null },
-    { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog },
+    { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "全部工作", dialog: null, is_start: false },
+    { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog, is_start: true },
+    { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog, is_start: true },
 ])
+const searchForm = ref<SearchData>({ job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" })
+const jobStartCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有类型" }, { id: "SYSTEM_WORD", label: "WORD文档抽取" }, { id: "SYSTEM_OCR", label: "PDF文档抽取" }])
+const jobCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有工序" }, { id: "SYSTEM_CHUNKS", label: "文档切片" }, { id: "SYSTEM_KB_EXTRACT", label: "知识抽取" }, { id: "SYSTEM_KB_BUILD", label: "知识构建" }])
 var textViewContent = "hello"
 const textViewData = ref(textViewContent)
 const jobStatusList = ref(JobSatus)
@@ -159,24 +220,36 @@ const jobFileTree = ref({
 const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
 const jobFileData = ref([])
 const jobFileInDir = ref([])
-const currentJob = ref<any>({ id: 0, job_category: "USER", job_name: "" })
+const currentJob = ref<ViewJobData>({ id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" })
 const currentActionJob = ref({ id: 0, job_category: "USER", job_name: "" })
 
 const route = useRoute()
 
-watch(route, (newValue, oldValue) => {
-    let para = newValue.params.id as string | undefined
+// watchEffect(route, (newValue, oldValue) => {
+//     let para = newValue.params.id as string | undefined
+//     if (para == undefined) {
+//         return
+//     }
+//     console.log('route changed')
+//     //queueId.value = para
+//     queueData.value.id = "0"
+//     loadQueueData() 
+// })
+watchEffect(() => {
+    let para = route.params.id as string | undefined
     if (para == undefined) {
         return
     }
     console.log('route changed')
-    queueId.value = para
+    //queueId.value = para
     queueData.value.id = "0"
+    // queueData.value.queue_category = "SYSTEM"
+    // queueData.value.queue_name = "ALL"
+    // queueData.value.title = "全部工作"
     loadQueueData()
 })
 
-
-const handleCreateJob = () => {
+const handleCreateJob = (category: string, name: string, dialog_title: string) => {
     if (queueData.value.id == "0") {
         ElNotification.info({
             title: '消息',
@@ -184,62 +257,99 @@ const handleCreateJob = () => {
         })
         return
     }
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    queueData.value.queue_category = category
+    queueData.value.queue_name = name
+    queueData.value.title = dialog_title
+    let queue_data = queueIdNameDict.value.find((item) => item.category == category && item.name == name)
     if (queue_data == undefined) {
         return
     }
-    // console.log(queue_data)
+    console.log("HHHH")
     if (queue_data.dialog != undefined) {
-        // console.log(queue_data.dialog)
         queue_data.dialog.showDialog()
     }
 }
 const handleViewFile = (job: any, path: string) => {
-    // console.log("handleViewFile")
+    console.log("handleViewFile")
     const url = '/api/file/view/' + job?.id + '?path=' + encodeURIComponent(path)
-    // console.log(url)
+    console.log(url)
     getFileContent(url).then((res) => {
-        // console.log(res)
+        console.log(res)
         textViewData.value = res.records[0].content
         textViewDialog.value.showDialog(true)
     })
 }
 const handleRefreshTable = () => {
-    loadQueueData()
+    handleCurrentPageChange(pageData.value.page)
 }
 function formatStatus(status: number) {
     return getJobStatus(status)
 }
-function formatFolderName(name: string) {
+function formatJobCategory(category: string) {
+    var title = ""
+    queueIdNameDict.value.forEach((item) => {
+        // console.log(category, 'category')
+        if ((item.category + "_" + item.name) == category) {
+            console.log(item.title)
+            title = item.title
 
-    var translate: any = {
-        logs: "日志",
-        results: "结果", chunks: "切片", files: "文件",
-        ocr_output: "文本转换结果", kb_extract: "知识抽取", kb_build: "知识构建",
-        upload: '文件上传', output: '输出'
-    }
-    // console.log(translate[name])
-    if (name in translate) {
-        return translate[name] as string
+        }
+    })
+    return title
+}
+function formatFolderName(name: string) {
+    let translate = new Map([
+        ["logs", "日志"],
+        ["results", "结果"],
+        ["chunks", "切片"],
+        ["files", "文件"],
+        ["ocr_output", "文本转换结果"],
+        ["kb_extract", "知识抽取"],
+        ["kb_build", "知识构建"],
+        ["upload", "文件上传"],
+        ["output", "输出"]]
+    )
+    if (translate.get(name) != undefined) {
+        return translate.get(name)
     }
     return name
 }
+function resetViewData() {
+    jobFileData.value = []
+    jobFileInDir.value = []
+    currentJob.value = { id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" }
+    searchForm.value = { job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" }
+    pageData.value.page = 1
+    pageData.value.page_size = 10
+}
+function loadJobs(reset: boolean) {
+    console.log("searchJobs")
+    if (reset) {
+        resetViewData()
+    }
+    searchJobs(searchForm.value, pageData.value.page, pageData.value.page_size).then((res) => {
+        pageData.value.page = res.meta.page
+        pageData.value.pages = res.meta.pages
+        pageData.value.total = res.meta.total
+        pageData.value.records = res.records
+    })
+}
 function loadQueueData() {
 
     jobFileData.value = []
     jobFileInDir.value = []
-    currentJob.value = { id: 0, job_category: "USER", job_name: "" }
+    //currentJob.value = {id:0,job_category:"USER",job_name:""}
     currentActionJob.value = { id: 0, job_category: "USER", job_name: "" }
     // 根据队列ID获取队列数据
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
 
     if (queue_data == undefined) {
-        queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'UNKNOWN', title: "未知队列" }
+        queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'ALL', title: "未知队列" }
         return
     }
 
     if (queue_data.dialog != null) {
-        // console.log("queue_data.dialog is allowed")
+        console.log("queue_data.dialog is allowed")
         allowCreateJob.value = true
     } else {
         allowCreateJob.value = false
@@ -252,14 +362,10 @@ function loadQueueData() {
         queueData.value.queue_category = res.records[0].queue_category
         queueData.value.queue_name = res.records[0].queue_name
         queueData.value.title = queue_data.title
+        console.log(queueData.value, ' queueData')
+        handleCurrentPageChange(1)
     })
-    getJobs(queue_data.category, queue_data.name, pageData.value.page, pageData.value.page_size).then((res) => {
-        pageData.value.page = res.meta.page
-        pageData.value.pages = res.meta.pages
-        pageData.value.total = res.meta.total
-        pageData.value.records = res.records
 
-    })
 }
 function jobMoved() {
     queueSelectDialog.value.showDialog(null, false)
@@ -273,7 +379,7 @@ function jobMoved() {
     })
 }
 function jobCreated() {
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
     if (queue_data == undefined) {
 
         ElNotification.info({
@@ -340,35 +446,52 @@ function handleTreeNodeClick(data: any) {
     }
 }
 function handleCurrentPageChange(page: number) {
-    getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
-        pageData.value.page = res.meta.page
-        pageData.value.pages = res.meta.pages
-        pageData.value.total = res.meta.total
-        pageData.value.records = res.records
-    })
+    pageData.value.page = page
+    loadJobs(false)
+    // let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    // console.log(queue_data)
+    // if (queue_data == undefined) {
+    //     return
+    // }
+    // if (queue_data?.is_start == false) {
+    //     getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
+    //         pageData.value.page = res.meta.page
+    //         pageData.value.pages = res.meta.pages
+    //         pageData.value.total = res.meta.total
+    //         pageData.value.records = res.records
+    //     }) 
+    // } else {
+    //     getStartQueueJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
+    //         pageData.value.page = res.meta.page
+    //         pageData.value.pages = res.meta.pages
+    //         pageData.value.total = res.meta.total 
+    //         pageData.value.records = res.records
+    //     })
+    // }
 }
 function handleJobMenuSelect(key: string, keyPath: string[]) {
-    // console.log(key, keyPath)
+    console.log(key, keyPath)
 }
 function handleViewJob(job: JobData) {
     if (job == null) {
         return
     }
-    jobFileData.value = []
-    jobFileInDir.value = []
-    getJob(job.id).then((res) => {
-        currentJob.value = res.records[0]
-        showDrawer.value = true
-        browseJobFile({ job_id: job.id, path: "" }).then((res) => {
-            for (let i = 0; i < res.records.length; i++) {
-                res.records[i]['label'] = res.records[i].name
-                res.records[i]['children'] = []
-                res.records[i]['display_name'] = formatFolderName(res.records[i].name)
-            }
-            jobFileData.value = res.records
-            // console.log("show drawer")
-        })
-    })
+    router.push({ name: 'job-view', params: { id: job.id } })
+    // jobFileData.value = []
+    // jobFileInDir.value = []
+    // getJob(job.id).then((res) => {
+    //     currentJob.value = res.records[0]
+    //     showDrawer.value = true
+    //     browseJobFile({ job_id:job.id, path:""}).then((res) => {        
+    //         for (let i = 0; i < res.records.length; i++) {
+    //             res.records[i]['label'] = res.records[i].name 
+    //             res.records[i]['children'] = []
+    //             res.records[i]['display_name'] = formatFolderName(res.records[i].name)
+    //         }
+    //         jobFileData.value = res.records
+    //         console.log("show drawer")
+    //     })
+    // })
 }
 function handleCurrentChange(data: any) {
     if (data == null) {
@@ -377,10 +500,28 @@ function handleCurrentChange(data: any) {
 
     //currentJob.value = data
 }
+function handleSearch() {
+    pageData.value.page = 1
+    pageData.value.pages = 0
+    pageData.value.total = 0
+    pageData.value.records = []
+    pageTitle.value = "搜索结果"
+    loadJobs(false)
+}
+function handleSearchReset() {
+    pageData.value.page = 1
+    pageData.value.pages = 0
+    pageData.value.total = 0
+    pageData.value.records = []
+    resetViewData()
+    pageTitle.value = "全部工作"
+    loadJobs(false)
+}
 onMounted(() => {
-    sessionId.value = getSessionVar("session_id") as string | undefined
-    queueId.value = route.params.id as string | undefined
-    loadQueueData()
+    sessionId.value = getSessionVar("session_id") as string | ""
+
+    //loadQueueData()
+    loadJobs(true)
 })
 </script>
 
@@ -390,4 +531,16 @@ onMounted(() => {
     margin-top: 10px;
     margin-bottom: 5px;
 }
+
+.el-dropdown {
+    vertical-align: top;
+}
+
+.el-dropdown+.el-dropdown {
+    margin-left: 15px;
+}
+
+.el-icon-arrow-down {
+    font-size: 12px;
+}
 </style>

+ 1 - 1
vite.config.ts

@@ -35,7 +35,7 @@ export default defineConfig(({ command, mode }) => {
     plugins: [
       vue(),
       vueJsx(),
-      //vueDevTools(),
+      vueDevTools(),
     ],
     resolve: {
       alias: {