Pārlūkot izejas kodu

导航,搜索,搜索结果

Kevin Jiang 3 gadi atpakaļ
vecāks
revīzija
55dce1b2fe

+ 11 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "name": "front",
       "version": "0.0.0",
       "dependencies": {
+        "@types/lodash": "^4.14.188",
         "ant-design-vue": "^3.2.13",
         "pinia": "^2.0.21",
         "vue": "^3.2.38",
@@ -882,6 +883,11 @@
       "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
+    "node_modules/@types/lodash": {
+      "version": "4.14.188",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.188.tgz",
+      "integrity": "sha512-zmEmF5OIM3rb7SbLCFYoQhO4dGt2FRM9AMkxvA3LaADOF1n8in/zGJlWji9fmafLoNyz+FoL6FE0SLtGIArD7w=="
+    },
     "node_modules/@types/node": {
       "version": "16.18.3",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.3.tgz",
@@ -7810,6 +7816,11 @@
       "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
+    "@types/lodash": {
+      "version": "4.14.188",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.188.tgz",
+      "integrity": "sha512-zmEmF5OIM3rb7SbLCFYoQhO4dGt2FRM9AMkxvA3LaADOF1n8in/zGJlWji9fmafLoNyz+FoL6FE0SLtGIArD7w=="
+    },
     "@types/node": {
       "version": "16.18.3",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.3.tgz",

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
   },
   "dependencies": {
+    "@types/lodash": "^4.14.188",
     "ant-design-vue": "^3.2.13",
     "pinia": "^2.0.21",
     "vue": "^3.2.38",

+ 51 - 25
src/components/NavMenu.vue

@@ -1,22 +1,42 @@
 <script setup lang="ts">
 import { FilePptOutlined, FileSearchOutlined, NodeIndexOutlined } from '@ant-design/icons-vue';
-import type { MenuProps } from 'ant-design-vue';
-import { ref, watch } from 'vue';
+import { onMounted, ref, watch } from 'vue';
+import { useRoute, RouterLink } from "vue-router";
+import _ from "lodash";
+
+const selectedKeys = ref<string[]>(['/']);
+const openKeys = ref<string[]>(['/']);
+
+const route = useRoute();
+
+onMounted(() => {
+  openKeys.value = getOpenKeys(route.path);
+  selectedKeys.value = [route.path];
+
+  watch(
+    () => route.path,
+    (path: string) => {
+      const currentOpenkeys = getOpenKeys(path);
+      openKeys.value = _.uniq(_.concat(openKeys.value, currentOpenkeys));
+    }
+  );
+});
+
+function getOpenKeys(path: string): string[] {
+  const parts = _.dropRight(path.split('/').filter(x => x != '').map(x => '/'+x), 1);
+  if (parts.length == 0) {
+    return ["/"];
+  }
+  let openKeys = parts.reduce((acc, item) => {
+    if (acc.length == 0) {
+      return acc.concat([item]);
+    }
+    return acc.concat([acc[acc.length-1] + item]);
+  }, [] as string[]);
+  openKeys.unshift("/");
+  return openKeys;
+}
 
-const selectedKeys = ref<string[]>(['1']);
-const openKeys = ref<string[]>(['sub1']);
-const handleClick: MenuProps['onClick'] = e => {
-  console.log('click', e);
-};
-const titleClick = (e: Event) => {
-  console.log('titleClick', e);
-};
-watch(
-  () => openKeys,
-  val => {
-    console.log('openKeys', val);
-  },
-);
 </script>
 
 <template>
@@ -24,30 +44,36 @@ watch(
     v-model:openKeys="openKeys"
     v-model:selectedKeys="selectedKeys"
     mode="inline"
-    @click="handleClick"
   >
-    <a-sub-menu key="sub1" @titleClick="titleClick">
+    <a-sub-menu key="/report">
       <template #icon>
         <FilePptOutlined />
       </template>
       <template #title>报告管理</template>
-      <a-menu-item key="1">我的报告</a-menu-item>
-      <a-menu-item key="2">模板管理</a-menu-item>
+      <a-menu-item key="/report/index">
+        <RouterLink to="/report/index">我的报告</RouterLink>
+      </a-menu-item>
+      <a-menu-item key="/report/template">
+        <RouterLink to="/report/template">模板管理</RouterLink>
+      </a-menu-item>
     </a-sub-menu>
-    <a-sub-menu key="sub2" @titleClick="titleClick">
+    <a-sub-menu key="/search">
       <template #icon>
         <FileSearchOutlined />
       </template>
       <template #title>搜索</template>
-      <a-menu-item key="5">智能搜索</a-menu-item>
+      <a-menu-item key="/search/index">
+        <RouterLink to="/search/index">智能搜索</RouterLink>
+      </a-menu-item>
     </a-sub-menu>
-    <a-sub-menu key="sub4">
+    <a-sub-menu key="/knowledge-graph">
       <template #icon>
         <NodeIndexOutlined />
       </template>
       <template #title>知识导航</template>
-      <a-menu-item key="9">建筑工程科技导航</a-menu-item>
+      <a-menu-item key="/knowledge-graph/nav">
+        <RouterLink to="/knowledge-graph/nav">建筑工程科技导航</RouterLink>
+      </a-menu-item>
     </a-sub-menu>
   </a-menu>
 </template>
-

+ 55 - 4
src/router/index.ts

@@ -1,22 +1,73 @@
 import { createRouter, createWebHistory } from "vue-router";
-import HomeView from "../views/HomeView.vue";
+import ReportView from "../views/report/ReportView.vue";
+import RouteView from "../views/RouteView.vue";
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
     {
       path: "/",
-      name: "home",
-      component: HomeView,
+      redirect: "/report/index"
+    },
+    {
+      path: "/report",
+      name: "Report",
+      component: RouteView,
+      children: [
+        {
+          path: "index",
+          name: "ReportIndex",
+          component: ReportView,
+        },
+        {
+          path: "template",
+          name: "ReportTemplate",
+          component: () => import("../views/report/ReportTemplateView.vue"),
+        },
+      ]
+    },
+    {
+      path: "/search",
+      name: "Search",
+      component: RouteView,
+      children: [
+        {
+          path: "index",
+          name: "Search",
+          component: () => import("../views/search/SearchView.vue")
+        },
+        {
+          path: "result",
+          name: "SearchResult",
+          component: () => import("../views/search/SearchResultView.vue")
+        }
+      ]
+    },
+    {
+      path: "/knowledge-graph",
+      name: "KnowledgeGraph",
+      component: RouteView,
+      children: [
+        {
+          path: "nav",
+          name: "KnowledgeGraphNav",
+          component: () => import("../views/knowledge-grapn/KnowLedgeGraphNavView.vue")
+        }
+      ]
     },
     {
       path: "/login",
-      name: "login",
+      name: "Login",
       // route level code-splitting
       // this generates a separate chunk (About.[hash].js) for this route
       // which is lazy-loaded when the route is visited.
       component: () => import("../views/LoginView.vue"),
     },
+    {
+      path: '/:pathMatch(.*)*',
+      name: 'NotFound',
+      component: () => import("../views/NotFound.vue"),
+    },
   ],
 });
 

+ 15 - 0
src/stores/side-bar.ts

@@ -0,0 +1,15 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+
+export const useSideBarStore = defineStore("sideBar", () => {
+  const collapsed = ref(false);
+
+  function setCollapse(collapse: boolean) {
+    collapsed.value = collapse
+  }
+
+  return {
+    collapsed,
+    setCollapse,
+  }
+});

+ 0 - 42
src/views/HomeView.vue

@@ -1,42 +0,0 @@
-<script setup lang="ts">
-import { ref } from "vue";
-import NavMenu from "@/components/NavMenu.vue";
-
-const collapsed = ref(false);
-</script>
-
-<template>
-  <a-layout>
-    <a-layout-sider
-      class="sider-nav-bar"
-      width="260"
-      theme="light"
-      v-model:collapsed="collapsed"
-      collapsible
-    >
-      <div class="logo">Logo & Brand</div>
-      <NavMenu />
-    </a-layout-sider>
-    <a-layout>
-      <a-layout-header theme="light">Header</a-layout-header>
-      <a-layout-content>Content</a-layout-content>
-      <a-layout-footer>Footer</a-layout-footer>
-    </a-layout>
-  </a-layout>
-</template>
-
-<style scoped lang="scss">
-.logo {
-  height: 32px;
-  background: rgba(128, 128, 128, 0.5);
-  margin: 16px;
-}
-
-.ant-layout-header {
-  background: #ffffff;
-}
-
-.sider-nav-bar {
-  height: 100vh;
-}
-</style>

+ 1 - 0
src/views/NotFound.vue

@@ -0,0 +1 @@
+<template>Page Not Found</template>

+ 58 - 0
src/views/RouteView.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { ref, watch } from "vue";
+import { RouterView } from "vue-router";
+import NavMenu from "@/components/NavMenu.vue";
+import { useSideBarStore } from "@/stores/side-bar";
+
+const sideBarStore = useSideBarStore();
+
+const brand = ref("技淘平台智慧产权大脑");
+
+watch(
+  () => sideBarStore.collapsed,
+  (value: boolean) => {
+    brand.value = value ? "大脑" : "技淘平台智慧产权大脑";
+  }
+);
+</script>
+
+<template>
+  <a-layout>
+    <a-layout-sider
+      class="sider-nav-bar"
+      width="260"
+      theme="light"
+      v-model:collapsed="sideBarStore.collapsed"
+      collapsible
+    >
+      <div class="logo">{{ brand }}</div>
+      <NavMenu />
+    </a-layout-sider>
+    <a-layout>
+      <a-layout-content class="main-content">
+        <RouterView />
+      </a-layout-content>
+    </a-layout>
+  </a-layout>
+</template>
+
+<style scoped lang="scss">
+.sider-nav-bar {
+  height: 100vh;
+  .logo {
+    height: 32px;
+    // background: rgba(128, 128, 128, 0.5);
+    margin: 16px;
+  }
+}
+
+.ant-layout-header {
+  background: #ffffff;
+  border-left: 1px solid #f0f2f5;
+}
+
+.main-content {
+  min-width: 800px;
+  padding: 20px 40px;
+}
+</style>

+ 1 - 0
src/views/knowledge-grapn/KnowLedgeGraphNavView.vue

@@ -0,0 +1 @@
+<template>知识导航</template>

+ 6 - 0
src/views/report/ReportTemplateView.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+</script>
+
+<template>
+  Template Manage
+</template>

+ 1 - 0
src/views/report/ReportView.vue

@@ -0,0 +1 @@
+<template>报告列表</template>

+ 163 - 0
src/views/search/SearchResultView.vue

@@ -0,0 +1,163 @@
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref } from "vue";
+import { useRouter } from "vue-router";
+import type { SelectProps } from "ant-design-vue";
+import { useSideBarStore } from "@/stores/side-bar";
+import SearchResultItem from "./components/SearchResultItem.vue";
+
+const sideBarStore = useSideBarStore();
+
+onMounted(() => {
+  // 收起左侧边栏
+  sideBarStore.setCollapse(true);
+});
+
+onUnmounted(() => {
+  sideBarStore.setCollapse(false);
+});
+
+// 关键词
+const keyword = ref("");
+// 搜索的字段
+const field = ref("TP");
+// 路由
+const router = useRouter();
+// 搜索事件监听
+const onSearch = () => {
+  router.push({name: 'SearchResult', query: { kw: keyword.value }});
+}
+// 搜索框字段选择项列表
+const fieldOptions = ref<SelectProps['options']>([
+  { value: 'TP', label: '主题', },
+  { value: 'TI', label: '标题', },
+  { value: 'AU', label: '作者', },
+  { value: 'KW', label: '关键词', },
+]);
+// 排序方式
+const sortOption = ref('Relevance');
+// 排序选择项列表
+const sortOptions = ref<SelectProps['options']>([
+  { value: 'Relevance', label: '按相关度排序' },
+  { value: 'DateDesc', label: '按时间由近到远排序' },
+  { value: 'DateAsc', label: '按时间由远到近排序' },
+  { value: 'CitedDesc', label: '按被引量排序' },
+]);
+
+/**
+ * 排序方式改变监听函数
+ * @param event 排序方式改变事件
+ */
+function handleSortChange(event: any) {
+  console.log('handleSortChange', event);
+}
+
+const currentPage = ref(1);
+</script>
+
+<template>
+  <div class="search-box-wrap">
+    <a-row type="flex">
+      <a-col>
+        <a-select
+          ref="select"
+          v-model:value="field"
+          style="width: 120px"
+          :options="fieldOptions"
+          size="large"
+        ></a-select>
+      </a-col>
+      <a-col flex="1">
+        <a-input-search
+          v-model:value="keyword"
+          placeholder="请输入关键词进行搜索"
+          enter-button="搜&nbsp;&nbsp;索"
+          size="large"
+          @search="onSearch"
+        />
+      </a-col>
+    </a-row>
+  </div>
+  <a-row type="flex" class="search-result-main" :gutter="10">
+    <a-col :span="5" class="left-filter-wrap">
+      <div class="left-filter-header">
+        筛选
+      </div>
+      <a-space direction="vertical" style="width: 100%">
+        <a-card title="Default size card">
+          <p>card content</p>
+          <p>card content</p>
+          <p>card content</p>
+        </a-card>
+        <a-card title="Small size card">
+          <p>card content</p>
+          <p>card content</p>
+          <p>card content</p>
+        </a-card>
+      </a-space>
+    </a-col>
+    <a-col :span="19" class="right-content-wrap">
+      <div class="right-header">
+        <a-space>
+          <span>搜索结果:共 XX 条</span>
+          <a-divider type="vertical"></a-divider>
+          <span>
+            <span>显示:</span>
+            <a-space>
+              <a href="#">简洁</a>
+              <a href="#">详细</a>
+            </a-space>
+          </span>
+          <a-divider type="vertical"></a-divider>
+          <a-select
+            ref="select"
+            v-model:value="sortOption"
+            style="width: 160px"
+            size="small"
+            :options="sortOptions"
+            @change="handleSortChange"
+          ></a-select>
+        </a-space>
+        <a-pagination v-model:current="currentPage" :total="50" show-less-items size="small" />
+      </div>
+      <div class="search-result-content">
+        <SearchResultItem />
+        <a-divider />
+        <SearchResultItem />
+      </div>
+    </a-col>
+  </a-row>
+</template>
+
+<style scoped lang="scss">
+.search-box-wrap {
+  padding: 0 120px;
+}
+
+.search-result-main {
+  margin-top: 20px;
+
+  .left-filter-header {
+    font-size: 1.25rem;
+  }
+
+  .right-content-wrap {
+    .right-header {
+      display: flex;
+      padding-top: 5px;
+      justify-content: space-between;
+    }
+
+    .search-result-content {
+      background-color: #ffffff;
+      margin-top: 18px;
+      padding: 10px;
+    }
+  }
+}
+
+@media (max-width: 1024px) {
+  .search-box-wrap {
+    padding: 0 20px;
+  }
+}
+</style>

