Browse Source

添加类型

Kevin Jiang 3 years ago
parent
commit
5b0d7010c4

+ 2 - 2
Dockerfile

@@ -2,9 +2,9 @@ FROM node:lts-alpine3.14 as build-stage
 
 WORKDIR /app
 COPY package.json /app/
-RUN yarn config set registry https://registry.npm.taobao.org -g && yarn install
+RUN npm install --registry=https://repo.huaweicloud.com/repository/npm/
 COPY . /app/
-RUN yarn build
+RUN npm run build
 
 FROM nginx:alpine
 WORKDIR /app

+ 32 - 29
src/mocks/search.handler.ts

@@ -4,33 +4,36 @@ import type {Response} from '@/types/response.types';
 import type {SearchResult} from '@/types/search.types';
 
 export const handlers = [
-    rest.post(`${urlPrefix}/search`, (req, res, ctx) => {
-        const response: Response<SearchResult> = {
-            status: 200,
-            msg: '',
-            data: {
-                total: 12,
-                docs: [
-                    {
-                        title: "标题",
-                        authors: ["作者1"],
-                        unit: "",
-                        keywords: ["关键词1"],
-                        summary: "摘要"
-                    },
-                    {
-                        title: "标题",
-                        authors: ["作者1"],
-                        unit: "",
-                        keywords: ["关键词1"],
-                        summary: "摘要"
-                    }
-                ]
-            }
-        };
-        return res(
-            ctx.status(200),
-            ctx.json(response)
-        );
-    }),
+  rest.post(`${urlPrefix}/search`, (req, res, ctx) => {
+    const response: Response<SearchResult> = {
+      status: 200,
+      msg: '',
+      data: {
+        total: 12,
+        docs: [
+          {
+            id: 1,
+            title: "标题",
+            authors: ["作者1"],
+            unit: "",
+            keywords: ["关键词1"],
+            abstract: "摘要"
+          },
+          {
+            id: 2,
+            title: "标题",
+            authors: ["作者1"],
+            unit: "",
+            keywords: ["关键词1"],
+            abstract: "摘要"
+          }
+        ],
+        aggs: {}
+      }
+    };
+    return res(
+      ctx.status(200),
+      ctx.json(response)
+    );
+  }),
 ];

+ 2 - 3
src/services/user.service.ts

@@ -1,10 +1,9 @@
-import type { SearchResult } from "@/types/search.types";
+import type { UserLoginRequest, UserLoginResponse } from "@/types/user.types";
 import type { Response } from "@/types/response.types";
 import type { AxiosResponse } from "axios";
 import httpClient from "./httpClient";
 
