教师请假添加附件,封装附件展示模块
This commit is contained in:
parent
9aa15f7252
commit
f42706629b
56
src/components/BasicFile/config.ts
Normal file
56
src/components/BasicFile/config.ts
Normal file
@ -0,0 +1,56 @@
|
||||
// BasicFile 组件配置文件
|
||||
|
||||
// 文件类型映射
|
||||
export const FILE_TYPE_MAP = {
|
||||
pdf: '/static/base/view/pdf.png',
|
||||
doc: '/static/base/view/word.png',
|
||||
docx: '/static/base/view/word.png',
|
||||
xls: '/static/base/view/excel.png',
|
||||
xlsx: '/static/base/view/excel.png',
|
||||
ppt: '/static/base/view/ppt.png',
|
||||
pptx: '/static/base/view/ppt.png',
|
||||
zip: '/static/base/view/zip.png',
|
||||
rar: '/static/base/view/zip.png',
|
||||
'7z': '/static/base/view/zip.png',
|
||||
} as const;
|
||||
|
||||
// 默认文件图标
|
||||
export const DEFAULT_FILE_ICON = '/static/base/view/more.png';
|
||||
|
||||
// 可预览的文件扩展名
|
||||
export const PREVIEWABLE_EXTENSIONS = [
|
||||
'pdf',
|
||||
'doc',
|
||||
'docx',
|
||||
'xls',
|
||||
'xlsx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'txt',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'bmp',
|
||||
'webp'
|
||||
];
|
||||
|
||||
// 视频文件扩展名
|
||||
export const VIDEO_EXTENSIONS = [
|
||||
'mp4',
|
||||
'avi',
|
||||
'mov',
|
||||
'wmv',
|
||||
'flv',
|
||||
'webm'
|
||||
];
|
||||
|
||||
// 图片文件扩展名
|
||||
export const IMAGE_EXTENSIONS = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'bmp',
|
||||
'webp'
|
||||
];
|
||||
341
src/components/BasicFile/detail.vue
Normal file
341
src/components/BasicFile/detail.vue
Normal file
@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class="basic-file-detail">
|
||||
<view class="section-title">附件</view>
|
||||
<view class="file-list" v-if="hasAttachments">
|
||||
<!-- 显示所有解析后的附件 -->
|
||||
<view
|
||||
v-for="(file, index) in parsedAttachments"
|
||||
:key="(file as any).id || index"
|
||||
class="file-item"
|
||||
>
|
||||
<view class="file-icon">
|
||||
<image
|
||||
:src="getFileIcon(getFileSuffix(file))"
|
||||
class="icon-image"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view class="file-info">
|
||||
<text class="file-name">{{ getFileName(file) }}</text>
|
||||
</view>
|
||||
<view class="file-actions">
|
||||
<u-button
|
||||
text="查看"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="previewFile(file)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="no-files">
|
||||
<text>暂无附件</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
previewFile as previewFileUtil
|
||||
} from "@/utils/filePreview";
|
||||
import { imagUrl } from "@/utils";
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
fileUrl?: string;
|
||||
fileName?: string;
|
||||
fileFormat?: string;
|
||||
files?: FileInfo[];
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
id?: string;
|
||||
name: string;
|
||||
size: number;
|
||||
url: string;
|
||||
resourName?: string;
|
||||
resourUrl?: string;
|
||||
resSuf?: string;
|
||||
}
|
||||
|
||||
// 定义默认属性值
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fileUrl: '',
|
||||
fileName: '',
|
||||
fileFormat: '',
|
||||
files: () => []
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'preview', file: FileInfo): void;
|
||||
}>();
|
||||
|
||||
// 检查是否有附件
|
||||
const hasAttachments = computed(() => {
|
||||
const attachments = [];
|
||||
|
||||
// 处理files数组
|
||||
if (props.files && props.files.length > 0) {
|
||||
attachments.push(...props.files);
|
||||
}
|
||||
// 处理逗号分隔的fileUrl字符串
|
||||
else if (props.fileUrl) {
|
||||
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
|
||||
|
||||
// 解析fileName字符串(如果存在)
|
||||
let fileNames: string[] = [];
|
||||
if (props.fileName) {
|
||||
fileNames = props.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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return attachments.length > 0;
|
||||
});
|
||||
|
||||
// 计算属性:解析后的附件列表
|
||||
const parsedAttachments = computed(() => {
|
||||
const attachments = [];
|
||||
|
||||
// 处理files数组
|
||||
if (props.files && props.files.length > 0) {
|
||||
attachments.push(...props.files);
|
||||
}
|
||||
// 处理逗号分隔的fileUrl字符串
|
||||
else if (props.fileUrl) {
|
||||
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
|
||||
|
||||
// 解析fileName字符串(如果存在)
|
||||
let fileNames: string[] = [];
|
||||
if (props.fileName) {
|
||||
fileNames = props.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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return attachments;
|
||||
});
|
||||
|
||||
// 获取文件名
|
||||
const getFileName = (file: FileInfo) => {
|
||||
if (file.resourName) {
|
||||
return file.resourName;
|
||||
}
|
||||
if (file.name) {
|
||||
// 如果name包含扩展名,去掉扩展名
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(0, lastDotIndex);
|
||||
}
|
||||
return file.name;
|
||||
}
|
||||
return '未知文件';
|
||||
};
|
||||
|
||||
// 获取文件后缀
|
||||
const getFileSuffix = (file: FileInfo) => {
|
||||
if (file.resSuf) {
|
||||
return file.resSuf;
|
||||
}
|
||||
if (file.url) {
|
||||
const lastDotIndex = file.url.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.url.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
if (file.name) {
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
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 previewFile = (file: FileInfo) => {
|
||||
emit('preview', file);
|
||||
|
||||
// 确定文件URL和名称
|
||||
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
|
||||
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
|
||||
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
|
||||
|
||||
// 统一使用 kkview 预览
|
||||
const fullFileName = fileSuf ? `${fileName}.${fileSuf}` : fileName;
|
||||
previewFileUtil(fileUrl, fullFileName, fileSuf)
|
||||
.catch((error: any) => {
|
||||
uni.showToast({
|
||||
title: '预览失败',
|
||||
icon: 'error'
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.basic-file-detail {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007aff;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon-image {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.file-type {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
background: #f0f0f0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.u-button {
|
||||
min-width: 20px;
|
||||
height: 26px;
|
||||
font-size: 12px;
|
||||
border-radius: 13px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-files {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
231
src/components/BasicFile/item.vue
Normal file
231
src/components/BasicFile/item.vue
Normal file
@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<view class="basic-file-item" v-if="hasAttachments">
|
||||
<view class="attachments-list">
|
||||
<view
|
||||
v-for="(file, fileIndex) in parsedFileList"
|
||||
:key="fileIndex"
|
||||
class="attachment-item"
|
||||
@click="handlePreview(file)"
|
||||
>
|
||||
<view class="attachment-icon">
|
||||
<image
|
||||
:src="getFileIcon(getFileSuffix(file))"
|
||||
class="icon-image"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="attachment-name">{{ getFileName(file) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
previewFile as previewFileUtil
|
||||
} from "@/utils/filePreview";
|
||||
import { imagUrl } from "@/utils";
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
fileUrl?: string;
|
||||
fileName?: string;
|
||||
fileFormat?: string;
|
||||
files?: FileInfo[];
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
name: string;
|
||||
size: number;
|
||||
url: string;
|
||||
resourName?: string;
|
||||
resourUrl?: string;
|
||||
resSuf?: string;
|
||||
resourSuf?: string;
|
||||
}
|
||||
|
||||
// 定义默认属性值
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fileUrl: '',
|
||||
fileName: '',
|
||||
fileFormat: '',
|
||||
files: () => []
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'preview', file: FileInfo): void;
|
||||
}>();
|
||||
|
||||
// 检查是否有附件
|
||||
const hasAttachments = computed(() => {
|
||||
return props.fileUrl || (props.files && props.files.length > 0);
|
||||
});
|
||||
|
||||
// 解析文件列表
|
||||
const parsedFileList = computed(() => {
|
||||
const fileList: FileInfo[] = [];
|
||||
|
||||
// 处理单个文件
|
||||
if (props.fileUrl && props.fileName) {
|
||||
const urls = props.fileUrl.split(',').map((url: string) => url.trim());
|
||||
const names = props.fileName.split(',').map((name: string) => name.trim());
|
||||
|
||||
urls.forEach((url: string, index: number) => {
|
||||
if (url) {
|
||||
const fileName = names[index] || `文件${index + 1}`;
|
||||
const fileSuffix = url.split('.').pop() || '';
|
||||
|
||||
fileList.push({
|
||||
name: fileName,
|
||||
url: url,
|
||||
resourName: fileName,
|
||||
resourUrl: url,
|
||||
resourSuf: fileSuffix,
|
||||
size: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理files数组
|
||||
if (props.files && props.files.length > 0) {
|
||||
fileList.push(...props.files);
|
||||
}
|
||||
|
||||
return fileList;
|
||||
});
|
||||
|
||||
// 获取文件名
|
||||
const getFileName = (file: FileInfo) => {
|
||||
if (file.resourName) {
|
||||
return file.resourName;
|
||||
}
|
||||
if (file.name) {
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(0, lastDotIndex);
|
||||
}
|
||||
return file.name;
|
||||
}
|
||||
return '未知文件';
|
||||
};
|
||||
|
||||
// 获取文件后缀
|
||||
const getFileSuffix = (file: FileInfo) => {
|
||||
if (file.resSuf) {
|
||||
return file.resSuf;
|
||||
}
|
||||
if (file.url) {
|
||||
const lastDotIndex = file.url.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.url.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
if (file.name) {
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
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 handlePreview = (file: FileInfo) => {
|
||||
emit('preview', file);
|
||||
|
||||
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : (file.url ? imagUrl(file.url) : '');
|
||||
const fileName = file.resourName || file.name || '未知文件';
|
||||
const fileSuf = getFileSuffix(file);
|
||||
|
||||
if (!fileUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 统一使用 kkview 预览
|
||||
const fullFileName = fileSuf ? `${fileName}.${fileSuf}` : fileName;
|
||||
previewFileUtil(fileUrl, fullFileName, fileSuf)
|
||||
.catch((error: any) => {
|
||||
uni.showToast({
|
||||
title: '预览失败',
|
||||
icon: 'error'
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.basic-file-item {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.attachments-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #ebeff5;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-icon {
|
||||
margin-right: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon-image {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
458
src/components/BasicFile/preview.vue
Normal file
458
src/components/BasicFile/preview.vue
Normal file
@ -0,0 +1,458 @@
|
||||
<template>
|
||||
<view class="basic-file-preview">
|
||||
<view class="section-title">附件</view>
|
||||
<view class="file-list" v-if="hasAttachments">
|
||||
<!-- 显示所有解析后的附件 -->
|
||||
<view
|
||||
v-for="(file, index) in parsedAttachments"
|
||||
:key="(file as any).id || index"
|
||||
class="file-item"
|
||||
>
|
||||
<view class="file-icon">
|
||||
<image
|
||||
:src="getFileIcon(getFileSuffix(file))"
|
||||
class="icon-image"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view class="file-info">
|
||||
<text class="file-name">{{ getFileName(file) }}</text>
|
||||
</view>
|
||||
<view class="file-actions">
|
||||
<u-button
|
||||
v-if="canPreview(getFileSuffix(file)) && !isVideo(getFileSuffix(file))"
|
||||
text="预览"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="previewFile(file)"
|
||||
/>
|
||||
<u-button
|
||||
v-if="isVideo(getFileSuffix(file))"
|
||||
text="播放"
|
||||
size="mini"
|
||||
type="success"
|
||||
@click="previewFile(file)"
|
||||
/>
|
||||
<u-button
|
||||
v-if="isImage(getFileSuffix(file))"
|
||||
text="查看"
|
||||
size="mini"
|
||||
type="warning"
|
||||
@click="previewFile(file)"
|
||||
/>
|
||||
<u-button
|
||||
text="下载"
|
||||
size="mini"
|
||||
type="info"
|
||||
@click="downloadFileAction(file)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="no-files">
|
||||
<text>暂无附件</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
isVideo,
|
||||
isImage,
|
||||
canPreview,
|
||||
previewFile as previewFileUtil,
|
||||
previewVideo as previewVideoUtil,
|
||||
previewImage as previewImageUtil,
|
||||
downloadFile as downloadFileUtil
|
||||
} from "@/utils/filePreview";
|
||||
import { imagUrl } from "@/utils";
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
fileUrl?: string;
|
||||
fileName?: string;
|
||||
fileFormat?: string;
|
||||
files?: FileInfo[];
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
id?: string;
|
||||
name: string;
|
||||
size: number;
|
||||
url: string;
|
||||
resourName?: string;
|
||||
resourUrl?: string;
|
||||
resSuf?: string;
|
||||
}
|
||||
|
||||
// 定义默认属性值
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fileUrl: '',
|
||||
fileName: '',
|
||||
fileFormat: '',
|
||||
files: () => []
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'preview', file: FileInfo): void;
|
||||
(e: 'download', file: FileInfo): void;
|
||||
}>();
|
||||
|
||||
// 检查是否有附件
|
||||
const hasAttachments = computed(() => {
|
||||
const attachments = [];
|
||||
|
||||
// 处理files数组
|
||||
if (props.files && props.files.length > 0) {
|
||||
attachments.push(...props.files);
|
||||
}
|
||||
// 处理逗号分隔的fileUrl字符串
|
||||
else if (props.fileUrl) {
|
||||
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
|
||||
|
||||
// 解析fileName字符串(如果存在)
|
||||
let fileNames: string[] = [];
|
||||
if (props.fileName) {
|
||||
fileNames = props.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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return attachments.length > 0;
|
||||
});
|
||||
|
||||
// 计算属性:解析后的附件列表
|
||||
const parsedAttachments = computed(() => {
|
||||
const attachments = [];
|
||||
// 处理files数组
|
||||
if (props.files && props.files.length > 0) {
|
||||
attachments.push(...props.files);
|
||||
}
|
||||
// 处理逗号分隔的fileUrl字符串
|
||||
else if (props.fileUrl) {
|
||||
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
|
||||
|
||||
// 解析fileName字符串(如果存在)
|
||||
let fileNames: string[] = [];
|
||||
if (props.fileName) {
|
||||
fileNames = props.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
|
||||
});
|
||||
});
|
||||
|
||||
console.log('parsedAttachments', fileNames, fileUrls);
|
||||
}
|
||||
|
||||
return attachments;
|
||||
});
|
||||
|
||||
// 获取文件名
|
||||
const getFileName = (file: FileInfo) => {
|
||||
if (file.resourName) {
|
||||
return file.resourName;
|
||||
}
|
||||
if (file.name) {
|
||||
// 如果name包含扩展名,去掉扩展名
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(0, lastDotIndex);
|
||||
}
|
||||
return file.name;
|
||||
}
|
||||
return '未知文件';
|
||||
};
|
||||
|
||||
// 获取文件后缀
|
||||
const getFileSuffix = (file: FileInfo) => {
|
||||
if (file.resSuf) {
|
||||
return file.resSuf;
|
||||
}
|
||||
if (file.url) {
|
||||
const lastDotIndex = file.url.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.url.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
if (file.name) {
|
||||
const lastDotIndex = file.name.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return file.name.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
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 previewFile = (file: FileInfo) => {
|
||||
emit('preview', file);
|
||||
|
||||
// 确定文件URL和名称
|
||||
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
|
||||
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
|
||||
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
|
||||
|
||||
// 根据文件类型选择预览方式
|
||||
if (isVideo(fileSuf)) {
|
||||
handlePreviewVideo(file);
|
||||
} else if (isImage(fileSuf)) {
|
||||
handlePreviewImage(file);
|
||||
} else if (canPreview(fileSuf)) {
|
||||
handlePreviewDocument(file);
|
||||
} else {
|
||||
// 不支持预览的文件类型,直接下载
|
||||
downloadFileAction(file);
|
||||
}
|
||||
};
|
||||
|
||||
// 文档预览
|
||||
const handlePreviewDocument = (file: FileInfo) => {
|
||||
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
|
||||
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
|
||||
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
|
||||
|
||||
previewFileUtil(fileUrl, fileName, fileSuf)
|
||||
.catch((error: any) => {
|
||||
// 预览失败时尝试下载
|
||||
downloadFileAction(file);
|
||||
});
|
||||
};
|
||||
|
||||
// 视频预览
|
||||
const handlePreviewVideo = (file: FileInfo) => {
|
||||
const videoUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
|
||||
const videoName = file.resourName || file.name;
|
||||
|
||||
previewVideoUtil(videoUrl, videoName)
|
||||
.catch((error: any) => {
|
||||
// 视频预览失败时的处理
|
||||
});
|
||||
};
|
||||
|
||||
// 图片预览
|
||||
const handlePreviewImage = (file: FileInfo) => {
|
||||
const imageUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
|
||||
|
||||
previewImageUtil(imageUrl)
|
||||
.catch((error: any) => {
|
||||
// 图片预览失败时的处理
|
||||
});
|
||||
};
|
||||
|
||||
// 文件下载
|
||||
const downloadFileAction = (file: FileInfo) => {
|
||||
emit('download', file);
|
||||
|
||||
const finalUrl = file.resourUrl ? imagUrl(file.resourUrl) : imagUrl(file.url);
|
||||
// 获取原始文件名
|
||||
const originalFileName = getOriginalFileName(file);
|
||||
|
||||
// 调用下载工具函数
|
||||
downloadFileUtil(finalUrl, originalFileName)
|
||||
.then(() => {
|
||||
uni.showToast({
|
||||
title: "下载成功",
|
||||
icon: "success",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('下载失败:', error);
|
||||
uni.showToast({
|
||||
title: "下载失败",
|
||||
icon: "error",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 获取原始文件名
|
||||
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;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.basic-file-preview {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007aff;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon-image {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.file-type {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
background: #f0f0f0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.u-button {
|
||||
min-width: 20px;
|
||||
height: 26px;
|
||||
font-size: 12px;
|
||||
border-radius: 13px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-files {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@ -22,6 +22,15 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 附件预览 -->
|
||||
<BasicFilePreview
|
||||
v-if="qjData.fileUrl"
|
||||
:file-url="qjData.fileUrl"
|
||||
:file-name="qjData.fileName"
|
||||
:file-format="qjData.fileFormat"
|
||||
class="m-15"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -29,6 +38,8 @@
|
||||
import { findQjById } from "@/api/base/jsQjApi";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import BasicFilePreview from "@/components/BasicFile/preview.vue";
|
||||
import BasicTitle from "@/components/BasicTitle/Title.vue";
|
||||
|
||||
const { getXxts } = useDataStore();
|
||||
|
||||
|
||||
@ -175,7 +175,7 @@ const getPkkbList = async () => {
|
||||
const newList = await QjPageUtils.getPkkbList(props.data);
|
||||
if (!newList || !newList.length) {
|
||||
console.log("没有排课信息");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
// 记录原始选课的数据
|
||||
const srcData: any = {};
|
||||
@ -196,6 +196,7 @@ const getPkkbList = async () => {
|
||||
})
|
||||
// 将kmMap转换成value对应的数组
|
||||
kmDkList.value = Object.values(kmMap);
|
||||
return dkList.value;
|
||||
};
|
||||
|
||||
const changeJsByTy = (selected: any, item: any) => {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<view class="p-15">
|
||||
<BasicForm @register="register">
|
||||
<template #dkmx>
|
||||
<view class="mt-15" v-if="formData.dkfs === 0">
|
||||
<view class="mt-15" v-show="formData.dkfs === 0">
|
||||
<JsQjDkEdit :data="formData" ref="dkRef" />
|
||||
</view>
|
||||
</template>
|
||||
@ -65,6 +65,10 @@ let formData = ref<any>({
|
||||
|
||||
const dkRef = ref<any>(null);
|
||||
|
||||
// 添加全局变量存储文件名和格式
|
||||
const fileName = ref<string>('');
|
||||
const fileFormat = ref<string>('');
|
||||
|
||||
const [register, { setValue, getValue }] = useForm({
|
||||
schema: [
|
||||
{
|
||||
@ -121,6 +125,34 @@ const [register, { setValue, getValue }] = useForm({
|
||||
},
|
||||
},
|
||||
{ interval: true },
|
||||
{
|
||||
field: "fileUrl",
|
||||
label: "上传附件",
|
||||
component: "BasicUpload",
|
||||
required: true,
|
||||
itemProps: {
|
||||
labelPosition: "top",
|
||||
},
|
||||
componentProps: {
|
||||
multiple: false,
|
||||
accept: "*/*",
|
||||
maxCount: 1,
|
||||
disabled: false,
|
||||
limit: 1,
|
||||
beforeUpload: (file: any) => {
|
||||
const fileFullName = file.name || '';
|
||||
const fileExt = fileFullName.split('.').pop() || '';
|
||||
const fileWithoutExt = fileFullName.replace(/\.[^/.]+$/, '');
|
||||
|
||||
// 存储到全局变量中
|
||||
fileName.value = fileWithoutExt;
|
||||
fileFormat.value = fileExt;
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
{ interval: true },
|
||||
{
|
||||
field: "dkfs",
|
||||
label: "代课方式",
|
||||
@ -210,6 +242,8 @@ const submit = async () => {
|
||||
// 注入审批人/抄送人
|
||||
params.sprList = formData.value.sprList || [];
|
||||
params.csrList = formData.value.csrList || [];
|
||||
params.fileName = fileName.value;
|
||||
params.fileFormat = fileFormat.value;
|
||||
if (fd.dkfs === 0) {
|
||||
const dkList = dkRef.value.getDkList();
|
||||
if (!dkList.length) {
|
||||
@ -246,6 +280,19 @@ const submit = async () => {
|
||||
} else {
|
||||
params.dkList = [];
|
||||
}
|
||||
} else if (fd.dkfs === 2) {
|
||||
let dkList = dkRef.value.getDkList();
|
||||
if (!dkList.length) {
|
||||
dkList = await dkRef.value.getPkkbList();
|
||||
}
|
||||
if (dkList.length > 0) {
|
||||
uni.showToast({
|
||||
title: "您在请假期间有课程,请选择自行协调或教科处协调",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
let submitApi = jsQjSqApi;
|
||||
if (props.data && props.data.id) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user