Browse Source

集成搜索历史记录后端数据

Kevin Jiang 2 years ago
parent
commit
b656edff17

+ 1 - 0
src/components/NavMenu.vue

@@ -29,6 +29,7 @@ onMounted(() => {
     (path: string) => {
       const currentOpenkeys = getOpenKeys(path);
       openKeys.value = _.uniq(_.concat(openKeys.value, currentOpenkeys));
+      selectedKeys.value = openKeys.value;
     }
   );
 });

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

@@ -1,10 +1,17 @@
-import type { UserLoginRequest, UserLoginResponse } from "@/types/user.types";
+import type { SearchHistoryRecord, UserLoginRequest, UserLoginResponse } from "@/types/user.types";
+import type { Page } from "@/types/pagination.types"
 import type { Response } from "@/types/response.types";
 import type { AxiosResponse } from "axios";
 import httpClient from "./httpClient";
 
+const urlPrefix = "/gw/user";
+
+function _url(path: string): string {
+  return `${urlPrefix}${path}`;
+}
+
 function login(param: UserLoginRequest): Promise<AxiosResponse<Response<UserLoginResponse>>> {
-  return httpClient.post("/gw/user/login", param);
+  return httpClient.post(_url("/login"), param);
 }
 
 async function checkLogin(): Promise<boolean> {
@@ -15,7 +22,22 @@ async function checkLogin(): Promise<boolean> {
   return false;
 }
 
+/**
+ * 获取指定页码和数量的搜索历史记录
+ * @param currentUser user对象的json字符串
+ * @param page 页码
+ * @param size 每页数量
+ * @returns Promise
+ */
+export function fetchHistory(currentUser: string, page: number, size: number) {
+  const url = _url("/searchHistory/getMySearchHistory");
+  const headers = { currentUser };
+  const params = { page, size };
+  return httpClient.get<Response<Page<SearchHistoryRecord>>>(url, { params, headers });
+}
+
 export default {
   login,
   checkLogin,
+  fetchHistory,
 }

+ 8 - 1
src/stores/auth.store.ts

@@ -1,4 +1,4 @@
-import { ref } from "vue";
+import { ref, type Ref } from "vue";
 import { defineStore } from "pinia";
 import userService from "@/services/user.service";
 
@@ -6,12 +6,17 @@ const tokenKey = "token";
 
 export const useAuthStore = defineStore('auth', () => {
   const token = ref(localStorage.getItem(tokenKey));
+  const currentUser: Ref<Object | null> = ref(null);
 
   function setToken(_token: string) {
     token.value = _token;
     localStorage.setItem(tokenKey, _token);
   }
 
+  function setCurrentUser(user: Object) {
+    currentUser.value = user;
+  }
+
   function isLoginIn() {
     return token.value && token.value.trim() != "";
   }
@@ -26,7 +31,9 @@ export const useAuthStore = defineStore('auth', () => {
 
   return {
     token,
+    currentUser,
     setToken,
+    setCurrentUser,
     isLoginIn,
     logout,
     checkLogin,

+ 15 - 0
src/stores/history.store.ts

@@ -0,0 +1,15 @@
+import { ref } from "vue";
+import { defineStore } from "pinia";
+
+export const useHistoryStore = defineStore('history', () => {
+  const searchHistory = ref<any>([]);
+
+  function setSearchHistory(histories: any[]) {
+    searchHistory.value = histories;
+  }
+
+  return {
+    searchHistory,
+    setSearchHistory
+  }
+});

+ 6 - 0
src/types/pagination.types.ts

@@ -0,0 +1,6 @@
+export interface Page<T> {
+  records: T[];
+  total: number;
+  size: number;
+  current: number;
+}

+ 2 - 1
src/types/request.types.ts

@@ -5,7 +5,8 @@ export interface SearchRequest {
   from?: number;
   size?: number;
   aggs?: SearchAggRequest;
-  sort?: any
+  sort?: any;
+  isSaveHis?: boolean;
 }
 
 export interface SearchAggRequest {}

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

@@ -11,3 +11,9 @@ export interface UserLoginRequest {
 export interface UserLoginResponse {
   token: string;
 }
+
+export interface SearchHistoryRecord {
+  id: number;
+  expression: string;
+  createdTime: string;
+}

+ 1 - 0
src/views/LoginView.vue

@@ -26,6 +26,7 @@ const onFinish = (values: any) => {
     type: 1
   }
   user.login(param).then((resp) => {
+    authStore.setCurrentUser(resp.data.data);
     authStore.setToken(resp.data.data.token);
     routeToHome();
   }).catch((err) => {

+ 20 - 17
src/views/history/SearchHistoryView.vue

@@ -1,35 +1,38 @@
 <script setup lang="ts">
-import { ref } from "vue";
+import { useAuthStore } from "@/stores/auth.store";
+import { ref, onMounted, type Ref } from "vue";
 import HistoryList from "./components/HistoryList.vue";
 import HistoryToolbar from "./components/HistoryToolbar.vue";
+import * as userService from "@/services/user.service"
+import type { SearchHistoryRecord } from "@/types/user.types"
 
-const data = ref([
-  {
-    id: 1,
-    label: '检索',
-    text: 'TP=(建筑)',
-    link: '/history/1'
-  },
-  {
-    id: 2,
-    label: '检索',
-    text: 'TP=(建筑)',
-    link: '/history/2'
-  }
-]);
+const authStore = useAuthStore();
+
+const records: Ref<SearchHistoryRecord[]> = ref([]);
 
 const currentPage = ref(1);
 
+const total = ref(0);
+
 function handleDelete(id: number) {
   console.log("delete", id);
 }
+
+onMounted(() => {
+  const currentUser = JSON.stringify(authStore.currentUser)
+  userService.fetchHistory(currentUser, 1, 10).then((resp) => {
+    const respData = resp.data.data;
+    records.value = respData.records;
+    total.value = respData.total;
+  })
+});
 </script>
 
 <template>
   <HistoryToolbar />
-  <HistoryList :data="data" @delete="handleDelete" class="history-list-wrap" />
+  <HistoryList :data="records" @delete="handleDelete" class="history-list-wrap" />
   <div class="pagination">
-    <a-pagination v-model:current="currentPage" simple :total="50">
+    <a-pagination v-model:current="currentPage" simple :total="total">
       <template #itemRender="{ type, originalElement }">
         <a-button v-if="type === 'prev'" size="small"> 上一页</a-button>
         <a-button v-else-if="type === 'next'" size="small">下一页</a-button>

+ 24 - 5
src/views/history/components/HistoryItem.vue

@@ -1,11 +1,13 @@
 <script setup lang="ts">
-import { DeleteOutlined } from "@ant-design/icons-vue";
 import type { PropType } from "vue";
-import type { HistoryItem } from "../history-component.type"
+import { DeleteOutlined } from "@ant-design/icons-vue";
+import type { SearchHistoryRecord } from "@/types/user.types";
+import { routeToSearch } from "@/router";
+import type { SearchRouteParam } from "@/types/route.type";
 
 const props = defineProps({
   data: {
-    type: Object as PropType<HistoryItem>,
+    type: Object as PropType<SearchHistoryRecord>,
     required: true,
   }
 });
@@ -14,6 +16,14 @@ const emits = defineEmits<{
   (e: "delete", id: number): void;
 }>();
 
+function routeToSearchResult(expression: string) {
+  const params: SearchRouteParam = {
+    type: 'advanced',
+    query: expression
+  }
+  routeToSearch(params);
+}
+
 function handleDelete() {
   emits("delete", props.data.id);
 }
@@ -23,8 +33,13 @@ function handleDelete() {
   <div class="history-item-wrap">
     <a-row type="flex" justify="space-between" :gutter="8">
       <a-col flex="1" class="item-content">
-        <span>{{ data.label }}:</span>
-        <a :href="data.link">{{ data.text }}</a>
+        <span class="item-time">{{ data.createdTime }}</span>
+        <span class="item-expression">
+          <span>检索:</span>
+          <a href="javascript:void(0)" @click="routeToSearchResult(data.expression)" type="link">
+            {{ data.expression }}
+          </a>
+        </span>
       </a-col>
       <a-col>
         <a-button type="text" @click="handleDelete">
@@ -50,6 +65,10 @@ function handleDelete() {
   .item-content {
     display: flex;
     align-items: center;
+
+    .item-expression {
+      margin-left: 2em;
+    }
   }
 }
 </style>

+ 2 - 2
src/views/history/components/HistoryList.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
+import type { SearchHistoryRecord } from "@/types/user.types";
 import type { PropType } from "vue";
-import type { HistoryItem } from "../history-component.type";
 import HistoryItemComp from "./HistoryItem.vue";
 
 defineProps({
   data: {
-    type: Array as PropType<HistoryItem[]>,
+    type: Array as PropType<SearchHistoryRecord[]>,
     required: true,
   }
 });

+ 18 - 5
src/views/search/SearchResultView.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
-import { onUnmounted, ref, computed, type Ref, onMounted, reactive } from "vue";
+import { onUnmounted, ref, computed, type Ref, onMounted, reactive, h } from "vue";
 import { useRoute } from "vue-router";
+import { LoadingOutlined } from '@ant-design/icons-vue';
 import { useSideBarStore } from "@/stores/side-bar";
 import SearchBox from "./components/SearchBox.vue";
 import SearchResultToolbar from "./components/SearchResultToolbar.vue";
@@ -44,6 +45,9 @@ const resultMode: Ref<ResultMode> = ref('detail');
 
 const query = ref('');
 
+// 加载状态
+const spinning = ref(true);
+
 interface SearchParams {
   query: string;
   yearSelected?: string[];
@@ -58,6 +62,7 @@ interface SearchParams {
  * @param searchParams 搜索状态数据
  */
 function performSearch(searchParams: SearchParams) {
+  spinning.value = true;
   let sortParam: Object = { "Relevance": "Relevance" };
   if (searchParams.sort) {
     const sort = searchParams.sort;
@@ -92,17 +97,18 @@ function performSearch(searchParams: SearchParams) {
     from,
     size,
     sort: sortParam,
+    isSaveHis: true,
   };
 
-  searchStore.search(param);
+  searchStore.search(param).then(() => spinning.value = false).catch(() => spinning.value = false);
   searchStore.aggYear({
     ...commonParams,
-    aggs: { year: {} },
+    aggs: { year: { sort: "key/desc" } },
     size: 0,
   });
   searchStore.aggAuthor({
     ...commonParams,
-    aggs: { AU: {} },
+    aggs: { AU: { sort: "count/desc" } },
     size: 0,
   });
 }
@@ -146,6 +152,11 @@ function onSimpleSearch() {
   doSearch();
 }
 
+function onAdvancedSearch() {
+  routeToSearch({ type: 'advanced', query: query.value })
+  doSearch();
+}
+
 /**
  * 根据url包含的信息,初始化状态信息
  */
@@ -257,7 +268,7 @@ onUnmounted(() => {
       />
     </div>
     <div class="search-box-wrap" v-if="searchType == 'advanced'">
-      <AdvancedSearchBox :query="query" />
+      <AdvancedSearchBox v-model:query="query" @search="onAdvancedSearch" />
     </div>
     <a-row type="flex" class="search-result-main" :gutter="10">
       <a-col :span="5">
@@ -289,6 +300,7 @@ onUnmounted(() => {
         </a-space>
       </a-col>
       <a-col :span="19">
+        <a-spin :spinning="spinning" :indicator="h(LoadingOutlined, { style: { fontSize: '24px', }, spin: true, })">
         <SearchResultToolbar
           v-if="searchResult"
           :data="searchResult"
@@ -299,6 +311,7 @@ onUnmounted(() => {
           @modeChange="onModeChange"
         />
         <SearchResultList :data="searchResult" :mode="resultMode" v-if="searchResult" />
+        </a-spin>
       </a-col>
     </a-row>
   </div>

+ 8 - 3
src/views/search/components/AdvancedSearchBox.vue

@@ -8,11 +8,15 @@ defineProps({
 
 const emits = defineEmits<{
   (e: "update:query", query: string): void;
+  (e: "search", query: string): void;
 }>();
 
-function onSearch(e: any) {
-  console.log(e);
-  emits("update:query", e);
+function onQueryChange(e: InputEvent) {
+  emits("update:query", (e.target as HTMLInputElement).value);
+}
+
+function onSearch(query: string) {
+  emits("search", query);
 }
 </script>
 
@@ -23,5 +27,6 @@ function onSearch(e: any) {
     enter-button="搜&nbsp;&nbsp;索"
     size="large"
     @search="onSearch"
+    @change="onQueryChange"
   />
 </template>