This commit is contained in:
2026-07-24 16:56:15 +08:00
180 changed files with 50264 additions and 1407 deletions

306
pages/WYXD/WYXD.vue Normal file
View File

@@ -0,0 +1,306 @@
<template>
<view class="status-bar"></view>
<view class="wyxd-container page-container">
<!-- ===== 顶部导航栏 ===== -->
<view class="wyxd-header">
<view class="custom-navbar">
<view class="navbar-left" @click="goBack">
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
<view class="navbar-before-title workspace-text">返回</view>
</view>
<view class="navbar-title workspace-text">{{ displayName }}</view>
<view class="navbar-right"></view>
</view>
</view>
<!-- ===== 主舞台 ===== -->
<view class="wyxd-content">
<!-- 加载中 / 错误状态 -->
<view v-if="loading || errorMsg" class="wyxd-state">
<view class="iconfont wyxd-state-icon"
:class="errorMsg ? 'icon-quxiao' : 'icon-sousuo'"></view>
<view class="wyxd-state-title">{{ errorMsg || statusText }}</view>
<view v-if="errorMsg" class="wyxd-state-desc">请返回重试</view>
</view>
<!-- 无内容 -->
<view v-else-if="!slides.length" class="wyxd-state">
<view class="iconfont icon-wenjianjia wyxd-state-icon"></view>
<view class="wyxd-state-title">暂无内容</view>
</view>
<!-- 幻灯片 -->
<view v-else class="wyxd-slide-area">
<scroll-view class="wyxd-slide-scroll" scroll-y :show-scrollbar="false">
<view class="wyxd-slide-card" v-html="currentSlide.html"></view>
</scroll-view>
</view>
</view>
<!-- ===== 底部翻页 ===== -->
<view class="wyxd-pager" v-if="slides.length > 1">
<view class="wyxd-pager-inner">
<view class="wyxd-nav-btn" :class="{ disabled: currentIndex <= 0 }" @click="prevSlide">
<view class="iconfont icon-fanhui" style="transform:rotate(180deg);font-size:24rpx;"></view>
</view>
<view class="wyxd-page-indicator">
<text class="wyxd-current">{{ pad(currentIndex + 1) }}</text>
<text class="wyxd-divider">/</text>
<text class="wyxd-total">{{ pad(slides.length) }}</text>
</view>
<view class="wyxd-nav-btn" :class="{ disabled: currentIndex >= slides.length - 1 }" @click="nextSlide">
<view class="iconfont icon-fanhui" style="font-size:24rpx;"></view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
isWyxdCached, getCachedWyxd, setCachedWyxd,
downloadWyxdAsZip, extractWyxd, parseSlides
} from '@/utils/wyxd-parser.js'
const workspaceId = ref('')
const filePath = ref('')
const fileName = ref('')
const userToken = ref('')
const slides = ref([])
const currentIndex = ref(0)
const status = ref('')
const loading = ref(false)
const errorMsg = ref('')
const statusText = computed(() => {
const map = {
downloading: '下载中...',
extracting: '解压中...',
parsing: '解析中...',
error: '加载失败'
}
return map[status.value] || '处理中...'
})
const displayName = computed(() => fileName.value || '未命名文档')
const currentSlide = computed(() => slides.value[currentIndex.value] || { html: '' })
const pad = (n) => String(n).padStart(2, '0')
const goBack = () => {
uni.navigateBack()
}
const prevSlide = () => {
if (currentIndex.value > 0) currentIndex.value--
}
const nextSlide = () => {
if (currentIndex.value < slides.value.length - 1) currentIndex.value++
}
const loadFromCache = () => {
const cached = getCachedWyxd(workspaceId.value, filePath.value)
if (!cached || !cached.slides || cached.slides.length === 0) {
startDownload()
return
}
slides.value = cached.slides
currentIndex.value = 0
loading.value = false
errorMsg.value = ''
status.value = ''
}
const startDownload = async () => {
if (!userToken.value || !workspaceId.value || !filePath.value) {
errorMsg.value = '缺少必要参数'
status.value = 'error'
return
}
try {
loading.value = true
errorMsg.value = ''
slides.value = []
currentIndex.value = 0
status.value = 'downloading'
const tempPath = await downloadWyxdAsZip(
userToken.value, workspaceId.value, filePath.value
)
status.value = 'extracting'
const extracted = await extractWyxd(tempPath)
status.value = 'parsing'
const slideList = parseSlides(extracted.html, extracted.header, extracted.images)
if (!slideList || slideList.length === 0) {
throw new Error('无法解析文档内容')
}
slides.value = slideList
currentIndex.value = 0
loading.value = false
status.value = ''
setCachedWyxd(workspaceId.value, filePath.value, {
fileName: fileName.value,
slides: slideList
})
} catch (err) {
loading.value = false
status.value = 'error'
const msg = typeof err === 'string' ? err : (err.message || '下载失败')
errorMsg.value = msg
console.error('[WYXD] download error:', err)
}
}
onLoad((options) => {
workspaceId.value = options.workspaceId || ''
filePath.value = options.filePath || ''
fileName.value = options.fileName || '未命名文档'
userToken.value = options.userToken || uni.getStorageSync('user_token') || ''
if (isWyxdCached(workspaceId.value, filePath.value)) {
loadFromCache()
} else {
startDownload()
}
})
</script>
<style scoped>
@import url("@/pages/WorkSpace/WorkSpace.css");
.wyxd-container {
display: flex;
flex-direction: column;
background: #f5f5f5;
overflow: hidden;
}
.wyxd-header {
flex-shrink: 0;
background: #fff;
}
.wyxd-content {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* ===== 状态占位 ===== */
.wyxd-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.wyxd-state-icon {
font-size: 80rpx;
color: #ccc;
margin-bottom: 20rpx;
}
.wyxd-state-title {
font-size: 28rpx;
font-weight: 600;
color: #666;
margin-bottom: 8rpx;
}
.wyxd-state-desc {
font-size: 24rpx;
color: #999;
}
/* ===== 幻灯片区域 ===== */
.wyxd-slide-area {
flex: 1;
min-height: 0;
padding: 20rpx;
}
.wyxd-slide-scroll {
height: 100%;
background: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.wyxd-slide-card {
padding: 32rpx;
min-height: 100%;
box-sizing: border-box;
}
/* ===== 底部翻页 ===== */
.wyxd-pager {
flex-shrink: 0;
background: #fff;
border-top: 1rpx solid #eee;
padding: 16rpx 0;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
}
.wyxd-pager-inner {
display: flex;
align-items: center;
justify-content: center;
}
.wyxd-nav-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 32rpx;
background: #f5f5f5;
margin: 0 32rpx;
}
.wyxd-nav-btn:active {
background: #e0e0e0;
}
.wyxd-nav-btn.disabled {
opacity: 0.3;
pointer-events: none;
}
.wyxd-page-indicator {
font-size: 26rpx;
color: #666;
display: flex;
align-items: center;
}
.wyxd-current {
color: #3b82f6;
font-weight: 700;
}
.wyxd-divider {
margin: 0 8rpx;
color: #ccc;
}
.wyxd-total {
color: #999;
}
</style>

View File

@@ -283,8 +283,10 @@
// 长按文件
const handleLongPress = (folder) => {
if (isSelectFolder.value) return;
const isWyxd = folder.name && folder.name.toLowerCase().endsWith('.wyxd');
const itemList = isWyxd ? ['下载', '删除', 'WYXD查看器'] : ['下载', '删除'];
uni.showActionSheet({
itemList: ['下载', '删除'], // ['删除', '下载', '压缩', '重命名']
itemList: itemList,
success: (res) => {
switch (res.tapIndex) {
case 0:
@@ -293,12 +295,9 @@
case 1:
handleDelete(folder);
break;
// case 2:
// handleDownload(folder);
// break;
// case 3:
// handleRename(folder);
// break;
case 2:
if (isWyxd) openWyxdViewer(folder);
break;
}
}
})
@@ -458,6 +457,17 @@
});
};
// WYXD 查看器入口
const openWyxdViewer = (folder) => {
const filePath = folder.path.startsWith('./') ? folder.path.slice(2) : folder.path;
uni.navigateTo({
url: '/pages/WYXD/WYXD?workspaceId=' + encodeURIComponent(workspaceId.value)
+ '&filePath=' + encodeURIComponent(filePath)
+ '&fileName=' + encodeURIComponent(folder.name)
+ '&userToken=' + encodeURIComponent(UserToken.value)
});
};
// 删除
const handleDelete = (folder) => {
uni.showModal({