提交 fbb94355 authored 作者: 龙菲's avatar 龙菲

优化目录

上级 cf12f22a
差异被折叠。
...@@ -13,15 +13,17 @@ ...@@ -13,15 +13,17 @@
"axios": "^1.8.4", "axios": "^1.8.4",
"element-plus": "^2.9.8", "element-plus": "^2.9.8",
"turn.js": "^1.0.5", "turn.js": "^1.0.5",
"vant": "^4.9.19",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-easy-lightbox": "^1.19.0", "vue-easy-lightbox": "^1.19.0",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"sass-embedded": "^1.89.1", "sass-embedded": "^1.89.1",
"unplugin-auto-import": "^19.1.2", "unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.5.0", "unplugin-vue-components": "^28.7.0",
"vite": "^6.2.4", "vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2" "vite-plugin-vue-devtools": "^7.7.2"
} }
......
...@@ -8,21 +8,14 @@ export {} ...@@ -8,21 +8,14 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
BookMarks: typeof import('./components/BookMarks.vue')['default']
BookReader: typeof import('./components/BookReader.vue')['default'] BookReader: typeof import('./components/BookReader.vue')['default']
copy: typeof import('./components/Guide copy.vue')['default'] copy: typeof import('./components/Guide copy.vue')['default']
ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
FileUpload: typeof import('./components/FileUpload.vue')['default'] FileUpload: typeof import('./components/FileUpload.vue')['default']
Guide: typeof import('./components/Guide.vue')['default'] Guide: typeof import('./components/Guide.vue')['default']
...@@ -32,7 +25,17 @@ declare module 'vue' { ...@@ -32,7 +25,17 @@ declare module 'vue' {
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default'] IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./components/icons/IconSupport.vue')['default'] IconSupport: typeof import('./components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./components/icons/IconTooling.vue')['default'] IconTooling: typeof import('./components/icons/IconTooling.vue')['default']
List: typeof import('./components/List.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink']
WelcomeItem: typeof import('./components/WelcomeItem.vue')['default'] RouterView: typeof import('vue-router')['RouterView']
VanButton: typeof import('vant/es')['Button']
VanCell: typeof import('vant/es')['Cell']
VanCol: typeof import('vant/es')['Col']
VanIcon: typeof import('vant/es')['Icon']
VanList: typeof import('vant/es')['List']
VanNavBar: typeof import('vant/es')['NavBar']
VanPopup: typeof import('vant/es')['Popup']
VanRow: typeof import('vant/es')['Row']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']
} }
} }
<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 @goToPage="$emit('goToPage', $event)" :level="level + 1" :bookmarks="item.children" />
</template>
</template>
</template>
<script setup name="BookMarks">
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>
\ No newline at end of file
<template> <template>
<div <div class="book-reader" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
class="book-reader"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
>
<!-- <div v-if="loading" class="loading-overlay"> <!-- <div v-if="loading" class="loading-overlay">
<div class="loader"></div> <div class="loader"></div>
<p>正在加载图片...</p> <p>正在加载图片...</p>
...@@ -11,56 +7,43 @@ ...@@ -11,56 +7,43 @@
<div class="magazine-viewport"> <div class="magazine-viewport">
<div ref="magazine" class="magazine"> <div ref="magazine" class="magazine">
<div <div v-for="(page, index) in processedPages" :key="index" :class="[
v-for="(page, index) in processedPages" 'page',
:key="index" `p${index + 1}`,
:class="[ {
'page', hard: !page.isWide && index % 2 === 0,
`p${index + 1}`, odd: !page.isWide && index % 2 !== 0,
{ even: !page.isWide && index % 2 === 1,
hard: !page.isWide && index % 2 === 0, mobile: isMobile,
odd: !page.isWide && index % 2 !== 0, },
even: !page.isWide && index % 2 === 1, ]">
mobile: isMobile, <img :data-page-index="index" :src="page.src" :alt="`第 ${page.page_num} 页`" @error="handleImageError(index)"
}, :class="['page-image', { 'wide-image': page.isWide }]" />
]"
>
<img
:data-page-index="index"
:src="page.src"
:alt="`第 ${page.page_num} 页`"
@error="handleImageError(index)"
:class="['page-image', { 'wide-image': page.isWide }]"
/>
<!-- 添加小图叠加层 --> <!-- 添加小图叠加层 -->
<div <div v-if="page.images && page.images.length > 0" class="small-images-overlay">
v-if="page.images && page.images.length > 0" <div v-for="(smallImage, imgIndex) in page.images" :key="imgIndex" class="small-image-container" :style="{
class="small-images-overlay" left: `${smallImage.position.x1 * 100}%`,
> top: `${smallImage.position.y1 * 100}%`,
<div width: `${(smallImage.position.x2 - smallImage.position.x1) * 100
v-for="(smallImage, imgIndex) in page.images"
:key="imgIndex"
class="small-image-container"
:style="{
left: `${smallImage.position.x1 * 100}%`,
top: `${smallImage.position.y1 * 100}%`,
width: `${
(smallImage.position.x2 - smallImage.position.x1) * 100
}%`, }%`,
height: `${ height: `${(smallImage.position.y2 - smallImage.position.y1) * 100
(smallImage.position.y2 - smallImage.position.y1) * 100
}%`, }%`,
}" }" @click="handleSmallImageClick(smallImage, page.page_num)">
@click="handleSmallImageClick(smallImage, page.page_num)"
>
<!-- <img :src="smallImage.url" :alt="`小图 ${imgIndex + 1}`" class="small-image" /> --> <!-- <img :src="smallImage.url" :alt="`小图 ${imgIndex + 1}`" class="small-image" /> -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<van-tabbar class="tab-bar">
<div class="tab-bar-item" v-for="(item, index) in tabbars" :key="index" @click="clickTabBar(item.code)">
<!-- 目录 -->
<van-icon :name="item.icon" size="20" />
</div>
</van-tabbar>
<!--
<div class="controls" :class="{ 'mobile-controls': isMobile }"> <div class="controls" :class="{ 'mobile-controls': isMobile }">
<el-button class="control-button" @click="previous" circle> <el-button class="control-button" @click="previous" circle>
<el-icon> <el-icon>
...@@ -87,17 +70,13 @@ ...@@ -87,17 +70,13 @@
<Menu /> <Menu />
</el-icon> </el-icon>
</el-button> </el-button>
</div> </div> -->
<div v-if="showExitMessage" class="exit-message">按 ESC 键退出阅读</div> <div v-if="showExitMessage" class="exit-message">按 ESC 键退出阅读</div>
<!-- 替换为 vue-easy-lightbox 预览组件 --> <!-- 替换为 vue-easy-lightbox 预览组件 -->
<vue-easy-lightbox <vue-easy-lightbox :visible="showViewer" :imgs="previewImages" :index="currentImageIndex"
:visible="showViewer" @hide="showViewer = false" />
:imgs="previewImages"
:index="currentImageIndex"
@hide="showViewer = false"
/>
<div class="mobile-gesture-hint" v-if="isMobile">左右滑动切换页面</div> <div class="mobile-gesture-hint" v-if="isMobile">左右滑动切换页面</div>
...@@ -119,7 +98,7 @@ import { ...@@ -119,7 +98,7 @@ import {
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import VueEasyLightbox from "vue-easy-lightbox"; import VueEasyLightbox from "vue-easy-lightbox";
import Guide from "./Guide.vue"; import Guide from "./Guide.vue";
import { showImagePreview } from 'vant';
const props = defineProps({ const props = defineProps({
pages: { pages: {
type: Array, type: Array,
...@@ -131,7 +110,33 @@ const props = defineProps({ ...@@ -131,7 +110,33 @@ const props = defineProps({
default: () => [], default: () => [],
}, },
}); });
const tabbars = ref([
{
label: '目录',
icon: 'apps-o',
code: 'menu'
},
{
label: '上一页',
icon: 'arrow-left',
code: 'previous'
},
{
label: '下一页',
icon: 'arrow',
code: 'next'
},
{
label: '放大',
icon: 'plus',
code: 'plus'
},
{
label: '缩小',
icon: 'minus',
code: 'minus'
}
])
const magazine = ref(null); const magazine = ref(null);
const loading = ref(true); const loading = ref(true);
const showExitMessage = ref(false); const showExitMessage = ref(false);
...@@ -493,6 +498,26 @@ const handleImageError = async (index) => { ...@@ -493,6 +498,26 @@ const handleImageError = async (index) => {
} }
}; };
const clickTabBar = (code) => {
switch (code) {
case 'menu':
showGuide()
break;
case 'previous':
previous()
break;
case 'next':
next()
break;
case 'minus':
zoomIn()
break;
case 'menu':
zoomOut()
break;
}
}
const next = () => { const next = () => {
if (magazine.value && isInitialized.value) { if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value); const $magazine = $(magazine.value);
...@@ -729,18 +754,27 @@ const handleSmallImageClick = (smallImage, pageNum) => { ...@@ -729,18 +754,27 @@ const handleSmallImageClick = (smallImage, pageNum) => {
// 获取当前页面所有小图的URL // 获取当前页面所有小图的URL
const currentPage = props.pages.find((page) => page.page_num === pageNum); const currentPage = props.pages.find((page) => page.page_num === pageNum);
if (currentPage && currentPage.images) { if (currentPage && currentPage.images) {
previewImages.value = currentPage.images.map((img) => ({ // // 设置当前点击的图片索引
src: img.url,
title: `第 ${pageNum} 页`,
}));
// 设置当前点击的图片索引
currentImageIndex.value = currentPage.images.findIndex( currentImageIndex.value = currentPage.images.findIndex(
(img) => img.url === smallImage.url (img) => img.url === smallImage.url
); );
const images = currentPage.images.map((img) => img.url)
console.log(12,images);
showImagePreview({
images,
closeable: true,
startPosition: currentImageIndex.value,
});
// previewImages.value = currentPage.images.map((img) => ({
// src: img.url,
// title: `第 ${pageNum} 页`,
// }));
// 显示预览组件
showViewer.value = true; // // 显示预览组件
// showViewer.value = true;
} }
}; };
...@@ -777,7 +811,7 @@ const showGuide = () => { ...@@ -777,7 +811,7 @@ const showGuide = () => {
}; };
</script> </script>
<style scoped> <style scoped lang="scss">
.book-reader { .book-reader {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
...@@ -820,12 +854,10 @@ const showGuide = () => { ...@@ -820,12 +854,10 @@ const showGuide = () => {
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;
} }
...@@ -1258,4 +1290,16 @@ const showGuide = () => { ...@@ -1258,4 +1290,16 @@ const showGuide = () => {
grid-template-columns: repeat(auto-fill, minmax(20vw, 1fr)) !important; grid-template-columns: repeat(auto-fill, minmax(20vw, 1fr)) !important;
} }
} }
.tab-bar {
display: flex;
.tab-bar-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
}
</style> </style>
<template> <template>
<el-drawer v-model="showDirectory" direction="ltr" style="wdith: 40%"> <!-- 圆角弹窗(底部) -->
<template #header> <van-popup v-model:show="showDirectory" round position="bottom" :style="{ height: '80%' }">
<h4>目录</h4> <van-nav-bar title="目录"/>
</template> <van-list :finished="true" finished-text="没有更多了">
<template #default> <BookMarks :bookmarks="bookmarks" @goToPage="goToPage"/>
<el-tree </van-list>
:data="bookmarks" </van-popup>
:props="defaultProps"
@node-click="handleNodeClick"
/>
</template>
<template #footer>
<div style="flex: auto">
<el-button type="primary" @click="confirmClick">关闭</el-button>
</div>
</template>
</el-drawer>
</template> </template>
<script setup> <script setup>
import { ArrowUpBold } from "@element-plus/icons-vue";
const $emit = defineEmits(["goToPage"]); const $emit = defineEmits(["goToPage"]);
const props = defineProps({ const props = defineProps({
bookmarks: { bookmarks: {
...@@ -121,9 +110,11 @@ defineExpose({ ...@@ -121,9 +110,11 @@ defineExpose({
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; /* 添加行间距和列间距 */ gap: 10px;
/* 添加行间距和列间距 */
position: relative; position: relative;
} }
.content { .content {
.back-top { .back-top {
position: absolute; position: absolute;
...@@ -137,19 +128,23 @@ defineExpose({ ...@@ -137,19 +128,23 @@ defineExpose({
cursor: pointer; cursor: pointer;
transition: transform 0.2s; transition: transform 0.2s;
overflow: hidden; overflow: hidden;
width: calc(33.333% - 10px); /* 调整宽度,减去间距 */ width: calc(33.333% - 10px);
/* 调整宽度,减去间距 */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
border: 1px solid transparent; border: 1px solid transparent;
&:hover { &:hover {
border: 1px solid #000; border: 1px solid #000;
} }
img { img {
width: 100%; width: 100%;
height: 200px; height: 200px;
object-fit: contain; object-fit: contain;
} }
.page-number { .page-number {
font-size: 20px; font-size: 20px;
position: absolute; position: absolute;
...@@ -161,8 +156,10 @@ defineExpose({ ...@@ -161,8 +156,10 @@ defineExpose({
color: #fff; color: #fff;
} }
} }
.active { .active {
border: 1px solid #000; border: 1px solid #000;
.page-number { .page-number {
color: var(--el-color-success); color: var(--el-color-success);
background-color: #fff; background-color: #fff;
...@@ -171,10 +168,13 @@ defineExpose({ ...@@ -171,10 +168,13 @@ defineExpose({
@media (max-width: 768px) { @media (max-width: 768px) {
.thumbnail-grid { .thumbnail-grid {
gap: 10px; /* 保持间距 */ gap: 10px;
/* 保持间距 */
} }
.thumbnail-item { .thumbnail-item {
width: calc(33.333% - 10px); /* 保持宽度调整 */ width: calc(33.333% - 10px);
/* 保持宽度调整 */
} }
} }
</style> </style>
<template> <template>
<div> <div>
<div class="nav"></div> <van-nav-bar :title="title" left-text="返回" left-arrow @click-left="onClickLeft" />
<BookReader :pages="pages" :bookmarks="bookmarks" /> <BookReader :pages="pages" :bookmarks="bookmarks" />
</div> </div>
</template> </template>
...@@ -8,9 +8,11 @@ ...@@ -8,9 +8,11 @@
import { getDocumentDetail } from "@/api"; import { getDocumentDetail } from "@/api";
import BookReader from "@/components/BookReader.vue"; import BookReader from "@/components/BookReader.vue";
const route = useRoute(); const route = useRoute();
const router = useRouter();
/* props */ /* props */
/* emit */ /* emit */
/* data */ /* data */
const title = ref('')
const pages = ref([]); const pages = ref([]);
const bookmarks = ref([]); const bookmarks = ref([]);
/* computed */ /* computed */
...@@ -30,9 +32,14 @@ async function loadData(id) { ...@@ -30,9 +32,14 @@ async function loadData(id) {
}; };
}); });
} }
function onClickLeft(){
router.go(-1)
}
onMounted(() => { onMounted(() => {
const { id } = route.params; const { id } = route.params;
bookmarks.value = JSON.parse(route.query.bookmarks); bookmarks.value = JSON.parse(localStorage.getItem(`bookmarks-${id}`));
title.value = route.query.title
loadData(id); loadData(id);
}); });
</script> </script>
......
<template> <template>
<div class="book-list-container"> <div class="book-list-container">
<el-row :gutter="16" class="book-card-list"> <van-nav-bar title="微方志">
<template #right>
<van-icon name="search" size="20" />
</template>
</van-nav-bar>
<van-row class="book-card-list">
<van-col span="8" v-for="(book, index) in books" :key="book.book_id + index">
<div class="book-card" shadow="hover" @click="openBook(book)">
<div class="book-cover">{{ book.name }}</div>
<div class="book-name">{{ book.name }}</div>
</div>
</van-col>
</van-row>
<!-- <el-row :gutter="16" class="book-card-list">
<el-col <el-col
v-for="(book, index) in books" v-for="(book, index) in books"
:key="book.book_id + index" :key="book.book_id + index"
:span="8" :span="8"
> >
<div class="book-card" shadow="hover" @click="openBook(book)"> <div class="book-card" shadow="hover" @click="openBook(book)">
<!-- 这里可根据实际书籍封面逻辑替换,示例用纯色背景占位 -->
<div class="book-cover">{{ book.name }}</div> <div class="book-cover">{{ book.name }}</div>
<div class="book-name">{{ book.name }}</div> <div class="book-name">{{ book.name }}</div>
</div> </div>
</el-col> </el-col>
</el-row> </el-row> -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from "vue"; import {
import { getDocList } from "@/api/index"; ref,
computed
} from "vue";
import {
getDocList
} from "@/api/index";
import router from "@/router"; import router from "@/router";
// 模拟接口返回数据类型 // 模拟接口返回数据类型
interface Book { interface Book {
...@@ -35,12 +53,14 @@ async function loadData() { ...@@ -35,12 +53,14 @@ async function loadData() {
const res = await getDocList(); const res = await getDocList();
books.value = res; books.value = res;
} }
function openBook(book) { function openBook(book) {
console.log(book); console.log(book);
const query = { const query = {
bookmarks: book.bookmarks, title:book.name
}; };
const path = `/detail/${book.book_id}`; const path = `/detail/${book.book_id}`;
localStorage.setItem(`bookmarks-${book.book_id}`, book.bookmarks)
router.push({ router.push({
query, query,
path, path,
...@@ -51,12 +71,15 @@ onMounted(() => { ...@@ -51,12 +71,15 @@ onMounted(() => {
}); });
</script> </script>
<style scoped lang="scss"> <style lang="scss" scoped>
.book-list-container { .book-list-container {
height: 100vh; height: 100vh;
padding: 30px; .book-card-list{
padding: 30px;
}
.book-card { .book-card {
border-radius: 8px; border-radius: 8px;
.book-cover { .book-cover {
background-color: rgba(65, 184, 131, 0.4); background-color: rgba(65, 184, 131, 0.4);
padding: 50px 10px; padding: 50px 10px;
......
...@@ -6,7 +6,7 @@ import vue from '@vitejs/plugin-vue' ...@@ -6,7 +6,7 @@ import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver,VantResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
...@@ -17,13 +17,13 @@ export default defineConfig({ ...@@ -17,13 +17,13 @@ export default defineConfig({
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ['vue', 'vue-router', 'pinia'], imports: ['vue', 'vue-router', 'pinia'],
// 自动导入 Element Plus 相关函数 // 自动导入 Element Plus 相关函数
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver(),VantResolver()],
// 生成自动导入的TS声明文件 // 生成自动导入的TS声明文件
dts: 'src/auto-imports.d.ts', dts: 'src/auto-imports.d.ts',
}), }),
Components({ Components({
// 自动导入组件 // 自动导入组件
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver(),VantResolver()],
// 生成自动导入的TS声明文件 // 生成自动导入的TS声明文件
dts: 'src/components.d.ts', dts: 'src/components.d.ts',
}), }),
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论