Browse Source

前后端对接,知识导航搜索节点

Kevin Jiang 3 years ago
parent
commit
69e25f6b06

+ 4 - 4
src/main.ts

@@ -9,10 +9,10 @@ import "ant-design-vue/dist/antd.less";
 
 import "./assets/main.scss";
 
-import { worker } from './mocks/browser';
-if (process.env.NODE_ENV === 'development') {
-  worker.start({onUnhandledRequest: 'bypass'})
-}
+// import { worker } from './mocks/browser';
+// if (process.env.NODE_ENV === 'development') {
+//   worker.start({onUnhandledRequest: 'bypass'})
+// }
 
 const app = createApp(App);
 

+ 2 - 2
src/services/httpClient.ts

@@ -1,7 +1,7 @@
 import {message} from "ant-design-vue";
 import axios, {AxiosError, type AxiosRequestConfig} from "axios";
 
-const urlPrefix = "http://192.168.100.234:8088/";
+const urlPrefix = "/";
 
 
 const httpClient = axios.create({
@@ -14,7 +14,7 @@ const httpClient = axios.create({
 httpClient.interceptors.request.use(function (config: AxiosRequestConfig) {
     // 在发送请求之前做些什么
     const token = localStorage.getItem('token');
-    if (token) {
+    if (token && config.headers) {
         config.headers.Authorization = token;
     }
     return config;

+ 9 - 6
src/services/search.service.ts

@@ -1,13 +1,16 @@
 import type {SearchResult} from "@/types/search.types";
+import type { SearchRequest } from "@/types/request.types";
 import type {Response} from "@/types/response.types";
 import type {AxiosResponse} from "axios";
 import httpClient from "./httpClient";
 
-function search(query: Object): Promise<AxiosResponse<Response<SearchResult>>> {
-    // const params = { query };
-    return httpClient.post("/gw/search/search", query);
+export function search(query: SearchRequest): Promise<AxiosResponse<Response<SearchResult>>> {
+  if (!query.dataSources) {
+    query["dataSources"] = ["CN"];
+  }
+  return httpClient.post("/gw/search/search", query);
 }
 
-export default {
-    search
-}
+// export default {
+//     search
+// }

+ 50 - 2
src/stores/knowledge-graph.ts

@@ -2,29 +2,77 @@ import { ref, type Ref } from "vue";
 import { defineStore } from "pinia";
 import * as kgService from "@/services/knowledgeGraph.service";
 import * as searchService from "@/services/search.service";
-import type { Property } from "@/types/knowledgeGraph.types";
+import type { GraphData, Property } from "@/types/knowledgeGraph.types";
 import type { SearchResult } from "@/types/search.types";
+import { message } from "ant-design-vue";
 
 export const useKnowledgeGraphStore = defineStore('knowledge-graph', () => {
   const property: Ref<Property> = ref({} as Property);
 
+  const graphData: Ref<GraphData> = ref({
+    // 点集
+    nodes: [
+      {
+        id: 'node1',
+        label: '的',
+      },
+      {
+        id: 'node2',
+        label: '的',
+      },
+      {
+        id: 'node3',
+        label: '的',
+      }
+    ],
+    // 边集
+    edges: [
+      {
+        source: 'node1',
+        target: 'node2',
+      },
+      {
+        source: 'node1',
+        target: 'node3',
+      }
+    ],
+  });
+
   const searchResult: Ref<SearchResult | null> = ref(null);
 
+  function fetchGraph(query?: string) {
+    // TODO
+    message.info('根据查询条件' + query + '查询图数据');
+    return new Promise((resolve) => {
+      resolve('TOTO');
+    });
+  }
+
+  /**
+   * 根据学科ID查询学科属性
+   * @param id 学科ID
+   */
   function fetchProperty(id: number) {
     kgService.fetchProperty(id).then((resp) => {
       property.value = resp.data.data;
     })
   }
 
+  /**
+   * 根据学科名称搜索文献
+   * @param name 学科名称
+   */
   function search(name: string) {
-    searchService.search(`TP=("${name}")`).then((resp) => {
+    searchService.search({query: `TP=("${name}")`}).then((resp) => {
       searchResult.value = resp.data.data;
     });
   }
 
   return {
+    graphData,
     property,
     searchResult,
+    fetchGraph,
     fetchProperty,
     search,
   }

+ 17 - 0
src/types/knowledgeGraph.types.ts

@@ -1,3 +1,20 @@
+type GraphNodeID = string;
+
+export interface GraphNode {
+  id: GraphNodeID;
+  label: string;
+}
+
+export interface GraphEdge {
+  source: GraphNodeID;
+  target: GraphNodeID;
+}
+
+export interface GraphData {
+  nodes: GraphNode[];
+  edges: GraphEdge[];
+}
+
 export interface Property {
   name: string,
   docCount: number,

+ 7 - 0
src/types/request.types.ts

@@ -0,0 +1,7 @@
+export interface SearchRequest {
+    dataSources?: string[];
+    query: string;
+    aggs?: SearchAggRequest;
+}
+
+export interface SearchAggRequest {}

+ 0 - 1
src/views/detail/DetailView.vue

@@ -3,7 +3,6 @@ import { useDocDetailStore } from '@/stores/doc-detail';
 import { useSideBarStore } from '@/stores/side-bar';
 import { onMounted, onUnmounted, computed } from 'vue';
 import { useRoute } from 'vue-router';
-import searchService from "@/services/search.service";
 import DocCatalog from './components/DocCatalog.vue';
 
 // 当前路由

+ 85 - 53
src/views/knowledge-grapn/KnowLedgeGraphView.vue

@@ -1,49 +1,31 @@
 <script setup lang="ts">
-import type { SearchResult } from "@/types/search.types";
-import { onMounted, ref, type Ref } from "vue";
+import { onMounted, ref, computed, type Ref } from "vue";
 import G6 from '@antv/g6';
+
+import * as searchService from "@/services/search.service";
+import type { SearchResult } from "@/types/search.types";
+import type { GraphData } from "@/types/knowledgeGraph.types";
 import SearchResultList from "../search/components/SearchResultList.vue";
 import PropertyComponent from "./components/PropertyComponent.vue";
+import { message } from "ant-design-vue";
+import { useKnowledgeGraphStore } from "@/stores/knowledge-graph";
+
+const kgGraphStore = useKnowledgeGraphStore();
 
 const keyword = ref('');
 
+const currentPage = ref(1);
+
 function onSearch() {
   console.log('keyword', keyword);
 }
 
 // 搜索结果
-const searchResult: Ref<SearchResult | null> = ref(null);
-
-const data = {
-  // 点集
-  nodes: [
-    {
-      id: 'node1',
-      label: 'node1',
-    },
-    {
-      id: 'node2',
-      label: 'node2',
-    },
-    {
-      id: 'node3',
-      label: 'node3',
-    }
-  ],
-  // 边集
-  edges: [
-    {
-      source: 'node1',
-      target: 'node2',
-    },
-    {
-      source: 'node1',
-      target: 'node3',
-    }
-  ],
-};
+const searchResult: Ref<SearchResult | null> = computed(() => kgGraphStore.searchResult);
 
-onMounted(() => {
+const data = computed(() => kgGraphStore.graphData);
+
+function initGraph(data: GraphData) {
   const graph = new G6.Graph({
     container: 'graph',
     layout: {
@@ -52,44 +34,66 @@ onMounted(() => {
       kr: 20,
     },
     modes: {
-      default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点
+      // 允许拖拽画布、放缩画布、拖拽节点
+      default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
     },
     nodeStateStyles: {
-      // 鼠标 hover 上节点,即 hover 状态为 true 时的样式
       hover: {
         fill: 'lightsteelblue',
       },
     },
     defaultNode: {
-      size: 60,
+      size: 56,
       type: 'circle',
       style: {
-        stroke: '#666',
-        fill: 'steelblue'
+        stroke: '#F36924',
+        fill: '#F79767'
       }
     },
     defaultEdge: {
       label: '下级学科',
       size: 1,
-      color: '#e2e2e2',
+      color: '#b6bbc5',
       style: {
         endArrow: {
           path: 'M 0,0 L 8,4 L 8,-4 Z',
-          fill: '#e2e2e2',
+          fill: '#b6bbc5',
         },
       },
     },
   });
-  graph.data(data); // 读取 Step 2 中的数据源到图上
-  graph.render(); // 渲染图
+  // 读取数据源到图上
+  graph.data(data as any);
+  // 渲染图
+  graph.render();
+  // 监听节点点击事件
   graph.on('node:click', event => {
-    console.log('node:click', event.item?.getID());
-    const index = data['nodes'].findIndex(n => n.id == event.item?.getID())
+    const nodes = data['nodes']
+    const index = nodes.findIndex(n => n.id == event.item?.getID())
     if (index > -1) {
-      console.log(data['nodes'][index])
+      const node = nodes[index];
+      searchSubject(node.label);
+
+      kgGraphStore.search(node.label);
     }
   });
+}
+
+onMounted(() => {
+  kgGraphStore.fetchGraph(keyword.value).then((resp) => {
+    message.info(resp as string);
+    initGraph(data.value);
+  });
 });
+
+function searchSubject(name: string) {
+  searchService.search({ "query": `TP=(${name})` }).then((resp) => {
+    searchResult.value = resp.data.data;
+  }).catch((err) => {
+    console.error(err);
+    message.warning('搜索学科分类遇到问题,请稍后再试');
+  })
+}
 </script>
 
 <template>
@@ -115,8 +119,17 @@ onMounted(() => {
     </a-col>
   </a-row>
   <div class="articles-wrap">
-    <SearchResultList :data="searchResult" v-if="searchResult" />
-    <a-empty v-else description="暂无数据" />
+    <div v-if="searchResult">
+      <SearchResultList :data="searchResult" class="articles" />
+      <a-pagination
+        v-model:current="currentPage"
+        :total="searchResult.total"
+        :show-total="(total: number) => `共 ${total} 条`"
+        :show-size-changer="false"
+        class="pagination"
+      />
+    </div>
+    <a-empty class="empty" v-else description="暂无数据,请点击学科导航节点" />
   </div>
 </template>
 
@@ -130,7 +143,7 @@ onMounted(() => {
 }
 
 .graph-container {
-  margin-top: 10px;
+  margin-top: 20px;
 
   .graph-wrap,
   .property-wrap {
@@ -140,7 +153,7 @@ onMounted(() => {
   }
 
   .graph-wrap {
-    height: 600px;
+    height: 460px;
 
     .graph {
       width: 100%;
@@ -154,9 +167,28 @@ onMounted(() => {
 }
 
 .articles-wrap {
-  margin-top: 10px;
-  padding: 10px;
-  background-color: white;
-  border: 1px solid $border-color;
+  margin-top: 20px;
+
+  .articles {
+    padding: 10px 20px;
+    background-color: white;
+    border: 1px solid $border-color;
+  }
+
+  .empty {
+    background-color: white;
+    border: 1px solid $border-color;
+    margin: 0;
+    padding: 30px 0;
+  }
+
+  .pagination {
+    float: right;
+    margin-top: 20px;
+
+    ::after {
+      clear: both;
+    }
+  }
 }
 </style>

+ 1 - 1
src/views/search/SearchResultView.vue

@@ -2,7 +2,7 @@
     import {onUnmounted, ref, type Ref, onMounted} from "vue";
     import {useRoute, useRouter} from "vue-router";
     import {useSideBarStore} from "@/stores/side-bar";
-    import searchService from "@/services/search.service";
+    import * as searchService from "@/services/search.service";
     import type {SearchResult} from "@/types/search.types";
     import SearchBox from "./components/SearchBox.vue";
     import SearchResultToolbar from "./components/SearchResultToolbar.vue";

+ 8 - 0
vite.config.ts

@@ -12,6 +12,14 @@ export default defineConfig({
       "@": fileURLToPath(new URL("./src", import.meta.url)),
     },
   },
+  server: {
+    proxy: {
+      '/gw': {
+        target: 'http://localhost:8088',
+        changeOrigin: true,
+      }
+    }
+  },
   css: {
     preprocessorOptions: {
       less: {