-function login(param: Object): Promise<AxiosResponse<Response<Object>>> {
-  // const params = { query };
+function login(param: UserLoginRequest): Promise<AxiosResponse<Response<UserLoginResponse>>> {
   return httpClient.post("/gw/user/login", param);
 }
 

+ 6 - 3
src/types/request.types.ts

@@ -1,7 +1,10 @@
 export interface SearchRequest {
-    dataSources?: string[];
-    query: string;
-    aggs?: SearchAggRequest;
+  dataSources?: string[];
+  query: string;
+  from?: number;
+  size?: number;
+  aggs?: SearchAggRequest;
+  sort?: any
 }
 
 export interface SearchAggRequest {}

+ 8 - 1
src/types/search.types.ts

@@ -4,11 +4,18 @@ export interface SearchResultDoc {
   authors: string[];
   unit: string;
   keywords: string[];
-  summary: string;
+  abstract: string;
 }
 
 export interface SearchResult {
   aggs: any;
   total: number;
   docs: SearchResultDoc[];
+  page?: number;
+  size?: number;
+}
+
+export interface AggItem {
+  key: string;
+  count: number;
 }

+ 13 - 0
src/types/user.types.ts

@@ -0,0 +1,13 @@
+
+/**
+ * 用户登录请求数据结构
+ */
+export interface UserLoginRequest {
+  mobilePhone: string;
+  password: string;
+  type: number;
+}
+
+export interface UserLoginResponse {
+  token: string;
+}

+ 105 - 113
src/views/LoginView.vue

@@ -1,11 +1,11 @@
-<script setup lang="ts">import {reactive} from 'vue';
-import {useRoute, useRouter} from "vue-router";
+<script setup lang="ts">import { reactive } from 'vue';
+import { useRoute, useRouter } from "vue-router";
 import user from "@/services/user.service";
 
 interface FormState {
-    username: string;
-    password: string;
-    remember: boolean;
+  username: string;
+  password: string;
+  remember: boolean;
 }
 
 // 路由管理器
@@ -14,129 +14,121 @@ const router = useRouter();
 const route = useRoute();
 
 const formState = reactive<FormState>({
-    username: '',
-    password: '',
-    remember: true,
+  username: '',
+  password: '',
+  remember: true,
 });
 const onFinish = (values: any) => {
-    console.log(values);
-    let param = {
-        mobilePhone: values.username,
-        password: values.password,
-        type: 1
-    }
-    user.login(param).then((resp) => {
-        localStorage.setItem('token', resp.data.data.token);
-        router.push({name: "SearchIndex"});
-    }).catch((err) => {
-        console.log(err);
-    })
+  console.log(values);
+  let param = {
+    mobilePhone: values.username,
+    password: values.password,
+    type: 1
+  }
+  user.login(param).then((resp) => {
+    localStorage.setItem('token', resp.data.data.token);
+    router.push({ name: "SearchIndex" });
+  }).catch((err) => {
+    console.log(err);
+  })
 };
 
 const onFinishFailed = (errorInfo: any) => {
-    console.log('Failed:', errorInfo);
+  console.log('Failed:', errorInfo);
 };
 
 </script>
 
 <template>
-    <div class="login-main">
-        <a-row type="flex">
-            <a-col class="left" :flex="1">
-                <h1 class="login-title">技淘平台智慧产权大脑</h1>
-                <p class="login-banner">
-                    <img src="@/assets/imgs/login-banner@2x.png"/>
-                </p>
-            </a-col>
-            <a-col class="right" :flex="1">
-                <h1>用户登录</h1>
-                <a-divider></a-divider>
-                <div class="login-form-wrapper">
-                    <a-form
-                            :model="formState"
-                            name="basic"
-                            :label-col="{ span: 4 }"
-                            :wrapper-col="{ span: 20 }"
-                            autocomplete="off"
-                            @finish="onFinish"
-                            @finishFailed="onFinishFailed"
-                    >
-                        <a-form-item
-                                label="用户名"
-                                name="username"
-                                :rules="[{ required: true, message: '请输入用户名' }]"
-                        >
-                            <a-input v-model:value="formState.username"/>
-                        </a-form-item>
-
-                        <a-form-item
-                                label="密码"
-                                name="password"
-                                :rules="[{ required: true, message: '请输入密码' }]"
-                        >
-                            <a-input-password v-model:value="formState.password"/>
-                        </a-form-item>
-
-                        <a-form-item :wrapper-col="{sm: {span: 20, offset: 4}, xs: {span: 24, offset: 0}}">
-                            <a-button type="primary" html-type="submit" block>登&nbsp;&nbsp;&nbsp;&nbsp;录</a-button>
-                        </a-form-item>
-                    </a-form>
-                    <p class="forgot-password-wrap">
-                        <a href="#">忘记密码?</a>
-                    </p>
-                </div>
-            </a-col>
-        </a-row>
-    </div>
+  <div class="login-main">
+    <a-row type="flex">
+      <a-col class="left" :flex="1">
+        <h1 class="login-title">技淘平台智慧产权大脑</h1>
+        <p class="login-banner">
+          <img src="@/assets/imgs/login-banner@2x.png" />
+        </p>
+      </a-col>
+      <a-col class="right" :flex="1">
+        <h1>用户登录</h1>
+        <a-divider></a-divider>
+        <div class="login-form-wrapper">
+          <a-form
+            :model="formState"
+            name="basic"
+            :label-col="{ span: 4 }"
+            :wrapper-col="{ span: 20 }"
+            autocomplete="off"
+            @finish="onFinish"
+            @finishFailed="onFinishFailed"
+          >
+            <a-form-item label="用户名" name="username" :rules="[{ required: true, message: '请输入用户名' }]">
+              <a-input v-model:value="formState.username" />
+            </a-form-item>
+
+            <a-form-item label="密码" name="password" :rules="[{ required: true, message: '请输入密码' }]">
+              <a-input-password v-model:value="formState.password" />
+            </a-form-item>
+
+            <a-form-item :wrapper-col="{ sm: { span: 20, offset: 4 }, xs: { span: 24, offset: 0 } }">
+              <a-button type="primary" html-type="submit" block>登&nbsp;&nbsp;&nbsp;&nbsp;录</a-button>
+            </a-form-item>
+          </a-form>
+          <p class="forgot-password-wrap">
+            <a href="#">忘记密码?</a>
+          </p>
+        </div>
+      </a-col>
+    </a-row>
+  </div>
 </template>
 
 <style scoped lang="scss">
-    .login-main {
-        height: 100vh;
-        background-color: #00A09A;
-
-        .left {
-            text-align: center;
-            padding-top: 100px;
-
-            .login-title {
-                color: #ffffff;
-            }
-
-            .login-banner {
-                margin: 0 auto;
-                width: 300px;
-
-                img {
-                    margin-top: 50px;
-                    width: 100%;
-                }
-            }
-        }
-
-        .right {
-            height: 100vh;
-            padding-top: 100px;
-            padding-left: 60px;
-            border-top-left-radius: 1em;
-            border-bottom-left-radius: 1em;
-            background-color: #ffffff;
-
-            .login-form-wrapper {
-                width: 400px;
-            }
-        }
-    }
+.login-main {
+  height: 100vh;
+  background-color: #00A09A;
 
-    .forgot-password-wrap {
-        text-align: right;
+  .left {
+    text-align: center;
+    padding-top: 100px;
 
-        // a {
-        //   color: #ffffff;
+    .login-title {
+      color: #ffffff;
+    }
 
-        //   &:hover {
-        //     text-decoration: underline;
-        //   }
-        // }
+    .login-banner {
+      margin: 0 auto;
+      width: 300px;
+
+      img {
+        margin-top: 50px;
+        width: 100%;
+      }
+    }
+  }
+
+  .right {
+    height: 100vh;
+    padding-top: 100px;
+    padding-left: 60px;
+    border-top-left-radius: 1em;
+    border-bottom-left-radius: 1em;
+    background-color: #ffffff;
+
+    .login-form-wrapper {
+      width: 400px;
     }
+  }
+}
+
+.forgot-password-wrap {
+  text-align: right;
+
+  // a {
+  //   color: #ffffff;
+
+  //   &:hover {
+  //   text-decoration: underline;
+  //   }
+  // }
+}
 </style>

+ 263 - 265
src/views/search/SearchResultView.vue

@@ -1,282 +1,280 @@
 <script setup lang="ts">
-    import {onUnmounted, ref, type Ref, onMounted} from "vue";
-    import {useRoute, useRouter} from "vue-router";
-    import {useSideBarStore} from "@/stores/side-bar";
-    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";
-    import SearchResultList from "./components/SearchResultList.vue";
-
-    const sideBarStore = useSideBarStore();
-    // 路由管理器
-    const router = useRouter();
-    // 当前路由对象
-    const route = useRoute();
-
-    // 关键词
-    const keyword = ref("");
-    // 字段
-    const field = ref("TP");
-    // 排序
-    const sort = ref("Relevance");
-
-    const page = ref(1);
-
-    const size = ref(10);
-
-    let authorChecked = ref(false);
-    let yearChecked = ref(false);
-
-    let yearValueArr: string[] = []
-    let authorValueArr: string[] = []
-
-    // 搜索结果
-    const searchResult: Ref<SearchResult | null> = ref(null);
-
-    // 搜索结果
-    const aggYearResult = ref(null);
-    // 搜索结果
-    const aggAuthorResult = ref(null);
-
-    // 根据年份聚合
-    function aggsYearSearch(query: string) {
-        const aggYear = {
-            name: 'year',
-            field: 'year'
-        }
-        const aggs: { [key: string]: any } = {}
-        aggs.year = aggYear;
-        const param = {
-            query: query,
-            aggs: aggs
-        };
-        searchService.search(param).then((resp) => {
-            aggYearResult.value = resp.data.data.aggs.year ? resp.data.data.aggs.year : []
-        }).catch((err) => {
-            console.log(err);
-        })
-    }
-
-    // 根据作者聚合
-    function aggsAuthorsSearch(query: string) {
-
-        const aggAuthors = {
-            name: 'authors',
-            field: 'authors.keyword'
-        }
-        const aggs: { [key: string]: any } = {}
-        aggs.authors = aggAuthors;
-        const param = {
-            query: query,
-            aggs: aggs
-        };
-        searchService.search(param).then((resp) => {
-            aggAuthorResult.value = resp.data.data.aggs.authors ? resp.data.data.aggs.authors : []
-        }).catch((err) => {
-            console.log(err);
-        })
-    }
-
-    function doSearch(field: string, keyword: string) {
-        if (keyword && keyword.length == 0) {
-            router.push({name: "SearchIndex"});
-            return;
-        }
-        router.push({name: "SearchResult", query: {kw: keyword}});
-        const query = `${field}=(${keyword})`;
-        let sortParam = null;
-        if (sort.value === 'Relevance') {
-            sortParam = {"Relevance": "Relevance"}
-        } else if (sort.value === 'DateDesc') {
-            sortParam = {"year": "DESC"}
-        } else if (sort.value === 'DateAsc') {
-            sortParam = {"year": "ASC"}
-        } else if (sort.value === 'CitedDesc') {
-            sortParam = {"cited": "ASC"}
-        }
-
-        const param: { [key: string]: any } = {
-            query: query,
-            from: page.value,
-            size: size.value,
-            sort: sortParam
-        };
-        const aggs: { [key: string]: any } = {}
-        if (yearValueArr && yearValueArr.length > 0) {
-            var yearQuery: string = "PY=";
-            var pyKw = '('
-            for (let i = 0; i < yearValueArr.length; i++) {
-                if (i == 0) {
-                    pyKw += yearValueArr[i]
-                } else {
-                    pyKw += " " + yearValueArr[i]
-                }
-
-                console.log(pyKw);
-                if (i == yearValueArr.length - 1) {
-                    pyKw += ')'
-                }
-            }
-            param.query += ' AND ' + yearQuery + pyKw
-        }
-
-        if (authorValueArr && authorValueArr.length > 0) {
-            var auQuery: string = "AU=";
-            var pyKw = '('
-            for (let i = 0; i < authorValueArr.length; i++) {
-                if (i == 0) {
-                    pyKw += authorValueArr[i]
-                } else {
-                    pyKw += " " + authorValueArr[i]
-                }
-
-                console.log(pyKw);
-                if (i == authorValueArr.length - 1) {
-                    pyKw += ')'
-                }
-            }
-            param.query += ' AND ' + auQuery + pyKw
-        }
-        searchResult.value = null
-        searchService.search(param).then((resp) => {
-            searchResult.value = resp.data.data;
-            if (yearValueArr && yearValueArr.length > 0) {
-                aggsAuthorsSearch(param.query)
-            }else {
-                aggsYearSearch(param.query)
-            }
-        }).catch((err) => {
-            console.log(err);
-        })
+import { onUnmounted, ref, type Ref, onMounted } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { useSideBarStore } from "@/stores/side-bar";
+import * as searchService from "@/services/search.service";
+import type { SearchResult, AggItem } from "@/types/search.types";
+import SearchBox from "./components/SearchBox.vue";
+import SearchResultToolbar from "./components/SearchResultToolbar.vue";
+import SearchResultList from "./components/SearchResultList.vue";
+import type { SearchRequest } from "@/types/request.types";
+
+const sideBarStore = useSideBarStore();
+// 路由管理器
+const router = useRouter();
+// 当前路由对象
+const route = useRoute();
+
+// 关键词
+const keyword = ref("");
+// 字段
+const field = ref("TP");
+// 排序
+const sort = ref("Relevance");
+
+const page = ref(1);
+
+const size = ref(10);
+
+let authorChecked = ref([]);
+let yearChecked = ref([]);
+
+let yearValueArr: string[] = []
+let authorValueArr: string[] = []
+
+// 搜索结果
+const searchResult: Ref<SearchResult | null> = ref(null);
+
+// 搜索结果
+const aggYearResult: Ref<AggItem[] | null> = ref(null);
+// 搜索结果
+const aggAuthorResult: Ref<AggItem[] | null> = ref(null);
+
+// 根据年份聚合
+function aggsYearSearch(query: string) {
+  const aggYear = {
+    name: 'year',
+    field: 'year'
+  }
+  const aggs: { [key: string]: any } = {}
+  aggs.year = aggYear;
+  const param = {
+    query: query,
+    aggs: aggs
+  };
+  searchService.search(param).then((resp) => {
+    aggYearResult.value = resp.data.data.aggs.year ? resp.data.data.aggs.year : []
+  }).catch((err) => {
+    console.log(err);
+  })
+}
+
+// 根据作者聚合
+function aggsAuthorsSearch(query: string) {
+
+  const aggAuthors = {
+    name: 'authors',
+    field: 'authors.keyword'
+  }
+  const aggs: { [key: string]: any } = {}
+  aggs.authors = aggAuthors;
+  const param = {
+    query: query,
+    aggs: aggs
+  };
+  searchService.search(param).then((resp) => {
+    aggAuthorResult.value = resp.data.data.aggs.authors ? resp.data.data.aggs.authors : []
+  }).catch((err) => {
+    console.log(err);
+  })
+}
+
+function doSearch(field: string, keyword: string) {
+  if (keyword && keyword.length == 0) {
+    router.push({ name: "SearchIndex" });
+    return;
+  }
+  router.push({ name: "SearchResult", query: { kw: keyword } });
+  const query = `${field}=(${keyword})`;
+  let sortParam = null;
+  if (sort.value === 'Relevance') {
+    sortParam = { "Relevance": "Relevance" }
+  } else if (sort.value === 'DateDesc') {
+    sortParam = { "year": "DESC" }
+  } else if (sort.value === 'DateAsc') {
+    sortParam = { "year": "ASC" }
+  } else if (sort.value === 'CitedDesc') {
+    sortParam = { "cited": "ASC" }
+  }
+
+  const param: SearchRequest = {
+    query: query,
+    from: page.value,
+    size: size.value,
+    sort: sortParam
+  };
+  const aggs: { [key: string]: any } = {}
+  if (yearValueArr && yearValueArr.length > 0) {
+    var yearQuery: string = "PY=";
+    var pyKw = '('
+    for (let i = 0; i < yearValueArr.length; i++) {
+      if (i == 0) {
+        pyKw += yearValueArr[i]
+      } else {
+        pyKw += " " + yearValueArr[i]
+      }
+
+      console.log(pyKw);
+      if (i == yearValueArr.length - 1) {
+        pyKw += ')'
+      }
     }
-
-
-    onMounted(() => {
-        // 收起左侧边栏
-        sideBarStore.setCollapse(true);
-        // 初始化关键词
-        if (route.query.kw) {
-            keyword.value = (route.query.kw as string).trim();
-        }
-        if (route.query.field) {
-            field.value = (route.query.field as string).trim();
-        }
-        // 搜索
-        doSearch(field.value, keyword.value);
-        const query = `${field.value}=(${keyword.value})`
-        aggsYearSearch(query);
-        aggsAuthorsSearch(query);
-    });
-
-    function changeFile(val) {
-        // 搜索
-        field.value = val
-        doSearch(field.value, keyword.value);
+    param.query += ' AND ' + yearQuery + pyKw
+  }
+
+  if (authorValueArr && authorValueArr.length > 0) {
+    var auQuery: string = "AU=";
+    var pyKw = '('
+    for (let i = 0; i < authorValueArr.length; i++) {
+      if (i == 0) {
+        pyKw += authorValueArr[i]
+      } else {
+        pyKw += " " + authorValueArr[i]
+      }
+
+      console.log(pyKw);
+      if (i == authorValueArr.length - 1) {
+        pyKw += ')'
+      }
     }
-
-    function changKeyword(val) {
-        keyword.value = val
-        doSearch(field.value, keyword.value);
-    }
-
-    function changeSort(val) {
-        sort.value = val
-        doSearch(field.value, keyword.value);
-    }
-
-    function changePagination(p, s) {
-        page.value = p
-        size.value = s
-        doSearch(field.value, keyword.value);
+    param.query += ' AND ' + auQuery + pyKw
+  }
+  searchResult.value = null
+  searchService.search(param).then((resp) => {
+    searchResult.value = resp.data.data;
+    if (yearValueArr && yearValueArr.length > 0) {
+      aggsAuthorsSearch(param.query)
+    } else {
+      aggsYearSearch(param.query)
     }
-
-    function changeYearChecked() {
-        yearValueArr = []
-        authorValueArr = []
-        for (let valueKey in yearChecked.value) {
-            yearValueArr.push(yearChecked.value[valueKey])
-        }
-        doSearch(field.value, keyword.value);
-    }
-
-    function changeAuthorChecked() {
-        authorValueArr = []
-        yearValueArr = []
-        for (let valueKey in authorChecked.value) {
-            authorValueArr.push(authorChecked.value[valueKey])
-        }
-        doSearch(field.value, keyword.value);
-    }
-
-    onUnmounted(() => {
-        sideBarStore.setCollapse(false);
-    });
+  }).catch((err) => {
+    console.log(err);
+  })
+}
+
+onMounted(() => {
+  // 收起左侧边栏
+  sideBarStore.setCollapse(true);
+  // 初始化关键词
+  if (route.query.kw) {
+    keyword.value = (route.query.kw as string).trim();
+  }
+  if (route.query.field) {
+    field.value = (route.query.field as string).trim();
+  }
+  // 搜索
+  doSearch(field.value, keyword.value);
+  const query = `${field.value}=(${keyword.value})`
+  aggsYearSearch(query);
+  aggsAuthorsSearch(query);
+});
+
+function changeFile(val: string) {
+  // 搜索
+  field.value = val
+  doSearch(field.value, keyword.value);
+}
+
+function changKeyword(val: string) {
+  keyword.value = val
+  doSearch(field.value, keyword.value);
+}
+
+function changeSort(val: any) {
+  sort.value = val
+  doSearch(field.value, keyword.value);
+}
+
+function changePagination(p: any, s: any) {
+  page.value = p
+  size.value = s
+  doSearch(field.value, keyword.value);
+}
+
+function changeYearChecked() {
+  yearValueArr = []
+  authorValueArr = []
+  for (let valueKey in yearChecked.value) {
+    yearValueArr.push(yearChecked.value[valueKey])
+  }
+  doSearch(field.value, keyword.value);
+}
+
+function changeAuthorChecked() {
+  authorValueArr = []
+  yearValueArr = []
+  for (let valueKey in authorChecked.value) {
+    authorValueArr.push(authorChecked.value[valueKey])
+  }
+  doSearch(field.value, keyword.value);
+}
+
+onUnmounted(() => {
+  sideBarStore.setCollapse(false);
+});
 
 </script>
 
 <template>
-    <div class="search-result-wrap">
-        <div class="search-box-wrap">
-            <SearchBox v-model:keyword="keyword" v-model:field="field" @search="doSearch" @update:field="changeFile"
-                       @update:keyword="changKeyword"/>
-        </div>
-        <a-row type="flex" class="search-result-main" :gutter="10">
-            <a-col :span="5">
-                <div class="left-filter-header">
-                    筛选
-                </div>
-                <a-space direction="vertical" style="width: 100%" v-if="searchResult">
-                    <a-card title="年份">
-                        <a-checkbox-group v-model:value="yearChecked" style="width: 100%" @change="changeYearChecked"
-                                          v-if="aggYearResult && aggYearResult.length > 0">
-                            <a-row v-for="(item, index) in aggYearResult" :key="index">
-                                <a-col :span="20">
-                                    <a-checkbox :value="item.key">{{item.key}}</a-checkbox>
-                                </a-col>
-                                <a-col :span="4"> {{item.count}}</a-col>
-                            </a-row>
-                        </a-checkbox-group>
-                    </a-card>
-                    <a-card title="作者">
-                        <a-checkbox-group v-model:value="authorChecked" style="width: 100%"
-                                          @change="changeAuthorChecked">
-                            <a-row v-for="(item, index) in aggAuthorResult" :key="index">
-                                <a-col :span="20">
-                                    <a-checkbox :value="item.key">{{item.key}}</a-checkbox>
-                                </a-col>
-                                <a-col :span="4"> {{item.count}}</a-col>
-                            </a-row>
-                        </a-checkbox-group>
-                    </a-card>
-                </a-space>
-            </a-col>
-            <a-col :span="19">
-                <SearchResultToolbar :data="searchResult" v-model:sort="sort" v-if="searchResult"
-                                     @update:sort="changeSort" @update:pagination="changePagination"/>
-                <SearchResultList :data="searchResult" v-if="searchResult"/>
-            </a-col>
-        </a-row>
+  <div class="search-result-wrap">
+    <div class="search-box-wrap">
+      <SearchBox v-model:keyword="keyword" v-model:field="field" @search="doSearch" @update:field="changeFile"
+        @update:keyword="changKeyword" />
     </div>
+    <a-row type="flex" class="search-result-main" :gutter="10">
+      <a-col :span="5">
+        <div class="left-filter-header">
+          筛选
+        </div>
+        <a-space direction="vertical" style="width: 100%" v-if="searchResult">
+          <a-card title="年份">
+            <a-checkbox-group v-model:value="yearChecked" style="width: 100%" @change="changeYearChecked"
+              v-if="aggYearResult && aggYearResult.length > 0">
+              <a-row v-for="(item, index) in aggYearResult" :key="index">
+                <a-col :span="20">
+                  <a-checkbox :value="item.key">{{ item.key }}</a-checkbox>
+                </a-col>
+                <a-col :span="4"> {{ item.count }}</a-col>
+              </a-row>
+            </a-checkbox-group>
+          </a-card>
+          <a-card title="作者">
+            <a-checkbox-group v-model:value="authorChecked" style="width: 100%" @change="changeAuthorChecked">
+              <a-row v-for="(item, index) in aggAuthorResult" :key="index">
+                <a-col :span="20">
+                  <a-checkbox :value="item.key">{{ item.key }}</a-checkbox>
+                </a-col>
+                <a-col :span="4"> {{ item.count }}</a-col>
+              </a-row>
+            </a-checkbox-group>
+          </a-card>
+        </a-space>
+      </a-col>
+      <a-col :span="19">
+        <SearchResultToolbar :data="searchResult" v-model:sort="sort" v-if="searchResult" @update:sort="changeSort"
+          @update:pagination="changePagination" />
+        <SearchResultList :data="searchResult" v-if="searchResult" />
+      </a-col>
+    </a-row>
+  </div>
 </template>
 
 <style scoped lang="scss">
-    .search-result-wrap {
-        margin-top: 20px;
+.search-result-wrap {
+  margin-top: 20px;
 
-        .search-box-wrap {
-            padding: 0 120px;
-        }
+  .search-box-wrap {
+    padding: 0 120px;
+  }
 
-        .search-result-main {
-            margin-top: 20px;
-        }
+  .search-result-main {
+    margin-top: 20px;
+  }
 
-        @media (max-width: 1024px) {
-            .search-box-wrap {
-                padding: 0 20px;
-            }
-        }
+  @media (max-width: 1024px) {
+    .search-box-wrap {
+      padding: 0 20px;
     }
-
+  }
+}
 </style>

+ 1 - 1
src/views/search/components/SearchResultItem.vue

@@ -13,7 +13,7 @@ defineProps({
 <template>
   <div class="item">
     <h1>
-      <RouterLink :to="'/detail/' + data._id" target="_blank">
+      <RouterLink :to="'/detail/' + data.id" target="_blank">
         {{ data.title }}
       </RouterLink>
     </h1>

+ 60 - 67
src/views/search/components/SearchResultToolbar.vue

@@ -1,84 +1,77 @@
 <script setup lang="ts">
-    import type {SelectProps} from "ant-design-vue";
-    import {ref} from "vue";
-    import type {PropType} from "vue";
-    import type {SearchResult} from "@/types/search.types";
+import type { SelectProps } from "ant-design-vue";
+import { ref } from "vue";
+import type { PropType } from "vue";
+import type { SearchResult } from "@/types/search.types";
 
-    // 排序选择项列表
-    const sortOptions = ref<SelectProps["options"]>([
-        {value: "Relevance", label: "按相关度排序"},
-        {value: "DateDesc", label: "按时间由近到远排序"},
-        {value: "DateAsc", label: "按时间由远到近排序"},
-        {value: "CitedDesc", label: "按被引量排序"},
-    ]);
+// 排序选择项列表
+const sortOptions = ref<SelectProps["options"]>([
+  { value: "Relevance", label: "按相关度排序" },
+  { value: "DateDesc", label: "按时间由近到远排序" },
+  { value: "DateAsc", label: "按时间由远到近排序" },
+  { value: "CitedDesc", label: "按被引量排序" },
+]);
 
-    defineProps({
-        data: {
-            type: Object as PropType<SearchResult>,
-            required: true,
-        },
-        sort: {
-            type: String,
-            required: true,
-            validator(value: string) {
-                return ["Relevance", "DateDesc", "DateAsc", "CitedDesc"].includes(value);
-            }
-        }
-    });
+defineProps({
+  data: {
+    type: Object as PropType<SearchResult>,
+    required: true,
+  },
+  sort: {
+    type: String,
+    required: true,
+    validator(value: string) {
+      return ["Relevance", "DateDesc", "DateAsc", "CitedDesc"].includes(value);
+    }
+  }
+});
 
-    const emits = defineEmits<{
-        (e: "update:pagination", field: string, keyword: string): void;
-        (e: "update:sort", sort: string): void;
-    }>();
+const emits = defineEmits<{
+  (e: "update:pagination", field: string, keyword: string): void;
+  (e: "update:sort", sort: string): void;
+}>();
 
-    /**
-     * 排序方式改变监听函数
-     * @param value 排序方式
-     */
-    function handleSortChange(value: string) {
-        emits("update:sort", value);
-    }
+/**
+ * 排序方式改变监听函数
+ * @param value 排序方式
+ */
+function handleSortChange(value: string) {
+  emits("update:sort", value);
+}
 
-    function onChange(size, page) {
-        emits("update:pagination", size, page);
-    }
+function onChange(size: string, page: string) {
+  emits("update:pagination", size, page);
+}
 
-    const currentPage = ref(1);
+const currentPage = ref(1);
 
 </script>
 
 <template>
-    <div class="search-result-toolbar">
+  <div class="search-result-toolbar">
+    <a-space>
+      <span>搜索结果:共 {{ data.total }} 条</span>
+      <a-divider type="vertical"></a-divider>
+      <!-- <span>
+        <span>显示:</span>
         <a-space>
-            <span>搜索结果:共 {{ data.total }} 条</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"
-                    :value="sort"
-                    style="width: 160px"
-                    size="small"
-                    :options="sortOptions"
-                    @change="handleSortChange"
-            ></a-select>
+        <a href="#">简洁</a>
+        <a href="#">详细</a>
         </a-space>
-        <a-pagination v-model:current="data.page" v-model:pageSize = "data.size" :total="data.total" show-less-items size="small"
-                      @change="onChange"/>
-    </div>
+      </span>
+      <a-divider type="vertical"></a-divider> -->
+      <a-select ref="select" :value="sort" style="width: 160px" size="small" :options="sortOptions"
+        @change="handleSortChange"></a-select>
+    </a-space>
+    <a-pagination v-model:current="data.page" v-model:pageSize="data.size" :total="data.total" show-less-items
+      size="small" @change="onChange" />
+  </div>
 </template>
 
 <style scoped lang="scss">
-
-    .search-result-toolbar {
-        display: flex;
-        padding-top: 5px;
-        justify-content: space-between;
-    }
+.search-result-toolbar {
+  display: flex;
+  padding-top: 5px;
+  justify-content: space-between;
+}
 </style>