707 lines
18 KiB
Vue
707 lines
18 KiB
Vue
<template>
|
||
<view class="add-resource-page">
|
||
<!-- 表单内容 -->
|
||
<scroll-view scroll-y class="form-scroll-view">
|
||
<view class="form-container">
|
||
<!-- 资源目录 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">资源目录 <text class="required">*</text></text>
|
||
<view class="picker-row" @click="showResourceTypeTree">
|
||
<text :class="{ placeholder: !formData.resourType }">
|
||
{{ getResourceTypeText() || '请选择资源目录' }}
|
||
</text>
|
||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 课题名称 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">课题名称 <text class="required">*</text></text>
|
||
<uni-easyinput
|
||
v-model="formData.resourName"
|
||
placeholder="请输入课题名称"
|
||
:inputBorder="false"
|
||
class="content-input"
|
||
></uni-easyinput>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 课时 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">课时 <text class="required">*</text></text>
|
||
<uni-easyinput
|
||
v-model="formData.hour"
|
||
placeholder="请输入课时"
|
||
type="number"
|
||
:inputBorder="false"
|
||
class="content-input"
|
||
></uni-easyinput>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 资源类别 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">资源类别 <text class="required">*</text></text>
|
||
<picker
|
||
mode="selector"
|
||
:range="categoryOptions"
|
||
range-key="label"
|
||
@change="handleCategoryChange"
|
||
>
|
||
<view class="picker-row">
|
||
<text :class="{ placeholder: !formData.category }">
|
||
{{ getCategoryText() || '请选择资源类别' }}
|
||
</text>
|
||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 资源描述 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">资源描述</text>
|
||
<uni-easyinput
|
||
type="textarea"
|
||
autoHeight
|
||
v-model="formData.content"
|
||
placeholder="请输入资源描述"
|
||
:inputBorder="false"
|
||
class="content-input"
|
||
></uni-easyinput>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 上传资源 -->
|
||
<view class="info-card">
|
||
<view class="form-item">
|
||
<text class="form-label">上传资源 <text class="required">*</text></text>
|
||
<view class="attachment-list">
|
||
<view
|
||
v-for="(att, index) in formData.attachments"
|
||
:key="index"
|
||
class="attachment-item"
|
||
>
|
||
<uni-icons
|
||
:type="getAttachmentIcon(att.type)"
|
||
size="20"
|
||
color="#666"
|
||
class="attachment-icon"
|
||
></uni-icons>
|
||
<text class="attachment-name" @click="previewAttachment(att)">{{
|
||
att.name
|
||
}}</text>
|
||
<uni-icons
|
||
type="closeempty"
|
||
size="18"
|
||
color="#999"
|
||
class="remove-icon"
|
||
@click="removeAttachment(index)"
|
||
></uni-icons>
|
||
</view>
|
||
</view>
|
||
<view class="add-attachment-placeholder" @click="addAttachment">
|
||
<view class="add-icon"
|
||
><uni-icons type="plusempty" size="20" color="#ccc"></uni-icons
|
||
></view>
|
||
<text class="placeholder-text">添加图文/视频/文件</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部提交按钮 -->
|
||
<view class="bottom-actions">
|
||
<button class="action-btn confirm-btn" @click="handleSubmitForm" :disabled="isSubmitting">
|
||
{{ isSubmitting ? '提交中...' : '提交' }}
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 资源目录选择树 -->
|
||
<BasicTree
|
||
ref="treeRef"
|
||
:range="treeData"
|
||
idKey="key"
|
||
rangeKey="title"
|
||
title="选择资源目录"
|
||
:multiple="false"
|
||
:selectParent="false"
|
||
@confirm="onTreeConfirm"
|
||
@cancel="onTreeCancel"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { zymxSaveApi } from "@/api/base/server";
|
||
import { zymlFindTreeApi } from "@/api/base/server";
|
||
import { attachmentUpload } from "@/api/system/upload";
|
||
import { imagUrl } from "@/utils";
|
||
import { useDicStore } from "@/store/modules/dic";
|
||
import BasicTree from "@/components/BasicTree/Tree.vue";
|
||
|
||
const { findByPid } = useDicStore();
|
||
|
||
interface Attachment {
|
||
name: string;
|
||
type: string;
|
||
url: string;
|
||
size?: number;
|
||
path?: string;
|
||
id?: number;
|
||
}
|
||
|
||
// 表单数据
|
||
const formData = reactive({
|
||
resourName: '',
|
||
resourType: '',
|
||
category: '',
|
||
content: '',
|
||
fileId: '',
|
||
filePath: '',
|
||
resSuf: '',
|
||
hour: '',
|
||
id: '',
|
||
fileName: '',
|
||
attachments: [] as Attachment[] // 新增附件列表
|
||
});
|
||
|
||
// 树形数据
|
||
const treeData = ref([]);
|
||
|
||
// 树组件引用
|
||
const treeRef = ref();
|
||
|
||
// 提交状态
|
||
const isSubmitting = ref(false);
|
||
|
||
// 资源类别选项 - 从字典表获取
|
||
const categoryOptions = ref([]);
|
||
|
||
// 加载资源类别数据
|
||
const loadCategoryOptions = async () => {
|
||
try {
|
||
// 使用正确的字典ID
|
||
const result = await findByPid({ pid: 1391443399 });
|
||
|
||
// 检查返回的数据结构
|
||
if (result && result.resultCode === 1 && result.result && Array.isArray(result.result) && result.result.length > 0) {
|
||
categoryOptions.value = result.result.map(item => ({
|
||
value: item.dictionaryCode,
|
||
label: item.dictionaryValue
|
||
}));
|
||
} else {
|
||
// 如果没有数据,使用默认选项
|
||
categoryOptions.value = [
|
||
{ value: '1', label: '课件' },
|
||
{ value: '2', label: '教案' },
|
||
{ value: '3', label: '学案' },
|
||
{ value: '4', label: '作业' },
|
||
{ value: '5', label: '试卷' },
|
||
{ value: '6', label: '教材' },
|
||
{ value: '7', label: '示范课' },
|
||
{ value: '8', label: '音视频合集' },
|
||
];
|
||
}
|
||
} catch (error) {
|
||
// 如果加载失败,使用默认选项
|
||
categoryOptions.value = [
|
||
{ value: '1', label: '课件' },
|
||
{ value: '2', label: '教案' },
|
||
{ value: '3', label: '学案' },
|
||
{ value: '4', label: '作业' },
|
||
{ value: '5', label: '试卷' },
|
||
{ value: '6', label: '教材' },
|
||
{ value: '7', label: '示范课' },
|
||
{ value: '8', label: '音视频合集' },
|
||
];
|
||
}
|
||
};
|
||
|
||
|
||
|
||
// 显示资源目录选择树
|
||
const showResourceTypeTree = () => {
|
||
if (treeRef.value) {
|
||
treeRef.value._show();
|
||
}
|
||
};
|
||
|
||
// 树形选择确认
|
||
const onTreeConfirm = (selectedItems: any[]) => {
|
||
if (selectedItems.length > 0) {
|
||
const selectedItem = selectedItems[0]; // 单选模式
|
||
formData.resourType = selectedItem.key;
|
||
}
|
||
};
|
||
|
||
// 树形选择取消
|
||
const onTreeCancel = () => {
|
||
// 取消选择资源目录
|
||
};
|
||
|
||
// 资源类别选择
|
||
const handleCategoryChange = (e: any) => {
|
||
const index = e.detail.value;
|
||
const selectedItem = categoryOptions.value[index];
|
||
formData.category = selectedItem.value;
|
||
};
|
||
|
||
// 添加附件
|
||
const addAttachment = () => {
|
||
uni.chooseFile({
|
||
count: 5,
|
||
type: 'all',
|
||
success: async (res) => {
|
||
const tempFiles = res.tempFiles;
|
||
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
|
||
uni.showLoading({ title: '上传中...' });
|
||
|
||
try {
|
||
for (const file of tempFiles) {
|
||
const fileInfo = file as any;
|
||
let fileType = 'file';
|
||
const fileName = fileInfo.name || '';
|
||
const fileExtension = fileName.split('.').pop()?.toLowerCase();
|
||
|
||
// 根据文件扩展名判断类型
|
||
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(fileExtension || '')) {
|
||
fileType = 'image';
|
||
} else if (['mp4', 'mov', 'avi', 'wmv', 'flv'].includes(fileExtension || '')) {
|
||
fileType = 'video';
|
||
} else if (['mp3', 'wav', 'aac', 'ogg'].includes(fileExtension || '')) {
|
||
fileType = 'audio';
|
||
}
|
||
|
||
// 根据MIME类型判断
|
||
if (fileInfo.type && typeof fileInfo.type === 'string' &&
|
||
(fileInfo.type.startsWith('image/') || fileInfo.type.startsWith('video/') || fileInfo.type.startsWith('audio/'))) {
|
||
fileType = fileInfo.type.split('/')[0];
|
||
}
|
||
|
||
// 先添加到附件列表
|
||
formData.attachments.push({
|
||
name: fileName,
|
||
type: fileType,
|
||
url: '',
|
||
size: fileInfo.size,
|
||
path: fileInfo.path,
|
||
id: Date.now()
|
||
});
|
||
|
||
// 上传文件
|
||
await uploadFile(fileInfo);
|
||
}
|
||
|
||
uni.showToast({ title: '附件上传完成', icon: 'success' });
|
||
} catch (error) {
|
||
console.error('附件上传失败:', error);
|
||
uni.showToast({ title: '附件上传失败', icon: 'error' });
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择附件失败:', err);
|
||
if (err.errMsg && !err.errMsg.includes('cancel')) {
|
||
uni.showToast({ title: '选择附件失败', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 移除附件
|
||
const removeAttachment = (index: number) => {
|
||
formData.attachments.splice(index, 1);
|
||
};
|
||
|
||
// 预览附件
|
||
const previewAttachment = (attachment: Attachment) => {
|
||
// 如果是图片类型,可以预览
|
||
if (attachment.type === 'image') {
|
||
const fullUrl = imagUrl(attachment.url);
|
||
uni.previewImage({
|
||
urls: [fullUrl],
|
||
current: fullUrl,
|
||
});
|
||
} else {
|
||
uni.showToast({
|
||
title: `预览 ${attachment.name} 功能待实现`,
|
||
icon: 'none',
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取附件图标
|
||
const getAttachmentIcon = (type: string): string => {
|
||
if (type === 'image') return 'image';
|
||
if (type === 'video') return 'videocam';
|
||
if (type === 'audio') return 'mic';
|
||
return 'paperclip';
|
||
};
|
||
|
||
// 上传文件
|
||
const uploadFile = async (file: any) => {
|
||
uni.showLoading({ title: '上传中...' });
|
||
|
||
try {
|
||
// 使用 attachmentUpload 接口
|
||
const uploadResult: any = await attachmentUpload(file.path as any);
|
||
|
||
if (uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) {
|
||
// 保存原始的 filePath(用于提交到服务器)
|
||
const originalPath = uploadResult.result[0].filePath;
|
||
const fileId = uploadResult.result[0].id;
|
||
const fileType = uploadResult.result[0].fileType;
|
||
|
||
// 更新最后一个附件的信息
|
||
const lastAttachment = formData.attachments[formData.attachments.length - 1];
|
||
if (lastAttachment) {
|
||
lastAttachment.url = originalPath;
|
||
lastAttachment.id = fileId;
|
||
}
|
||
|
||
// 同时更新原有的文件信息(保持兼容性)
|
||
formData.fileId = fileId;
|
||
formData.filePath = originalPath;
|
||
formData.resSuf = fileType;
|
||
formData.fileName = file.name;
|
||
|
||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||
} else {
|
||
throw new Error('上传失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('上传失败:', error);
|
||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||
// 移除上传失败的附件
|
||
formData.attachments.pop();
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
};
|
||
|
||
// 提交表单
|
||
const handleSubmitForm = async () => {
|
||
// 表单验证
|
||
if (!formData.resourName.trim()) {
|
||
uni.showToast({ title: '请输入课题名称', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
if (!formData.resourType) {
|
||
uni.showToast({ title: '请选择资源目录', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
if (!formData.hour.trim()) {
|
||
uni.showToast({ title: '请输入课时', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
if (!formData.category) {
|
||
uni.showToast({ title: '请选择资源类别', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
if (formData.attachments.length === 0) {
|
||
uni.showToast({ title: '请上传资源文件', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
if (isSubmitting.value) {
|
||
return;
|
||
}
|
||
|
||
isSubmitting.value = true;
|
||
|
||
try {
|
||
const params = {
|
||
resourName: formData.resourName,
|
||
resourType: formData.resourType,
|
||
category: formData.category,
|
||
remark: formData.content,
|
||
resourId: formData.fileId, // 保留原有的文件ID
|
||
resourUrl: formData.filePath, // 保留原有的文件路径
|
||
resSuf: formData.resSuf, // 保留原有的文件后缀
|
||
hour: formData.hour,
|
||
id: formData.id
|
||
};
|
||
|
||
|
||
|
||
const result = await zymxSaveApi(params);
|
||
|
||
if (result.resultCode === 1) {
|
||
uni.showToast({ title: '操作成功', icon: 'success' });
|
||
// 返回上一页并刷新列表
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
// 通过事件总线通知列表页面刷新
|
||
uni.$emit('refreshResourceList');
|
||
}, 1500);
|
||
} else {
|
||
uni.showToast({ title: '操作失败', icon: 'none' });
|
||
}
|
||
} catch (error) {
|
||
console.error('提交失败:', error);
|
||
uni.showToast({ title: '操作失败,请重试', icon: 'none' });
|
||
} finally {
|
||
isSubmitting.value = false;
|
||
}
|
||
};
|
||
|
||
// 重置表单数据
|
||
const resetFormData = () => {
|
||
formData.resourName = '';
|
||
formData.resourType = '';
|
||
formData.category = '';
|
||
formData.content = '';
|
||
formData.fileId = '';
|
||
formData.filePath = '';
|
||
formData.resSuf = '';
|
||
formData.hour = '';
|
||
formData.id = '';
|
||
formData.fileName = '';
|
||
formData.attachments = []; // 重置附件列表
|
||
};
|
||
|
||
// 加载树形数据
|
||
const loadTreeData = async () => {
|
||
try {
|
||
const res = await zymlFindTreeApi();
|
||
// 处理返回的数据结构,确保与BasicTree组件兼容
|
||
if (res && Array.isArray(res)) {
|
||
treeData.value = res.map(item => ({
|
||
key: item.key || item.id,
|
||
title: item.title || item.name,
|
||
children: item.children ? item.children.map(child => ({
|
||
key: child.key || child.id,
|
||
title: child.title || child.name,
|
||
children: child.children || []
|
||
})) : []
|
||
}));
|
||
} else {
|
||
treeData.value = res.result || [];
|
||
}
|
||
|
||
} catch (error) {
|
||
|
||
// 如果加载失败,使用空数组
|
||
treeData.value = [];
|
||
}
|
||
};
|
||
|
||
// 获取资源目录文本
|
||
const getResourceTypeText = () => {
|
||
// 递归查找选中的项目
|
||
const findSelectedItem = (items: any[], targetKey: string): any => {
|
||
for (const item of items) {
|
||
if (item.key === targetKey) {
|
||
return item;
|
||
}
|
||
if (item.children && item.children.length > 0) {
|
||
const found = findSelectedItem(item.children, targetKey);
|
||
if (found) return found;
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const selectedItem = findSelectedItem(treeData.value, formData.resourType);
|
||
return selectedItem ? selectedItem.title : '';
|
||
};
|
||
|
||
// 获取资源类别文本
|
||
const getCategoryText = () => {
|
||
const selectedItem = categoryOptions.value.find(item => item.value === formData.category);
|
||
return selectedItem ? selectedItem.label : '';
|
||
};
|
||
|
||
onMounted(async () => {
|
||
loadTreeData();
|
||
loadCategoryOptions(); // 加载资源类别选项
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.add-resource-page {
|
||
min-height: 100vh;
|
||
background-color: #f4f5f7;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
|
||
|
||
.form-scroll-view {
|
||
flex: 1;
|
||
}
|
||
|
||
.form-container {
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.info-card {
|
||
background: white;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
padding: 20rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 10rpx;
|
||
display: block;
|
||
}
|
||
|
||
.required {
|
||
color: #ff3b30;
|
||
}
|
||
|
||
.content-input {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
|
||
:deep(.uni-easyinput__content) {
|
||
background: transparent;
|
||
}
|
||
|
||
:deep(.uni-easyinput__content-input) {
|
||
color: #333;
|
||
}
|
||
|
||
:deep(.uni-easyinput__placeholder-class) {
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.picker-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
color: #333;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.placeholder {
|
||
color: #999;
|
||
}
|
||
|
||
.attachment-list {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.attachment-item {
|
||
display: flex;
|
||
align-items: center;
|
||
background-color: #f8f9fa;
|
||
border-radius: 6px;
|
||
padding: 8px 12px;
|
||
margin-bottom: 8px;
|
||
border: 1px solid #e9ecef;
|
||
|
||
.attachment-icon {
|
||
margin-right: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.attachment-name {
|
||
flex-grow: 1;
|
||
font-size: 14px;
|
||
color: #495057;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.remove-icon {
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
opacity: 0.7;
|
||
&:hover {
|
||
opacity: 1;
|
||
color: #dc3545 !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
.add-attachment-placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
border: 1px dashed #d5d8de;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
background-color: #f8f8f8;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
|
||
.add-icon {
|
||
width: 30px;
|
||
height: 30px;
|
||
border: 1px solid #d5d8de;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-right: 12px;
|
||
background-color: #fff;
|
||
|
||
.uni-icons {
|
||
color: #999 !important;
|
||
}
|
||
}
|
||
|
||
.placeholder-text {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
|
||
&:active {
|
||
background-color: #eee;
|
||
}
|
||
}
|
||
|
||
.bottom-actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
padding: 30rpx;
|
||
background: white;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.confirm-btn {
|
||
width: 100%;
|
||
padding: 20rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
border: none;
|
||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||
color: white;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 122, 255, 0.3);
|
||
}
|
||
|
||
.confirm-btn:disabled {
|
||
opacity: 0.6;
|
||
}
|
||
</style> |