OfficeSystem/pages/submit-task/index.vue
2025-11-06 16:49:06 +08:00

804 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="submit-task-page">
<!-- 自定义导航栏 -->
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<view style="padding: 16px">
<!-- 输入提交说明 -->
<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>
</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="tempProgress !== null ? tempProgress : 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: tempProgress === 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"
>
{{ isEditMode ? '确认更新' : '确认提交' }}
</uv-button>
</view>
</view>
</template>
<script setup>
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({
description: '',
progress: null,
images: [],
files: []
});
// 任务ID
const taskId = ref(null);
// 编辑模式标识
const isEditMode = ref(false);
const editRecordIndex = ref(-1);
const editRecordData = 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;
// 检查是否是编辑模式
if (options.mode === 'edit' || options.recordIndex !== undefined) {
isEditMode.value = true;
editRecordIndex.value = parseInt(options.recordIndex || -1);
// 从存储中获取编辑数据
const editData = uni.getStorageSync('editSubmitRecord');
if (editData && editData.record) {
editRecordData.value = editData.record;
// 预填充表单数据
formData.value.description = editData.record.content || '';
formData.value.progress = editData.record.progress !== null && editData.record.progress !== undefined
? editData.record.progress
: null;
// 处理附件
if (editData.record.attachments && editData.record.attachments.length > 0) {
editData.record.attachments.forEach(attachment => {
if (attachment.type === 'image' && attachment.path) {
formData.value.images.push(attachment.path);
} else if (attachment.type === 'file') {
formData.value.files.push({
name: attachment.name || '文件',
path: attachment.path || '',
size: attachment.size || 0
});
}
});
}
// 清除存储的编辑数据
uni.removeStorageSync('editSubmitRecord');
}
}
});
// 取消
const handleCancel = () => {
uni.showModal({
title: '提示',
content: '确定要取消提交吗?未保存的内容将丢失',
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
};
// 进度变化
const onProgressChange = (e) => {
tempProgress.value = e.detail.value;
};
// 选择进度(点击按钮直接确认)
const selectProgress = (progress) => {
formData.value.progress = progress;
showProgressPicker.value = false;
};
// 确认进度
const confirmProgress = () => {
formData.value.progress = tempProgress.value;
showProgressPicker.value = false;
};
// 选择图片并自动上传到七牛云
const chooseImages = async () => {
try {
const remainingCount = 9 - formData.value.images.length;
if (remainingCount <= 0) {
uni.showToast({
title: '最多只能添加9张图片',
icon: 'none'
});
return;
}
// 使用封装好的选择并上传功能
const urls = await chooseAndUploadImages({
count: remainingCount,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera']
});
// 将上传后的URL添加到图片列表
formData.value.images = [...formData.value.images, ...urls];
} catch (err) {
console.error('选择或上传图片失败:', err);
uni.showToast({
title: err.message || '选择图片失败',
icon: 'none'
});
}
};
// 预览图片
const previewImage = (index) => {
uni.previewImage({
urls: formData.value.images,
current: index
});
};
// 删除图片
const removeImage = (index) => {
formData.value.images.splice(index, 1);
};
// 选择文件(安卓平台)
const chooseFiles = () => {
const remainingCount = 5 - formData.value.files.length;
if (remainingCount <= 0) {
uni.showToast({
title: '最多只能添加5个文件',
icon: 'none'
});
return;
}
// 安卓平台使用 plus API 调用原生文件选择器
if (typeof plus !== 'undefined') {
try {
const Intent = plus.android.importClass('android.content.Intent');
const main = plus.android.runtimeMainActivity();
// 创建文件选择 Intent
const intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType('*/*');
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 允许多选
// 启动文件选择器
main.startActivityForResult(intent, 1001);
// 监听文件选择结果
const originalOnActivityResult = main.onActivityResult;
main.onActivityResult = (requestCode, resultCode, data) => {
if (requestCode === 1001) {
if (resultCode === -1 && data) { // RESULT_OK = -1
try {
const clipData = data.getClipData();
const files = [];
// 获取文件名的方法
const getFileName = (uri) => {
try {
const cursor = main.getContentResolver().query(uri, null, null, null, null);
if (cursor && cursor.moveToFirst()) {
const nameIndex = cursor.getColumnIndex('_display_name');
if (nameIndex !== -1) {
const fileName = cursor.getString(nameIndex);
cursor.close();
return fileName;
}
cursor.close();
}
} catch (e) {
console.error('获取文件名失败:', e);
}
return null;
};
if (clipData) {
// 多选文件
const count = clipData.getItemCount();
for (let i = 0; i < count && files.length < remainingCount; i++) {
const item = clipData.getItemAt(i);
const uri = item.getUri();
const uriString = uri.toString();
// 获取文件名
let fileName = getFileName(uri) || `file_${Date.now()}_${i}`;
files.push({
name: fileName,
path: uriString, // 保存 URI 字符串
size: 0
});
}
} else {
// 单选文件
const uri = data.getData();
if (uri) {
const uriString = uri.toString();
let fileName = getFileName(uri) || `file_${Date.now()}`;
files.push({
name: fileName,
path: uriString, // 保存 URI 字符串
size: 0
});
}
}
if (files.length > 0) {
formData.value.files = [...formData.value.files, ...files];
}
// 恢复原始的 onActivityResult
if (originalOnActivityResult) {
main.onActivityResult = originalOnActivityResult;
}
} catch (error) {
console.error('处理文件选择结果失败:', error);
uni.showToast({
title: '处理文件失败',
icon: 'none'
});
}
}
} else {
// 调用原始的 onActivityResult
if (originalOnActivityResult) {
originalOnActivityResult(requestCode, resultCode, data);
}
}
};
} catch (error) {
console.error('打开文件选择器失败:', error);
uni.showToast({
title: '文件选择功能暂不可用',
icon: 'none'
});
}
} else {
uni.showToast({
title: '当前环境不支持文件选择',
icon: 'none'
});
}
};
// 删除文件
const removeFile = (index) => {
formData.value.files.splice(index, 1);
};
// 格式化时间为中文格式:年月日星期几时分秒
const formatTimeToChinese = (date) => {
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const weekday = weekdays[date.getDay()];
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${weekday} ${hour}:${minute}:${second}`;
};
// 提交任务
const handleSubmit = async () => {
if (!canSubmit.value) {
uni.showToast({
title: '请至少填写提交说明或添加附件',
icon: 'none'
});
return;
}
if (!taskId.value) {
uni.showToast({
title: '任务ID不能为空',
icon: 'none'
});
return;
}
uni.showLoading({
title: isEditMode.value ? '更新中...' : '提交中...'
});
try {
// 合并所有附件URL图片和文件
const allAttaches = [
...formData.value.images, // 图片已经是七牛云URL
...formData.value.files.map(file => file.path) // 文件路径如果是URL则直接使用如果是本地路径可能需要先上传
].filter(url => url && url.trim() !== ''); // 过滤空值
// 将附件数组转换为逗号分隔的字符串
const submitAttaches = allAttaches.join(',');
// 调用提交接口
await submitTask({
id: taskId.value,
submitAttaches: submitAttaches,
submitRemark: formData.value.description.trim()
});
uni.hideLoading();
// 提交成功后的处理
if (isEditMode.value) {
// 编辑模式:更新现有记录
const updatedRecord = {
userName: editRecordData.value?.userName || '当前用户', // 保持原用户名
time: formatTimeToChinese(new Date()), // 更新时间
content: formData.value.description.trim() || '',
progress: formData.value.progress,
attachments: [
...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
};
// 将更新的记录存储到本地,供任务详情页使用
uni.setStorageSync('updatedSubmitRecord', {
recordIndex: editRecordIndex.value,
record: updatedRecord
});
uni.showToast({
title: '更新成功',
icon: 'success'
});
} else {
// 新建模式:添加新记录
const submitRecord = {
userName: '当前用户', // TODO: 从用户信息获取
time: formatTimeToChinese(new Date()),
content: formData.value.description.trim() || '',
progress: formData.value.progress,
attachments: [
...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
};
// 将提交记录存储到本地,供任务详情页使用
uni.setStorageSync('newSubmitRecord', submitRecord);
uni.showToast({
title: '提交成功',
icon: 'success'
});
}
// 延迟返回,让用户看到成功提示
setTimeout(() => {
uni.navigateBack();
}, 1500);
} catch (error) {
uni.hideLoading();
console.error('提交任务失败:', error);
uni.showToast({
title: error.message || '提交失败,请重试',
icon: 'none',
duration: 2000
});
}
};
</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;
}
/* 表单项 */
.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>