+ 63 - 0
src/views/search/SearchView.vue

@@ -0,0 +1,63 @@
+<script setup lang="ts">
+import type { SelectProps } from "ant-design-vue";
+import { ref } from "vue";
+import { useRouter } from "vue-router";
+
+const keyword = ref("");
+
+const field = ref("TP");
+
+const router = useRouter();
+
+const onSearch = () => {
+  router.push({name: 'SearchResult', query: { kw: keyword.value }});
+}
+
+const fieldOptions = ref<SelectProps['options']>([
+  { value: 'TP', label: '主题', },
+  { value: 'TI', label: '标题', },
+  { value: 'AU', label: '作者', },
+  { value: 'KW', label: '关键词', },
+]);
+</script>
+
+<template>
+  <div class="search-wrap">
+    <h1>智能搜索</h1>
+    <a-divider></a-divider>
+    <a-row type="flex">
+      <a-col>
+        <a-select
+          ref="select"
+          v-model:value="field"
+          style="width: 120px"
+          :options="fieldOptions"
+          size="large"
+        ></a-select>
+      </a-col>
+      <a-col flex="1">
+        <a-input-search
+          v-model:value="keyword"
+          placeholder="请输入关键词进行搜索"
+          enter-button="搜&nbsp;&nbsp;索"
+          size="large"
+          @search="onSearch"
+        />
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.search-wrap {
+  width: 80%;
+  margin: 0 auto;
+  margin-top: 10%;
+
+  h1 {
+    font-size: 2rem;
+    text-align: center;
+  }
+
+}
+</style>

+ 15 - 0
src/views/search/components/SearchResultItem.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="item">
+    <h1>标题</h1>
+    <p>作者:作者1,作者2</p>
+    <p>机构:机构</p>
+    <p>关键词:关键词1,关键词2</p>
+    <p>摘要:作者1,作者2</p>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.item {
+  padding: 5px;
+}
+</style>