Bookmark.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. <template>
  2. <Drawer v-model:visible="show" title="书签" @close="emits('close')">
  3. <div class="bookmark">
  4. <el-button class="bookmark__add" :icon="Plus" @click="addBookmark" />
  5. <p class="bookmark__title">总数:{{ list.length }}</p>
  6. <div v-loading="loading" class="bookmark-list">
  7. <div v-for="(item, idx) in list" :key="item.id" class="bookmark-item">
  8. <div class="bookmark-item__inner" @click="goToDetail(item)">
  9. <p>书签</p>
  10. <p>{{ item.createTime }}</p>
  11. </div>
  12. <svg-icon
  13. class="bookmark-item__close"
  14. name="icon_delete"
  15. width="24px"
  16. height="24px"
  17. color="var(--el-color-primary)"
  18. @click="handleDelete(item, idx)"
  19. />
  20. </div>
  21. <el-empty v-if="!list.length && !loading" description="暂无数据" />
  22. </div>
  23. </div>
  24. </Drawer>
  25. </template>
  26. <script setup>
  27. import { computed, ref, watch } from "vue";
  28. import { Plus } from "@element-plus/icons-vue";
  29. import { saveLabelApi, getLabelListApi, deleteLabelApi } from "@/api";
  30. import { useEpubStore, useDetailStore } from "@/stores";
  31. import Drawer from "./Drawer.vue";
  32. const props = defineProps(["visible"]);
  33. const emits = defineEmits(["update:visible", "close"]);
  34. const epubStore = useEpubStore();
  35. const detailStore = useDetailStore();
  36. const list = ref([]);
  37. const loading = ref(false);
  38. const show = computed({
  39. get() {
  40. return props.visible;
  41. },
  42. set(v) {
  43. emits("update:visible", v);
  44. },
  45. });
  46. const addBookmark = async () => {
  47. const { startCfi, page } = await epubStore.refreshLocation();
  48. const data = await saveLabelApi({
  49. type: "label",
  50. bookId: detailStore.detail.id,
  51. content: JSON.stringify({
  52. location: startCfi,
  53. page,
  54. }),
  55. });
  56. list.value.push({ ...data, content: JSON.parse(data.content) });
  57. };
  58. const getLabelList = async () => {
  59. try {
  60. loading.value = true;
  61. const data = await getLabelListApi(detailStore.detail.id, "label");
  62. list.value = data.map((i) => ({
  63. ...i,
  64. content: JSON.parse(i.content),
  65. }));
  66. } finally {
  67. loading.value = false;
  68. }
  69. };
  70. const goToDetail = (item) => {
  71. epubStore.goToChapter(item.content.location, item.content.page);
  72. };
  73. const handleDelete = (item, idx) => {
  74. list.value.splice(idx, 1);
  75. deleteLabelApi(item.id);
  76. };
  77. watch(show, (v) => {
  78. if (v && !list.value.length) {
  79. getLabelList();
  80. }
  81. });
  82. </script>
  83. <style lang="scss" scoped>
  84. .bookmark {
  85. position: relative;
  86. padding: 0 30px;
  87. &__add {
  88. position: absolute;
  89. top: -15px;
  90. right: 30px;
  91. }
  92. &__title {
  93. padding-bottom: 3px;
  94. border-bottom: 1px solid #d9d9d9;
  95. }
  96. &-list {
  97. padding: 7.5px 0;
  98. min-height: 300px;
  99. }
  100. &-item {
  101. position: relative;
  102. margin: 7.5px 0;
  103. padding-left: 17px;
  104. display: flex;
  105. align-items: center;
  106. &__inner {
  107. flex: 1;
  108. cursor: pointer;
  109. p:last-child {
  110. color: var(--text-color-placeholder);
  111. }
  112. }
  113. &__close {
  114. cursor: pointer;
  115. }
  116. &::before {
  117. content: "";
  118. position: absolute;
  119. top: 50%;
  120. left: 0;
  121. width: 5px;
  122. height: 31px;
  123. background: var(--el-color-primary);
  124. transform: translateY(-50%);
  125. }
  126. }
  127. }
  128. </style>