chenlei 9 hónapja
szülő
commit
1a9c50a16a

+ 39 - 26
packages/base/src/stores/epub.js

@@ -64,10 +64,10 @@ export const useEpubStore = defineStore("epub", () => {
    *   }
    * })
    */
-  const init = (options = {}) => {
+  const init = async (options = {}) => {
     fileName.value = getFileName(options.url);
 
-    initEpub(options);
+    await initEpub(options);
     initTheme(options.themes);
     const locationStorage = localStorage.getItem(
       `${EPUB_LOCATION}-${fileName.value}`
@@ -82,36 +82,49 @@ export const useEpubStore = defineStore("epub", () => {
   };
 
   const initEpub = (options) => {
-    const _book = ePub(options.url);
-    const _rendition = _book.renderTo("reader", {
-      width: "100%",
-      height: "100%",
-      ...(options.renderOptions || {}),
-    });
-    console.log(_book, _rendition);
+    return new Promise((res, rej) => {
+      const _book = ePub();
 
-    _rendition.hooks.render.register((v) => {
-      bodyNode = v.document.body;
-      convertText();
-    });
+      _book
+        .open(options.url)
+        .then(() => {
+          const _rendition = _book.renderTo("reader", {
+            width: "100%",
+            height: "100%",
+            ...(options.renderOptions || {}),
+          });
 
-    _book.loaded.metadata.then((res) => {
-      metadata.value = res;
-    });
+          _rendition.hooks.render.register((v) => {
+            bodyNode = v.document.body;
+            convertText();
+          });
 
-    _book.loaded.navigation.then((res) => {
-      navigation.value = res.toc.map((item) => ({
-        ...item,
-        label: item.label.replace(/\s+/g, ""),
-      }));
-    });
+          _book.loaded.metadata.then((res) => {
+            metadata.value = res;
+          });
 
-    book.value = _book;
-    rendition.value = _rendition;
+          _book.loaded.navigation.then((res) => {
+            navigation.value = res.toc.map((item) => ({
+              ...item,
+              label: item.label.replace(/\s+/g, ""),
+            }));
+          });
+
+          book.value = _book;
+          rendition.value = _rendition;
+          res();
+        })
+        .catch((err) => {
+          console.log(err);
+          options.error?.();
+          rej();
+        });
+    });
   };
 
   const clear = () => {
-    rendition.value.clear();
+    console.log(rendition.value);
+    rendition.value?.clear();
     rendition.value = null;
     book.value = null;
     fileName.value = null;
@@ -210,7 +223,7 @@ export const useEpubStore = defineStore("epub", () => {
    * @param {String | Number} page
    */
   const goToChapter = async (cfi, page) => {
-    await rendition.value.display(cfi);
+    await rendition.value.display();
 
     if (page) {
       nextTick(() => {

+ 11 - 1
packages/mobile/src/views/Detail/index.vue

@@ -45,8 +45,9 @@
 </template>
 
 <script setup>
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { ref, computed, watch } from "vue";
+import { showNotify } from "vant";
 import { storeToRefs } from "pinia";
 import { useRouteQuery } from "@vueuse/router";
 import { useBaseStore } from "@/stores";
@@ -95,6 +96,7 @@ const TABS = [
 ];
 
 const route = useRoute();
+const router = useRouter();
 const baseUrl = getBaseUrl();
 const baseStore = useBaseStore();
 const { isLogin } = storeToRefs(baseStore);
@@ -116,6 +118,14 @@ const getBookDetail = async () => {
   const data = await (isLogin.value ? getBookDetail2Api : getBookDetailApi)(
     route.params.id
   );
+  if (!data) {
+    showNotify({
+      type: "warning",
+      message: "图书丢失了",
+    });
+    router.replace({ name: "home" });
+    return;
+  }
   detail.value = data;
 };
 

+ 1 - 1
packages/pc/.env.development

@@ -1 +1 @@
-VITE_BASE_URL=http://192.168.20.61:8072
+VITE_BASE_URL=https://sit-liushaoqibwg.4dage.com

+ 1 - 0
packages/pc/package.json

@@ -20,6 +20,7 @@
     "pinia": "^2.1.7",
     "vue": "^3.4.29",
     "vue-clipboard3": "^2.0.0",
+    "vue-qrcode": "^2.2.2",
     "vue-router": "^4.3.3",
     "vue-virtual-scroller": "2.0.0-beta.8"
   },

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 10 - 0
packages/pc/src/assets/svgs/icon_share_yellow.svg


+ 12 - 10
packages/pc/src/components/TopNav/components/MobileDialog.vue

@@ -1,12 +1,21 @@
 <template>
   <el-dialog v-model="show" class="mobile-dialog" width="425px">
-    <div class="mobile-dialog__scan" />
+    <vue-qrcode
+      class="mobile-dialog__scan"
+      type="image/png"
+      :color="{
+        dark: '#000000',
+        light: '#ffffff',
+      }"
+      :value="mobileUrl"
+    />
     <span>手机扫描二维码</span>
   </el-dialog>
 </template>
 
 <script setup>
-import { computed, ref, watch, nextTick } from "vue";
+import { computed, ref } from "vue";
+import VueQrcode from "vue-qrcode";
 
 const props = defineProps({
   visible: {
@@ -15,7 +24,7 @@ const props = defineProps({
   },
 });
 const emits = defineEmits(["update:visible"]);
-const loading = ref(false);
+const mobileUrl = ref(location.origin + "/mobile/index.html");
 
 const show = computed({
   get() {
@@ -25,13 +34,6 @@ const show = computed({
     emits("update:visible", v);
   },
 });
-
-watch(show, (v) => {
-  v &&
-    nextTick(() => {
-      login();
-    });
-});
 </script>
 
 <style lang="scss">

+ 6 - 0
packages/pc/src/components/TopNav/index.scss

@@ -25,6 +25,10 @@
     }
   }
 
+  &-lf {
+    padding-right: 10px;
+  }
+
   &-rg {
     display: flex;
     align-items: center;
@@ -34,6 +38,7 @@
       align-items: center;
       gap: 57px;
       font-size: 18px;
+      white-space: nowrap;
       font-family: "Source Han Serif CN-Bold";
 
       li {
@@ -53,6 +58,7 @@
     align-items: center;
     gap: 20px;
     font-size: 18px;
+    white-space: nowrap;
     font-family: "Source Han Serif CN-Bold";
     cursor: pointer;
 

+ 1 - 0
packages/pc/src/components/TopNav/index.vue

@@ -200,6 +200,7 @@ const handleLike = async () => {
       await updateBookCollectApi(detail.value.id, {
         status: 1,
       });
+      detail.value.isCollect = 1;
     } finally {
       joinLoading.value = false;
     }

+ 2 - 1
packages/pc/src/utils/index.js

@@ -4,7 +4,8 @@ export const isDevelopment = import.meta.env.MODE === "development";
 
 export const getBaseUrl = () => {
   const baseUrl = getBaseURL();
-  return `${baseUrl}${isDevelopment ? "/api" : ""}`;
+  return baseUrl;
+  // return `${baseUrl}${isDevelopment ? "/api" : ""}`;
 };
 
 export const getFixUrl = (path) => {

+ 1 - 0
packages/pc/src/views/Detail/components/Comment.vue

@@ -66,6 +66,7 @@ const { pageNum, loading, list, noData, noMore, getList } = usePagination(
     return getMessageListApi({
       ...params,
       auditStatus: 1,
+      bookId: detailStore.detail.id,
     });
   }
 );

+ 9 - 5
packages/pc/src/views/Detail/components/Reader/index.vue

@@ -78,14 +78,14 @@
 
 <script setup>
 import { onMounted, onBeforeUnmount, ref, watch } from "vue";
-import { ElNotification } from "element-plus";
+import { ElMessage, ElNotification } from "element-plus";
 import useClipboard from "vue-clipboard3";
 import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
+import { storeToRefs } from "pinia";
+import { getBaseUrl } from "@/utils";
 import { useEpubStore, useDetailStore, useBaseStore } from "@/stores";
 import { COLOR_MAP } from "./constants";
 import { updateBookCollectApi, saveLabelApi, deleteLabelApi } from "@/api";
-import { storeToRefs } from "pinia";
-import { getBaseUrl } from "@/utils";
 
 const props = defineProps(["detail", "isLogin"]);
 
@@ -99,9 +99,13 @@ const selectedCfi = ref("");
 const baseUrl = getBaseUrl();
 const bookLoading = ref(true);
 
-onMounted(() => {
-  epubStore.init({
+onMounted(async () => {
+  await epubStore.init({
     url: baseUrl + props.detail.filePath,
+    error() {
+      ElMessage.error("图书加载失败");
+      bookLoading.value = false;
+    },
   });
 
   epubStore.rendition.hooks.render.register((v) => {

+ 32 - 1
packages/pc/src/views/Detail/components/Toolbar/index.vue

@@ -3,7 +3,7 @@
     v-for="(item, idx) in list"
     :key="item.key"
     class="detail-toolbar-item"
-    :class="{ active: active === idx && active !== list.length - 1 }"
+    :class="{ active: active === idx && active !== list.length - 2 }"
     :style="{
       top: item.top + 'px',
     }"
@@ -29,6 +29,7 @@
 <script setup>
 import { ref, computed, watch } from "vue";
 import { useFullscreen } from "@vueuse/core";
+import useClipboard from "vue-clipboard3";
 import { useEpubStore, useDetailStore, useBaseStore } from "@/stores";
 import Directory from "../Directory.vue";
 import Setting from "../Setting.vue";
@@ -47,6 +48,8 @@ const settingVisible = ref(false);
 const noteVisible = ref(false);
 const bookmarkVisible = ref(false);
 const commentVisible = ref(false);
+const { toClipboard } = useClipboard();
+
 const list = computed(() => {
   const stack = [
     {
@@ -85,6 +88,12 @@ const list = computed(() => {
       icon: "icon_comment_yellow",
       key: "comment",
     },
+    {
+      top: 0,
+      label: "分享",
+      icon: "icon_share_yellow",
+      key: "share",
+    },
   ];
   if (isSupported.value) {
     stack.push({
@@ -128,6 +137,9 @@ const handleToolbar = (key, idx) => {
     case "comment":
       commentVisible.value = true;
       break;
+    case "share":
+      handleClipboard();
+      break;
     case "fullscreen":
       toggle();
       break;
@@ -136,6 +148,25 @@ const handleToolbar = (key, idx) => {
   active.value = idx;
 };
 
+const handleClipboard = async () => {
+  try {
+    await toClipboard(location.href);
+    ElNotification({
+      type: "success",
+      message: "复制成功",
+      position: "bottom-right",
+      offset: 20,
+    });
+  } catch (err) {
+    ElNotification({
+      type: "error",
+      message: "复制失败",
+      position: "bottom-right",
+      offset: 20,
+    });
+  }
+};
+
 const handleClose = () => {
   active.value = -1;
 };

+ 7 - 1
packages/pc/src/views/Detail/index.vue

@@ -13,7 +13,7 @@
 
 <script setup>
 import { computed, ref, watch, onUnmounted } from "vue";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { THEMES } from "@lsq/base";
 import { getBookDetailApi, getBookDetail2Api } from "@/api";
 import { storeToRefs } from "pinia";
@@ -25,6 +25,7 @@ import DetailVideo from "./components/Video/index.vue";
 import IntroductionDialog from "./components/IntroductionDialog.vue";
 
 const route = useRoute();
+const router = useRouter();
 const baseStore = useBaseStore();
 const epubStore = useEpubStore();
 const detailStore = useDetailStore();
@@ -54,6 +55,11 @@ const getBookDetail = async () => {
     const data = await (isLogin.value ? getBookDetail2Api : getBookDetailApi)(
       route.params.id
     );
+    if (!data) {
+      ElMessage.warning("图书丢失了");
+      router.replace({ name: "home" });
+      return;
+    }
     detail.value = data;
   } finally {
     loading.value = false;

+ 2 - 1
packages/pc/src/views/Home/index.vue

@@ -68,7 +68,8 @@ onMounted(() => {
   };
   // 鼠标移入
   window.hoverObject = (val) => {
-    const item = list.value[val.target.index];
+    const item = list.value.find((i) => i.thumb.indexOf(val.imgName) > -1);
+    if (!item) return;
 
     txt.value = {
       title: item.name,

+ 6 - 2
packages/pc/src/views/Stack/components/SearchInput/index.vue

@@ -6,13 +6,17 @@
       @input="emit('update:modelValue', $event.target.value)"
     />
 
-    <img src="@/assets/images/icon_search2-min.png" draggable="false" />
+    <img
+      src="@/assets/images/icon_search2-min.png"
+      draggable="false"
+      @click="emit('search')"
+    />
   </div>
 </template>
 
 <script setup>
 const props = defineProps(["modelValue"]);
-const emit = defineEmits(["update:modelValue"]);
+const emit = defineEmits(["update:modelValue", "search"]);
 </script>
 
 <style lang="scss" scoped>

+ 6 - 2
packages/pc/src/views/Stack/components/Sidebar/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="stack-sidebar">
     <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-      <search-input v-model="searchKey" />
+      <search-input v-model="searchKey" @search="handleSearch" />
 
       <el-tab-pane label="按书籍分类" name="book">
         <el-scrollbar v-loading="bookLoading" scroll-y max-height="100%">
@@ -115,7 +115,11 @@ const getBreadCrumb = (item) => {
 
 const handleSelect = (item) => {
   checkedId.value = item.id;
-  emits("select", getBreadCrumb(item));
+  emits("select", "classify", getBreadCrumb(item));
+};
+
+const handleSearch = () => {
+  emits("select", "search", searchKey.value);
 };
 
 const findItemById = (data, id) => {

+ 11 - 2
packages/pc/src/views/Stack/index.vue

@@ -75,8 +75,17 @@ const handlePage = (page) => {
   getList();
 };
 
-const handleSelect = (list) => {
-  breadcrumb.value = list;
+const resetSearch = () => {
+  pageNum.value = 1;
+  getList();
+};
+
+const handleSelect = (type, data) => {
+  if (type === "classify") {
+    // 切换分类
+    breadcrumb.value = data;
+  }
+  resetSearch();
 };
 
 watch(breadcrumb, (v) => {

+ 1 - 1
packages/pc/vite.config.js

@@ -19,7 +19,7 @@ export default defineConfig({
     port: 80,
     proxy: {
       "/api": {
-        target: "http://192.168.20.61:8072",
+        target: "https://sit-liushaoqibwg.4dage.com",
         changeOrigin: true,
       },
     },

+ 198 - 0
pnpm-lock.yaml

@@ -126,6 +126,9 @@ importers:
       vue-clipboard3:
         specifier: ^2.0.0
         version: 2.0.0
+      vue-qrcode:
+        specifier: ^2.2.2
+        version: 2.2.2(qrcode@1.5.4)(vue@3.5.11)
       vue-router:
         specifier: ^4.3.3
         version: 4.4.5(vue@3.5.11)
@@ -1284,6 +1287,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /ansi-regex@5.0.1:
+    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+    engines: {node: '>=8'}
+    dev: false
+
   /ansi-styles@2.2.1:
     resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
     engines: {node: '>=0.10.0'}
@@ -1296,6 +1304,13 @@ packages:
       color-convert: 1.9.3
     dev: true
 
+  /ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+    dependencies:
+      color-convert: 2.0.1
+    dev: false
+
   /anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -1486,6 +1501,11 @@ packages:
       set-function-length: 1.2.2
     dev: true
 
+  /camelcase@5.3.1:
+    resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+    engines: {node: '>=6'}
+    dev: false
+
   /caniuse-lite@1.0.30001668:
     resolution: {integrity: sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==}
     dev: true
@@ -1547,6 +1567,14 @@ packages:
       tiny-emitter: 2.1.0
     dev: false
 
+  /cliui@6.0.0:
+    resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 6.2.0
+    dev: false
+
   /clone@2.1.2:
     resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
     engines: {node: '>=0.8'}
@@ -1566,10 +1594,21 @@ packages:
       color-name: 1.1.3
     dev: true
 
+  /color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+    dependencies:
+      color-name: 1.1.4
+    dev: false
+
   /color-name@1.1.3:
     resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
     dev: true
 
+  /color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+    dev: false
+
   /commander@7.2.0:
     resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
     engines: {node: '>= 10'}
@@ -1697,6 +1736,11 @@ packages:
       ms: 2.1.3
     dev: true
 
+  /decamelize@1.2.0:
+    resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
   /decode-uri-component@0.2.2:
     resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
     engines: {node: '>=0.10'}
@@ -1751,6 +1795,10 @@ packages:
     resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
     dev: false
 
+  /dijkstrajs@1.0.3:
+    resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+    dev: false
+
   /dom-serializer@0.2.2:
     resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
     dependencies:
@@ -1831,6 +1879,10 @@ packages:
       - '@vue/composition-api'
     dev: false
 
+  /emoji-regex@8.0.0:
+    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+    dev: false
+
   /emojis-list@3.0.0:
     resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
     engines: {node: '>= 4'}
@@ -2086,6 +2138,14 @@ packages:
     engines: {node: '>=14.16'}
     dev: false
 
+  /find-up@4.1.0:
+    resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+    engines: {node: '>=8'}
+    dependencies:
+      locate-path: 5.0.0
+      path-exists: 4.0.0
+    dev: false
+
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
     dependencies:
@@ -2148,6 +2208,11 @@ packages:
     engines: {node: '>=6.9.0'}
     dev: true
 
+  /get-caller-file@2.0.5:
+    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+    engines: {node: 6.* || 8.* || >= 10.*}
+    dev: false
+
   /get-intrinsic@1.2.4:
     resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
     engines: {node: '>= 0.4'}
@@ -2434,6 +2499,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /is-fullwidth-code-point@3.0.0:
+    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+    engines: {node: '>=8'}
+    dev: false
+
   /is-glob@4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
@@ -2628,6 +2698,13 @@ packages:
       pkg-types: 1.2.1
     dev: true
 
+  /locate-path@5.0.0:
+    resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+    engines: {node: '>=8'}
+    dependencies:
+      p-locate: 4.1.0
+    dev: false
+
   /lodash-es@4.17.21:
     resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
     dev: false
@@ -2860,11 +2937,35 @@ packages:
       isobject: 3.0.1
     dev: true
 
+  /p-limit@2.3.0:
+    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+    engines: {node: '>=6'}
+    dependencies:
+      p-try: 2.2.0
+    dev: false
+
+  /p-locate@4.1.0:
+    resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+    engines: {node: '>=8'}
+    dependencies:
+      p-limit: 2.3.0
+    dev: false
+
+  /p-try@2.2.0:
+    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+    engines: {node: '>=6'}
+    dev: false
+
   /pascalcase@0.1.1:
     resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+    dev: false
+
   /pathe@0.2.0:
     resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
     dev: true
@@ -2923,6 +3024,11 @@ packages:
       pathe: 1.1.2
     dev: true
 
+  /pngjs@5.0.0:
+    resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+    engines: {node: '>=10.13.0'}
+    dev: false
+
   /posix-character-classes@0.1.1:
     resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
     engines: {node: '>=0.10.0'}
@@ -3005,6 +3111,16 @@ packages:
       posthtml-render: 1.4.0
     dev: true
 
+  /qrcode@1.5.4:
+    resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+    dependencies:
+      dijkstrajs: 1.0.3
+      pngjs: 5.0.0
+      yargs: 15.4.1
+    dev: false
+
   /query-string@4.3.4:
     resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
     engines: {node: '>=0.10.0'}
@@ -3074,6 +3190,15 @@ packages:
     engines: {node: '>=0.10'}
     dev: true
 
+  /require-directory@2.1.1:
+    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /require-main-filename@2.0.0:
+    resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+    dev: false
+
   /resolve-url@0.2.1:
     resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
     deprecated: https://github.com/lydell/resolve-url#deprecated
@@ -3173,6 +3298,10 @@ packages:
     hasBin: true
     dev: true
 
+  /set-blocking@2.0.0:
+    resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+    dev: false
+
   /set-function-length@1.2.2:
     resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
     engines: {node: '>= 0.4'}
@@ -3307,6 +3436,15 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /string-width@4.2.3:
+    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+    engines: {node: '>=8'}
+    dependencies:
+      emoji-regex: 8.0.0
+      is-fullwidth-code-point: 3.0.0
+      strip-ansi: 6.0.1
+    dev: false
+
   /string.prototype.trim@1.2.9:
     resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
     engines: {node: '>= 0.4'}
@@ -3347,6 +3485,13 @@ packages:
       ansi-regex: 2.1.1
     dev: true
 
+  /strip-ansi@6.0.1:
+    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+    engines: {node: '>=8'}
+    dependencies:
+      ansi-regex: 5.0.1
+    dev: false
+
   /strip-literal@2.1.0:
     resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
     dependencies:
@@ -3842,6 +3987,17 @@ packages:
       vue: 3.5.12
     dev: false
 
+  /vue-qrcode@2.2.2(qrcode@1.5.4)(vue@3.5.11):
+    resolution: {integrity: sha512-SbrXq/mSb1g2tbDyXPe9gy9KiMYsvxWKRErlpij1BqiFoHwQckheZV63CTw6yRLLUVG2RXAVlX+APkpdCK7SQQ==}
+    peerDependencies:
+      qrcode: ^1.0.0
+      vue: ^2.7.0 || ^3.0.0
+    dependencies:
+      qrcode: 1.5.4
+      tslib: 2.8.0
+      vue: 3.5.11
+    dev: false
+
   /vue-resize@2.0.0-alpha.1(vue@3.5.11):
     resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==}
     peerDependencies:
@@ -3940,6 +4096,10 @@ packages:
       is-symbol: 1.0.4
     dev: true
 
+  /which-module@2.0.1:
+    resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+    dev: false
+
   /which-typed-array@1.1.15:
     resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
     engines: {node: '>= 0.4'}
@@ -3951,6 +4111,44 @@ packages:
       has-tostringtag: 1.0.2
     dev: true
 
+  /wrap-ansi@6.2.0:
+    resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+    engines: {node: '>=8'}
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+    dev: false
+
+  /y18n@4.0.3:
+    resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+    dev: false
+
   /yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
     dev: true
+
+  /yargs-parser@18.1.3:
+    resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+    engines: {node: '>=6'}
+    dependencies:
+      camelcase: 5.3.1
+      decamelize: 1.2.0
+    dev: false
+
+  /yargs@15.4.1:
+    resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+    engines: {node: '>=8'}
+    dependencies:
+      cliui: 6.0.0
+      decamelize: 1.2.0
+      find-up: 4.1.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      require-main-filename: 2.0.0
+      set-blocking: 2.0.0
+      string-width: 4.2.3
+      which-module: 2.0.1
+      y18n: 4.0.3
+      yargs-parser: 18.1.3
+    dev: false