Files
im-uniapp/pages/workbench/task/components/TaskList.vue
2025-07-12 13:46:29 +08:00

390 lines
8.8 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="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">
<view class="task-title" :class="{ 'single-line': isSingleLine(task.taskTitle) }">{{ task.taskTitle || '未命名任务' }}</view>
<view class="task-status" :class="getStatusClass(task.state)">
{{ getStatusText(task.state) }}
</view>
</view>
<!-- 置顶标识 -->
<view v-if="task.ownRank === 1" class="top-badge">
<uni-icons type="arrow-up" size="12" color="#007aff"></uni-icons>
</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: {
backgroundColor: '#007aff',
color: '#fff'
}
})
}
}
// 删除功能
if (this.config.canDelete) {
options.push({
text: '删除',
style: {
backgroundColor: '#ff4757',
color: '#fff'
}
})
}
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)
}
},
// 处理任务完成
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进行中的任务才能完成
if (task.status !== 0 || task.state !== 0) {
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: '进行中',
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'
},
// 判断是否为单行文字
isSingleLine(text) {
if (!text) return true
// 估算文字长度假设每个中文字符约等于2个英文字符
const estimatedLength = text.replace(/[\u4e00-\u9fa5]/g, 'aa').length
// 如果估算长度小于等于20个字符认为是单行
return estimatedLength <= 20
}
}
}
</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 {
background-color: $im-primary;
border-color: $im-primary;
}
&.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;
height: 88rpx; /* 固定高度 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
/* 单行文字时垂直居中 */
&.single-line {
display: flex;
flex-direction: column;
justify-content: center;
-webkit-line-clamp: 1;
}
}
.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;
background-color: rgba($im-primary, 0.1);
border: 1rpx solid $im-primary;
border-radius: 50%;
width: 28rpx;
height: 28rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 6rpx rgba($im-primary, 0.2);
}
.empty-state {
padding: 100rpx 20rpx;
text-align: center;
}
</style>