2025-07-11 18:05:05 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="detail-container">
|
|
|
|
|
|
<view v-if="loading" class="loading"><u-loading mode="circle" text="加载中..." /></view>
|
|
|
|
|
|
<view v-else-if="taskDetail">
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<!-- 任务标题 -->
|
2025-07-11 18:05:05 +08:00
|
|
|
|
<view class="title">{{ taskDetail.taskTitle }}</view>
|
2025-07-12 10:37:32 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 基本信息 -->
|
|
|
|
|
|
<view class="section-title">基本信息</view>
|
|
|
|
|
|
<view class="info-grid">
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">任务状态:</text>
|
|
|
|
|
|
<text class="value status-tag" :class="getStatusClass(taskDetail.state)">
|
|
|
|
|
|
{{ getStatusText(taskDetail.state) }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">优先级:</text>
|
|
|
|
|
|
<text class="value priority-tag" :class="getPriorityClass(taskDetail.taskRank)">
|
|
|
|
|
|
{{ getPriorityText(taskDetail.taskRank) }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">负责人:</text>
|
|
|
|
|
|
<text class="value">{{ taskDetail.workerNickName || '-' }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">创建人:</text>
|
|
|
|
|
|
<text class="value">{{ taskDetail.createUserNickName || '-' }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">所属项目:</text>
|
|
|
|
|
|
<text class="value">{{ taskDetail.projectName || '-' }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 时间信息 -->
|
|
|
|
|
|
<view class="section-title">时间信息</view>
|
|
|
|
|
|
<view class="info-grid">
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">开始时间:</text>
|
|
|
|
|
|
<text class="value">{{ formatDate(taskDetail.beginTime) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">结束时间:</text>
|
|
|
|
|
|
<text class="value">{{ formatDate(taskDetail.finishTime) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item" v-if="taskDetail.overDays > 0">
|
|
|
|
|
|
<text class="label">超期天数:</text>
|
|
|
|
|
|
<text class="value over-days">{{ taskDetail.overDays }}天</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="info-item">
|
|
|
|
|
|
<text class="label">创建时间:</text>
|
|
|
|
|
|
<text class="value">{{ formatDate(taskDetail.createTime) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务描述 -->
|
2025-07-11 18:05:05 +08:00
|
|
|
|
<view class="section-title">任务描述</view>
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<view v-if="taskDetail.content" class="content-text">{{ taskDetail.content }}</view>
|
|
|
|
|
|
<view v-else class="empty-content">暂无任务描述</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 备注 -->
|
|
|
|
|
|
<view v-if="taskDetail.remark" class="section-title">备注</view>
|
|
|
|
|
|
<view v-if="taskDetail.remark" class="content-text">{{ taskDetail.remark }}</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 报工进度(仅报工任务显示) -->
|
|
|
|
|
|
<view v-if="isReportTask" class="section-title">报工进度</view>
|
|
|
|
|
|
<view v-if="isReportTask && taskDetail.taskItemVoList && taskDetail.taskItemVoList.length">
|
2025-07-11 18:05:05 +08:00
|
|
|
|
<view v-for="item in taskDetail.taskItemVoList" :key="item.itemId" class="item-block">
|
|
|
|
|
|
<view class="item-row"><text class="item-label">进度时间:</text>{{ item.signTime || '-' }}</view>
|
|
|
|
|
|
<view class="item-row"><text class="item-label">进度区间:</text>{{ item.beginTime || '-' }} ~ {{ item.endTime || '-' }}</view>
|
|
|
|
|
|
<view class="item-row"><text class="item-label">进度内容:</text></view>
|
|
|
|
|
|
<mp-html v-if="item.content" :content="item.content" />
|
|
|
|
|
|
<view class="item-row"><text class="item-label">完成时间:</text>{{ item.completedTime || '-' }}</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<view v-if="isReportTask && (!taskDetail.taskItemVoList || taskDetail.taskItemVoList.length === 0)" class="empty-progress">
|
|
|
|
|
|
暂无报工进度
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 附件展示 -->
|
|
|
|
|
|
<view v-if="taskDetail.accessory" class="section-title">附件</view>
|
|
|
|
|
|
<view v-if="taskDetail.accessory" class="attachment-list">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(file, index) in attachmentFiles"
|
|
|
|
|
|
:key="`${file.ossId}_${index}`"
|
|
|
|
|
|
class="attachment-item"
|
|
|
|
|
|
@click="downloadFile(file)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="file-icon">
|
|
|
|
|
|
<u-icon :name="getFileIcon(file.originalName)" size="24" color="#007aff"></u-icon>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="file-info">
|
|
|
|
|
|
<text class="file-name">{{ getFileName(file.originalName) }}</text>
|
|
|
|
|
|
<text class="file-size">{{ formatFileSize(file.fileSize) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="file-action">
|
|
|
|
|
|
<u-icon name="download" size="20" color="#007aff"></u-icon>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-if="taskDetail.accessory && attachmentFiles.length === 0" class="no-attachment">
|
|
|
|
|
|
暂无附件
|
|
|
|
|
|
</view>
|
2025-07-11 18:05:05 +08:00
|
|
|
|
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<!-- 新增报工按钮(仅报工任务显示) -->
|
|
|
|
|
|
<u-button v-if="isReportTask" type="primary" class="add-btn" @click="openAddPopup">新增报工</u-button>
|
2025-07-11 18:05:05 +08:00
|
|
|
|
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<!-- 新增报工弹窗(仅报工任务显示) -->
|
|
|
|
|
|
<uni-popup v-if="isReportTask" ref="addPopup" type="bottom" :mask-click="true">
|
2025-07-11 18:05:05 +08:00
|
|
|
|
<view class="add-dialog-bottom">
|
|
|
|
|
|
<view class="dialog-title">新增报工</view>
|
|
|
|
|
|
<u-form :model="addForm" ref="addFormRef">
|
|
|
|
|
|
<u-form-item label="进度区间">
|
|
|
|
|
|
<view class="period-row">{{ addForm.beginTime }} ~ {{ addForm.endTime }}</view>
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
|
<u-form-item label="进度时间">
|
|
|
|
|
|
<view class="period-row">{{ addForm.signTime }}</view>
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
|
<u-form-item label="完成时间">
|
|
|
|
|
|
<view class="period-row">{{ addForm.completedTime }}</view>
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
|
<u-form-item label="进度内容">
|
|
|
|
|
|
<u-textarea v-model="addForm.content" placeholder="请输入进度内容" autoHeight />
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
|
<u-form-item label="备注">
|
|
|
|
|
|
<u-textarea v-model="addForm.remark" placeholder="备注(可选)" autoHeight />
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
|
</u-form>
|
|
|
|
|
|
<view class="dialog-actions">
|
|
|
|
|
|
<u-button type="primary" @click="submitAdd">提交</u-button>
|
|
|
|
|
|
<u-button @click="$refs.addPopup.close()">取消</u-button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</uni-popup>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else class="empty">未查询到任务详情</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { getTask } from '@/api/oa/task.js'
|
|
|
|
|
|
import { addOaTaskItem } from '@/api/oa/taskItem.js'
|
2025-07-12 10:37:32 +08:00
|
|
|
|
import { getFilesByIds } from '@/api/common/upload.js'
|
2025-07-11 18:05:05 +08:00
|
|
|
|
import mpHtml from 'uni_modules/mp-html/components/mp-html/mp-html.vue'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
components: { mpHtml },
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
taskDetail: null,
|
|
|
|
|
|
loading: true,
|
2025-07-12 10:37:32 +08:00
|
|
|
|
attachmentFiles: [],
|
2025-07-11 18:05:05 +08:00
|
|
|
|
addForm: {
|
|
|
|
|
|
beginTime: '',
|
|
|
|
|
|
endTime: '',
|
|
|
|
|
|
content: '',
|
|
|
|
|
|
remark: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-12 10:37:32 +08:00
|
|
|
|
computed: {
|
|
|
|
|
|
// 判断是否为报工任务
|
|
|
|
|
|
isReportTask() {
|
|
|
|
|
|
return this.taskDetail && this.taskDetail.status == 1
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-11 18:05:05 +08:00
|
|
|
|
onLoad(options) {
|
|
|
|
|
|
const taskId = options.id
|
|
|
|
|
|
if (taskId) {
|
|
|
|
|
|
this.taskId = taskId
|
|
|
|
|
|
this.fetchDetail(taskId)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async fetchDetail(taskId) {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getTask(taskId)
|
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
|
this.taskDetail = res.data
|
2025-07-12 10:37:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载附件信息
|
|
|
|
|
|
if (res.data.accessory) {
|
|
|
|
|
|
await this.loadAttachmentFiles(res.data.accessory)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.attachmentFiles = []
|
|
|
|
|
|
}
|
2025-07-11 18:05:05 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: res.msg || '获取详情失败', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
openAddPopup() {
|
|
|
|
|
|
// 自动获取进度区间
|
|
|
|
|
|
let begin = '', end = '';
|
|
|
|
|
|
const items = this.taskDetail?.taskItemVoList || [];
|
|
|
|
|
|
if (items.length > 0) {
|
|
|
|
|
|
// 取最后一个item的endTime为下一个周期的beginTime
|
|
|
|
|
|
const last = items[items.length - 1];
|
|
|
|
|
|
begin = last.endTime || this.taskDetail.beginTime;
|
|
|
|
|
|
// 结束时间=begin+timeGap天
|
|
|
|
|
|
const gap = this.taskDetail.timeGap || 7;
|
|
|
|
|
|
const bDate = new Date(begin.replace(/-/g, '/'));
|
|
|
|
|
|
bDate.setDate(bDate.getDate() + gap);
|
|
|
|
|
|
end = bDate.getFullYear() + '-' + String(bDate.getMonth() + 1).padStart(2, '0') + '-' + String(bDate.getDate()).padStart(2, '0');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
begin = this.taskDetail.beginTime;
|
|
|
|
|
|
end = this.taskDetail.finishTime;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 进度时间=beginTime,完成时间=当天
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
const todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
|
|
|
|
|
|
this.addForm = {
|
|
|
|
|
|
beginTime: begin,
|
|
|
|
|
|
endTime: end,
|
|
|
|
|
|
signTime: begin,
|
|
|
|
|
|
completedTime: todayStr,
|
|
|
|
|
|
content: '',
|
|
|
|
|
|
remark: ''
|
|
|
|
|
|
};
|
|
|
|
|
|
this.$refs.addPopup.open();
|
|
|
|
|
|
},
|
|
|
|
|
|
async submitAdd() {
|
|
|
|
|
|
if (!this.addForm.beginTime || !this.addForm.endTime || !this.addForm.content) {
|
|
|
|
|
|
uni.showToast({ title: '请填写进度内容', icon: 'none' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const data = {
|
|
|
|
|
|
taskId: this.taskId,
|
|
|
|
|
|
beginTime: this.addForm.beginTime,
|
|
|
|
|
|
endTime: this.addForm.endTime,
|
|
|
|
|
|
signTime: this.addForm.signTime,
|
|
|
|
|
|
completedTime: this.addForm.completedTime,
|
|
|
|
|
|
content: this.addForm.content,
|
|
|
|
|
|
remark: this.addForm.remark || null,
|
|
|
|
|
|
status: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await addOaTaskItem(data)
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
uni.showToast({ title: '新增报工成功', icon: 'success' })
|
|
|
|
|
|
this.$refs.addPopup.close()
|
|
|
|
|
|
this.addForm = { beginTime: '', endTime: '', signTime: '', completedTime: '', content: '', remark: '' }
|
|
|
|
|
|
this.fetchDetail(this.taskId)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: res.msg || '新增失败', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
|
}
|
2025-07-12 10:37:32 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 加载附件文件信息
|
|
|
|
|
|
async loadAttachmentFiles(accessory) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ossIds = accessory.split(',').filter(id => id.trim())
|
|
|
|
|
|
if (ossIds.length > 0) {
|
|
|
|
|
|
const files = await getFilesByIds(ossIds)
|
|
|
|
|
|
this.attachmentFiles = files || []
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.attachmentFiles = []
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载附件失败:', error)
|
|
|
|
|
|
this.attachmentFiles = []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 下载文件
|
|
|
|
|
|
downloadFile(file) {
|
|
|
|
|
|
// 使用uni.downloadFile下载文件
|
|
|
|
|
|
uni.downloadFile({
|
|
|
|
|
|
url: file.url,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.statusCode === 200) {
|
|
|
|
|
|
// 保存文件到本地
|
|
|
|
|
|
uni.saveFile({
|
|
|
|
|
|
tempFilePath: res.tempFilePath,
|
|
|
|
|
|
success: (saveRes) => {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '文件已保存到本地',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.error('保存文件失败:', err)
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '保存文件失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.error('下载文件失败:', err)
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '下载文件失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件图标
|
|
|
|
|
|
getFileIcon(fileName) {
|
|
|
|
|
|
const ext = fileName.split('.').pop().toLowerCase()
|
|
|
|
|
|
const iconMap = {
|
|
|
|
|
|
'pdf': 'file-text',
|
|
|
|
|
|
'doc': 'file-text',
|
|
|
|
|
|
'docx': 'file-text',
|
|
|
|
|
|
'xls': 'file-text',
|
|
|
|
|
|
'xlsx': 'file-text',
|
|
|
|
|
|
'ppt': 'file-text',
|
|
|
|
|
|
'pptx': 'file-text',
|
|
|
|
|
|
'txt': 'file-text',
|
|
|
|
|
|
'jpg': 'image',
|
|
|
|
|
|
'jpeg': 'image',
|
|
|
|
|
|
'png': 'image',
|
|
|
|
|
|
'gif': 'image',
|
|
|
|
|
|
'zip': 'folder',
|
|
|
|
|
|
'rar': 'folder',
|
|
|
|
|
|
'7z': 'folder',
|
|
|
|
|
|
'dwg': 'file-text'
|
|
|
|
|
|
}
|
|
|
|
|
|
return iconMap[ext] || 'file-text'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件名
|
|
|
|
|
|
getFileName(fileName) {
|
|
|
|
|
|
if (!fileName) return ''
|
|
|
|
|
|
return fileName.includes('/') ? fileName.split('/').pop() : fileName
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化文件大小
|
|
|
|
|
|
formatFileSize(size) {
|
|
|
|
|
|
if (!size) return ''
|
|
|
|
|
|
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'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化日期
|
|
|
|
|
|
formatDate(dateStr) {
|
|
|
|
|
|
if (!dateStr) return '未设置'
|
|
|
|
|
|
const date = new Date(dateStr)
|
|
|
|
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
|
getStatusText(state) {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
15: '申请延期',
|
|
|
|
|
|
0: '进行中',
|
|
|
|
|
|
1: '完成等待评分',
|
|
|
|
|
|
2: '完成'
|
|
|
|
|
|
}
|
|
|
|
|
|
return statusMap[state] || '未知状态'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态样式类
|
|
|
|
|
|
getStatusClass(state) {
|
|
|
|
|
|
const classMap = {
|
|
|
|
|
|
15: 'status-pending',
|
|
|
|
|
|
0: 'status-processing',
|
|
|
|
|
|
1: 'status-waiting',
|
|
|
|
|
|
2: 'status-completed'
|
|
|
|
|
|
}
|
|
|
|
|
|
return classMap[state] || 'status-unknown'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取优先级文本
|
|
|
|
|
|
getPriorityText(priority) {
|
|
|
|
|
|
const priorityMap = {
|
|
|
|
|
|
0: '普通',
|
|
|
|
|
|
1: '低',
|
|
|
|
|
|
2: '中',
|
|
|
|
|
|
3: '高',
|
|
|
|
|
|
4: '紧急'
|
|
|
|
|
|
}
|
|
|
|
|
|
return priorityMap[priority] || '未设置'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取优先级样式类
|
|
|
|
|
|
getPriorityClass(priority) {
|
|
|
|
|
|
const classMap = {
|
|
|
|
|
|
0: 'priority-normal',
|
|
|
|
|
|
1: 'priority-low',
|
|
|
|
|
|
2: 'priority-medium',
|
|
|
|
|
|
3: 'priority-high',
|
|
|
|
|
|
4: 'priority-urgent'
|
|
|
|
|
|
}
|
|
|
|
|
|
return classMap[priority] || 'priority-unknown'
|
2025-07-11 18:05:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.detail-container {
|
|
|
|
|
|
padding: 32rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.row {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.label {
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
}
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
margin-top: 32rpx;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.item-block {
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.item-row {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.item-label {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
.empty-progress, .empty {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #bbb;
|
|
|
|
|
|
margin: 40rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loading {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin: 80rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.add-btn {
|
|
|
|
|
|
margin-top: 40rpx;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.add-dialog-bottom {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
|
|
padding: 40rpx 32rpx 32rpx 32rpx;
|
|
|
|
|
|
width: 100vw;
|
|
|
|
|
|
max-width: 750rpx;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-bottom: 32rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 24rpx;
|
|
|
|
|
|
margin-top: 32rpx;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.period-row {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
padding: 12rpx 0 12rpx 8rpx;
|
|
|
|
|
|
}
|
2025-07-12 10:37:32 +08:00
|
|
|
|
|
|
|
|
|
|
.attachment-list {
|
|
|
|
|
|
margin-top: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.attachment-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.attachment-item:hover {
|
|
|
|
|
|
background-color: #e9ecef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-icon {
|
|
|
|
|
|
margin-right: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-name {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-size {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-action {
|
|
|
|
|
|
margin-left: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no-attachment {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
padding: 40rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-grid {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-item .label {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
width: 160rpx;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-item .value {
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-text {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
border-left: 4rpx solid #1890ff;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-content {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
padding: 40rpx 0;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag {
|
|
|
|
|
|
padding: 4rpx 12rpx;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-pending {
|
|
|
|
|
|
background-color: #fff2e8;
|
|
|
|
|
|
color: #fa8c16;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-processing {
|
|
|
|
|
|
background-color: #e6f7ff;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-waiting {
|
|
|
|
|
|
background-color: #fff7e6;
|
|
|
|
|
|
color: #fa8c16;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-completed {
|
|
|
|
|
|
background-color: #f6ffed;
|
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-tag {
|
|
|
|
|
|
padding: 4rpx 12rpx;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-normal {
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-low {
|
|
|
|
|
|
background-color: #f6ffed;
|
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-medium {
|
|
|
|
|
|
background-color: #fff7e6;
|
|
|
|
|
|
color: #fa8c16;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-high {
|
|
|
|
|
|
background-color: #fff2f0;
|
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.priority-urgent {
|
|
|
|
|
|
background-color: #f9f0ff;
|
|
|
|
|
|
color: #722ed1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.over-days {
|
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
2025-07-11 18:05:05 +08:00
|
|
|
|
</style>
|