This commit is contained in:
ywyonui 2025-09-15 23:50:54 +08:00
commit f372b341cc
16 changed files with 788 additions and 251 deletions

57
src/api/base/pbApi.ts Normal file
View File

@ -0,0 +1,57 @@
import { get, post } from "@/utils/request";
/**
*
*/
export const getPbPageApi = async (params: any) => {
return await get("/api/pb/findPage", params);
};
/**
* ID获取选课排班详情
*/
export const getPbByIdApi = async (id: string) => {
return await get(`/api/pb/${id}`);
};
/**
*
*/
export const createPbApi = async (params: any) => {
return await post("/api/pb", params);
};
/**
*
*/
export const updatePbApi = async (params: any) => {
return await post("/api/pb/update", params);
};
/**
*
*/
export const deletePbApi = async (id: string) => {
return await post(`/api/pb/delete/${id}`);
};
/**
* ID获取排班列表
*/
export const getPbListByXkIdApi = async (params: any) => {
return await get("/api/pb/findByXkId", params);
};
/**
*
*/
export const getXcCourseListApi = async (params: any) => {
return await get("/api/pb/getXcCourseList", params);
};
/**
*
*/
export const getKyXcCourseListApi = async (params: any) => {
return await get("/api/pb/getKyXcCourseList", params);
};

View File

@ -1,57 +0,0 @@
import { get, post } from "@/utils/request";
/**
*
*/
export const getXkPbPageApi = async (params: any) => {
return await get("/api/xkPb/findPage", params);
};
/**
* ID获取选课排班详情
*/
export const getXkPbByIdApi = async (id: string) => {
return await get(`/api/xkPb/${id}`);
};
/**
*
*/
export const createXkPbApi = async (params: any) => {
return await post("/api/xkPb", params);
};
/**
*
*/
export const updateXkPbApi = async (params: any) => {
return await post("/api/xkPb/update", params);
};
/**
*
*/
export const deleteXkPbApi = async (id: string) => {
return await post(`/api/xkPb/delete/${id}`);
};
/**
* ID获取排班列表
*/
export const getXkPbListByXkIdApi = async (params: any) => {
return await get("/api/xkPb/findByXkId", params);
};
/**
*
*/
export const getXcCourseListApi = async (params: any) => {
return await get("/api/xkPb/getXcCourseList", params);
};
/**
*
*/
export const getKyXcCourseListApi = async (params: any) => {
return await get("/api/xkPb/getKyXcCourseList", params);
};

View File

