显示图片

This commit is contained in:
WindowBird 2025-11-06 16:49:06 +08:00
parent 74c2fda0ef
commit 3c2365660b
3 changed files with 295 additions and 106 deletions

View File

@ -97,3 +97,23 @@ export const getQiniuUploadToken = () => {
});
};
/**
* 提交任务
* @param {Object} params 请求参数
* @param {string} params.id 任务ID
* @param {string} params.submitAttaches 附件, 逗号分隔的URL字符串
* @param {string} params.submitRemark 备注
* @returns {Promise} 返回提交结果
*/
export const submitTask = ({ id, submitAttaches, submitRemark }) => {
return uni.$uv.http.put('/bst/task/submit', {
id: id,
submitAttaches: submitAttaches || '',
submitRemark: submitRemark || ''
}, {
custom: {
auth: true // 启用 token 认证
}
});
};

View File

@ -122,6 +122,7 @@
import { ref, computed, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { chooseAndUploadImages } from '@/utils/qiniu.js';
import { submitTask } from '@/common/api.js';
//
const formData = ref({
@ -416,7 +417,7 @@ const formatTimeToChinese = (date) => {
};
//
const handleSubmit = () => {
const handleSubmit = async () => {
if (!canSubmit.value) {
uni.showToast({
title: '请至少填写提交说明或添加附件',
@ -425,51 +426,48 @@ const handleSubmit = () => {
return;
}
if (!taskId.value) {
uni.showToast({
title: '任务ID不能为空',
icon: 'none'
});
return;
}
uni.showLoading({
title: isEditMode.value ? '更新中...' : '提交中...'
});
//
const submitData = {
taskId: taskId.value,
description: formData.value.description.trim(),
progress: formData.value.progress,
images: formData.value.images,
files: formData.value.files.map(file => ({
name: file.name,
path: file.path,
size: file.size
}))
};
try {
// URL
const allAttaches = [
...formData.value.images, // URL
...formData.value.files.map(file => file.path) // URL使
].filter(url => url && url.trim() !== ''); //
// TODO:
// 使API
// uni.request({
// url: isEditMode.value ? '/api/task/submit/update' : '/api/task/submit',
// method: isEditMode.value ? 'PUT' : 'POST',
// data: submitData,
// success: (res) => {
// //
// },
// fail: (err) => {
// //
// }
// });
//
const submitAttaches = allAttaches.join(',');
//
await submitTask({
id: taskId.value,
submitAttaches: submitAttaches,
submitRemark: formData.value.description.trim()
});
//
setTimeout(() => {
uni.hideLoading();
//
if (isEditMode.value) {
//
const updatedRecord = {
userName: editRecordData.value?.userName || '当前用户', //
time: formatTimeToChinese(new Date()), //
content: submitData.description || '',
progress: submitData.progress,
content: formData.value.description.trim() || '',
progress: formData.value.progress,
attachments: [
...submitData.images.map(img => ({ type: 'image', path: img })),
...submitData.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
...formData.value.images.map(img => ({ type: 'image', path: img })),
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
],
canEdit: true, //
showDelayBtn: editRecordData.value?.showDelayBtn || false
@ -490,11 +488,11 @@ const handleSubmit = () => {
const submitRecord = {
userName: '当前用户', // TODO:
time: formatTimeToChinese(new Date()),
content: submitData.description || '',
progress: submitData.progress,
content: formData.value.description.trim() || '',
progress: formData.value.progress,
attachments: [
...submitData.images.map(img => ({ type: 'image', path: img })),
...submitData.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
...formData.value.images.map(img => ({ type: 'image', path: img })),
...formData.value.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
],
canEdit: true,
showDelayBtn: false
@ -513,7 +511,15 @@ const handleSubmit = () => {
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 1000);
} catch (error) {
uni.hideLoading();
console.error('提交任务失败:', error);
uni.showToast({
title: error.message || '提交失败,请重试',
icon: 'none',
duration: 2000
});
}
};
</script>

View File

@ -72,8 +72,21 @@
</view>
<view class="task-content-wrapper">
<text class="task-content-text">{{ task.content }}</text>
<text class="task-content-text">{{ task.content }}</text>
<text class="task-content-text">{{ task.content }}</text>
</view>
<!-- 任务图片展示 -->
<view class="task-images-wrapper" v-if="task.pictures && task.pictures.length > 0">
<view
class="task-image-item"
v-for="(imageUrl, imgIndex) in task.pictures"
:key="imgIndex"
@click="previewTaskImages(task.pictures, imgIndex)"
>
<image
:src="imageUrl"
mode="aspectFill"
class="task-image"
/>
</view>
</view>
<view class="delay-btn-wrapper">
<uv-button type="error" size="small" @click="applyDelay">申请延期</uv-button>
@ -120,23 +133,30 @@
<text class="progress-label">任务进度</text>
<text class="progress-value">{{ record.progress }}%</text>
</view>
<view class="record-attachments" v-if="record.attachments && record.attachments.length > 0">
<!-- 图片附件展示一行三个 -->
<view class="record-images-wrapper" v-if="record.imageAttachments && record.imageAttachments.length > 0">
<view
class="attachment-item"
v-for="(attachment, attIndex) in record.attachments"
:key="attIndex"
class="record-image-item"
v-for="(imageUrl, imgIndex) in record.imageAttachments"
:key="imgIndex"
@click="previewRecordImages(record.imageAttachments, imgIndex)"
>
<image
v-if="attachment.type === 'image'"
:src="attachment.path"
:src="imageUrl"
mode="aspectFill"
class="attachment-image"
@click="previewAttachmentImage(record.attachments, attIndex)"
class="record-image"
/>
<view v-else class="file-attachment">
<text class="file-icon">📄</text>
<text class="file-name">{{ attachment.name }}</text>
</view>
</view>
</view>
<!-- 文件附件展示 -->
<view class="record-files-wrapper" v-if="record.fileAttachments && record.fileAttachments.length > 0">
<view
class="file-attachment-item"
v-for="(file, fileIndex) in record.fileAttachments"
:key="fileIndex"
>
<text class="file-icon">📄</text>
<text class="file-name">{{ file.name }}</text>
</view>
</view>
<view class="delay-btn-wrapper" v-if="record.showDelayBtn">
@ -290,6 +310,21 @@ const getOwnerNames = (memberList) => {
return memberList.map(member => member.userName || member.name || '').filter(name => name).join('、');
};
// URL
const parseAttachUrls = (attachStr) => {
if (!attachStr) return [];
if (typeof attachStr !== 'string') return [];
//
return attachStr.split(',').map(url => url.trim()).filter(url => url);
};
// URL
const isImageUrl = (url) => {
if (!url || typeof url !== 'string') return false;
return /\.(jpg|jpeg|png|gif|bmp|webp)(\?|$)/i.test(url);
};
//
const transformSubmitRecords = (submitList) => {
if (!Array.isArray(submitList) || submitList.length === 0) {
@ -297,28 +332,62 @@ const transformSubmitRecords = (submitList) => {
}
return submitList.map(item => {
//
let attachments = [];
// URL
let imageAttachments = [];
let fileAttachments = [];
if (item.attaches) {
try {
// attaches
const attachData = typeof item.attaches === 'string' ? JSON.parse(item.attaches) : item.attaches;
// JSON
let attachData = null;
try {
attachData = typeof item.attaches === 'string' ? JSON.parse(item.attaches) : item.attaches;
} catch (e) {
// JSONURL
attachData = null;
}
if (Array.isArray(attachData)) {
attachments = attachData.map(att => {
//
const fileName = att.name || att.fileName || '';
//
attachData.forEach(att => {
const filePath = att.path || att.url || att.filePath || '';
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName);
const fileName = att.name || att.fileName || '';
const isImage = fileName ? /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName) : isImageUrl(filePath);
return {
type: isImage ? 'image' : 'file',
name: fileName,
path: filePath
};
if (isImage && filePath) {
imageAttachments.push(filePath);
} else if (filePath) {
fileAttachments.push({
name: fileName || '文件',
path: filePath
});
}
});
} else {
// URL
const urls = parseAttachUrls(item.attaches);
urls.forEach(url => {
if (isImageUrl(url)) {
imageAttachments.push(url);
} else {
// URL
const fileName = url.split('/').pop().split('?')[0] || '文件';
fileAttachments.push({
name: fileName,
path: url
});
}
});
}
} catch (e) {
console.error('解析附件数据失败:', e);
// URL
const urls = parseAttachUrls(item.attaches);
urls.forEach(url => {
if (isImageUrl(url)) {
imageAttachments.push(url);
}
});
}
}
@ -329,7 +398,8 @@ const transformSubmitRecords = (submitList) => {
time: formatTimeToChinese(item.createTime) || '',
content: item.remark || item.description || item.taskDescription || '', //
progress: null, // API
attachments: attachments,
imageAttachments: imageAttachments,
fileAttachments: fileAttachments,
showDelayBtn: false, //
canEdit: true //
};
@ -402,17 +472,22 @@ const deleteRecord = (index) => {
});
};
//
const previewAttachmentImage = (attachments, index) => {
const imageUrls = attachments
.filter(att => att.type === 'image')
.map(att => att.path);
const currentIndex = attachments.slice(0, index).filter(att => att.type === 'image').length;
if (imageUrls.length > 0) {
//
const previewTaskImages = (imageUrls, index) => {
if (imageUrls && imageUrls.length > 0) {
uni.previewImage({
urls: imageUrls,
current: currentIndex
current: index
});
}
};
//
const previewRecordImages = (imageUrls, index) => {
if (imageUrls && imageUrls.length > 0) {
uni.previewImage({
urls: imageUrls,
current: index
});
}
};
@ -427,7 +502,7 @@ const getTagType = (tagText) => {
const getTagStyle = (tagText) => {
const status = getStatusFromTagText(tagText);
const styleConfig = getTaskStatusStyle(status);
return {
return {
backgroundColor: styleConfig.backgroundColor,
color: styleConfig.color,
borderColor: styleConfig.borderColor
@ -499,6 +574,9 @@ const loadTaskData = async (taskId) => {
//
const submitRecords = transformSubmitRecords(res.submitList || []);
// URL
const taskPictures = res.picture ? parseAttachUrls(res.picture) : [];
//
task.value = {
id: res.id || taskId,
@ -511,6 +589,7 @@ const loadTaskData = async (taskId) => {
responsible: getOwnerNames(res.memberList || []),
publishTime: res.createTime ? formatTimeToChinese(res.createTime) : '',
content: res.description || '',
pictures: taskPictures, //
submitRecords: submitRecords,
// 使
rawData: res
@ -555,13 +634,52 @@ onLoad((options) => {
}
});
//
const convertOldFormatRecord = (record) => {
// imageAttachments fileAttachments
if (record.imageAttachments !== undefined || record.fileAttachments !== undefined) {
return record;
}
// attachments
if (record.attachments && Array.isArray(record.attachments)) {
const imageAttachments = [];
const fileAttachments = [];
record.attachments.forEach(att => {
if (att.type === 'image' && att.path) {
imageAttachments.push(att.path);
} else if (att.type === 'file') {
fileAttachments.push({
name: att.name || '文件',
path: att.path || ''
});
}
});
return {
...record,
imageAttachments: imageAttachments,
fileAttachments: fileAttachments
};
}
//
return {
...record,
imageAttachments: [],
fileAttachments: []
};
};
//
onShow(() => {
//
const newSubmitRecord = uni.getStorageSync('newSubmitRecord');
if (newSubmitRecord) {
//
task.value.submitRecords.unshift(newSubmitRecord);
//
const convertedRecord = convertOldFormatRecord(newSubmitRecord);
task.value.submitRecords.unshift(convertedRecord);
//
activeTab.value = 'records';
//
@ -573,8 +691,9 @@ onShow(() => {
if (updatedSubmitRecord) {
const { recordIndex, record } = updatedSubmitRecord;
if (recordIndex !== undefined && recordIndex >= 0 && recordIndex < task.value.submitRecords.length) {
//
task.value.submitRecords[recordIndex] = record;
//
const convertedRecord = convertOldFormatRecord(record);
task.value.submitRecords[recordIndex] = convertedRecord;
//
activeTab.value = 'records';
}
@ -935,38 +1054,82 @@ onShow(() => {
font-weight: 600;
}
.record-attachments {
/* 任务图片展示(一行最多三张) */
.task-images-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.task-image-item {
/* 一行三个:每个图片宽度 = (100% - 2个gap) / 3 */
width: calc((100% - 16px) / 3);
aspect-ratio: 1;
border-radius: 4px;
overflow: hidden;
background-color: #e0e0e0;
flex-shrink: 0;
}
.task-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 提交记录图片展示(一行三个) */
.record-images-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.attachment-item {
.attachment-image {
width: 80px;
height: 80px;
border-radius: 4px;
background-color: #e0e0e0;
}
.file-attachment {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background-color: #f5f5f5;
border-radius: 4px;
}
.file-icon {
font-size: 16px;
}
.file-name {
font-size: 14px;
color: #333;
}
.record-image-item {
/* 一行三个:每个图片宽度 = (100% - 2个gap) / 3 */
width: calc((100% - 16px) / 3);
aspect-ratio: 1;
border-radius: 4px;
overflow: hidden;
background-color: #e0e0e0;
flex-shrink: 0;
}
.record-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 文件附件展示 */
.record-files-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.file-attachment-item {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background-color: #f5f5f5;
border-radius: 4px;
}
.file-icon {
font-size: 16px;
}
.file-name {
font-size: 14px;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.no-record {