| | |
| | | <template> |
| | | <view class="attachment-upload"> |
| | | <!-- 附件悬浮按钮 --> |
| | | <view |
| | | class="attachment-btn" |
| | | :style="{ |
| | | right: position.right, |
| | | bottom: position.bottom, |
| | | backgroundColor: bgColor, |
| | | display: showButton ? 'flex' : 'none', |
| | | }" |
| | | @click="togglePopup" |
| | | > |
| | | <view class="attachment-btn" :style="{ |
| | | right: position.right, |
| | | bottom: position.bottom, |
| | | backgroundColor: bgColor, |
| | | display: showButton ? 'flex' : 'none', |
| | | }" @click="togglePopup"> |
| | | <text>附件</text> |
| | | <text class="badge" v-if="totalFileCount > 0">{{ totalFileCount }}</text> |
| | | </view> |
| | | |
| | | <!-- 附件弹层 --> |
| | | <uni-popup |
| | | ref="popup" |
| | | type="bottom" |
| | | :safe-area="false" |
| | | @change="onPopupChange" |
| | | > |
| | | <uni-popup ref="popup" type="bottom" :safe-area="false" @change="onPopupChange"> |
| | | <view class="attachment-popup"> |
| | | <!-- 弹层标题 --> |
| | | <view class="popup-header"> |
| | | <text class="title">附件管理</text> |
| | | <uni-icons |
| | | v-if="!readonly" |
| | | type="plus" |
| | | size="24" |
| | | :color="mainColor" |
| | | @click="chooseFile" |
| | | /> |
| | | <uni-icons v-if="!readonly" type="plus" size="24" :color="mainColor" @click="chooseFile" /> |
| | | <uni-icons type="close" size="24" color="#999" @click="closePopup" /> |
| | | </view> |
| | | |
| | | <!-- 标签页切换 --> |
| | | <view v-if="showGradeSlip" class="attachment-tabs"> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentTab === 'base' }" |
| | | @click="currentTab = 'base'" |
| | | > |
| | | <view class="tab-item" :class="{ active: currentTab === 'base' }" @click="currentTab = 'base'"> |
| | | <text>基础附件</text> |
| | | </view> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentTab === 'grade' }" |
| | | @click="currentTab = 'grade'" |
| | | > |
| | | <view class="tab-item" :class="{ active: currentTab === 'grade' }" @click="currentTab = 'grade'"> |
| | | <text>成绩单附件</text> |
| | | <text class="required-mark" v-if="isGradeRequired">*</text> |
| | | </view> |
| | |
| | | <scroll-view scroll-y class="file-list"> |
| | | <!-- 基础附件列表 --> |
| | | <template v-if="currentTab === 'base' || !showGradeSlip"> |
| | | <view |
| | | class="file-item" |
| | | v-for="(file, index) in baseFiles" |
| | | :key="'base-' + index" |
| | | > |
| | | <view class="file-item" v-for="(file, index) in baseFiles" :key="'base-' + index"> |
| | | <view class="file-icon" @click="previewFile(file)"> |
| | | <uni-icons |
| | | :type="getFileIcon(file.type)" |
| | | size="24" |
| | | :color="getFileColor(file.type)" |
| | | /> |
| | | <uni-icons :type="getFileIcon(file.type)" size="24" :color="getFileColor(file.type)" /> |
| | | </view> |
| | | <view class="file-info" @click="previewFile(file)"> |
| | | <text class="file-name">{{ file.originalFilename || file.name }}</text> |
| | |
| | | <text class="file-status" v-if="file.status === 'uploading'">上传中...</text> |
| | | <text class="file-status error" v-else-if="file.status === 'error'">上传失败</text> |
| | | </view> |
| | | <uni-icons |
| | | v-if="!readonly" |
| | | type="trash" |
| | | size="20" |
| | | color="#ff4d4f" |
| | | @click="(e) => removeFile('base', index, e)" |
| | | /> |
| | | <uni-icons v-if="!readonly" type="trash" size="20" color="#ff4d4f" |
| | | @click="(e) => removeFile('base', index, e)" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- 成绩单附件列表 --> |
| | | <template v-if="currentTab === 'grade' && showGradeSlip"> |
| | | <view |
| | | class="file-item" |
| | | v-for="(file, index) in gradeFiles" |
| | | :key="'grade-' + index" |
| | | > |
| | | <view class="file-item" v-for="(file, index) in gradeFiles" :key="'grade-' + index"> |
| | | <view class="file-icon" @click="previewFile(file)"> |
| | | <uni-icons |
| | | :type="getFileIcon(file.type)" |
| | | size="24" |
| | | :color="getFileColor(file.type)" |
| | | /> |
| | | <uni-icons :type="getFileIcon(file.type)" size="24" :color="getFileColor(file.type)" /> |
| | | </view> |
| | | <view class="file-info" @click="previewFile(file)"> |
| | | <text class="file-name">{{ file.originalFilename || file.name }}</text> |
| | |
| | | <text class="file-status" v-if="file.status === 'uploading'">上传中...</text> |
| | | <text class="file-status error" v-else-if="file.status === 'error'">上传失败</text> |
| | | </view> |
| | | <uni-icons |
| | | v-if="!readonly" |
| | | type="trash" |
| | | size="20" |
| | | color="#ff4d4f" |
| | | @click="(e) => removeFile('grade', index, e)" |
| | | /> |
| | | <uni-icons v-if="!readonly" type="trash" size="20" color="#ff4d4f" |
| | | @click="(e) => removeFile('grade', index, e)" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- 空状态 --> |
| | | <view class="empty" v-if="currentFileList.length === 0"> |
| | | <uni-icons type="info" size="24" color="#999" /> |
| | | <text v-if="currentTab === 'base' || !showGradeSlip">暂无基础附件</text> |
| | | <text v-if="currentTab === 'base' || !showGradeSlip">暂无附件</text> |
| | | <text v-else-if="currentTab === 'grade'">暂无成绩单附件</text> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <view class="popup-footer" v-if="!readonly"> |
| | | <button class="btn" @click="chooseFile">添加{{ currentTab === 'grade' ? '成绩单' : '基础' }}附件</button> |
| | | <button class="btn" @click="chooseFile">添加</button> |
| | | <button class="btn primary" @click="confirmUpload">确认上传</button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 文件选择器 --> |
| | | <uni-file-picker |
| | | ref="filePicker" |
| | | v-if="!readonly" |
| | | :auto-upload="false" |
| | | file-mediatype="all" |
| | | :limit="maxCount - currentFileList.length" |
| | | :image-styles="imageStyles" |
| | | @select="onFileSelect" |
| | | @delete="onFileDelete" |
| | | style="display: none" |
| | | /> |
| | | <uni-file-picker ref="filePicker" v-if="!readonly" :auto-upload="false" file-mediatype="all" |
| | | :limit="maxCount - currentFileList.length" :image-styles="imageStyles" @select="onFileSelect" |
| | | @delete="onFileDelete" style="display: none" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | }); |
| | | |
| | | const emit = defineEmits([ |
| | | "update:files", |
| | | "update:files", |
| | | "update:gradesFiles", // 新增事件:更新成绩单附件 |
| | | "upload", |
| | | "upload", |
| | | "preview", |
| | | "upload-grade", // 新增事件:上传成绩单附件 |
| | | "upload-base" // 新增事件:上传基础附件 |
| | |
| | | // 明确确定当前是基础附件还是成绩单附件 |
| | | const isGradeTab = props.showGradeSlip && currentTab.value === 'grade'; |
| | | const targetFiles = isGradeTab ? gradeFiles.value : baseFiles.value; |
| | | |
| | | |
| | | const newFiles = e.tempFiles |
| | | .filter((file) => { |
| | | const fileExt = file.name ? file.name.split(".").pop().toLowerCase() : ""; |
| | |
| | | if (event) { |
| | | event.stopPropagation(); |
| | | } |
| | | |
| | | |
| | | // 严格按类型删除 |
| | | if (type === 'grade') { |
| | | gradeFiles.value.splice(index, 1); |
| | |
| | | // 预览文件 |
| | | const previewFile = (file) => { |
| | | const fullUrl = getFullUrl(file.url); |
| | | |
| | | |
| | | if (file.type && supportedImageTypes.includes(file.type)) { |
| | | // 预览图片 |
| | | const allFiles = getAllFiles(); |
| | |
| | | const uploadFile = (file, type) => { |
| | | return new Promise((resolve, reject) => { |
| | | const token = uni.getStorageSync('token'); |
| | | |
| | | |
| | | uni.uploadFile({ |
| | | url: '/api/common/upload', |
| | | filePath: file.path || file.url, |
| | |
| | | success: (res) => { |
| | | if (res.statusCode === 200) { |
| | | const data = JSON.parse(res.data); |
| | | console.log(data,'文件'); |
| | | |
| | | console.log(data, '文件'); |
| | | |
| | | if (data.code === 200) { |
| | | resolve({ |
| | | ...data, |
| | |
| | | |
| | | try { |
| | | // 分别处理基础附件和成绩单附件的上传 |
| | | const pendingBaseFiles = baseFiles.value.filter(file => |
| | | const pendingBaseFiles = baseFiles.value.filter(file => |
| | | !file.url || file.status === 'pending'); |
| | | const pendingGradeFiles = gradeFiles.value.filter(file => |
| | | const pendingGradeFiles = gradeFiles.value.filter(file => |
| | | !file.url || file.status === 'pending'); |
| | | |
| | | |
| | | // 上传基础附件 |
| | | for (const file of pendingBaseFiles) { |
| | | try { |
| | |
| | | newFileName: res.newFileName, |
| | | originalFilename: res.originalFilename, |
| | | status: 'success', |
| | | size:res.size |
| | | size: res.size |
| | | }); |
| | | emit("upload-base", file); |
| | | } catch (error) { |
| | | console.error('基础附件上传失败:', error); |
| | | console.error('上传失败:', error); |
| | | file.status = 'error'; |
| | | uni.showToast({ |
| | | title: `文件 ${file.name} 上传失败`, |
| | |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | // 上传成绩单附件 |
| | | for (const file of pendingGradeFiles) { |
| | | try { |
| | |
| | | }); |
| | | } |
| | | } |
| | | console.log(baseFiles.value,'1'); |
| | | console.log(gradeFiles.value,'2'); |
| | | |
| | | console.log(baseFiles.value, '1'); |
| | | console.log(gradeFiles.value, '2'); |
| | | |
| | | // 更新文件列表 |
| | | emit("update:files", baseFiles.value); |
| | | emit("update:gradesFiles", gradeFiles.value); |
| | | // emit("upload", allFiles); |
| | | |
| | | |
| | | uni.showToast({ |
| | | title: '上传完成', |
| | | icon: 'success' |
| | |
| | | display: flex; |
| | | border-bottom: 1px solid #eee; |
| | | margin-bottom: 20rpx; |
| | | |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | |
| | | position: relative; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | |
| | | |
| | | &.active { |
| | | color: #67AFAB; |
| | | font-weight: bold; |
| | | border-bottom: 4rpx solid #67AFAB; |
| | | } |
| | | |
| | | |
| | | .required-mark { |
| | | color: red; |
| | | position: absolute; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 其他样式保持不变 |
| | | .attachment-btn { |
| | | position: fixed; |
| | |
| | | |
| | | .uni-icons { |
| | | padding: 10rpx; |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | |
| | | |
| | | .file-status { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | display: block; |
| | | |
| | | |
| | | &.error { |
| | | color: #ff4d4f; |
| | | } |
| | |
| | | |
| | | .uni-icons { |
| | | padding: 10rpx; |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |