소스 검색

收藏列表

Kevin Jiang 2 년 전
부모
커밋
61056e9f3a

+ 25 - 0
src/client/browsingHistory.client.ts

@@ -0,0 +1,25 @@
+import httpClient from "@/services/httpClient";
+import type { Response } from "@/types/response.types";
+import * as urlHelper from "@/libs/url.lib"
+import type { BrowsingHistories } from "@/types/browsingHistory.types";
+
+const _url = urlHelper.withPrefix("/gw/user/browsing");
+
+export async function fetch(page: number, dateNum?: number): Promise<BrowsingHistories> {
+  const params: {[index: string]: number} = { page }
+  if (dateNum) {
+    params['dateNum'] = dateNum
+  }
+  const resp = await httpClient.get<Response<BrowsingHistories>>(_url("/getPage"), { params })
+  return resp.data.data
+}
+
+export async function remove(id: number): Promise<boolean> {
+  const resp = await httpClient.delete<Response<boolean>>(_url(`/delById/${id}`))
+  return resp.data.data
+}
+
+export async function removeAll(): Promise<boolean> {
+  const resp = await httpClient.delete<Response<boolean>>(_url("/delByUserId"))
+  return resp.data.data
+}

+ 5 - 4
src/client/collection.client.ts

@@ -1,6 +1,7 @@
 import httpClient from "@/services/httpClient";
 import type { Response } from "@/types/response.types";
 import * as urlHelper from "@/libs/url.lib"
+import type { Collections } from "@/types/collection.types";
 
 const _url = urlHelper.withPrefix("/gw/user/collect");
 
@@ -10,8 +11,8 @@ const _url = urlHelper.withPrefix("/gw/user/collect");
  * @param title 文献标题
  * @returns 收藏的ID
  */
-export async function add(paperId: number, title: string): Promise<number> {
-  const resp = await httpClient.post<Response<number>>(_url("/add"), {paperId, title})
+export async function add(paperId: number, title: string, authors: string, units: string): Promise<number> {
+  const resp = await httpClient.post<Response<number>>(_url("/add"), {paperId, title, authors, units})
   return resp.data.data
 }
 
@@ -30,7 +31,7 @@ export async function isCollected(paperId: number): Promise<number> {
   return resp.data.data
 }
 
-export async function list(page: number): Promise<void> {
-  const resp = await httpClient.get<Response<void>>(_url("/getPage"), { params: { page } })
+export async function list(page: number): Promise<Collections> {
+  const resp = await httpClient.get<Response<Collections>>(_url("/getPage"), { params: { page } })
   return resp.data.data
 }

+ 14 - 0
src/services/browsingHistory.service.ts

@@ -0,0 +1,14 @@
+import * as browsingHistoryClient from "@/client/browsingHistory.client"
+import type { BrowsingHistories } from "@/types/browsingHistory.types"
+
+export async function fetch(page: number, dateNum?: number): Promise<BrowsingHistories> {
+  return browsingHistoryClient.fetch(page, dateNum)
+}
+
+export async function remove(id: number) {
+  return browsingHistoryClient.remove(id)
+}
+
+export async function removeAll() {
+  return browsingHistoryClient.removeAll()
+}

+ 7 - 2
src/services/collection.service.ts

@@ -1,7 +1,8 @@
 import * as collectionClient from "@/client/collection.client"
+import type { Collections } from "@/types/collection.types"
 
-export async function add(paperId: number, title: string): Promise<number> {
-  return collectionClient.add(paperId, title)
+export async function add(paperId: number, title: string, authors: string, units: string): Promise<number> {
+  return collectionClient.add(paperId, title, authors, units)
 }
 
 export async function remove(id: number): Promise<boolean> {
@@ -11,3 +12,7 @@ export async function remove(id: number): Promise<boolean> {
 export async function isCollected(paperId: number): Promise<number> {
   return collectionClient.isCollected(paperId)
 }
+
+export async function list(page: number): Promise<Collections> {
+  return collectionClient.list(page)
+}

+ 0 - 23
src/services/user.service.ts

@@ -48,33 +48,10 @@ export function delAllHistory(currentUser: string) {
   return httpClient.delete<Response<Object>>(url, { headers });
 }
 
-export function fetchBrowsing(currentUser: string, page: number, size: number, dateNum: number) {
-  const url = _url("/browsing/getPage");
-  const headers = { currentUser };
-  const params = { page, size, dateNum };
-  return httpClient.get<Response<Page<SearchHistoryRecord>>>(url, { params, headers });
-}
-
-export function delBrowsingById(currentUser: string, id: number) {
-  const url = _url("/browsing/delById/" + id);
-  const headers = { currentUser };
-  return httpClient.delete<Response<Object>>(url, { headers });
-}
-
-export function delAllBrowsing(currentUser: string) {
-  const url = _url("/browsing/delByUserId");
-  const headers = { currentUser };
-  return httpClient.delete<Response<Object>>(url, { headers });
-}
-
 export default {
   login,
   checkLogin,
   fetchHistory,
   delHistory,
   delAllHistory,
-
-  fetchBrowsing,
-  delBrowsingById,
-  delAllBrowsing
 }

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

@@ -0,0 +1,13 @@
+export interface BrowsingHistoryItem {
+  id: number;
+  paperId: number;
+  title: string;
+  authors: string;
+  units: string;
+  updateAt: string;
+}
+
+export interface BrowsingHistories {
+  total: number;
+  items: BrowsingHistoryItem[];
+}

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

@@ -0,0 +1,13 @@
+export interface CollectionItem {
+  id: number;
+  userId: number;
+  paperId: number;
+  title: string;
+  authors: string;
+  units: string;
+}
+
+export interface Collections {
+  total: number;
+  items: CollectionItem[];
+}

+ 2 - 0
src/types/doc.types.ts

@@ -25,6 +25,8 @@ export interface PaperDetail {
   year: string;
   summary: string;
   keywords: string[];
+  authors: string;
+  units: string;
   filePath: string;
   sections: Section[];
 }

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

@@ -6,6 +6,7 @@ import { useRoute } from 'vue-router';
 import PaperSection from './components/PaperSection.vue';
 import { StarOutlined, StarFilled } from '@ant-design/icons-vue';
 import * as collectionService from "@/services/collection.service"
+import { message } from 'ant-design-vue';
 
 // 当前路由
 const route = useRoute();
@@ -27,6 +28,7 @@ function onSubSectionClick(page: number) {
 
 function onCollectionClick() {
   if (detail.value) {
+    // 已经收藏的数据进行取消收藏
     if (collectId.value > 0) {
       collectionService.remove(collectId.value).then((ok) => {
         if (ok) {
@@ -34,9 +36,13 @@ function onCollectionClick() {
         }
       })
     } else {
-      collectionService.add(detail.value.id, detail.value.title).then((id) => {
+      const docDetail = detail.value;
+      collectionService.add(docDetail.id, docDetail.title, docDetail.authors, docDetail.units).then((id) => {
+        message.success(`收藏 ${docDetail.title} 成功`);
         collectId.value = id
         isCollected.value = true
+      }).catch(() => {
+        message.error('服务器暂时不可用,请稍后再试')
       })
     }
   }

+ 54 - 24
src/views/favorite/FavoriteView.vue

@@ -1,41 +1,71 @@
 <script setup lang="ts">
-import { ref } from "vue";
+import { onMounted, ref } from "vue";
 import PageHeader from "@/components/PageHeader.vue";
+import * as collectionService from "@/services/collection.service"
+import type { Collections } from "@/types/collection.types";
+import SpinComponent from "@/components/SpinComponent.vue";
 
 const currentPage = ref(1);
+const collections = ref<Collections | null>(null)
+const loading = ref(false)
 
-function handleCancel(id: number) {
-  console.log("cancel", id);
+function fetchList(page: number = 1) {
+  loading.value = true
+  collectionService.list(page).then((colls) => {
+    collections.value = colls
+    loading.value = false
+  }).catch(() => {
+    loading.value = false
+  })
 }
+
+function onRemove(index: number, id: number) {
+  collectionService.remove(id).then(() => {
+    collections.value?.items.splice(index, 1)
+    fetchList()
+  })
+}
+
+function onPaginationChange(page: number) {
+  fetchList(page)
+}
+
+onMounted(() => {
+  fetchList()
+})
 </script>
 
 <template>
   <PageHeader title="我的收藏" />
   <a-tabs>
     <a-tab-pane key="favorite" tab="文献收藏">
-      <div class="item-wrap" v-for="(i) in [1, 2, 3, 4]" :key="i">
-        <a-row type="flex" justify="space-between" :gutter="8">
-          <a-col>
-            <h4>
-              <a href="#">工程检测</a>
-            </h4>
-            <div class="metadata">作者:item.author 机构:item.unit</div>
-          </a-col>
-          <a-col>
-            <a-button type="link" @click="handleCancel(i)">取消收藏</a-button>
-          </a-col>
-        </a-row>
-      </div>
+      <SpinComponent :spinning="loading">
+        <div class="item-wrap" v-for="(item, index) in collections?.items" :key="index">
+          <a-row type="flex" justify="space-between" :gutter="8">
+            <a-col>
+              <h4>
+                <RouterLink :to="'/detail/' + item.paperId" target="_blank">{{ item.title }}</RouterLink>
+              </h4>
+              <div class="metadata">作者:{{ item.authors }} 机构:{{ item.units }}</div>
+            </a-col>
+            <a-col>
+              <a-popconfirm
+                title="确定取消收藏?"
+                ok-text="确定"
+                cancel-text="取消"
+                @confirm="onRemove(index, item.id)"
+                placement="topRight"
+              >
+                <a href="javascript:void(0)">取消收藏</a>
+              </a-popconfirm>
+            </a-col>
+          </a-row>
+        </div>
+      </SpinComponent>
     </a-tab-pane>
   </a-tabs>
-  <div class="pagination">
-    <a-pagination v-model:current="currentPage" simple :total="50">
-      <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>
-        <component :is="originalElement" v-else></component>
-      </template>
-    </a-pagination>
+  <div class="pagination" v-if="collections">
+    <a-pagination v-model:current="currentPage" :total="collections?.total" @change="onPaginationChange" />
   </div>
 </template>
 

+ 27 - 37
src/views/history/DocumentHistoryView.vue

@@ -1,35 +1,34 @@
 <script setup lang="ts">
-import { onMounted, type Ref, ref } from "vue";
-import HistoryList from "./components/HistoryList.vue";
+import { onMounted, ref } from "vue";
+import BrowsingHistoryList from "./components/BrowsingHistoryList.vue";
 import HistoryToolbar from "./components/HistoryToolbar.vue";
-import type { SearchHistoryRecord } from "@/types/user.types";
-import * as userService from "@/services/user.service";
-import { useAuthStore } from "@/stores/auth.store";
+import * as browsingHistoryService from "@/services/browsingHistory.service"
+import type { BrowsingHistories } from "@/types/browsingHistory.types";
+import SpinComponent from "@/components/SpinComponent.vue";
 
-const authStore = useAuthStore();
-
-const records: Ref<SearchHistoryRecord[]> = ref([]);
+const histories = ref<BrowsingHistories | null>(null);
 
 const currentPage = ref(1);
 
 const page = ref(1);
 
-const total = ref(0);
+const filterValue = ref('3');
 
-const filterValue = ref("3");
+const loading = ref(false)
 
 function fetchBrowsing() {
-  const currentUser = JSON.stringify(authStore.currentUser)
-  userService.fetchBrowsing(currentUser, page.value, 10, Number(filterValue.value)).then((resp) => {
-    const respData = resp.data.data;
-    records.value = respData.records;
-    total.value = respData.total;
+  loading.value = true
+  browsingHistoryService.fetch(page.value, parseInt(filterValue.value)).then((resp) => {
+    histories.value = resp;
+    loading.value = false
+  }).catch(() => {
+    loading.value = false
   })
 }
 
 function handleDelete(id: number) {
-  const currentUser = JSON.stringify(authStore.currentUser)
-  userService.delBrowsingById(currentUser, id).then(() => {
+  loading.value = true
+  browsingHistoryService.remove(id).then(() => {
     fetchBrowsing()
   })
 }
@@ -40,20 +39,15 @@ function onfilterChange(value: string) {
 }
 
 function ondeleteAll() {
-  const currentUser = JSON.stringify(authStore.currentUser)
-  userService.delAllBrowsing(currentUser).then(() => {
+  loading.value = true
+  browsingHistoryService.removeAll().then(() => {
     fetchBrowsing()
   })
 }
 
-function onNext() {
-  page.value = currentPage.value + 1;
-  fetchBrowsing();
-}
-
-function onPrev() {
-  page.value = currentPage.value - 1;
-  fetchBrowsing();
+function onPaginationChange(p: number) {
+  page.value = p
+  fetchBrowsing()
 }
 
 onMounted(() => {
@@ -63,15 +57,11 @@ onMounted(() => {
 
 <template>
   <HistoryToolbar @filterChange="onfilterChange" @deleteAll="ondeleteAll" />
-  <HistoryList :data="records" @delete="handleDelete" class="history-list-wrap" />
+  <SpinComponent :spinning="loading">
+    <BrowsingHistoryList v-if="histories" :data="histories.items" @delete="handleDelete" class="history-list-wrap" />
+  </SpinComponent>
   <div class="pagination">
-    <a-pagination v-model:current="currentPage" simple :total="total">
-      <template #itemRender="{ type, originalElement }">
-        <a-button v-if="type === 'prev'" size="small" @click="onPrev()"> 上一页</a-button>
-        <a-button v-else-if="type === 'next'" size="small" @click="onNext()">下一页</a-button>
-        <component :is="originalElement" v-else></component>
-      </template>
-    </a-pagination>
+    <a-pagination v-model:current="currentPage" :total="histories?.total" @change="onPaginationChange" />
   </div>
 </template>
 
@@ -84,8 +74,8 @@ onMounted(() => {
   margin-top: 16px;
   text-align: right;
 
-  .ant-pagination {
-    height: 28px;
+  :deep(.ant-pagination) {
+    height: 32px;
   }
 }
 </style>

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

@@ -1,10 +1,11 @@
 <script setup lang="ts">
 import { useAuthStore } from "@/stores/auth.store";
 import { ref, onMounted, type Ref } from "vue";
-import HistoryList from "./components/HistoryList.vue";
+import SearcHistoryList from "./components/SearchHistoryList.vue";
 import HistoryToolbar from "./components/HistoryToolbar.vue";
 import * as userService from "@/services/user.service"
 import type { SearchHistoryRecord } from "@/types/user.types"
+import SpinComponent from "@/components/SpinComponent.vue";
 
 const authStore = useAuthStore();
 
@@ -18,12 +19,18 @@ const total = ref(0);
 
 const filterValue = ref("3");
 
+const loading = ref(false)
+
 function fetchHistory() {
+  loading.value = true
   const currentUser = JSON.stringify(authStore.currentUser)
   userService.fetchHistory(currentUser, currentPage.value, 10, Number(filterValue.value)).then((resp) => {
     const respData = resp.data.data;
     records.value = respData.records;
     total.value = respData.total;
+    loading.value = false
+  }).catch(() => {
+    loading.value = false
   })
 }
 
@@ -46,14 +53,9 @@ function ondeleteAll() {
   })
 }
 
-function onNext() {
-  page.value = currentPage.value + 1;
-  fetchHistory();
-}
-
-function onPrev() {
-  page.value = currentPage.value - 1;
-  fetchHistory();
+function onPaginationChange(p: number) {
+  page.value = p
+  fetchHistory()
 }
 
 onMounted(() => {
@@ -63,15 +65,11 @@ onMounted(() => {
 
 <template>
   <HistoryToolbar @filterChange="onfilterChange" @deleteAll="ondeleteAll" />
-  <HistoryList :data="records" @delete="handleDelete" class="history-list-wrap" />
+  <SpinComponent :spinning="loading">
+    <SearcHistoryList :data="records" @delete="handleDelete" class="history-list-wrap" />
+  </SpinComponent>
   <div class="pagination">
-    <a-pagination v-model:current="currentPage" simple :total="total">
-      <template #itemRender="{ type, originalElement }">
-        <a-button v-if="type === 'prev'" size="small" @click="onPrev()"> 上一页</a-button>
-        <a-button v-else-if="type === 'next'" size="small" @click="onNext()">下一页</a-button>
-        <component :is="originalElement" v-else></component>
-      </template>
-    </a-pagination>
+    <a-pagination v-model:current="currentPage" :total="total" @change="onPaginationChange" />
   </div>
 </template>
 
@@ -84,8 +82,8 @@ onMounted(() => {
   margin-top: 16px;
   text-align: right;
 
-  .ant-pagination {
-    height: 28px;
+  :deep(.ant-pagination) {
+    height: 32px;
   }
 }
 </style>

+ 69 - 0
src/views/history/components/BrowsingHistoryItem.vue

@@ -0,0 +1,69 @@
+<script setup lang="ts">
+import type { PropType } from "vue";
+import { DeleteOutlined } from "@ant-design/icons-vue";
+import type { BrowsingHistoryItem } from "@/types/browsingHistory.types";
+
+const props = defineProps({
+  data: {
+    type: Object as PropType<BrowsingHistoryItem>,
+    required: true,
+  }
+});
+
+const emits = defineEmits<{
+  (e: "delete", id: number): void;
+}>();
+
+function handleDelete() {
+  emits("delete", props.data.id);
+}
+</script>
+
+<template>
+  <div class="history-item-wrap">
+    <a-row type="flex" justify="space-between" :gutter="8">
+      <a-col flex="1" class="item-content">
+        <span class="item-time">{{ data.updateAt }}</span>
+        <span class="item-expression" v-if="data.title">
+          <RouterLink :to="'/detail/' + data.paperId" target="_blank">{{ data.title }}</RouterLink>
+        </span>
+      </a-col>
+      <a-col>
+        <a-popconfirm
+          title="确定删除?"
+          ok-text="确定"
+          cancel-text="取消"
+          @confirm="handleDelete"
+          placement="topRight"
+        >
+          <a-button type="text">
+            <template #icon><delete-outlined /></template>
+          </a-button>
+        </a-popconfirm>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import "@/assets/base.scss";
+
+.history-item-wrap {
+  padding: 8px 16px;
+  border: 1px solid $border-color-primary;
+  border-radius: 5px;
+
+  &:hover {
+    background-color: #fcfcfc;
+  }
+
+  .item-content {
+    display: flex;
+    align-items: center;
+
+    .item-expression {
+      margin-left: 2em;
+    }
+  }
+}
+</style>

+ 42 - 0
src/views/history/components/BrowsingHistoryList.vue

@@ -0,0 +1,42 @@
+<script setup lang="ts">
+import type { BrowsingHistoryItem } from "@/types/browsingHistory.types";
+import type { PropType } from "vue";
+import BrowsingHistoryItemComp from "./BrowsingHistoryItem.vue";
+
+defineProps({
+  data: {
+    type: Array as PropType<BrowsingHistoryItem[]>,
+    required: true,
+  }
+});
+
+const emits = defineEmits<{
+  (e: "delete", id: number): void;
+}>();
+
+function handleDelete(id: number) {
+  emits('delete', id);
+}
+</script>
+
+<template>
+  <div class="histories">
+    <BrowsingHistoryItemComp
+      v-for="(item, i) in data"
+      :data="item"
+      :key="i"
+      @delete="handleDelete(item.id)"
+      class="history-item-comp"
+    />
+  </div>
+</template>
+
+<style scoped lang="scss">
+.history-item-comp {
+  margin-top: 8px;
+
+  &:first-child {
+    margin-top: 0;
+  }
+}
+</style>

+ 9 - 1
src/views/history/components/HistoryToolbar.vue

@@ -49,7 +49,15 @@ function handleDeleteAll() {
       ></a-select>
     </a-col>
     <a-col>
-      <a-button @click="handleDeleteAll">清除所有记录</a-button>
+      <a-popconfirm
+        title="确定删除?"
+        ok-text="确定"
+        cancel-text="取消"
+        @confirm="handleDeleteAll"
+        placement="topRight"
+      >
+        <a-button>清除所有记录</a-button>
+      </a-popconfirm>
     </a-col>
   </a-row>
 </template>

+ 11 - 3
src/views/history/components/HistoryItem.vue

@@ -42,9 +42,17 @@ function handleDelete() {
         </span>
       </a-col>
       <a-col>
-        <a-button type="text" @click="handleDelete">
-          <template #icon><delete-outlined /></template>
-        </a-button>
+        <a-popconfirm
+          title="确定删除?"
+          ok-text="确定"
+          cancel-text="取消"
+          @confirm="handleDelete"
+          placement="topRight"
+        >
+          <a-button type="text">
+            <template #icon><delete-outlined /></template>
+          </a-button>
+        </a-popconfirm>
       </a-col>
     </a-row>
   </div>

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

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import type { SearchHistoryRecord } from "@/types/user.types";
 import type { PropType } from "vue";
-import HistoryItemComp from "./HistoryItem.vue";
+import HistoryItemComp from "./SearchHistoryItem.vue";
 
 defineProps({
   data: {