提交任务

This commit is contained in:
WindowBird 2025-11-05 15:23:52 +08:00
parent 14678f00f7
commit f479d0d5a0
3 changed files with 715 additions and 24 deletions

View File

@ -54,6 +54,13 @@
"style": { "style": {
"navigationBarTitleText": "任务列表" "navigationBarTitleText": "任务列表"
} }
},
{
"path": "pages/submit-task/index",
"style": {
"navigationBarTitleText": "提交详情",
"navigationStyle": "custom"
}
} }
], ],

640
pages/submit-task/index.vue Normal file
View File

@ -0,0 +1,640 @@
<template>
<view class="submit-task-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<text class="nav-btn" @click="handleCancel"></text>
<text class="nav-title">提交详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 输入提交说明 -->
<view class="form-item">
<view class="form-icon">📄</view>
<textarea
v-model="formData.description"
class="description-input"
placeholder="输入提交说明"
placeholder-style="color: #999;"
:maxlength="500"
auto-height
/>
</view>
<!-- 输入任务进度 -->
<view class="form-item clickable-item" @click="openProgressPicker">
<view class="form-icon">👤%</view>
<view class="form-content">
<text v-if="formData.progress !== null" class="form-value">{{ formData.progress }}%</text>
<text v-else class="form-placeholder">输入任务进度</text>
</view>
<text class="arrow"></text>
</view>
<!-- 添加照片 -->
<view class="form-item clickable-item" @click="chooseImages">
<view class="form-icon">🏔</view>
<text class="form-label">添加照片</text>
<text class="arrow"></text>
</view>
<!-- 照片预览 -->
<view class="images-preview" v-if="formData.images.length > 0">
<view
class="image-item"
v-for="(image, index) in formData.images"
:key="index"
>
<image :src="image" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
<view class="remove-btn" @click="removeImage(index)"></view>
</view>
</view>
<!-- 添加文件 -->
<view class="form-item clickable-item" @click="chooseFiles">
<view class="form-icon">📄</view>
<text class="form-label">添加文件</text>
<text class="arrow"></text>
</view>
<!-- 文件列表 -->
<view class="files-list" v-if="formData.files.length > 0">
<view
class="file-item"
v-for="(file, index) in formData.files"
:key="index"
>
<text class="file-icon">📄</text>
<text class="file-name">{{ file.name }}</text>
<view class="remove-btn" @click="removeFile(index)"></view>
</view>
</view>
</scroll-view>
<!-- 进度选择弹窗 -->
<view v-if="showProgressPicker" class="modal-mask" @click="showProgressPicker = false">
<view class="modal-content progress-modal" @click.stop>
<view class="modal-title">选择任务进度</view>
<view class="progress-content">
<slider
:value="formData.progress || 0"
:min="0"
:max="100"
:step="10"
:show-value="true"
activeColor="#1976d2"
@change="onProgressChange"
/>
<view class="progress-options">
<view
class="progress-option"
v-for="progress in progressOptions"
:key="progress"
:class="{ active: formData.progress === progress }"
@click="selectProgress(progress)"
>
<text>{{ progress }}%</text>
</view>
</view>
</view>
<view class="modal-actions">
<text class="modal-btn cancel-btn" @click="showProgressPicker = false">取消</text>
<text class="modal-btn confirm-btn" @click="confirmProgress">确定</text>
</view>
</view>
</view>
<!-- 确认提交按钮 -->
<view class="submit-button-wrapper">
<uv-button
type="primary"
size="normal"
:disabled="!canSubmit"
@click="handleSubmit"
>
确认提交
</uv-button>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
//
const formData = ref({
description: '',
progress: null,
images: [],
files: []
});
// ID
const taskId = ref(null);
//
const showProgressPicker = ref(false);
const tempProgress = ref(null);
//
const openProgressPicker = () => {
tempProgress.value = formData.value.progress !== null ? formData.value.progress : 0;
showProgressPicker.value = true;
};
//
const progressOptions = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
//
const canSubmit = computed(() => {
return formData.value.description.trim() !== '' ||
formData.value.images.length > 0 ||
formData.value.files.length > 0;
});
//
onLoad((options) => {
taskId.value = options.taskId || options.id;
});
//
const handleCancel = () => {
uni.showModal({
title: '提示',
content: '确定要取消提交吗?未保存的内容将丢失',
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
};
//
const onProgressChange = (e) => {
tempProgress.value = e.detail.value;
};
//
const selectProgress = (progress) => {
tempProgress.value = progress;
};
//
const confirmProgress = () => {
formData.value.progress = tempProgress.value;
showProgressPicker.value = false;
};
//
const chooseImages = () => {
uni.chooseImage({
count: 9 - formData.value.images.length,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.value.images = [...formData.value.images, ...res.tempFilePaths];
},
fail: (err) => {
console.error('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
};
//
const previewImage = (index) => {
uni.previewImage({
urls: formData.value.images,
current: index
});
};
//
const removeImage = (index) => {
formData.value.images.splice(index, 1);
};
//
const chooseFiles = () => {
// #ifdef H5 || APP-PLUS
uni.showActionSheet({
itemList: ['从相册选择', '拍照'],
success: (res) => {
if (res.tapIndex === 0) {
//
uni.chooseImage({
count: 9,
success: (res) => {
//
const files = res.tempFilePaths.map((path, index) => ({
name: `image_${Date.now()}_${index}.jpg`,
path: path,
size: 0
}));
formData.value.files = [...formData.value.files, ...files];
}
});
} else {
uni.showToast({
title: '暂不支持文件选择',
icon: 'none'
});
}
}
});
// #endif
// #ifdef MP
uni.chooseFile({
count: 5 - formData.value.files.length,
type: 'file',
success: (res) => {
const files = res.tempFiles.map(file => ({
name: file.name || `file_${Date.now()}.${file.path.split('.').pop()}`,
path: file.path,
size: file.size
}));
formData.value.files = [...formData.value.files, ...files];
},
fail: (err) => {
console.error('选择文件失败:', err);
uni.showToast({
title: '选择文件失败',
icon: 'none'
});
}
});
// #endif
};
//
const removeFile = (index) => {
formData.value.files.splice(index, 1);
};
//
const handleSubmit = () => {
if (!canSubmit.value) {
uni.showToast({
title: '请至少填写提交说明或添加附件',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中...'
});
//
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
}))
};
// TODO:
// 使API
// uni.request({
// url: '/api/task/submit',
// method: 'POST',
// data: submitData,
// success: (res) => {
// //
// },
// fail: (err) => {
// //
// }
// });
//
setTimeout(() => {
uni.hideLoading();
//
const submitRecord = {
userName: '当前用户', // TODO:
time: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-'),
content: submitData.description || '',
progress: submitData.progress,
attachments: [
...submitData.images.map(img => ({ type: 'image', path: img })),
...submitData.files.map(file => ({ type: 'file', name: file.name, path: file.path }))
],
canEdit: true,
showDelayBtn: false
};
// 使
uni.setStorageSync('newSubmitRecord', submitRecord);
uni.showToast({
title: '提交成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 1000);
};
</script>
<style lang="scss" scoped>
.submit-task-page {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
padding-bottom: 80px;
}
/* 自定义导航栏 */
.custom-navbar {
background-color: #fff;
border-bottom: 1px solid #eee;
position: sticky;
top: 0;
z-index: 100;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
height: 44px;
}
.nav-btn {
font-size: 20px;
color: #333;
padding: 4px 8px;
cursor: pointer;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.nav-placeholder {
width: 36px;
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
padding: 16px;
}
/* 表单项 */
.form-item {
display: flex;
align-items: center;
padding: 16px;
background-color: #fff;
border-radius: 8px;
margin-bottom: 12px;
gap: 12px;
}
.clickable-item {
cursor: pointer;
&:active {
background-color: #f5f5f5;
}
}
.form-icon {
font-size: 20px;
flex-shrink: 0;
}
.form-content {
flex: 1;
display: flex;
flex-direction: column;
}
.form-label {
flex: 1;
font-size: 15px;
color: #333;
}
.form-value {
font-size: 15px;
color: #333;
font-weight: 500;
}
.form-placeholder {
font-size: 15px;
color: #999;
}
.arrow {
font-size: 20px;
color: #999;
flex-shrink: 0;
}
.description-input {
flex: 1;
min-height: 80px;
font-size: 15px;
color: #333;
line-height: 1.6;
}
/* 图片预览 */
.images-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
padding: 0 16px;
}
.image-item {
position: relative;
width: 100px;
height: 100px;
border-radius: 8px;
overflow: hidden;
}
.preview-image {
width: 100%;
height: 100%;
}
.remove-btn {
position: absolute;
top: 4px;
right: 4px;
width: 24px;
height: 24px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
cursor: pointer;
}
/* 文件列表 */
.files-list {
padding: 0 16px;
margin-bottom: 12px;
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
background-color: #fff;
border-radius: 8px;
margin-bottom: 8px;
gap: 12px;
}
.file-icon {
font-size: 20px;
flex-shrink: 0;
}
.file-name {
flex: 1;
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 进度选择弹窗 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background-color: #fff;
border-radius: 12px;
width: 90%;
max-width: 400px;
padding: 20px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 20px;
}
.progress-content {
padding: 20px 0;
}
.progress-options {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 20px;
}
.progress-option {
flex: 1;
min-width: 60px;
padding: 10px;
text-align: center;
background-color: #f5f5f5;
border-radius: 6px;
font-size: 14px;
color: #666;
cursor: pointer;
&.active {
background-color: #1976d2;
color: #fff;
}
&:active {
opacity: 0.8;
}
}
.modal-actions {
display: flex;
gap: 12px;
margin-top: 20px;
}
.modal-btn {
flex: 1;
padding: 12px;
text-align: center;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666;
}
.confirm-btn {
background-color: #1976d2;
color: #fff;
}
/* 提交按钮 */
.submit-button-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background-color: #fff;
border-top: 1px solid #eee;
z-index: 100;
}
</style>

View File

@ -98,18 +98,26 @@
</view> </view>
</view> </view>
</view> </view>
<view class="record-content-wrapper"> <view class="record-content-wrapper" v-if="record.content">
<text class="record-content-text">{{ record.content }}</text>
<text class="record-content-text">{{ record.content }}</text>
<text class="record-content-text">{{ record.content }}</text> <text class="record-content-text">{{ record.content }}</text>
</view> </view>
<view class="record-progress" v-if="record.progress !== null && record.progress !== undefined">
<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-attachments" v-if="record.attachments && record.attachments.length > 0">
<view <view
class="attachment-item" class="attachment-item"
v-for="(attachment, attIndex) in record.attachments" v-for="(attachment, attIndex) in record.attachments"
:key="attIndex" :key="attIndex"
> >
<view v-if="attachment.type === 'image'" class="image-placeholder"></view> <image
v-if="attachment.type === 'image'"
:src="attachment.path"
mode="aspectFill"
class="attachment-image"
@click="previewAttachmentImage(record.attachments, attIndex)"
/>
<view v-else class="file-attachment"> <view v-else class="file-attachment">
<text class="file-icon">📄</text> <text class="file-icon">📄</text>
<text class="file-name">{{ attachment.name }}</text> <text class="file-name">{{ attachment.name }}</text>
@ -131,16 +139,14 @@
<view class="btn-wrapper"> <view class="btn-wrapper">
<uv-button type="primary" size="normal" @click="submitTask">提交任务</uv-button> <uv-button type="primary" size="normal" @click="submitTask">提交任务</uv-button>
</view> </view>
<view class="btn-wrapper">
<uv-button type="error" size="normal" @click="applyDelay">申请延期</uv-button>
</view>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad,onShow } from '@dcloudio/uni-app';
// //
const activeTab = ref('info'); const activeTab = ref('info');
@ -226,6 +232,21 @@ 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) {
uni.previewImage({
urls: imageUrls,
current: currentIndex
});
}
};
// //
const getTagClass = (status) => { const getTagClass = (status) => {
const statusMap = { const statusMap = {
@ -262,19 +283,8 @@ const completeTask = () => {
// //
const submitTask = () => { const submitTask = () => {
uni.showModal({ uni.navigateTo({
title: '提交任务', url: `/pages/submit-task/index?taskId=${task.value.id || ''}`
content: '确定要提交任务吗?',
success: (res) => {
if (res.confirm) {
console.log("提交任务", task.value.id);
uni.showToast({
title: '任务已提交',
icon: 'success'
});
// API
}
}
}); });
}; };
@ -324,6 +334,19 @@ onLoad((options) => {
uni.removeStorageSync('taskDetailData'); uni.removeStorageSync('taskDetailData');
} }
}); });
//
onShow(() => {
const newSubmitRecord = uni.getStorageSync('newSubmitRecord');
if (newSubmitRecord) {
//
task.value.submitRecords.unshift(newSubmitRecord);
//
activeTab.value = 'records';
//
uni.removeStorageSync('newSubmitRecord');
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -660,6 +683,27 @@ onLoad((options) => {
color: #333; color: #333;
} }
.record-progress {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding: 8px 12px;
background-color: #f5f5f5;
border-radius: 6px;
}
.progress-label {
font-size: 14px;
color: #666;
}
.progress-value {
font-size: 14px;
color: #1976d2;
font-weight: 600;
}
.record-attachments { .record-attachments {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -668,11 +712,11 @@ onLoad((options) => {
} }
.attachment-item { .attachment-item {
.image-placeholder { .attachment-image {
width: 80px; width: 80px;
height: 80px; height: 80px;
background-color: #e0e0e0;
border-radius: 4px; border-radius: 4px;
background-color: #e0e0e0;
} }
.file-attachment { .file-attachment {