提交 79d95f31 authored 作者: 龙菲's avatar 龙菲

增加pc端的抽屉

上级 016d0c67
...@@ -8,14 +8,17 @@ export {} ...@@ -8,14 +8,17 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
BookMarks: typeof import('./components/BookMarks.vue')['default'] BookMarks: typeof import('./components/GuideMobile/bookMarks.vue')['default']
BookReader: typeof import('./components/BookReader/index.vue')['default'] BookReader: typeof import('./components/BookReader/index.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
FileUpload: typeof import('./components/FileUpload.vue')['default'] FileUpload: typeof import('./components/FileUpload.vue')['default']
Guide: typeof import('./components/BookReader/guide.vue')['default'] Guide: typeof import('./components/GuideMobile/guide.vue')['default']
GuideMobile: typeof import('./components/GuideMobile/index.vue')['default']
GuidePc: typeof import('./components/GuidePc/index.vue')['default']
IconCommunity: typeof import('./components/icons/IconCommunity.vue')['default'] IconCommunity: typeof import('./components/icons/IconCommunity.vue')['default']
IconDocumentation: typeof import('./components/icons/IconDocumentation.vue')['default'] IconDocumentation: typeof import('./components/icons/IconDocumentation.vue')['default']
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default'] IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
...@@ -29,4 +32,7 @@ declare module 'vue' { ...@@ -29,4 +32,7 @@ declare module 'vue' {
VanNavBar: typeof import('vant/es')['NavBar'] VanNavBar: typeof import('vant/es')['NavBar']
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
} }
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class="book-reader" class="book-reader"
@touchstart="handleTouchStart" @touchstart="handleTouchStart"
@touchend="handleTouchEnd" @touchend="handleTouchEnd"
v-loading="loading"
> >
<div class="magazine-viewport"> <div class="magazine-viewport">
<div ref="magazine" class="magazine"> <div ref="magazine" class="magazine">
...@@ -69,7 +70,16 @@ ...@@ -69,7 +70,16 @@
<!-- <div class="mobile-gesture-hint" v-if="isMobile">左右滑动切换页面</div> --> <!-- <div class="mobile-gesture-hint" v-if="isMobile">左右滑动切换页面</div> -->
<!-- Replace directory modal with Element Plus dialog --> <!-- Replace directory modal with Element Plus dialog -->
<Guide ref="guideRef" :bookmarks="bookmarks" @goToPage="goToPage"></Guide> <GuideMobile
ref="guideMobileRef"
:bookmarks="bookmarks"
@goToPage="goToPage"
></GuideMobile>
<GuidePc
ref="guidePcRef"
:bookmarks="bookmarks"
@goToPage="goToPage"
></GuidePc>
</div> </div>
<div class="footer"> <div class="footer">
<el-row :gutter="10" class="tab-bar"> <el-row :gutter="10" class="tab-bar">
...@@ -91,7 +101,7 @@ ...@@ -91,7 +101,7 @@
<svg-icon name="to-left" size="22" @click="previous"></svg-icon> <svg-icon name="to-left" size="22" @click="previous"></svg-icon>
<div class="page-count">{{ currentPage }}</div> <div class="page-count">{{ currentPage }}</div>
<svg-icon name="to-right" size="22" @click="next"></svg-icon> <svg-icon name="to-right" size="22" @click="next"></svg-icon>
<svg-icon name="to-end" size="30" @clicl="toEnd"></svg-icon> <svg-icon name="to-end" size="30" @click="toEnd"></svg-icon>
</el-col> </el-col>
<el-col class="right" :span="8"> <el-col class="right" :span="8">
<!-- <svg-icon name="download" size="20"></svg-icon> --> <!-- <svg-icon name="download" size="20"></svg-icon> -->
...@@ -107,8 +117,7 @@ import "turn.js"; ...@@ -107,8 +117,7 @@ import "turn.js";
// import * as 'turn' from '/public/js/turn' // import * as 'turn' from '/public/js/turn'
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from "vue"; import { ref, onMounted, onUnmounted, watch, nextTick, computed } from "vue";
import VueEasyLightbox from "vue-easy-lightbox"; import VueEasyLightbox from "vue-easy-lightbox";
import Guide from "./guide.vue"; import GuideMobile from "../GuideMobile/index.vue";
import { showImagePreview } from "vant";
import audioFlip from "@/assets/audio/flip.mp3"; import audioFlip from "@/assets/audio/flip.mp3";
const props = defineProps({ const props = defineProps({
pages: { pages: {
...@@ -148,7 +157,8 @@ const processedPages = ref([]); ...@@ -148,7 +157,8 @@ const processedPages = ref([]);
const pageTurnSound = ref(null); const pageTurnSound = ref(null);
const audioInitialized = ref(false); const audioInitialized = ref(false);
const directoryImages = ref([]); const directoryImages = ref([]);
const guideRef = ref(null); const guideMobileRef = ref(null);
const guidePcRef = ref(null);
const currentPage = ref(0); const currentPage = ref(0);
const isMuted = ref(false); // 默认不静音 const isMuted = ref(false); // 默认不静音
...@@ -198,7 +208,6 @@ const processPages = (pages) => { ...@@ -198,7 +208,6 @@ const processPages = (pages) => {
})); }));
}; };
// 修改图片加载函数
const loadImage = async (pageIndex) => { const loadImage = async (pageIndex) => {
const page = processedPages.value[pageIndex]; const page = processedPages.value[pageIndex];
if (!page || page.isLoaded) return true; if (!page || page.isLoaded) return true;
...@@ -224,7 +233,8 @@ const loadImage = async (pageIndex) => { ...@@ -224,7 +233,8 @@ const loadImage = async (pageIndex) => {
try { try {
const img = new Image(); const img = new Image();
await new Promise((resolve, reject) => { await Promise.race([
new Promise((resolve, reject) => {
img.onload = () => { img.onload = () => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
loadingTimeouts.value.delete(pageIndex); loadingTimeouts.value.delete(pageIndex);
...@@ -239,9 +249,19 @@ const loadImage = async (pageIndex) => { ...@@ -239,9 +249,19 @@ const loadImage = async (pageIndex) => {
reject(new Error("Image load failed")); reject(new Error("Image load failed"));
}; };
img.src = src; img.src = src;
}); }),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 3000)
),
]);
return true; return true;
} catch (error) { } catch (error) {
// 如果加载失败,直接显示占位图
const $img = $(magazine.value).find(`.p${pageIndex + 1} img`);
if ($img.length) {
$img[0].src =
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lvIDlp4vmlbDmja7lupM8L3RleHQ+PC9zdmc+";
}
return false; return false;
} finally { } finally {
loadingQueue.value.delete(pageIndex); loadingQueue.value.delete(pageIndex);
...@@ -255,9 +275,9 @@ const loadVisiblePages = async (currentPage) => { ...@@ -255,9 +275,9 @@ const loadVisiblePages = async (currentPage) => {
const $magazine = $(magazine.value); const $magazine = $(magazine.value);
const totalPages = $magazine.turn("pages"); const totalPages = $magazine.turn("pages");
// 只加载当前页和下一 // 加载当前页、上一页、下一页和下下
const startPage = currentPage; const startPage = Math.max(1, currentPage - 1);
const endPage = Math.min(totalPages, currentPage + 1); const endPage = Math.min(totalPages, currentPage + 2);
const loadTasks = []; const loadTasks = [];
for (let i = startPage; i <= endPage; i++) { for (let i = startPage; i <= endPage; i++) {
...@@ -272,9 +292,12 @@ const loadVisiblePages = async (currentPage) => { ...@@ -272,9 +292,12 @@ const loadVisiblePages = async (currentPage) => {
} }
try { try {
loading.value = true;
await Promise.all(loadTasks); await Promise.all(loadTasks);
} catch (error) { } catch (error) {
console.error("加载页面失败:", error); console.error("加载页面失败:", error);
} finally {
loading.value = false;
} }
}; };
...@@ -356,6 +379,11 @@ const initBook = async () => { ...@@ -356,6 +379,11 @@ const initBook = async () => {
turning: async (event, page) => { turning: async (event, page) => {
loading.value = true; loading.value = true;
await loadVisiblePages(page); await loadVisiblePages(page);
// 确保加载完成后才继续翻页
setTimeout(() => {
event.preventDefault();
$magazine.turn("page", page);
}, 100);
}, },
turned: async (event, page) => { turned: async (event, page) => {
currentPage.value = page; // 更新当前页码 currentPage.value = page; // 更新当前页码
...@@ -375,13 +403,13 @@ const initBook = async () => { ...@@ -375,13 +403,13 @@ const initBook = async () => {
}; };
// 修改加载图片函数 // 修改加载图片函数
const loadImages = async () => { const loadImages = async () => {
loading.value = true; // loading.value = true;
try { try {
await initBook(); await initBook();
} catch (error) { } catch (error) {
console.error("加载图片失败:", error); console.error("加载图片失败:", error);
} finally { } finally {
loading.value = false; // loading.value = false;
} }
}; };
...@@ -478,13 +506,13 @@ const previous = () => { ...@@ -478,13 +506,13 @@ const previous = () => {
}; };
const toFirst = () => { const toFirst = () => {
const firstPage = props.pages[0].page; const firstPage = props.pages[0].page_num;
$magazine.turn("page", firstPage); goToPage(firstPage);
}; };
const toEnd = () => { const toEnd = () => {
const endPage = props.pages[props.pages.length - 1].page; const endPage = props.pages[props.pages.length - 1].page_num;
$magazine.turn("page", endPage); goToPage(endPage);
}; };
const zoomIn = () => { const zoomIn = () => {
...@@ -772,7 +800,11 @@ const goToPage = (pageNum) => { ...@@ -772,7 +800,11 @@ const goToPage = (pageNum) => {
}; };
const showGuide = () => { const showGuide = () => {
guideRef.value.show(); if (isMobile.value) {
guideMobileRef.value.show();
} else {
guidePcRef.value.show();
}
}; };
</script> </script>
...@@ -843,22 +875,22 @@ const showGuide = () => { ...@@ -843,22 +875,22 @@ const showGuide = () => {
transform-style: preserve-3d; transform-style: preserve-3d;
} }
.magazine::after { // .magazine::after {
content: ""; // content: "";
position: absolute; // position: absolute;
top: 0; // top: 0;
left: 50%; // left: 50%;
width: 1px; // width: 1px;
height: 100%; // height: 100%;
background: linear-gradient( // background: linear-gradient(
to bottom, // to bottom,
rgba(0, 0, 0, 0.1) 0%, // rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0.2) 50%, // rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0.1) 100% // rgba(0, 0, 0, 0.1) 100%
); // );
z-index: 10; // z-index: 10;
pointer-events: none; // pointer-events: none;
} // }
.page { .page {
background-color: white; background-color: white;
...@@ -1034,6 +1066,9 @@ const showGuide = () => { ...@@ -1034,6 +1066,9 @@ const showGuide = () => {
color: #ccc; color: #ccc;
width: 100%; width: 100%;
height: 100%; height: 100%;
.svg-icon {
cursor: pointer;
}
.left, .left,
.right, .right,
.center { .center {
......
<template> <template>
<!-- 圆角弹窗(底部) --> <!-- 圆角弹窗(底部) -->
<van-popup v-model:show="showDirectory" round position="bottom" :style="{ height: '80%' }"> <van-popup
<van-nav-bar title="目录"/> v-model:show="showDirectory"
round
position="bottom"
:style="{ height: '80%' }"
>
<van-nav-bar title="目录" />
<van-list :finished="true" finished-text="没有更多了"> <van-list :finished="true" finished-text="没有更多了">
<BookMarks :bookmarks="bookmarks" @goToPage="goToPage"/> <BookMarks :bookmarks="bookmarks" @goToPage="goToPage" />
</van-list> </van-list>
</van-popup> </van-popup>
</template> </template>
<script setup> <script setup>
import BookMarks from "./bookMarks.vue";
const $emit = defineEmits(["goToPage"]); const $emit = defineEmits(["goToPage"]);
const props = defineProps({ const props = defineProps({
bookmarks: { bookmarks: {
......
<template>
<template v-for="item in bookmarks" :key="item">
<van-cell
:title="item.title"
:style="{ paddingLeft: `${level * 20 + 30}px` }"
@click.prevent="clickCell(item.page)"
/>
<template v-if="item.children">
<BookMarks :bookmarks="bookmarks" @goToPage="goToPage" />
</template>
</template>
</template>
<script setup name="BookMarksPc">
const $emit = defineEmits(["goToPage"]);
const props = defineProps({
bookmarks: {
type: Array,
default: () => [],
},
level: {
type: Number,
default: 0, // 默认层级是 0(第一层)
},
});
const clickCell = (page) => {
$emit("goToPage", page);
};
</script>
<style lang="scss" scoped></style>
<template>
<el-drawer
title="目录"
v-model="visible"
direction="ltr"
size="30%"
:before-close="close"
:destroy-on-close="true"
:show-close="true"
:wrapperClosable="true"
>
<BookMarks :bookmarks="bookmarks" @goToPage="goToPage" />
</el-drawer>
</template>
<script lang="ts" setup>
/* props */
const props = defineProps({
bookmarks: {
type: Array,
default: () => [],
},
currentPage: {
type: Number,
default: 0,
},
});
/* emit */
const $emit = defineEmits(["goToPage"]);
/* data */
const visible = ref(false);
/* computed */
/* methods */
function close() {
visible.value = false;
}
function goToPage(pageNum) {
$emit("goToPage", pageNum);
close();
}
function show() {
visible.value = true;
}
defineExpose({
show,
});
</script>
<style lang="scss" scoped></style>
<template> <template>
<div> <div>
<div class="header"> <div class="header">
<el-button type="primary" link class="back">返回</el-button> <el-button type="primary" link class="back" @click="goBack"
>返回</el-button
>
微方志 微方志
</div> </div>
<BookReader :pages="pages" :bookmarks="bookmarks" /> <BookReader :pages="pages" :bookmarks="bookmarks" />
...@@ -36,7 +38,7 @@ async function loadData(id) { ...@@ -36,7 +38,7 @@ async function loadData(id) {
}); });
} }
function onClickLeft() { function goBack() {
router.go(-1); router.go(-1);
} }
onMounted(() => { onMounted(() => {
......
...@@ -61,7 +61,12 @@ export default defineConfig(({ mode, command }) => { ...@@ -61,7 +61,12 @@ export default defineConfig(({ mode, command }) => {
'/api': { '/api': {
target: 'http://222.85.214.245:9666', target: 'http://222.85.214.245:9666',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(new RegExp('^' + env.VITE_API_BASE_URL), '/api'),
bypass(req, res, options) {
const realUrl = options.target + (options.rewrite ? options.rewrite(req.url) : '');
console.log(realUrl); // 在终端显示
res.setHeader('A-Real-Url', realUrl); // 添加响应标头(A-Real-Url为自定义命名),在浏览器中显示
},
} }
}, },
host: '0.0.0.0', host: '0.0.0.0',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论