1212 lines
29 KiB
Vue
1212 lines
29 KiB
Vue
<template>
|
||
<view class="create-task-container">
|
||
<!-- 表单内容 -->
|
||
<view class="form-container">
|
||
<form @submit="submitForm">
|
||
<!-- 任务主题 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">任务主题</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<input
|
||
v-model="form.taskTitle"
|
||
class="form-input"
|
||
placeholder="请输入任务主题"
|
||
maxlength="100"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 工作类型 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">工作类型</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<picker
|
||
@change="onTaskTypeChange"
|
||
:value="taskTypeIndex"
|
||
:range="taskTypeOptions"
|
||
range-key="label"
|
||
class="form-picker"
|
||
>
|
||
<view class="picker-text">
|
||
{{ taskTypeOptions[taskTypeIndex] ? taskTypeOptions[taskTypeIndex].label : '请选择工作类型' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 任务模式 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">任务模式</text>
|
||
</view>
|
||
<view class="radio-group">
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: form.status === 0 }"
|
||
@click="form.status = 0"
|
||
>
|
||
<view class="radio-circle"></view>
|
||
<text class="radio-text">单任务模式</text>
|
||
</view>
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: form.status === 1 }"
|
||
@click="form.status = 1"
|
||
>
|
||
<view class="radio-circle"></view>
|
||
<text class="radio-text">报工模式</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 报工周期 -->
|
||
<view class="form-item" v-if="form.status === 1">
|
||
<view class="form-label">
|
||
<text class="label-text">报工周期(天)</text>
|
||
</view>
|
||
<input
|
||
v-model="form.timeGap"
|
||
type="number"
|
||
class="form-input"
|
||
placeholder="请输入报工周期"
|
||
min="1"
|
||
max="10"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 任务时间 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">开始时间</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<picker
|
||
mode="date"
|
||
@change="onBeginTimeChange"
|
||
class="form-picker"
|
||
>
|
||
<view class="picker-text">
|
||
{{ form.beginTime || '请选择开始时间' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item" v-if="form.status === 0">
|
||
<view class="form-label">
|
||
<text class="label-text">结束时间</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<picker
|
||
mode="date"
|
||
@change="onFinishTimeChange"
|
||
class="form-picker"
|
||
>
|
||
<view class="picker-text">
|
||
{{ form.finishTime || '请选择结束时间' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 优先级 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">优先级</text>
|
||
</view>
|
||
<view class="radio-group">
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: form.taskGrade === '0' }"
|
||
@click="form.taskGrade = '0'"
|
||
>
|
||
<view class="radio-circle"></view>
|
||
<text class="radio-text">一般</text>
|
||
</view>
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: form.taskGrade === '1' }"
|
||
@click="form.taskGrade = '1'"
|
||
>
|
||
<view class="radio-circle"></view>
|
||
<text class="radio-text">中等</text>
|
||
</view>
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: form.taskGrade === '2' }"
|
||
@click="form.taskGrade = '2'"
|
||
>
|
||
<view class="radio-circle"></view>
|
||
<text class="radio-text">紧急</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 关联项目 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">关联项目</text>
|
||
</view>
|
||
<picker
|
||
@change="onProjectChange"
|
||
:value="projectIndex"
|
||
:range="projectList"
|
||
range-key="projectName"
|
||
class="form-picker"
|
||
>
|
||
<view class="picker-text">
|
||
{{ projectIndex === -1 ? '请选择项目' : projectList[projectIndex].projectName }}
|
||
</view>
|
||
</picker>
|
||
<view v-if="projectIndex !== -1" class="selected-project" style="margin-top: 10rpx;">
|
||
<text>{{ projectList[projectIndex].projectName }}</text>
|
||
<text class="remove-btn" @click="removeProject" style="color:#ff4757;margin-left:20rpx;">移除</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 执行人 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">执行人</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<!-- 已选用户标签 -->
|
||
<view v-if="selectedUsers.length > 0" class="user-tags">
|
||
<view v-for="user in selectedUsers" :key="user.userId" class="user-tag">
|
||
<text>{{ user.nickName }}</text>
|
||
<text class="remove-btn" @click="removeUser(user)">×</text>
|
||
</view>
|
||
</view>
|
||
<button type="primary" size="mini" @click="openUserPopup">点击选择</button>
|
||
<!-- 弹窗 -->
|
||
<uni-popup ref="userPopup" type="center">
|
||
<view class="user-select-popup">
|
||
<view class="popup-header">
|
||
<text>选择执行人</text>
|
||
<text class="close-btn" @click="closeUserPopup">×</text>
|
||
</view>
|
||
<scroll-view scroll-y class="user-list" style="height: 400rpx;">
|
||
<view
|
||
v-for="user in userList"
|
||
:key="user.userId"
|
||
class="user-row"
|
||
@click="toggleUser(user)"
|
||
>
|
||
<checkbox :checked="isSelected(user)" />
|
||
<text>{{ user.nickName }}</text>
|
||
<text class="user-dept">{{ user.deptName }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
<view class="popup-footer">
|
||
<button type="primary" size="mini" @click="confirmUserSelect">确定</button>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
</view>
|
||
|
||
<!-- 详细描述 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">详细描述</text>
|
||
</view>
|
||
<textarea
|
||
v-model="form.content"
|
||
class="form-textarea"
|
||
placeholder="请输入详细描述"
|
||
maxlength="1000"
|
||
auto-height
|
||
/>
|
||
</view>
|
||
|
||
<!-- 附件 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">附件</text>
|
||
</view>
|
||
<view class="file-upload">
|
||
<view class="upload-btn" @click="chooseFile">
|
||
<text class="upload-icon">+</text>
|
||
<text class="upload-text">选择文件</text>
|
||
</view>
|
||
|
||
<view class="file-list" v-if="fileList.length > 0">
|
||
<view
|
||
class="file-item"
|
||
v-for="(file, index) in fileList"
|
||
:key="index"
|
||
>
|
||
<text class="file-name">{{ file.name }}</text>
|
||
<text class="file-size">{{ formatFileSize(file.size) }}</text>
|
||
<text class="remove-file" @click="removeFile(index)">×</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交按钮 -->
|
||
<view class="submit-container">
|
||
<button
|
||
type="primary"
|
||
class="submit-btn"
|
||
:loading="loading"
|
||
@click="submitForm"
|
||
>
|
||
{{ loading ? '提交中...' : '提交' }}
|
||
</button>
|
||
</view>
|
||
</form>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { addTask } from '@/api/oa/task'
|
||
import { listProject } from '@/api/oa/project'
|
||
import { selectUser, deptTreeSelect } from '@/api/oa/user'
|
||
import { uploadFiles } from '@/api/common/upload'
|
||
|
||
export default {
|
||
name: 'CreateTask',
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
form: {
|
||
taskTitle: '',
|
||
taskType: '',
|
||
status: 0,
|
||
timeGap: 3,
|
||
beginTime: '',
|
||
finishTime: '',
|
||
taskGrade: '0',
|
||
projectId: '',
|
||
workerIds: '',
|
||
content: '',
|
||
accessory: ''
|
||
},
|
||
taskTypeOptions: [
|
||
{ label: '技术审查', value: 1 },
|
||
{ label: '初步设计', value: 2 },
|
||
{ label: '图纸详细设计', value: 3 },
|
||
{ label: 'PLC程序设计', value: 4 },
|
||
{ label: '画面设计', value: 5 },
|
||
{ label: '调试(传动、打点、精度)', value: 6 },
|
||
{ label: '验收资料', value: 7 },
|
||
{ label: '验收报告/差旅记录', value: 8 },
|
||
{ label: '项目结项', value: 9 }
|
||
],
|
||
taskTypeIndex: 0,
|
||
projectList: [],
|
||
projectIndex: -1,
|
||
userList: [],
|
||
selectedUsers: [],
|
||
// userPopupVisible: false, // 不再需要
|
||
fileList: [],
|
||
uploadedFiles: []
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.initData()
|
||
},
|
||
methods: {
|
||
// 初始化数据
|
||
async initData() {
|
||
await Promise.all([
|
||
this.getProjectList(),
|
||
this.getUserList()
|
||
])
|
||
},
|
||
|
||
// 获取项目列表
|
||
async getProjectList() {
|
||
try {
|
||
const res = await listProject({
|
||
pageNum: 1,
|
||
pageSize: 999
|
||
})
|
||
this.projectList = res.rows || []
|
||
} catch (error) {
|
||
console.error('获取项目列表失败:', error)
|
||
uni.showToast({
|
||
title: '获取项目列表失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 获取用户列表
|
||
async getUserList() {
|
||
try {
|
||
const res = await selectUser({ pageNum: 1, pageSize: 999, deptId: 100 })
|
||
// const resr = await deptTreeSelect()
|
||
this.userList = res.rows || []
|
||
} catch (error) {
|
||
console.error('获取用户列表失败:', error)
|
||
uni.showToast({
|
||
title: '获取用户列表失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 工作类型选择
|
||
onTaskTypeChange(e) {
|
||
this.taskTypeIndex = e.detail.value
|
||
this.form.taskType = this.taskTypeOptions[this.taskTypeIndex].value
|
||
},
|
||
|
||
// 开始时间选择
|
||
onBeginTimeChange(e) {
|
||
this.form.beginTime = e.detail.value + ' 00:00:00'
|
||
},
|
||
|
||
// 结束时间选择
|
||
onFinishTimeChange(e) {
|
||
this.form.finishTime = e.detail.value + ' 23:59:59'
|
||
},
|
||
|
||
// 项目选择
|
||
onProjectChange(e) {
|
||
this.projectIndex = e.detail.value
|
||
this.form.projectId = this.projectList[this.projectIndex].projectId
|
||
},
|
||
// 移除项目
|
||
removeProject() {
|
||
this.projectIndex = -1
|
||
this.form.projectId = ''
|
||
},
|
||
|
||
// 打开用户选择器
|
||
openUserPopup() {
|
||
this.$refs.userPopup.open()
|
||
},
|
||
// 关闭用户选择器
|
||
closeUserPopup() {
|
||
this.$refs.userPopup.close()
|
||
},
|
||
// 勾选/取消用户
|
||
toggleUser(user) {
|
||
const index = this.selectedUsers.findIndex(u => u.userId === user.userId)
|
||
if (index > -1) {
|
||
this.selectedUsers.splice(index, 1)
|
||
} else {
|
||
this.selectedUsers.push(user)
|
||
}
|
||
},
|
||
// 判断用户是否已选
|
||
isSelected(user) {
|
||
return this.selectedUsers.some(u => u.userId === user.userId)
|
||
},
|
||
// 移除已选用户
|
||
removeUser(user) {
|
||
const index = this.selectedUsers.findIndex(u => u.userId === user.userId)
|
||
if (index > -1) {
|
||
this.selectedUsers.splice(index, 1)
|
||
}
|
||
},
|
||
// 确认选择
|
||
confirmUserSelect() {
|
||
this.form.workerIds = this.selectedUsers.map(u => u.userId).join(',')
|
||
this.closeUserPopup()
|
||
},
|
||
|
||
|
||
|
||
// 选择文件
|
||
chooseFile() {
|
||
// 安卓平台使用 plus.io.pickFiles
|
||
// #ifdef APP-PLUS
|
||
if (typeof plus !== 'undefined' && plus.io) {
|
||
console.log('使用安卓平台文件选择API')
|
||
this.doPickFiles()
|
||
return
|
||
}
|
||
// #endif
|
||
|
||
// 微信小程序环境
|
||
if (typeof wx !== 'undefined' && wx.chooseMessageFile) {
|
||
wx.chooseMessageFile({
|
||
count: 5,
|
||
type: 'all',
|
||
success: (res) => {
|
||
const processedFiles = res.tempFiles.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
path: file.path || file.tempFilePath,
|
||
type: file.type
|
||
}))
|
||
|
||
// 验证文件
|
||
const validFiles = this.validateFiles(processedFiles)
|
||
if (validFiles.length > 0) {
|
||
this.fileList = [...this.fileList, ...validFiles]
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择文件失败:', err)
|
||
uni.showToast({
|
||
title: '选择文件失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
// H5平台使用 uni.chooseFile
|
||
// #ifdef H5
|
||
uni.chooseFile({
|
||
count: 5,
|
||
type: 'all',
|
||
success: (res) => {
|
||
const processedFiles = res.tempFiles.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
path: file.path || file.tempFilePath || file.url,
|
||
type: file.type
|
||
}))
|
||
|
||
// 验证文件
|
||
const validFiles = this.validateFiles(processedFiles)
|
||
if (validFiles.length > 0) {
|
||
this.fileList = [...this.fileList, ...validFiles]
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择文件失败:', err)
|
||
uni.showToast({
|
||
title: '选择文件失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
// 其他平台提示不支持
|
||
// #ifndef APP-PLUS || H5 || MP-WEIXIN
|
||
uni.showToast({
|
||
title: '当前平台不支持文件选择',
|
||
icon: 'none'
|
||
})
|
||
// #endif
|
||
},
|
||
|
||
// 移除文件
|
||
removeFile(index) {
|
||
this.fileList.splice(index, 1)
|
||
},
|
||
|
||
// 根据文件名获取文件类型
|
||
getFileType(fileName) {
|
||
const ext = fileName.split('.').pop().toLowerCase()
|
||
const typeMap = {
|
||
'jpg': 'image/jpeg',
|
||
'jpeg': 'image/jpeg',
|
||
'png': 'image/png',
|
||
'gif': 'image/gif',
|
||
'bmp': 'image/bmp',
|
||
'webp': 'image/webp',
|
||
'pdf': 'application/pdf',
|
||
'doc': 'application/msword',
|
||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'xls': 'application/vnd.ms-excel',
|
||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
'ppt': 'application/vnd.ms-powerpoint',
|
||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||
'txt': 'text/plain',
|
||
'zip': 'application/zip',
|
||
'rar': 'application/x-rar-compressed',
|
||
'7z': 'application/x-7z-compressed'
|
||
}
|
||
return typeMap[ext] || 'application/octet-stream'
|
||
},
|
||
|
||
// 执行文件选择
|
||
doPickFiles() {
|
||
try {
|
||
plus.io.pickFiles({
|
||
multiple: true,
|
||
maximum: 5,
|
||
filter: 'none',
|
||
onSuccess: (files) => {
|
||
console.log('安卓平台选择文件成功:', files)
|
||
const processedFiles = files.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
path: file.path,
|
||
type: file.type || this.getFileType(file.name)
|
||
}))
|
||
|
||
// 验证文件
|
||
const validFiles = this.validateFiles(processedFiles)
|
||
if (validFiles.length > 0) {
|
||
this.fileList = [...this.fileList, ...validFiles]
|
||
console.log('处理后的文件列表:', this.fileList)
|
||
}
|
||
},
|
||
onFail: (error) => {
|
||
console.error('安卓平台选择文件失败:', error)
|
||
// 尝试使用备用方法
|
||
this.fallbackPickFiles()
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('plus.io.pickFiles 不可用:', error)
|
||
// 尝试使用备用方法
|
||
this.fallbackPickFiles()
|
||
}
|
||
},
|
||
|
||
// 备用文件选择方法
|
||
fallbackPickFiles() {
|
||
// 使用 uni.chooseImage 作为备用方案
|
||
uni.chooseImage({
|
||
count: 5,
|
||
sizeType: ['original'],
|
||
sourceType: ['album'],
|
||
success: (res) => {
|
||
console.log('备用方法选择文件成功:', res)
|
||
const processedFiles = res.tempFilePaths.map((path, index) => ({
|
||
name: `image_${Date.now()}_${index}.jpg`,
|
||
size: 0,
|
||
path: path,
|
||
type: 'image/jpeg'
|
||
}))
|
||
|
||
// 验证文件
|
||
const validFiles = this.validateFiles(processedFiles)
|
||
if (validFiles.length > 0) {
|
||
this.fileList = [...this.fileList, ...validFiles]
|
||
console.log('处理后的文件列表:', this.fileList)
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
console.error('备用方法选择文件失败:', error)
|
||
uni.showToast({
|
||
title: '选择文件失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
// 验证文件
|
||
validateFiles(files) {
|
||
const allowedTypes = ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg']
|
||
const maxSize = 200 * 1024 * 1024 // 200MB
|
||
|
||
const validFiles = []
|
||
const invalidFiles = []
|
||
|
||
files.forEach(file => {
|
||
// 文件类型验证
|
||
const ext = file.name.split('.').pop().toLowerCase()
|
||
if (!allowedTypes.includes(ext)) {
|
||
invalidFiles.push(`${file.name} (格式不支持)`)
|
||
return
|
||
}
|
||
|
||
// 文件大小验证
|
||
if (file.size > maxSize) {
|
||
invalidFiles.push(`${file.name} (超过200MB)`)
|
||
return
|
||
}
|
||
|
||
validFiles.push(file)
|
||
})
|
||
|
||
// 显示无效文件提示
|
||
if (invalidFiles.length > 0) {
|
||
uni.showToast({
|
||
title: `以下文件不符合要求:${invalidFiles.join(', ')}`,
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
}
|
||
|
||
return validFiles
|
||
},
|
||
|
||
|
||
|
||
// 格式化文件大小
|
||
formatFileSize(size) {
|
||
if (size < 1024) {
|
||
return size + 'B'
|
||
} else if (size < 1024 * 1024) {
|
||
return (size / 1024).toFixed(2) + 'KB'
|
||
} else {
|
||
return (size / (1024 * 1024)).toFixed(2) + 'MB'
|
||
}
|
||
},
|
||
|
||
// 上传文件
|
||
async uploadFiles() {
|
||
if (this.fileList.length === 0) {
|
||
return []
|
||
}
|
||
|
||
try {
|
||
const results = await uploadFiles(this.fileList, {
|
||
allowedTypes: ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'],
|
||
maxSize: 200,
|
||
extraData: 1,
|
||
onProgress: (progress) => {
|
||
console.log(`上传进度: ${progress.progress.toFixed(1)}% - ${progress.file.name}`)
|
||
}
|
||
})
|
||
|
||
return results
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
},
|
||
|
||
// 表单验证
|
||
validateForm() {
|
||
if (!this.form.taskTitle.trim()) {
|
||
uni.showToast({
|
||
title: '请输入任务主题',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.taskType) {
|
||
uni.showToast({
|
||
title: '请选择工作类型',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.beginTime) {
|
||
uni.showToast({
|
||
title: '请选择开始时间',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (this.form.status === 0 && !this.form.finishTime) {
|
||
uni.showToast({
|
||
title: '请选择结束时间',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (this.form.workerIds === '') {
|
||
uni.showToast({
|
||
title: '请选择执行人',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
return true
|
||
},
|
||
|
||
// 提交表单
|
||
async submitForm() {
|
||
if (!this.validateForm()) {
|
||
return
|
||
}
|
||
|
||
this.loading = true
|
||
|
||
try {
|
||
// 上传文件
|
||
if (this.fileList.length > 0) {
|
||
// 显示上传进度提示
|
||
uni.showLoading({
|
||
title: '正在上传文件...',
|
||
mask: true
|
||
})
|
||
|
||
const uploadedFiles = await this.uploadFiles()
|
||
|
||
// 隐藏上传进度提示
|
||
uni.hideLoading()
|
||
|
||
// 提取ossId并拼接成字符串
|
||
const ossIds = uploadedFiles.map(file => file.ossId).filter(Boolean)
|
||
this.form.accessory = ossIds.join(',')
|
||
|
||
console.log('上传完成,ossId字符串:', this.form.accessory)
|
||
} else {
|
||
this.form.accessory = ''
|
||
}
|
||
|
||
// 显示提交提示
|
||
uni.showLoading({
|
||
title: '正在创建任务...',
|
||
mask: true
|
||
})
|
||
|
||
// 提交任务
|
||
await addTask(this.form)
|
||
|
||
// 隐藏提交提示
|
||
uni.hideLoading()
|
||
|
||
uni.showToast({
|
||
title: '任务创建成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 返回上一页
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
|
||
} catch (error) {
|
||
console.error('创建任务失败:', error)
|
||
|
||
// 隐藏所有loading
|
||
uni.hideLoading()
|
||
|
||
// 显示错误信息
|
||
const errorMessage = error.message || '创建任务失败'
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.create-task-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding-bottom: 120rpx;
|
||
}
|
||
|
||
.form-container {
|
||
max-width: 90vw;
|
||
margin: 0 auto;
|
||
box-sizing: border-box;
|
||
padding: 30rpx 20rpx;
|
||
}
|
||
|
||
.form-item {
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.form-label {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.label-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.required {
|
||
color: #ff4757;
|
||
margin-left: 8rpx;
|
||
}
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.form-picker {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 20rpx;
|
||
background-color: #fff;
|
||
|
||
.picker-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.radio-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.radio-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 30rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fff;
|
||
|
||
&.active {
|
||
border-color: #007aff;
|
||
background-color: #f0f8ff;
|
||
|
||
.radio-circle {
|
||
background-color: #007aff;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
background-color: #fff;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
}
|
||
|
||
.radio-circle {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border: 2rpx solid #ddd;
|
||
border-radius: 50%;
|
||
margin-right: 16rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.radio-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.user-selector {
|
||
min-height: 80rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 20rpx;
|
||
background-color: #fff;
|
||
|
||
.selected-users {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.user-tag {
|
||
display: flex;
|
||
align-items: center;
|
||
background-color: #f0f8ff;
|
||
border: 1rpx solid #007aff;
|
||
border-radius: 20rpx;
|
||
padding: 8rpx 16rpx;
|
||
|
||
.user-name {
|
||
font-size: 24rpx;
|
||
color: #007aff;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.remove-btn {
|
||
font-size: 24rpx;
|
||
color: #007aff;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.placeholder {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 200rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 20rpx;
|
||
font-size: 28rpx;
|
||
background-color: #fff;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.file-upload {
|
||
.upload-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 160rpx;
|
||
border: 2rpx dashed #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fafafa;
|
||
|
||
.upload-icon {
|
||
font-size: 48rpx;
|
||
color: #999;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.file-list {
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.file-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.file-name {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
flex: 1;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.file-size {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.remove-file {
|
||
font-size: 32rpx;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.submit-container {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
border-top: 1rpx solid #eee;
|
||
|
||
.submit-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background-color: #007aff;
|
||
color: #fff;
|
||
border-radius: 8rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.user-popup {
|
||
background-color: #fff;
|
||
border-radius: 20rpx 20rpx 0 0;
|
||
max-height: 80vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
|
||
.popup-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.close-btn {
|
||
font-size: 40rpx;
|
||
color: #999;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.user-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 0 30rpx;
|
||
}
|
||
|
||
.user-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 30rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
|
||
.user-avatar {
|
||
margin-right: 20rpx;
|
||
|
||
.avatar-img {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
|
||
.user-info {
|
||
flex: 1;
|
||
|
||
.user-nickname {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
display: block;
|
||
}
|
||
|
||
.user-dept {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.user-check {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border: 2rpx solid #007aff;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #007aff;
|
||
|
||
.check-icon {
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.popup-footer {
|
||
padding: 30rpx;
|
||
border-top: 1rpx solid #eee;
|
||
|
||
.confirm-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background-color: #007aff;
|
||
color: #fff;
|
||
border-radius: 8rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
.user-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
.user-tag {
|
||
background: #e6f7ff;
|
||
color: #007aff;
|
||
border-radius: 20rpx;
|
||
padding: 6rpx 16rpx;
|
||
margin-right: 10rpx;
|
||
margin-bottom: 10rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.remove-btn {
|
||
margin-left: 8rpx;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
.user-select-popup {
|
||
width: 90vw;
|
||
max-width: 700rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
}
|
||
.popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 24rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
}
|
||
.close-btn {
|
||
font-size: 40rpx;
|
||
color: #999;
|
||
font-weight: bold;
|
||
}
|
||
.popup-body {
|
||
display: flex;
|
||
height: 400rpx;
|
||
}
|
||
.dept-tree {
|
||
width: 180rpx;
|
||
border-right: 1rpx solid #eee;
|
||
background: #f8f8f8;
|
||
padding: 10rpx 0;
|
||
}
|
||
.dept-item {
|
||
padding: 16rpx 24rpx;
|
||
cursor: pointer;
|
||
}
|
||
.dept-item.active {
|
||
background: #e6f7ff;
|
||
color: #007aff;
|
||
}
|
||
.dept-children {
|
||
margin-left: 10rpx;
|
||
}
|
||
.user-list {
|
||
flex: 1;
|
||
padding: 10rpx 0 10rpx 10rpx;
|
||
overflow-y: auto;
|
||
}
|
||
.user-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
}
|
||
.user-row .user-dept {
|
||
margin-left: 16rpx;
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
}
|
||
.popup-footer {
|
||
padding: 20rpx;
|
||
text-align: right;
|
||
}
|
||
</style>
|