@ -155,3 +155,6 @@ const setDefaultValue = () => {

View File

@ -13,7 +13,7 @@
<template v-for="(item,index) in head" :key="index"> <template v-for="(item,index) in head" :key="index">
<view class="font-w-500 px-5 flex-row justify-between items-center" style="width: 115px" v-if="index==0" <view class="font-w-500 px-5 flex-row justify-between items-center" style="width: 115px" v-if="index==0"
:style="{ position: 'sticky', left: '0', backgroundColor: '#aacbfb', zIndex: 10 }"> :style="{ position: 'sticky', left: '0', backgroundColor: '#aacbfb', zIndex: 10 }">
<view v-for="(zitem,xindex) in item['name'].split(',')" :key="'z_'+xindex"> <view v-for="(zitem,xindex) in (item['name'] || '').split(',')" :key="'z_'+xindex">
{{ zitem }} {{ zitem }}
</view> </view>
</view> </view>
@ -27,7 +27,7 @@
<view class="font-13 flex-row justify-between items-center px-5" style="width: 115px" v-if="dindex==0" <view class="font-13 flex-row justify-between items-center px-5" style="width: 115px" v-if="dindex==0"
:style="{ position: 'sticky', left: '0', backgroundColor: '#f8f8f8', zIndex: 10 }"> :style="{ position: 'sticky', left: '0', backgroundColor: '#f8f8f8', zIndex: 10 }">
<view> {{ bitem['gradeFullName'] }}</view> <view> {{ bitem['gradeFullName'] }}</view>
<view>{{ 100 + parseFloat(bitem['totalScore']) }}</view> <view>{{ calculateTotalScore(bitem) }}</view>
</view> </view>
<!-- <view class="font-13 flex-col-center" v-else-if="dindex==1"--> <!-- <view class="font-13 flex-col-center" v-else-if="dindex==1"-->
<!-- :style="{ position: 'sticky', left: '0', backgroundColor: '#f8f8f8', zIndex: 10 }">--> <!-- :style="{ position: 'sticky', left: '0', backgroundColor: '#f8f8f8', zIndex: 10 }">-->
@ -35,10 +35,10 @@
<!-- </view>--> <!-- </view>-->
<view class="font-13 flex-col-center" v-else> <view class="font-13 flex-col-center" v-else>
<view v-if="bitem['itemScoreMap'][ ditem['id']]"> <view v-if="bitem['itemScoreMap'][ ditem['id']]">
{{ bitem['itemScoreMap'][ditem['id']] }} {{ calculateItemScore(bitem, ditem) }}
</view> </view>
<view v-else> <view v-else>
<image src="/static/base/tb.jpg" class="wi-50 he-50"></image> {{ getBaseScore(ditem) }}
</view> </view>
</view> </view>
</template> </template>
@ -65,9 +65,56 @@ onMounted(() => {
height.value = getWindowHeight() height.value = getWindowHeight()
}) })
const head = ref([]) const head = ref<any[]>([])
const dataLiat = ref<any>([]) const dataLiat = ref<any>([])
// baseScore * 5
function getBaseScore(ditem: any) {
const inspectItem = head.value.find(item => item.id === ditem.id);
if (inspectItem && inspectItem.baseScore) {
return inspectItem.baseScore * 5;
}
return 0;
}
// baseScore * 5 -
function calculateItemScore(bitem: any, ditem: any) {
if (bitem.itemScoreMap && bitem.itemScoreMap[ditem.id]) {
// baseScore
const inspectItem = head.value.find(item => item.id === ditem.id);
if (inspectItem && inspectItem.baseScore) {
const baseScore = inspectItem.baseScore * 5; //
const deduction = bitem.itemScoreMap[ditem.id]; //
// = baseScore * 5 -
return baseScore + deduction; // deduction
}
}
return 0;
}
//
function calculateTotalScore(bitem: any) {
let totalScore = 0;
// head
head.value.forEach(item => {
if (item.id && item.baseScore) {
const baseScore = item.baseScore * 5; //
let deduction = 0; //
//
if (bitem.itemScoreMap && bitem.itemScoreMap[item.id]) {
deduction = bitem.itemScoreMap[item.id];
}
// = baseScore * 5 -
totalScore += baseScore + deduction; // deduction
}
});
return totalScore;
}
async function getWeekData() { async function getWeekData() {
let res = await inspectItemFindAllApi(); let res = await inspectItemFindAllApi();
let arr: any = [{name: "班级,得分"}]; let arr: any = [{name: "班级,得分"}];
@ -79,8 +126,25 @@ async function getWeekData() {
// arr.push(); // arr.push();
head.value = arr head.value = arr
let res1 = await evaluationGetWeekReportApi(); let res1 = await evaluationGetWeekReportApi();
const grouped = groupBy(res1.result, 'gradeNum');
dataLiat.value = values(grouped); //
//
const grouped = groupBy(res1.result, (item) => {
// "1"""
const match = item.gradeFullName.match(/^(.+?)(\d+班)$/);
return match ? match[1] : item.gradeFullName;
});
//
const gradeNames = Object.keys(grouped).sort((a, b) => {
//
const numA = parseInt(a.replace(/[^\d]/g, '')) || 0;
const numB = parseInt(b.replace(/[^\d]/g, '')) || 0;
return numA - numB;
});
//
dataLiat.value = gradeNames.map(gradeName => grouped[gradeName]);
// forEach(result, (data) => { // forEach(result, (data) => {
// let rowDatas: any = []; // let rowDatas: any = [];
// if (data && data.length > 0) { // if (data && data.length > 0) {

View File

@ -30,70 +30,21 @@
<view class="file-section"> <view class="file-section">
<view class="section-title">附件</view> <view class="section-title">附件</view>
<view class="file-list" v-if="hasAttachments"> <view class="file-list" v-if="hasAttachments">
<!-- 处理单个附件从gwInfo直接获取 --> <!-- 显示所有解析后的附件 -->
<view <view
v-if="gwInfo.fileUrl" v-for="(file, index) in parsedAttachments"
:key="(file as any).id || index"
class="file-item" class="file-item"
@click="previewSingleFile"
> >
<view class="file-icon"> <view class="file-icon">
<text v-if="isImage(gwInfo.fileFormat || '')">🖼</text> <image
<text v-else-if="isVideo(gwInfo.fileFormat || '')">🎥</text> :src="getFileIcon(getFileSuffix(file))"
<text v-else-if="canPreview(gwInfo.fileFormat || '')">📄</text> class="icon-image"
<text v-else>📎</text> mode="aspectFit"
</view>
<view class="file-info">
<text class="file-name">{{ gwInfo.fileName || '未知文件' }}</text>
<text class="file-type">{{ (gwInfo.fileFormat || 'unknown').toUpperCase() }}</text>
</view>
<view class="file-actions">
<u-button
v-if="canPreview(gwInfo.fileFormat || '') && !isVideo(gwInfo.fileFormat || '')"
text="预览"
size="mini"
type="primary"
@click.stop="previewSingleFile"
/> />
<u-button
v-if="isVideo(gwInfo.fileFormat || '')"
text="播放"
size="mini"
type="success"
@click.stop="previewSingleFile"
/>
<u-button
v-if="isImage(gwInfo.fileFormat || '')"
text="查看"
size="mini"
type="warning"
@click.stop="previewSingleFile"
/>
<u-button
text="下载"
size="mini"
type="info"
@click.stop="downloadSingleFile"
/>
</view>
</view>
<!-- 处理多个附件从files数组获取 -->
<view
v-for="(file, index) in gwInfo.files"
:key="index"
class="file-item"
@click="previewFile(file)"
>
<view class="file-icon">
<text v-if="isImage(getFileSuffix(file))">🖼</text>
<text v-else-if="isVideo(getFileSuffix(file))">🎥</text>
<text v-else-if="canPreview(getFileSuffix(file))">📄</text>
<text v-else>📎</text>
</view> </view>
<view class="file-info"> <view class="file-info">
<text class="file-name">{{ getFileName(file) }}</text> <text class="file-name">{{ getFileName(file) }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
<text class="file-type">{{ getFileSuffix(file).toUpperCase() }}</text>
</view> </view>
<view class="file-actions"> <view class="file-actions">
<u-button <u-button
@ -101,27 +52,27 @@
text="预览" text="预览"
size="mini" size="mini"
type="primary" type="primary"
@click.stop="previewFile(file)" @click="previewFile(file)"
/> />
<u-button <u-button
v-if="isVideo(getFileSuffix(file))" v-if="isVideo(getFileSuffix(file))"
text="播放" text="播放"
size="mini" size="mini"
type="success" type="success"
@click.stop="previewFile(file)" @click="previewFile(file)"
/> />
<u-button <u-button
v-if="isImage(getFileSuffix(file))" v-if="isImage(getFileSuffix(file))"
text="查看" text="查看"
size="mini" size="mini"
type="warning" type="warning"
@click.stop="previewFile(file)" @click="previewFile(file)"
/> />
<u-button <u-button
text="下载" text="下载"
size="mini" size="mini"
type="info" type="info"
@click.stop="downloadFile(file)" @click="downloadFileAction(file)"
/> />
</view> </view>
</view> </view>
@ -208,7 +159,6 @@
<text class="time">{{ formatTime(log.operationTime) }}</text> <text class="time">{{ formatTime(log.operationTime) }}</text>
</view> </view>
<view class="log-content"> <view class="log-content">
<text class="type">{{ log.operationType }}</text>
<text class="content">{{ log.operationContent }}</text> <text class="content">{{ log.operationContent }}</text>
</view> </view>
<view class="log-detail" v-if="log.beforeChange || log.afterChange"> <view class="log-detail" v-if="log.beforeChange || log.afterChange">
@ -265,7 +215,7 @@
<!-- 操作记录详情弹窗 --> <!-- 操作记录详情弹窗 -->
<u-popup v-model="showLogDetailModal" mode="center"> <u-popup :show="showLogDetailModal" @close="showLogDetailModal = false" mode="center">
<view class="detail-modal"> <view class="detail-modal">
<view class="detail-header"> <view class="detail-header">
<text class="detail-title">操作详情</text> <text class="detail-title">操作详情</text>
@ -287,6 +237,48 @@
</view> </view>
</view> </view>
</u-popup> </u-popup>
<!-- 下载路径选择弹窗 -->
<u-popup :show="showDownloadModal" @close="showDownloadModal = false" mode="center">
<view class="download-modal">
<view class="download-header">
<text class="download-title">文件下载</text>
</view>
<view class="download-content">
<view class="file-info-section">
<text class="file-label">文件名</text>
<text class="file-name">{{ downloadFileName }}</text>
</view>
<view class="path-info-section">
<text class="path-label">下载路径</text>
<text class="path-text">将下载到默认下载目录</text>
</view>
<view class="download-tips">
<text class="tips-text">点击确认后将开始下载文件</text>
</view>
<!-- 下载中状态 -->
<view class="download-progress" v-if="isDownloading">
<view class="progress-spinner"></view>
<text class="progress-text">正在下载文件请稍候...</text>
</view>
</view>
<view class="download-actions" v-if="!isDownloading">
<u-button
text="取消"
type="default"
size="large"
@click="cancelDownload"
style="margin-right: 10px;"
/>
<u-button
text="确认下载"
type="primary"
size="large"
@click="confirmDownload"
/>
</view>
</view>
</u-popup>
</BasicLayout> </BasicLayout>
</template> </template>
@ -305,7 +297,8 @@ import {
canPreview, canPreview,
previewFile as previewFileUtil, previewFile as previewFileUtil,
previewVideo as previewVideoUtil, previewVideo as previewVideoUtil,
previewImage as previewImageUtil previewImage as previewImageUtil,
downloadFile
} from "@/utils/filePreview"; } from "@/utils/filePreview";
// //
@ -332,6 +325,7 @@ interface GwInfo {
} }
interface FileInfo { interface FileInfo {
id?: string; // ID
name: string; name: string;
size: number; size: number;
url: string; url: string;
@ -379,6 +373,10 @@ const gwId = ref("");
// //
const showLogDetailModal = ref(false); const showLogDetailModal = ref(false);
const showDownloadModal = ref(false);
const downloadUrl = ref('');
const downloadFileName = ref('');
const isDownloading = ref(false);
// store // store
const { getUser, getJs } = useUserStore(); const { getUser, getJs } = useUserStore();
@ -396,9 +394,54 @@ const ccExpanded = ref(false);
// //
const logExpanded = ref(false); const logExpanded = ref(false);
//
const parsedAttachments = computed(() => {
const attachments = [];
// files
if (gwInfo.value.files && gwInfo.value.files.length > 0) {
attachments.push(...gwInfo.value.files);
}
// fileUrl
else if (gwInfo.value.fileUrl) {
const fileUrls = gwInfo.value.fileUrl.split(',').map(url => url.trim()).filter(url => url);
// fileName
let fileNames: string[] = [];
if (gwInfo.value.fileName) {
fileNames = gwInfo.value.fileName.split(',').map(name => name.trim()).filter(name => name);
}
// URL
fileUrls.forEach((url, index) => {
// 使URL
let displayName = fileNames[index] || url.split('/').pop() || `文件${index + 1}`;
const fileFormat = url.split('.').pop() || 'unknown';
// resourName
let resourName = displayName;
if (displayName.includes('.' + fileFormat)) {
resourName = displayName.replace('.' + fileFormat, '');
}
attachments.push({
id: `file_${index}`,
name: displayName, //
url: url,
resourUrl: url,
resourName: resourName, //
resSuf: fileFormat,
size: 0 // URL
});
});
}
return attachments;
});
// //
const hasAttachments = computed(() => { const hasAttachments = computed(() => {
return gwInfo.value.fileUrl || (gwInfo.value.files && gwInfo.value.files.length > 0); return parsedAttachments.value.length > 0;
}); });
// //
@ -683,7 +726,13 @@ const getCurrentUserApproverId = (currentUserId: string) => {
}; };
// gwInfo // gwInfo
const previewSingleFile = () => { const previewSingleFile = (event?: Event) => {
//
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!gwInfo.value.fileUrl) { if (!gwInfo.value.fileUrl) {
return; return;
} }
@ -706,69 +755,34 @@ const previewSingleFile = () => {
}; };
// //
const downloadSingleFile = () => { const downloadSingleFile = (event?: Event) => {
//
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!gwInfo.value.fileUrl) { if (!gwInfo.value.fileUrl) {
return; return;
} }
const fileUrl = imagUrl(gwInfo.value.fileUrl); const finalUrl = imagUrl(gwInfo.value.fileUrl);
const fileName = gwInfo.value.fileName || '未知文件'; const fileName = gwInfo.value.fileName || '未知文件';
const fileFormat = gwInfo.value.fileFormat || ''; const fileFormat = gwInfo.value.fileFormat || '';
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName; const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
// 1: 使 fetch blob //
fetch(fileUrl) showDownloadPathModal(finalUrl, fullFileName);
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.blob();
})
.then(blob => {
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fullFileName; // 使 download
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
uni.showToast({
title: '开始下载',
icon: 'success'
});
})
.catch(error => {
console.error('fetch下载失败:', error);
// 2: fetch
try {
const link = document.createElement('a');
link.href = fileUrl;
link.download = fullFileName;
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
uni.showToast({
title: '开始下载',
icon: 'success'
});
} catch (fallbackError) {
console.error('备用下载也失败:', fallbackError);
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
});
}; };
// //
const previewFile = (file: FileInfo) => { const previewFile = (file: FileInfo, event?: Event) => {
//
if (event) {
event.stopPropagation();
event.preventDefault();
}
// URL // URL
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url; const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name; const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
@ -848,65 +862,22 @@ const handlePreviewImageSingle = (imageUrl: string) => {
}); });
}; };
// // -
const downloadFile = (file: FileInfo) => { const downloadFileAction = (file: FileInfo, event?: Event) => {
downloadFileAction(file); //
}; if (event) {
event.stopPropagation();
event.preventDefault();
}
// const finalUrl = file.resourUrl ? imagUrl(file.resourUrl) : imagUrl(file.url);
const downloadFileAction = (file: FileInfo) => { //
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url; const originalFileName = getOriginalFileName(file);
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
// 1: 使 fetch blob console.log('下载文件:', { finalUrl, originalFileName, file });
fetch(fileUrl)
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.blob();
})
.then(blob => {
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName; // 使 download
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
uni.showToast({ //
title: '开始下载', showDownloadPathModal(finalUrl, originalFileName);
icon: 'success'
});
})
.catch(error => {
console.error('fetch下载失败:', error);
// 2: fetch
try {
const link = document.createElement('a');
link.href = fileUrl;
link.download = fileName;
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
uni.showToast({
title: '开始下载',
icon: 'success'
});
} catch (fallbackError) {
console.error('备用下载也失败:', fallbackError);
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
});
}; };
// //
@ -952,6 +923,31 @@ const getFileSuffix = (file: FileInfo) => {
return 'unknown'; return 'unknown';
}; };
//
const getFileIcon = (fileType: string) => {
const type = fileType.toLowerCase();
switch (type) {
case 'pdf':
return '/static/base/view/pdf.png';
case 'doc':
case 'docx':
return '/static/base/view/word.png';
case 'xls':
case 'xlsx':
return '/static/base/view/excel.png';
case 'ppt':
case 'pptx':
return '/static/base/view/ppt.png';
case 'zip':
case 'rar':
case '7z':
return '/static/base/view/zip.png';
default:
return '/static/base/view/more.png';
}
};
// //
const getStatusClass = (status: any) => { const getStatusClass = (status: any) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
@ -1040,6 +1036,314 @@ const getUrgencyText = (level: string) => {
return levelMap[level] || "未知"; return levelMap[level] || "未知";
}; };
//
const getOriginalFileName = (file: FileInfo) => {
// 使resourName使name
let fileName = '';
if (file.resourName) {
// resourName使
if (file.resSuf && !file.resourName.includes('.' + file.resSuf)) {
fileName = `${file.resourName}.${file.resSuf}`;
} else {
fileName = file.resourName;
}
} else if (file.name) {
// name使
if (file.resSuf && !file.name.includes('.' + file.resSuf)) {
fileName = `${file.name}.${file.resSuf}`;
} else {
fileName = file.name;
}
} else {
// URL
const urlFileName = file.url.split('/').pop() || '未知文件';
fileName = urlFileName;
}
return fileName;
};
//
const showDownloadPathModal = (url: string, fileName: string) => {
downloadUrl.value = url;
downloadFileName.value = fileName;
showDownloadModal.value = true;
};
//
const confirmDownload = () => {
//
isDownloading.value = true;
//
const systemInfo = uni.getSystemInfoSync();
const { platform } = systemInfo;
//
const isH5 = platform === 'web' || typeof window !== 'undefined';
console.log('平台检测:', { platform, systemInfo, isH5 });
if (isH5) {
console.log('使用H5下载方式');
// H5使
downloadForH5(downloadUrl.value, downloadFileName.value);
} else {
console.log('使用原生下载方式');
// 使
downloadFile(downloadUrl.value, downloadFileName.value)
.then(() => {
//
isDownloading.value = false;
showDownloadModal.value = false;
uni.showToast({
title: "下载成功",
icon: "success",
});
})
.catch((error) => {
console.error('下载失败:', error);
//
isDownloading.value = false;
showDownloadModal.value = false;
uni.showToast({
title: "下载失败",
icon: "error",
});
});
}
};
// H5
const downloadForH5 = (url: string, fileName: string) => {
try {
console.log('H5下载开始:', { url, fileName });
// 使
forceDownload(url, fileName);
} catch (error) {
console.error('H5下载失败:', error);
// 退
fallbackDownload(url, fileName);
}
};
//
const forceDownload = (url: string, fileName: string) => {
try {
console.log('尝试强制下载:', { url, fileName });
// 1: 使fetchblob
fetch(url, {
method: 'GET',
mode: 'cors',
cache: 'no-cache'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
console.log('文件下载成功创建blob下载');
// blob URL
const blobUrl = window.URL.createObjectURL(blob);
//
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName;
link.style.display = 'none';
// DOM
document.body.appendChild(link);
link.click();
// DOMblob URL
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
//
isDownloading.value = false;
showDownloadModal.value = false;
uni.showToast({
title: "下载成功",
icon: "success",
});
})
.catch(error => {
console.error('Fetch下载失败尝试其他方法:', error);
// fetch
tryAlternativeDownload(url, fileName);
});
} catch (error) {
console.error('强制下载失败,尝试其他方法:', error);
tryAlternativeDownload(url, fileName);
}
};
//
const tryAlternativeDownload = (url: string, fileName: string) => {
console.log('尝试替代下载方法');
// 1: 使XMLHttpRequest
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
//
isDownloading.value = false;
showDownloadModal.value = false;
uni.showToast({
title: "下载成功",
icon: "success",
});
console.log('XMLHttpRequest下载成功');
} else {
throw new Error(`HTTP error! status: ${xhr.status}`);
}
};
xhr.onerror = function() {
console.error('XMLHttpRequest下载失败');
console.log('调用手动下载提示');
showManualDownloadModal(url, fileName);
};
xhr.send();
} catch (error) {
console.error('XMLHttpRequest失败:', error);
console.log('调用手动下载提示catch');
showManualDownloadModal(url, fileName);
}
};
//
const showManualDownloadModal = (url: string, fileName: string) => {
console.log('尝试原生下载');
// 使
try {
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log('原生下载已触发');
//
setTimeout(() => {
isDownloading.value = false;
showDownloadModal.value = false;
}, 1000);
uni.showToast({
title: "开始下载",
icon: "success",
});
} catch (error) {
console.error('原生下载失败:', error);
//
isDownloading.value = false;
showDownloadModal.value = false;
uni.showToast({
title: "下载失败",
icon: "error",
});
}
};
//
const directDownload = (url: string, fileName: string) => {
try {
// URL
const downloadUrl = url.includes('?')
? `${url}&download=1&attachment=1`
: `${url}?download=1&attachment=1`;
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
link.target = '_self'; // 使_self_blank
link.style.display = 'none';
// DOM
document.body.appendChild(link);
link.click();
// DOM
document.body.removeChild(link);
uni.showToast({
title: "开始下载",
icon: "success",
});
} catch (error) {
console.error('直接下载失败:', error);
//
uni.showModal({
title: '下载提示',
content: `由于浏览器限制,无法自动下载文件。请长按链接手动保存:\n${url}`,
showCancel: false,
confirmText: '知道了'
});
}
};
//
const fallbackDownload = (url: string, fileName: string) => {
try {
console.log('使用备用下载方法');
// 使
directDownload(url, fileName);
} catch (error) {
console.error('备用下载也失败:', error);
//
uni.showModal({
title: '下载提示',
content: `由于浏览器限制,无法自动下载文件。请长按链接手动保存:\n${url}`,
showCancel: false,
confirmText: '知道了'
});
}
};
//
const cancelDownload = () => {
showDownloadModal.value = false;
};
// //
const getCreatorName = (tjrId: string) => { const getCreatorName = (tjrId: string) => {
if (!tjrId) return "未知"; if (!tjrId) return "未知";
@ -1232,9 +1536,16 @@ onMounted(() => {
.file-icon { .file-icon {
margin-right: 12px; margin-right: 12px;
font-size: 24px;
width: 32px; width: 32px;
text-align: center; height: 32px;
display: flex;
align-items: center;
justify-content: center;
.icon-image {
width: 28px;
height: 28px;
}
} }
.file-info { .file-info {
@ -1524,4 +1835,121 @@ onMounted(() => {
} }
} }
} }
.download-modal {
width: 85vw;
max-width: 450px;
padding: 20px;
background: #fff;
border-radius: 12px;
.download-header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
.download-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
}
.download-content {
margin-bottom: 25px;
.file-info-section {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
.file-label {
font-weight: 500;
color: #666;
margin-right: 10px;
flex-shrink: 0;
width: 60px;
}
.file-name {
color: #333;
font-size: 14px;
word-break: break-all;
flex: 1;
background: #f8f9fa;
padding: 8px 12px;
border-radius: 6px;
border: 1px solid #e9ecef;
}
}
.path-info-section {
display: flex;
align-items: center;
margin-bottom: 15px;
.path-label {
font-weight: 500;
color: #666;
margin-right: 10px;
flex-shrink: 0;
width: 60px;
}
.path-text {
color: #007aff;
font-size: 14px;
flex: 1;
}
}
.download-tips {
text-align: center;
.tips-text {
color: #999;
font-size: 12px;
}
}
}
.download-actions {
display: flex;
justify-content: center;
gap: 10px;
.u-button {
flex: 1;
max-width: 120px;
}
}
.download-progress {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
.progress-spinner {
width: 24px;
height: 24px;
border: 2px solid #f3f3f3;
border-top: 2px solid #007aff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
.progress-text {
color: #666;
font-size: 14px;
}
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style> </style>

View File

@ -79,7 +79,11 @@
@click="previewAttachmentFile(file)" @click="previewAttachmentFile(file)"
> >
<view class="attachment-icon"> <view class="attachment-icon">
<text>📄</text> <image
:src="getFileIcon(getFileSuffix(file))"
class="icon-image"
mode="aspectFit"
/>
</view> </view>
<text class="attachment-name">{{ getFileName(file) }}</text> <text class="attachment-name">{{ getFileName(file) }}</text>
</view> </view>
@ -529,6 +533,30 @@ const getFileSuffix = (file: FileInfo) => {
return 'unknown'; return 'unknown';
}; };
//
const getFileIcon = (fileType: string) => {
const type = fileType.toLowerCase();
switch (type) {
case 'pdf':
return '/static/base/view/pdf.png';
case 'doc':
case 'docx':
return '/static/base/view/word.png';
case 'xls':
case 'xlsx':
return '/static/base/view/excel.png';
case 'ppt':
case 'pptx':
return '/static/base/view/ppt.png';
case 'zip':
case 'rar':
case '7z':
return '/static/base/view/zip.png';
default:
return '/static/base/view/more.png';
}
};
// //
watch(dataList, (val) => { watch(dataList, (val) => {
// //
@ -810,8 +838,17 @@ onUnmounted(() => {
.attachment-icon { .attachment-icon {
margin-right: 6px; margin-right: 6px;
font-size: 16px; width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0; flex-shrink: 0;
.icon-image {
width: 18px;
height: 18px;
}
} }
.attachment-name { .attachment-name {

View File

@ -174,6 +174,9 @@ let inspectionParams = ref({
rows: 10, rows: 10,
kyXcId: xkkc.value.id, kyXcId: xkkc.value.id,
jsId: js.value.id, jsId: js.value.id,
njId: xkkc.value.njId,
bjId: xkkc.value.bjId,
njmcId: xkkc.value.njmcId,
}); });
// //

View File

@ -91,7 +91,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { getKyXcCourseListApi } from "@/api/base/xkPbApi"; import { getKyXcCourseListApi } from "@/api/base/pbApi";
import { useDataStore } from "@/store/modules/data"; import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { onBeforeUnmount, onMounted, ref } from "vue"; import { onBeforeUnmount, onMounted, ref } from "vue";
@ -121,12 +121,12 @@ onMounted(async () => {
// globaldata // globaldata
let pbData = dataStore.getGlobal; let pbData = dataStore.getGlobal;
if (!pbData || !pbData.xcbt || !pbData.xcbt.includes('课业辅导巡计划')) { if (!pbData || !pbData.xcbt ) {
pbData = dataStore.getData; pbData = dataStore.getData;
} }
// //
if (!pbData || !pbData.xcbt || pbData.xcbt.includes('课业辅导巡计划') === false) { if (!pbData || !pbData.xcbt ) {
uni.showToast({ uni.showToast({
title: '数据异常,请重新选择排班', title: '数据异常,请重新选择排班',
icon: 'none' icon: 'none'
@ -256,7 +256,7 @@ const goXc = (xkkc: any) => {
const pbData = dataStore.getGlobal; const pbData = dataStore.getGlobal;
// //
if (!pbData || !pbData.xcbt || !pbData.xcbt.includes('课业辅导巡计划')) { if (!pbData || !pbData.xcbt ) {
uni.showToast({ uni.showToast({
title: '数据异常,请重新选择排班', title: '数据异常,请重新选择排班',
icon: 'none' icon: 'none'
@ -269,6 +269,7 @@ const goXc = (xkkc: any) => {
...xkkc, ...xkkc,
id: xkkc.id, // ID id: xkkc.id, // ID
pbId: pbData.id, // ID - 使ID pbId: pbData.id, // ID - 使ID
pbJsId: xkkc.id, // IDpbJsId
xclx: pbData.xclx, xclx: pbData.xclx,
xcbt: pbData.xcbt, xcbt: pbData.xcbt,
xqmc: pbData.xqmc, xqmc: pbData.xqmc,
@ -309,7 +310,7 @@ const goRecord = (xkkc: any) => {
const pbData = dataStore.getGlobal; const pbData = dataStore.getGlobal;
// //
if (!pbData || !pbData.xcbt || !pbData.xcbt.includes('课业辅导巡计划')) { if (!pbData || !pbData.xcbt ) {
uni.showToast({ uni.showToast({
title: '数据异常,请重新选择排班', title: '数据异常,请重新选择排班',
icon: 'none' icon: 'none'

View File

@ -521,6 +521,7 @@ const submit = async () => {
njId: xkkc.value.njId || '', // ID njId: xkkc.value.njId || '', // ID
njmcId: xkkc.value.njmcId || '', // ID njmcId: xkkc.value.njmcId || '', // ID
bjId: xkkc.value.bjId || '', // ID bjId: xkkc.value.bjId || '', // ID
pbJsId: xkkc.value.pbJsId || xkkc.value.id, // 使pbJsIdID
xctime: now.format("YYYY-MM-DD HH:mm:ss"), xctime: now.format("YYYY-MM-DD HH:mm:ss"),
zp: getImageUrls(), zp: getImageUrls(),
sp: getVideoUrls(), sp: getVideoUrls(),

View File

@ -69,7 +69,7 @@ import {
} from "vue"; } from "vue";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data"; import { useDataStore } from "@/store/modules/data";
import { getXkPbPageApi } from "@/api/base/xkPbApi"; import { getPbPageApi } from "@/api/base/pbApi";
import dayjs from "dayjs"; import dayjs from "dayjs";
const { getJs } = useUserStore(); const { getJs } = useUserStore();
@ -114,7 +114,7 @@ const loadPbList = async (isRefresh = false) => {
// xqId: '', // ID // xqId: '', // ID
}; };
const res: any = await getXkPbPageApi(params); const res: any = await getPbPageApi(params);
// API // API
if (res && (res.resultCode == 1 || res.rows || res.result)) { if (res && (res.resultCode == 1 || res.rows || res.result)) {

View File

@ -103,7 +103,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { jsdXkListApi } from "@/api/base/server"; import { jsdXkListApi } from "@/api/base/server";
import { getXcCourseListApi } from "@/api/base/xkPbApi"; import { getXcCourseListApi } from "@/api/base/pbApi";
import { useDataStore } from "@/store/modules/data"; import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { onBeforeUnmount, onMounted, ref } from "vue"; import { onBeforeUnmount, onMounted, ref } from "vue";

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB