2025-07-12 10:37:32 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="task-list-container">
|
|
|
|
|
|
<!-- 任务列表 -->
|
|
|
|
|
|
<view class="task-list">
|
|
|
|
|
|
<uni-swipe-action>
|
|
|
|
|
|
<uni-swipe-action-item
|
|
|
|
|
|
v-for="(task, index) in taskList"
|
|
|
|
|
|
:key="task.taskId"
|
|
|
|
|
|
:right-options="getSwipeOptions(task)"
|
|
|
|
|
|
@click="handleSwipeClick($event, index)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="task-item"
|
|
|
|
|
|
@click="handleTaskClick(task)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 任务完成checkbox -->
|
|
|
|
|
|
<view v-if="config.showCheckbox && task.status === 0" class="task-checkbox">
|
|
|
|
|
|
<view v-if="task.state === 0" class="checkbox-container" @click.stop="handleTaskComplete(task)">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="custom-checkbox"
|
|
|
|
|
|
:class="{ 'checked': checkboxStates[task.taskId] }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<uni-icons
|
|
|
|
|
|
v-if="checkboxStates[task.taskId]"
|
|
|
|
|
|
type="checkmarkempty"
|
|
|
|
|
|
size="16"
|
|
|
|
|
|
color="#fff"
|
|
|
|
|
|
></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="task.state === 1" class="checkbox-container">
|
|
|
|
|
|
<uni-icons type="checkmarkempty" size="20" color="#999"></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="task.state === 2" class="checkbox-container">
|
|
|
|
|
|
<uni-icons type="checkmarkempty" size="20" color="#52c41a"></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else class="checkbox-container">
|
|
|
|
|
|
<view class="custom-checkbox disabled">
|
|
|
|
|
|
<uni-icons type="checkmarkempty" size="16" color="#999"></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务内容 -->
|
|
|
|
|
|
<view class="task-content">
|
2025-07-12 11:21:09 +08:00
|
|
|
|
<view class="task-title" :class="{ 'single-line': isSingleLine(task.taskTitle) }">{{ task.taskTitle || '未命名任务' }}</view>
|
2025-07-12 10:37:32 +08:00
|
|
|
|
<view class="task-status" :class="getStatusClass(task.state)">
|
|
|
|
|
|
{{ getStatusText(task.state) }}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 置顶标识 -->
|
|
|
|
|
|
<view v-if="task.ownRank === 1" class="top-badge">
|
2025-07-12 13:46:29 +08:00
|
|
|
|
<uni-icons type="arrow-up" size="12" color="#007aff"></uni-icons>
|
2025-07-12 10:37:32 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</uni-swipe-action-item>
|
|
|
|
|
|
</uni-swipe-action>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
|
<view v-if="taskList.length === 0 && !loading" class="empty-state">
|
|
|
|
|
|
<u-empty :text="config.emptyText || '暂无任务'" mode="list"></u-empty>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 加载更多 -->
|
|
|
|
|
|
<u-load-more
|
|
|
|
|
|
:status="loadMoreStatus"
|
|
|
|
|
|
@loadmore="$emit('loadMore')"
|
|
|
|
|
|
></u-load-more>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'TaskList',
|
|
|
|
|
|
props: {
|
|
|
|
|
|
// 任务列表数据
|
|
|
|
|
|
taskList: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => []
|
|
|
|
|
|
},
|
|
|
|
|
|
// 加载状态
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
|
|
|
|
|
// 加载更多状态
|
|
|
|
|
|
loadMoreStatus: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: 'more' // more, loading, noMore
|
|
|
|
|
|
},
|
|
|
|
|
|
// 配置对象
|
|
|
|
|
|
config: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => ({
|
|
|
|
|
|
showCheckbox: true, // 是否显示checkbox
|
|
|
|
|
|
canComplete: true, // 是否可以完成任务
|
|
|
|
|
|
canDelete: true, // 是否可以删除任务
|
|
|
|
|
|
canTop: true, // 是否可以置顶任务
|
|
|
|
|
|
emptyText: '暂无任务', // 空状态文本
|
|
|
|
|
|
detailPage: '/pages/workbench/task/reportTaskDetail' // 详情页面路径
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
checkboxStates: {} // 存储checkbox的状态
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
// 获取左划操作选项
|
|
|
|
|
|
getSwipeOptions(task) {
|
|
|
|
|
|
const options = []
|
|
|
|
|
|
// 置顶功能
|
|
|
|
|
|
if (this.config.canTop) {
|
|
|
|
|
|
if (task.ownRank === 1) {
|
|
|
|
|
|
// 已置顶,显示取消置顶
|
|
|
|
|
|
options.push({
|
|
|
|
|
|
text: '取消置顶',
|
|
|
|
|
|
style: {
|
|
|
|
|
|
backgroundColor: '#999',
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未置顶,显示置顶
|
|
|
|
|
|
options.push({
|
|
|
|
|
|
text: '置顶',
|
|
|
|
|
|
style: {
|
2025-07-12 13:46:29 +08:00
|
|
|
|
backgroundColor: '#007aff',
|
2025-07-12 10:37:32 +08:00
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除功能
|
|
|
|
|
|
if (this.config.canDelete) {
|
|
|
|
|
|
options.push({
|
|
|
|
|
|
text: '删除',
|
|
|
|
|
|
style: {
|
|
|
|
|
|
backgroundColor: '#ff4757',
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-11-11 10:33:15 +08:00
|
|
|
|
|
|
|
|
|
|
if (task.state !== 2) {
|
|
|
|
|
|
options.push({
|
|
|
|
|
|
text: '验收',
|
|
|
|
|
|
style: {
|
|
|
|
|
|
backgroundColor: '#00b300',
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return options
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 处理左划操作点击
|
|
|
|
|
|
handleSwipeClick(e, index) {
|
|
|
|
|
|
const { content, position } = e
|
|
|
|
|
|
|
|
|
|
|
|
if (!content) {
|
|
|
|
|
|
console.error('Invalid swipe event:', e)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const task = this.taskList[index]
|
|
|
|
|
|
if (!task) {
|
|
|
|
|
|
console.error('Task not found at index:', index)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (content.text === '置顶') {
|
|
|
|
|
|
this.$emit('setTaskTop', task, 1)
|
|
|
|
|
|
} else if (content.text === '取消置顶') {
|
|
|
|
|
|
this.$emit('setTaskTop', task, 0)
|
|
|
|
|
|
} else if (content.text === '删除') {
|
|
|
|
|
|
this.$emit('deleteTask', task)
|
2025-11-11 10:33:15 +08:00
|
|
|
|
} else if (content.text == '验收') {
|
|
|
|
|
|
this.$emit('completeTask', task)
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 处理任务完成
|
|
|
|
|
|
handleTaskComplete(task) {
|
|
|
|
|
|
if (!this.config.canComplete) return
|
|
|
|
|
|
console.log('handleTaskComplete called, task:', task)
|
|
|
|
|
|
console.log('task.status:', task.status, 'task.state:', task.state)
|
|
|
|
|
|
|
|
|
|
|
|
// 只有单任务(status为0)且状态为0(进行中)的任务才能完成
|
2025-11-11 10:33:15 +08:00
|
|
|
|
if (task.status !== 0 && (task.state == 0 || task.state == 1)) {
|
2025-07-12 10:37:32 +08:00
|
|
|
|
console.log('Task cannot be completed, status:', task.status, 'state:', task.state)
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: '该任务当前状态无法完成',
|
|
|
|
|
|
showCancel: false
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 先设置checkbox为选中状态
|
|
|
|
|
|
this.$set(this.checkboxStates, task.taskId, true)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Showing confirmation modal')
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '确认完成',
|
|
|
|
|
|
content: `确定要将任务"${task.taskTitle}"标记为完成吗?`,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
this.$emit('completeTask', task)
|
|
|
|
|
|
// 清除checkbox状态
|
|
|
|
|
|
this.$set(this.checkboxStates, task.taskId, false)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 用户取消,重置checkbox状态为false
|
|
|
|
|
|
console.log('用户取消,重置checkbox状态')
|
|
|
|
|
|
this.$set(this.checkboxStates, task.taskId, false)
|
|
|
|
|
|
console.log('checkbox状态已重置:', this.checkboxStates[task.taskId])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到任务详情页面
|
|
|
|
|
|
handleTaskClick(task) {
|
|
|
|
|
|
this.$emit('taskClick', task)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
|
getStatusText(state) {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
15: '申请延期',
|
|
|
|
|
|
0: '进行中',
|
2025-11-11 10:33:15 +08:00
|
|
|
|
1: '待验收',
|
|
|
|
|
|
2: '已完成'
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
return statusMap[state] || '未知状态'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态样式类
|
|
|
|
|
|
getStatusClass(state) {
|
|
|
|
|
|
const classMap = {
|
|
|
|
|
|
15: 'status-pending',
|
|
|
|
|
|
0: 'status-processing',
|
|
|
|
|
|
1: 'status-waiting',
|
|
|
|
|
|
2: 'status-completed'
|
|
|
|
|
|
}
|
|
|
|
|
|
return classMap[state] || 'status-unknown'
|
2025-07-12 11:21:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为单行文字
|
|
|
|
|
|
isSingleLine(text) {
|
|
|
|
|
|
if (!text) return true
|
|
|
|
|
|
// 估算文字长度,假设每个中文字符约等于2个英文字符
|
|
|
|
|
|
const estimatedLength = text.replace(/[\u4e00-\u9fa5]/g, 'aa').length
|
|
|
|
|
|
// 如果估算长度小于等于20个字符,认为是单行
|
|
|
|
|
|
return estimatedLength <= 20
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.task-list-container {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
padding-bottom: 120rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-list {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-item {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-checkbox {
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-checkbox {
|
|
|
|
|
|
width: 36rpx;
|
|
|
|
|
|
height: 36rpx;
|
|
|
|
|
|
border: 2rpx solid #ddd;
|
|
|
|
|
|
border-radius: 6rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&.checked {
|
2025-07-12 13:46:29 +08:00
|
|
|
|
background-color: $im-primary;
|
|
|
|
|
|
border-color: $im-primary;
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
border-color: #ddd;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
line-height: 1.4;
|
2025-07-12 11:21:09 +08:00
|
|
|
|
height: 88rpx; /* 固定高度 */
|
2025-07-12 10:37:32 +08:00
|
|
|
|
display: -webkit-box;
|
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
2025-07-12 11:21:09 +08:00
|
|
|
|
|
|
|
|
|
|
/* 单行文字时垂直居中 */
|
|
|
|
|
|
&.single-line {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
-webkit-line-clamp: 1;
|
|
|
|
|
|
}
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-status {
|
|
|
|
|
|
padding: 8rpx 16rpx;
|
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
&.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.top-badge {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 8rpx;
|
|
|
|
|
|
right: 8rpx;
|
2025-07-12 13:46:29 +08:00
|
|
|
|
background-color: rgba($im-primary, 0.1);
|
|
|
|
|
|
border: 1rpx solid $im-primary;
|
2025-07-12 10:37:32 +08:00
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
width: 28rpx;
|
|
|
|
|
|
height: 28rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2025-07-12 13:46:29 +08:00
|
|
|
|
box-shadow: 0 2rpx 6rpx rgba($im-primary, 0.2);
|
2025-07-12 10:37:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
padding: 100rpx 20rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|