Merge branch 'master' of http://119.29.194.155:8894/zwq/zhxy-jsd
This commit is contained in:
commit
5ab1697d2b
@ -163,7 +163,11 @@ export const typesFindTreeApi = async () => {
|
||||
export const resourcesFindPageApi = async (params: any) => {
|
||||
return await get("/api/resources/findPage", params);
|
||||
};
|
||||
// 获取资源分页
|
||||
|
||||
export const resourcesSaveApi = async (params: any) => {
|
||||
return await post("/api/resources/save", params);
|
||||
};
|
||||
|
||||
export const resourcesAddNumByTypeApi = async (params: any) => {
|
||||
return await post("/api/resources/addNumByType", params);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const ip: string = "127.0.0.1:8897";
|
||||
// const ip: string = "yufangzc.com";
|
||||
// const ip: string = "yufangzc.com";
|
||||
const fwqip: string = "yufangzc.com";
|
||||
// const ip: string = "lzcxsx.cn";
|
||||
const fwqip: string = "lzcxsx.cn";
|
||||
//打包服务器接口代理标识
|
||||
const SERVERAGENT: string = "/jsd-api";
|
||||
//本地代理url地址,配置了就启动代理,没配置就不启动代理
|
||||
@ -13,7 +13,9 @@ export const BASE_URL: string =
|
||||
export const BASE_WS_URL: string = `wss://${ip}`;
|
||||
//图片地址
|
||||
// export const BASE_IMAGE_URL: string = process.env.NODE_ENV == "development" ? `https://${ip}` : `https://${fwqip}`;
|
||||
export const BASE_IMAGE_URL = `http://${fwqip}`;
|
||||
export const BASE_IMAGE_URL = `https://${fwqip}`;
|
||||
// kkFileView预览服务地址
|
||||
export const KK_FILE_VIEW_URL: string = `http://${fwqip}:8891`;
|
||||
//存token的key
|
||||
export const AUTH_KEY: string = "satoken";
|
||||
//token过期返回状态码
|
||||
|
||||
@ -51,6 +51,24 @@
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/web-viewer/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "文件预览",
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/video-player/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "视频播放",
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/updatePopup/updatePopup",
|
||||
"style": {
|
||||
|
||||
159
src/pages/system/video-player/index.vue
Normal file
159
src/pages/system/video-player/index.vue
Normal file
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view class="video-player-page">
|
||||
<view class="player-header">
|
||||
<view class="header-left">
|
||||
<u-icon name="arrow-left" size="20" @click="goBack"></u-icon>
|
||||
<text class="header-title">{{ videoTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="video-container">
|
||||
<video
|
||||
:src="videoUrl"
|
||||
:poster="posterUrl"
|
||||
:title="videoTitle"
|
||||
controls
|
||||
show-center-play-btn
|
||||
show-play-btn
|
||||
show-fullscreen-btn
|
||||
show-progress
|
||||
enable-progress-gesture
|
||||
@error="handleVideoError"
|
||||
@fullscreenchange="handleFullscreenChange"
|
||||
@play="handleVideoPlay"
|
||||
@pause="handleVideoPause"
|
||||
@ended="handleVideoEnded"
|
||||
class="video-player"
|
||||
></video>
|
||||
</view>
|
||||
|
||||
<view class="video-info">
|
||||
<view class="info-title">{{ videoTitle }}</view>
|
||||
<view class="info-desc" v-if="videoDesc">{{ videoDesc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const videoUrl = ref('');
|
||||
const videoTitle = ref('视频播放');
|
||||
const videoDesc = ref('');
|
||||
const posterUrl = ref('');
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.url) {
|
||||
videoUrl.value = decodeURIComponent(options.url);
|
||||
}
|
||||
if (options.title) {
|
||||
videoTitle.value = decodeURIComponent(options.title);
|
||||
}
|
||||
if (options.desc) {
|
||||
videoDesc.value = decodeURIComponent(options.desc);
|
||||
}
|
||||
if (options.poster) {
|
||||
posterUrl.value = decodeURIComponent(options.poster);
|
||||
}
|
||||
|
||||
console.log('视频播放URL:', videoUrl.value);
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const handleVideoError = (e) => {
|
||||
console.error('视频播放错误:', e);
|
||||
uni.showToast({
|
||||
title: '视频播放失败,请检查网络连接',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const handleFullscreenChange = (e) => {
|
||||
console.log('全屏状态变化:', e.detail.fullScreen);
|
||||
};
|
||||
|
||||
const handleVideoPlay = () => {
|
||||
console.log('视频开始播放');
|
||||
};
|
||||
|
||||
const handleVideoPause = () => {
|
||||
console.log('视频暂停');
|
||||
};
|
||||
|
||||
const handleVideoEnded = () => {
|
||||
console.log('视频播放结束');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.video-player-page {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.player-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
max-width: 400rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
padding: 30rpx;
|
||||
background-color: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
111
src/pages/system/web-viewer/index.vue
Normal file
111
src/pages/system/web-viewer/index.vue
Normal file
@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<view class="web-viewer">
|
||||
<view class="viewer-header">
|
||||
<view class="header-left">
|
||||
<u-icon name="arrow-left" size="20" @click="goBack"></u-icon>
|
||||
<text class="header-title">{{ pageTitle }}</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<u-button size="mini" @click="refreshPage">刷新</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<web-view
|
||||
:src="previewUrl"
|
||||
@message="handleMessage"
|
||||
@error="handleError"
|
||||
class="viewer-content"
|
||||
></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const previewUrl = ref('');
|
||||
const pageTitle = ref('文件预览');
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.url) {
|
||||
previewUrl.value = decodeURIComponent(options.url);
|
||||
}
|
||||
if (options.title) {
|
||||
pageTitle.value = decodeURIComponent(options.title);
|
||||
}
|
||||
|
||||
console.log('Web-View预览URL:', previewUrl.value);
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const refreshPage = () => {
|
||||
// 重新加载页面
|
||||
const currentUrl = previewUrl.value;
|
||||
previewUrl.value = '';
|
||||
setTimeout(() => {
|
||||
previewUrl.value = currentUrl;
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleMessage = (e) => {
|
||||
console.log('Web-view消息:', e.detail);
|
||||
};
|
||||
|
||||
const handleError = (e) => {
|
||||
console.error('Web-view错误:', e.detail);
|
||||
uni.showToast({
|
||||
title: '预览加载失败,请检查网络连接',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.web-viewer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.viewer-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
max-width: 400rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.viewer-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -27,20 +27,20 @@
|
||||
>范围: {{ data.njmc + data.bjmc }}</text
|
||||
>
|
||||
<view class="footer-actions">
|
||||
<u-icon
|
||||
name="list"
|
||||
size="22"
|
||||
color="#409EFF"
|
||||
<image
|
||||
src="@/static/base/details.png"
|
||||
mode="aspectFit"
|
||||
class="footer-action-icon"
|
||||
style="width:22px;height:22px;"
|
||||
@click="goToFeedback(data.id)"
|
||||
class="footer-action-icon"
|
||||
/>
|
||||
<u-icon
|
||||
<image
|
||||
v-if="data.jlStatus === 'A'"
|
||||
name="arrow-right"
|
||||
size="22"
|
||||
color="#FFA726"
|
||||
@click="goToPush(data.id)"
|
||||
src="@/static/base/push.png"
|
||||
mode="aspectFit"
|
||||
class="footer-action-icon"
|
||||
style="width:22px;height:22px;"
|
||||
@click="goToPush(data.id)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
|
||||
@ -111,6 +111,22 @@ onMounted(async () => {
|
||||
const res = await typesFindTreeApi();
|
||||
typeTree.value = res.result || [];
|
||||
console.log('教学资源页面加载完成', res);
|
||||
|
||||
// 默认选择语文科目
|
||||
if (typeTree.value.length > 0) {
|
||||
// 查找语文科目
|
||||
const chineseSubject = typeTree.value.find(item =>
|
||||
item.title && (item.title.includes('语文') || item.title.includes('语文'))
|
||||
);
|
||||
|
||||
if (chineseSubject) {
|
||||
// 选择语文科目
|
||||
selectType(chineseSubject);
|
||||
} else {
|
||||
// 如果没找到语文,选择第一个科目
|
||||
selectType(typeTree.value[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -115,7 +115,7 @@
|
||||
<text class="card-title">二维码信息</text>
|
||||
<view class="info-item">
|
||||
<text class="info-label">二维码有效期:</text>
|
||||
<text class="info-value">60秒</text>
|
||||
<text class="info-value">{{ qrExpireTime }}秒</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">当前时间:</text>
|
||||
@ -126,6 +126,38 @@
|
||||
<button @click="goBack" class="back-btn">返回</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 重复签到阶段 -->
|
||||
<view v-else-if="currentStep === 'alreadySigned'" class="already-signed-step">
|
||||
<view class="already-signed-content">
|
||||
<u-icon name="checkmark-circle" size="80" color="#67C23A" />
|
||||
<text class="already-signed-title">您已签到成功</text>
|
||||
<text class="already-signed-subtitle">您已经完成本次签到,请勿重复签到</text>
|
||||
<text class="already-signed-tip">如需重新签到,请联系会议组织者</text>
|
||||
|
||||
<view class="meeting-info-card">
|
||||
<text class="card-title">会议信息</text>
|
||||
<view class="info-item">
|
||||
<text class="info-label">会议名称:</text>
|
||||
<text class="info-value">{{ meetingInfo?.qdmc || '未设置' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">会议时间:</text>
|
||||
<text class="info-value">{{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">会议地点:</text>
|
||||
<text class="info-value">{{ meetingInfo?.qdwz || '未设置' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">签到时间:</text>
|
||||
<text class="info-value">{{ formatTime(qdzxRecord?.qdwctime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button @click="goBack" class="back-btn">返回</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 签名组件 -->
|
||||
@ -139,11 +171,20 @@ import { qdFindByIdApi, qdzxSignInApi, qdzxFindByQdParamsApi, qdzxFindByQdAndJsA
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
// 页面参数
|
||||
const options = ref<any>({});
|
||||
|
||||
// 页面加载时接收参数
|
||||
onLoad((params) => {
|
||||
options.value = params;
|
||||
});
|
||||
|
||||
// 签到相关变量
|
||||
const qdId = ref('');
|
||||
const qdzxId = ref('');
|
||||
const qrExpireTime = ref(60); // 默认60秒
|
||||
|
||||
// 当前步骤
|
||||
const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired'>('confirm');
|
||||
const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired' | 'alreadySigned'>('confirm');
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<any>(null);
|
||||
@ -163,21 +204,26 @@ const showSignature = ref<boolean>(false);
|
||||
// 获取用户store
|
||||
const userStore = useUserStore();
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (options?.qdId) {
|
||||
qdId.value = options.qdId;
|
||||
// 页面初始化
|
||||
onMounted(async () => {
|
||||
// 获取签到ID
|
||||
if (options.value?.qdId) {
|
||||
qdId.value = options.value.qdId;
|
||||
} else {
|
||||
uni.showToast({ title: '参数错误', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (options?.qdzxId) {
|
||||
qdzxId.value = options.qdzxId;
|
||||
|
||||
// 获取二维码过期时间参数
|
||||
if (options.value?.rqgqtime) {
|
||||
qrExpireTime.value = parseInt(options.value.rqgqtime) || 60;
|
||||
}
|
||||
|
||||
// 第一步:获取缓存的教师信息
|
||||
const jsData = userStore.getJs;
|
||||
if (jsData && Object.keys(jsData).length > 0) {
|
||||
userInfo.value = jsData;
|
||||
console.log('获取到教师信息:', jsData);
|
||||
} else {
|
||||
console.error('未找到教师信息,请先登录');
|
||||
uni.showToast({ title: '请先登录', icon: 'none' });
|
||||
// 跳转到登录页面
|
||||
setTimeout(() => {
|
||||
@ -189,21 +235,13 @@ onLoad(async (options) => {
|
||||
}
|
||||
|
||||
// 验证二维码时间戳是否过期
|
||||
if (options?.timestamp) {
|
||||
const qrTimestamp = parseInt(options.timestamp);
|
||||
if (options.value?.timestamp) {
|
||||
const qrTimestamp = parseInt(options.value.timestamp);
|
||||
const currentTimestamp = Date.now();
|
||||
const timeDiff = currentTimestamp - qrTimestamp;
|
||||
const maxValidTime = 60 * 1000; // 60秒有效期
|
||||
|
||||
console.log('二维码时间戳验证:', {
|
||||
qrTimestamp,
|
||||
currentTimestamp,
|
||||
timeDiff,
|
||||
maxValidTime
|
||||
});
|
||||
const maxValidTime = qrExpireTime.value * 1000; // 转换为毫秒
|
||||
|
||||
if (timeDiff > maxValidTime) {
|
||||
console.error('二维码已过期');
|
||||
currentStep.value = 'qrExpired';
|
||||
return;
|
||||
}
|
||||
@ -225,18 +263,19 @@ const checkTeacherSignInPermission = async () => {
|
||||
// 找到签到记录
|
||||
qdzxRecord.value = result.result;
|
||||
qdzxId.value = result.result.id;
|
||||
console.log('找到教师签到记录:', result.result);
|
||||
|
||||
// 第三步:获取签到详情,验证是否需要签名
|
||||
await loadMeetingInfo();
|
||||
} else {
|
||||
// 没有找到签到记录,显示"不在签到名单中"界面
|
||||
} else if (result && result.resultCode === 0) {
|
||||
// 后端明确返回未找到记录
|
||||
currentStep.value = 'notInList';
|
||||
await loadMeetingInfo(); // 仍然加载会议信息用于显示
|
||||
} else {
|
||||
// 其他错误情况
|
||||
uni.showToast({ title: '查询签到权限失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询教师签到权限失败:', error);
|
||||
uni.showToast({ title: '查询签到权限失败', icon: 'none' });
|
||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
@ -259,6 +298,13 @@ const loadMeetingInfo = async () => {
|
||||
|
||||
// 只有在找到qdzxId时才检查是否需要签名
|
||||
if (qdzxId.value) {
|
||||
// 检查是否已经签到
|
||||
if (qdzxRecord.value?.qdStatus === '1') {
|
||||
// 已经签到,跳转到重复签到界面
|
||||
currentStep.value = 'alreadySigned';
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否需要签名
|
||||
if (meetingInfo.value?.mdqz === '1') {
|
||||
// 需要签名,调用签名组件
|
||||
@ -270,7 +316,6 @@ const loadMeetingInfo = async () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载会议信息失败:', error);
|
||||
uni.showToast({ title: '加载会议信息失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
@ -292,7 +337,6 @@ const handleSignature = async () => {
|
||||
currentStep.value = 'confirm';
|
||||
} catch (error) {
|
||||
showSignature.value = false;
|
||||
console.error('签名失败:', error);
|
||||
uni.showToast({ title: '签名失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
@ -310,24 +354,15 @@ const submitSignIn = async () => {
|
||||
params.signatureImage = sign_file.value;
|
||||
}
|
||||
|
||||
console.log('提交签到参数:', JSON.stringify(params, null, 2));
|
||||
console.log('参数类型检查:', {
|
||||
qdzxId: typeof params.qdzxId,
|
||||
signInTime: typeof params.signInTime,
|
||||
signatureImage: typeof params.signatureImage
|
||||
});
|
||||
|
||||
const result = await qdzxSignInApi(params);
|
||||
console.log('签到结果:', result);
|
||||
|
||||
if (result && result.resultCode === 1) {
|
||||
currentStep.value = 'success';
|
||||
} else {
|
||||
const errorMsg = result?.msg || result?.message || '签到失败';
|
||||
const errorMsg = result?.message || '签到失败';
|
||||
uni.showToast({ title: errorMsg, icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('签到失败:', error);
|
||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' });
|
||||
}
|
||||
};
|
||||
@ -627,4 +662,35 @@ const goBack = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 重复签到阶段 */
|
||||
.already-signed-step {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 40px 24px;
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.already-signed-content {
|
||||
.already-signed-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.already-signed-subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.already-signed-tip {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -57,7 +57,7 @@
|
||||
<text class="tips-title">使用说明</text>
|
||||
</view>
|
||||
<view class="tips-content">
|
||||
<text class="tip-item">1. 二维码有效期为60秒,请及时扫描</text>
|
||||
<text class="tip-item">1. 二维码有效期为{{ qrExpireTime }}秒,请及时扫描</text>
|
||||
<text class="tip-item">2. 使用微信扫一扫功能扫描二维码</text>
|
||||
<text class="tip-item">3. 扫描后将跳转到签到确认页面</text>
|
||||
<text class="tip-item">4. 请确保网络连接正常</text>
|
||||
@ -74,6 +74,7 @@ import { qdFindByIdApi, generateQRCodeApi } from '@/api/base/server';
|
||||
|
||||
// 页面参数
|
||||
const qdId = ref('');
|
||||
const qrExpireTime = ref(60); // 默认60秒
|
||||
|
||||
// 会议信息
|
||||
const meetingInfo = ref<any>(null);
|
||||
@ -88,6 +89,10 @@ onLoad((options) => {
|
||||
if (options?.qdId) {
|
||||
qdId.value = options.qdId;
|
||||
}
|
||||
|
||||
// 启动默认倒计时,等待二维码数据解析后更新
|
||||
startTimer();
|
||||
|
||||
loadMeetingInfo();
|
||||
});
|
||||
|
||||
@ -118,14 +123,24 @@ const initQRCode = async () => {
|
||||
if (result && result.resultCode === 1) {
|
||||
// 确保二维码数据是字符串格式
|
||||
const qrData = result.result || '';
|
||||
console.log('二维码数据:', qrData);
|
||||
|
||||
// 从二维码数据中解析rqgqtime参数
|
||||
const urlParams = new URLSearchParams(qrData.split('?')[1] || '');
|
||||
const rqgqtime = urlParams.get('rqgqtime');
|
||||
if (rqgqtime) {
|
||||
const expireTime = parseInt(rqgqtime) || 60;
|
||||
qrExpireTime.value = expireTime;
|
||||
qrTimer.value = expireTime;
|
||||
// 重新启动倒计时
|
||||
startTimer();
|
||||
}
|
||||
|
||||
generateQRCodeImage(qrData);
|
||||
} else {
|
||||
throw new Error('生成二维码失败');
|
||||
}
|
||||
|
||||
// 启动倒计时
|
||||
startTimer();
|
||||
// 倒计时已在onLoad中启动,这里不需要重复启动
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error);
|
||||
uni.showToast({ title: '生成二维码失败', icon: 'none' });
|
||||
@ -171,7 +186,6 @@ const generateQRCodeImage = (qrData: string) => {
|
||||
qrCanvas.drawImage(url, 0, 0, qrSize.value, qrSize.value);
|
||||
qrCanvas.draw();
|
||||
|
||||
console.log('二维码生成成功');
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('加载qrcode库失败:', error);
|
||||
@ -186,8 +200,6 @@ const generateQRCodeImage = (qrData: string) => {
|
||||
qrCanvas.draw();
|
||||
});
|
||||
|
||||
console.log('二维码数据:', qrData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成二维码异常:', error);
|
||||
// 异常处理,显示错误信息
|
||||
@ -204,7 +216,7 @@ const generateQRCodeImage = (qrData: string) => {
|
||||
|
||||
// 启动倒计时
|
||||
const startTimer = () => {
|
||||
qrTimer.value = 60;
|
||||
qrTimer.value = qrExpireTime.value;
|
||||
if (qrTimerInterval) {
|
||||
clearInterval(qrTimerInterval);
|
||||
}
|
||||
@ -224,6 +236,8 @@ const startTimer = () => {
|
||||
const refreshQRCode = async () => {
|
||||
try {
|
||||
await initQRCode();
|
||||
// 重新启动倒计时
|
||||
startTimer();
|
||||
uni.showToast({ title: '二维码已刷新', icon: 'success' });
|
||||
} catch (error) {
|
||||
uni.showToast({ title: '刷新失败', icon: 'none' });
|
||||
|
||||
419
src/utils/filePreview.ts
Normal file
419
src/utils/filePreview.ts
Normal file
@ -0,0 +1,419 @@
|
||||
/**
|
||||
* 文件预览工具函数
|
||||
* 提供跨平台的文件预览功能
|
||||
*/
|
||||
|
||||
import { KK_FILE_VIEW_URL } from '@/config';
|
||||
|
||||
// 文件类型判断
|
||||
export const isVideo = (fileType: string): boolean => {
|
||||
const videoTypes = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', '3gp', 'm4v'];
|
||||
return videoTypes.includes(fileType.toLowerCase());
|
||||
};
|
||||
|
||||
export const isImage = (fileType: string): boolean => {
|
||||
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||
return imageTypes.includes(fileType.toLowerCase());
|
||||
};
|
||||
|
||||
// 检查是否是压缩包文件
|
||||
export const isCompressedFile = (fileName: string): boolean => {
|
||||
const compressedExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'];
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
return compressedExtensions.includes(extension || '');
|
||||
};
|
||||
|
||||
// 检查文件是否可以预览
|
||||
export const canPreview = (fileName: string): boolean => {
|
||||
const previewableExtensions = [
|
||||
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
||||
'txt', 'rtf', 'odt', 'ods', 'odp',
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp',
|
||||
'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm',
|
||||
'mp3', 'wav', 'ogg', 'aac', 'm4a'
|
||||
];
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
return previewableExtensions.includes(extension || '');
|
||||
};
|
||||
|
||||
// 获取文件图标
|
||||
export const getFileIcon = (fileName: string): string => {
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
// 压缩包文件显示下载图标
|
||||
if (isCompressedFile(fileName)) {
|
||||
return '📥';
|
||||
}
|
||||
|
||||
switch (extension) {
|
||||
case 'pdf':
|
||||
return '📄';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return '📝';
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return '📊';
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return '📽️';
|
||||
case 'txt':
|
||||
return '📃';
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'bmp':
|
||||
case 'webp':
|
||||
return '🖼️';
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
case 'wmv':
|
||||
case 'flv':
|
||||
case 'webm':
|
||||
return '🎥';
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'ogg':
|
||||
case 'aac':
|
||||
case 'm4a':
|
||||
return '🎵';
|
||||
default:
|
||||
return '📄';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文件类型名称
|
||||
export const getFileTypeName = (fileName: string): string => {
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
// 压缩包文件
|
||||
if (isCompressedFile(fileName)) {
|
||||
switch (extension) {
|
||||
case 'zip':
|
||||
return 'ZIP压缩包';
|
||||
case 'rar':
|
||||
return 'RAR压缩包';
|
||||
case '7z':
|
||||
return '7Z压缩包';
|
||||
case 'tar':
|
||||
return 'TAR压缩包';
|
||||
case 'gz':
|
||||
return 'GZ压缩包';
|
||||
case 'bz2':
|
||||
return 'BZ2压缩包';
|
||||
default:
|
||||
return '压缩包';
|
||||
}
|
||||
}
|
||||
|
||||
switch (extension) {
|
||||
case 'pdf':
|
||||
return 'PDF文档';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return 'Word文档';
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return 'Excel表格';
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return 'PowerPoint演示';
|
||||
case 'txt':
|
||||
return '文本文件';
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'bmp':
|
||||
case 'webp':
|
||||
return '图片文件';
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
case 'wmv':
|
||||
case 'flv':
|
||||
case 'webm':
|
||||
return '视频文件';
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'ogg':
|
||||
case 'aac':
|
||||
case 'm4a':
|
||||
return '音频文件';
|
||||
default:
|
||||
return '未知文件';
|
||||
}
|
||||
};
|
||||
|
||||
// 跨平台文件预览
|
||||
export const previewFile = (fileUrl: string, fileName: string, fileType: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const { platform } = systemInfo;
|
||||
const type = fileType.toLowerCase();
|
||||
|
||||
console.log('=== 文件预览调试信息 ===');
|
||||
console.log('平台:', platform);
|
||||
console.log('系统信息:', systemInfo);
|
||||
console.log('原始文件URL:', fileUrl);
|
||||
console.log('文件名:', fileName);
|
||||
console.log('文件类型:', fileType);
|
||||
|
||||
// 检查是否是压缩包文件,如果是则直接下载
|
||||
if (isCompressedFile(fileName)) {
|
||||
console.log('=== 检测到压缩包文件,直接下载 ===');
|
||||
uni.showToast({ title: '压缩包文件不支持预览,将为您下载', icon: 'none' });
|
||||
downloadFile(fileUrl, fileName).then(resolve).catch(resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否支持预览的文件格式
|
||||
const canPreviewType = canPreview(fileName);
|
||||
|
||||
if (!canPreviewType) {
|
||||
// 不支持预览的文件格式,直接下载
|
||||
console.log('不支持预览的文件格式,执行下载');
|
||||
uni.showToast({ title: '该文件格式暂不支持预览,将为您下载', icon: 'none' });
|
||||
downloadFile(fileUrl, fileName).then(resolve).catch(resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查URL是否已经是kkFileView生成的压缩包内文件URL
|
||||
const isCompressedFileUrl = fileUrl.includes('kkCompressfileKey') && fileUrl.includes('kkCompressfilepath');
|
||||
|
||||
console.log('=== 压缩包内文件检测 ===');
|
||||
console.log('URL包含kkCompressfileKey:', fileUrl.includes('kkCompressfileKey'));
|
||||
console.log('URL包含kkCompressfilepath:', fileUrl.includes('kkCompressfilepath'));
|
||||
console.log('是否为压缩包内文件URL:', isCompressedFileUrl);
|
||||
|
||||
if (isCompressedFileUrl) {
|
||||
console.log('=== 压缩包内文件处理 ===');
|
||||
console.log('检测到压缩包内文件URL,直接使用');
|
||||
console.log('原始压缩包内文件URL:', fileUrl);
|
||||
|
||||
const previewUrl = fileUrl; // 直接使用这个URL,不重新编码
|
||||
console.log('最终预览URL (压缩包内文件):', previewUrl);
|
||||
|
||||
const needLandscape = ['ppt', 'pptx'].includes(type);
|
||||
console.log('是否需要横屏显示:', needLandscape);
|
||||
|
||||
const targetPageUrl = `/pages/system/web-viewer/index?url=${encodeURIComponent(previewUrl)}&title=${encodeURIComponent(fileName)}&landscape=${needLandscape ? '1' : '0'}`;
|
||||
console.log('跳转目标页面URL:', targetPageUrl);
|
||||
|
||||
uni.navigateTo({
|
||||
url: targetPageUrl,
|
||||
success: () => {
|
||||
console.log('跳转到压缩包内文件预览页面成功');
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转到压缩包内文件预览页面失败:', err);
|
||||
console.log('错误详情:', JSON.stringify(err));
|
||||
uni.showToast({ title: '预览加载失败,尝试下载', icon: 'none' });
|
||||
setTimeout(() => {
|
||||
downloadFile(fileUrl, fileName).then(resolve).catch(resolve);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
return; // 退出函数
|
||||
}
|
||||
|
||||
console.log('=== 普通文件处理 ===');
|
||||
|
||||
// 处理普通文件预览
|
||||
const finalFileUrl = fileUrl;
|
||||
console.log('DEBUG: 最终用于编码的URL (finalFileUrl):', finalFileUrl);
|
||||
|
||||
// 使用kkFileView进行预览
|
||||
const encodedUrl = btoa(finalFileUrl);
|
||||
console.log('使用kkFileView预览,Base64编码后的URL:', encodedUrl);
|
||||
|
||||
const previewUrl = `http://yufangzc.com:8891/onlinePreview?url=${encodeURIComponent(encodedUrl)}`;
|
||||
console.log('最终预览URL (普通文件):', previewUrl);
|
||||
|
||||
const needLandscape = ['ppt', 'pptx'].includes(type);
|
||||
console.log('是否需要横屏显示:', needLandscape);
|
||||
|
||||
const targetPageUrl = `/pages/system/web-viewer/index?url=${encodeURIComponent(previewUrl)}&title=${encodeURIComponent(fileName)}&landscape=${needLandscape ? '1' : '0'}`;
|
||||
console.log('跳转目标页面URL:', targetPageUrl);
|
||||
|
||||
uni.navigateTo({
|
||||
url: targetPageUrl,
|
||||
success: () => {
|
||||
console.log('跳转到预览页面成功');
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转到预览页面失败:', err);
|
||||
console.log('错误详情:', JSON.stringify(err));
|
||||
uni.showToast({ title: '预览加载失败,尝试下载', icon: 'none' });
|
||||
setTimeout(() => {
|
||||
downloadFile(fileUrl, fileName).then(resolve).catch(resolve);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
export const downloadFile = (fileUrl: string, fileName: string) => {
|
||||
const { platform } = uni.getSystemInfoSync();
|
||||
|
||||
if (platform === 'web') {
|
||||
return downloadForWeb(fileUrl, fileName);
|
||||
} else {
|
||||
return downloadForNative(fileUrl, fileName);
|
||||
}
|
||||
};
|
||||
|
||||
// H5平台下载
|
||||
const downloadForWeb = (url: string, filename: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([blob]));
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.setAttribute('download', filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
uni.showToast({ title: "开始下载", icon: "success" });
|
||||
resolve(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("H5下载失败:", err);
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 原生平台下载
|
||||
const downloadForNative = (url: string, filename: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showLoading({ title: "下载中..." });
|
||||
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const saved = await saveFile(res.tempFilePath, filename);
|
||||
if (saved) {
|
||||
// 尝试打开文件
|
||||
uni.openDocument({
|
||||
filePath: saved,
|
||||
success: () => {
|
||||
uni.showToast({ title: "文件已打开", icon: "success" });
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('文件打开失败:', err);
|
||||
uni.showToast({ title: "文件已下载,但无法打开", icon: "none" });
|
||||
resolve(true); // 下载成功,即使打开失败也算成功
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(new Error('保存失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理下载文件时出错:', error);
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(new Error('下载失败'));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("下载失败:", err);
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(err);
|
||||
},
|
||||
complete: () => uni.hideLoading()
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 保存文件
|
||||
const saveFile = (tempPath: string, filename: string): Promise<string | null> => {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
// 检查是否支持文件系统管理器
|
||||
if (typeof uni.getFileSystemManager === 'function') {
|
||||
const fs = uni.getFileSystemManager();
|
||||
const appPath = `${filename}`;
|
||||
fs.rename({
|
||||
oldPath: tempPath,
|
||||
newPath: appPath,
|
||||
success: () => resolve(appPath),
|
||||
fail: () => {
|
||||
console.warn('文件重命名失败,使用临时路径');
|
||||
resolve(tempPath);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 不支持文件系统管理器时,直接使用临时路径
|
||||
console.warn('当前平台不支持文件系统管理器,使用临时路径');
|
||||
resolve(tempPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存文件时出错:', error);
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 视频预览
|
||||
export const previewVideo = (videoUrl: string, videoTitle: string) => {
|
||||
console.log('=== 视频预览调试信息 ===');
|
||||
console.log('视频URL:', videoUrl);
|
||||
console.log('视频标题:', videoTitle);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const targetUrl = `/pages/system/video-player/index?url=${encodeURIComponent(videoUrl)}&title=${encodeURIComponent(videoTitle)}`;
|
||||
console.log('跳转目标URL:', targetUrl);
|
||||
|
||||
uni.navigateTo({
|
||||
url: targetUrl,
|
||||
success: () => {
|
||||
console.log('跳转到视频播放页面成功');
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转到视频播放页面失败:', err);
|
||||
console.log('错误详情:', JSON.stringify(err));
|
||||
|
||||
// 跳转失败时尝试使用系统播放器
|
||||
uni.showToast({
|
||||
title: '跳转失败,尝试使用系统播放器',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 延迟后尝试下载视频
|
||||
setTimeout(() => {
|
||||
downloadFile(videoUrl, videoTitle + '.mp4').then(resolve).catch(resolve);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 图片预览
|
||||
export const previewImage = (imageUrl: string) => {
|
||||
return new Promise((resolve) => {
|
||||
uni.previewImage({
|
||||
urls: [imageUrl],
|
||||
current: imageUrl,
|
||||
success: resolve,
|
||||
fail: resolve
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -89,7 +89,7 @@ export function get<T = any>(
|
||||
options
|
||||
)
|
||||
).then((res) => {
|
||||
if (res.resultCode == 1) {
|
||||
if (res.resultCode == 1 || res.resultCode == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
if (res.resultCode) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user