提交代码

This commit is contained in:
zlwu committed 2025-08-11 20:49:48 +08:00
1 parent ab09e6a2aa
commit 5a482ae823
4 files changed
+1601 -547

No files matched your search

+2 -1
View File
@@ -114,7 +114,8 @@ export const request = (options) => {
timeout: options.timeout || REQUEST_CONFIG.timeout,
responseType: options.responseType || 'text', // 支持responseType参数
header: {
'Content-Type': 'application/json',
// 吴自龙 注释 get方法,后台不接受json 20250811
// 'Content-Type': 'application/json',
authorization: token,
clientid: options.clientId || REQUEST_CONFIG.clientId,
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
+706
View File
@@ -0,0 +1,706 @@
<template>
<view class="task-finish-container">
<!-- 头部标题和操作 -->
<view class="header">
<text class="title">我的已办</text>
<view class="header-actions">
<u-icon name="search" size="20" color="#666" @click="toggleSearch"></u-icon>
<u-icon
name="account"
size="20"
color="#666"
style="margin-left: 16px"
@click="openUserSelect"
></u-icon>
</view>
</view>
<!-- 搜索栏 -->
<view v-if="showSearch" class="search-section">
<view class="search-input">
<u-input
v-model="queryParams.nodeName"
placeholder="请输入任务名称"
border="bottom"
clearable
@confirm="handleQuery"
></u-input>
</view>
<view class="search-input">
<u-input
v-model="queryParams.flowName"
placeholder="请输入流程定义名称"
border="bottom"
clearable
@confirm="handleQuery"
></u-input>
</view>
<view class="search-input">
<u-input
v-model="queryParams.flowCode"
placeholder="请输入流程定义编码"
border="bottom"
clearable
@confirm="handleQuery"
></u-input>
</view>
<view class="search-actions">
<u-button type="primary" size="small" @click="handleQuery">搜索</u-button>
<u-button size="small" @click="resetQuery">重置</u-button>
</view>
</view>
<!-- 申请人筛选显示 -->
<view v-if="userSelectCount > 0" class="user-filter-section">
<view class="filter-info">
<text class="filter-text">已选择 {{ userSelectCount }} 位申请人</text>
<u-button type="error" size="mini" @click="clearUserFilter">清除</u-button>
</view>
</view>
<!-- 内容区域 -->
<view class="content">
<!-- 加载状态 -->
<view v-if="loading" class="loading">
<u-loading-icon></u-loading-icon>
<text class="loading-text">加载中...</text>
</view>
<!-- 空数据 -->
<view v-else-if="taskList.length === 0" class="empty">
<view class="empty-content">
<u-icon name="file-text" size="80" color="#ddd"></u-icon>
<text class="empty-text">暂无数据</text>
</view>
</view>
<!-- 任务列表 -->
<view v-else class="task-list">
<view
v-for="item in taskList"
:key="item.id"
class="task-item"
@click="handleView(item)"
>
<!-- 任务信息 -->
<view class="task-header">
<view class="task-title-row">
<text class="task-name">{{ item.flowName }}</text>
<view
class="task-status"
:style="{
backgroundColor: getStatusStyle(item.flowStatus).color,
color: 'white',
}"
>
<text class="status-text">
{{ getStatusText(item.flowStatus) }}
</text>
</view>
</view>
<view class="task-info">
<text class="task-code">{{ item.flowCode }}</text>
<text class="task-category">{{ item.categoryName }}</text>
</view>
<text class="task-version">版本:v{{ item.version }}.0</text>
<text class="task-node-name">任务:{{ item.nodeName }}</text>
<text class="task-applicant">申请人:{{ item.createByName }}</text>
<!-- 办理人信息 -->
<view class="assignee-info">
<text class="assignee-label">办理人:</text>
<text class="assignee-name">{{ item.approveName || '无' }}</text>
</view>
<!-- 任务状态 -->
<view class="task-status-info">
<text class="task-status-label">任务状态:</text>
<view
class="task-status-tag"
:style="{
backgroundColor: getTaskStatusStyle(item.flowTaskStatus).color,
color: 'white',
}"
>
<text class="task-status-value">
{{ getTaskStatusText(item.flowTaskStatus) }}
</text>
</view>
</view>
<text class="task-time">{{ formatTime(item.createTime) }}</text>
</view>
<!-- 操作按钮 -->
<view class="task-actions" @click.stop>
<u-button type="info" size="mini" @click="handleView(item)">查看</u-button>
</view>
</view>
</view>
<!-- 分页加载 -->
<view v-if="taskList.length > 0" class="pagination">
<u-loadmore v-if="hasMore" status="loading" @loadmore="loadMore">
<text>正在加载更多...</text>
</u-loadmore>
<view v-else class="load-end">
<text>已加载全部数据</text>
</view>
</view>
</view>
<!-- 申请人选择弹窗 -->
<u-popup v-model="showUserSelector" mode="center" width="80%" border-radius="12">
<view class="user-selector-popup">
<view class="popup-header">
<text class="popup-title">选择申请人</text>
<u-icon name="close" size="20" color="#666" @click="closeUserSelect"></u-icon>
</view>
<view class="popup-content">
<text class="popup-tip">请输入申请人姓名或工号</text>
<view class="search-user">
<u-input
v-model="userSearchKeyword"
placeholder="搜索申请人"
clearable
></u-input>
<u-button
type="primary"
size="small"
@click="searchUsers"
style="margin-top: 12px"
>
搜索
</u-button>
</view>
<view v-if="searchUsers.length > 0" class="user-results">
<view
v-for="user in searchUsers"
:key="user.userId"
class="user-item"
:class="{ selected: selectedUserIds.includes(user.userId) }"
@click="toggleUserSelect(user)"
>
<text class="user-name">{{ user.userName }}</text>
<text class="user-dept">{{ user.deptName }}</text>
</view>
</view>
</view>
<view class="popup-actions">
<u-button @click="closeUserSelect">取消</u-button>
<u-button type="primary" @click="confirmUserSelect">确定</u-button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { workflowApi, getStatusStyle, formatTime, routerJump } from '@/common/workflow-api.js';
export default {
data() {
return {
loading: false,
showSearch: false,
showUserSelector: false,
taskList: [],
total: 0,
hasMore: true,
// 用户筛选相关
userSelectCount: 0,
selectedUserIds: [],
userSearchKeyword: '',
searchUsers: [],
queryParams: {
pageNum: 1,
pageSize: 10,
nodeName: '',
flowName: '',
flowCode: '',
createByIds: [],
},
};
},
onLoad() {
this.getFinishList();
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore();
}
},
onPullDownRefresh() {
this.refreshData();
},
methods: {
async getFinishList() {
if (this.loading) return;
this.loading = true;
try {
const res = await workflowApi.pageByTaskFinish(this.queryParams);
if (res.code === 200) {
if (this.queryParams.pageNum === 1) {
this.taskList = res.rows || [];
} else {
this.taskList.push(...(res.rows || []));
}
this.total = res.total;
this.hasMore = this.taskList.length < this.total;
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'error' });
}
} catch (e) {
console.error('加载已办任务失败:', e);
uni.showToast({ title: '网络错误', icon: 'error' });
} finally {
this.loading = false;
uni.stopPullDownRefresh();
}
},
async loadMore() {
if (!this.hasMore || this.loading) return;
this.queryParams.pageNum++;
await this.getFinishList();
},
async refreshData() {
this.queryParams.pageNum = 1;
this.hasMore = true;
await this.getFinishList();
},
toggleSearch() {
this.showSearch = !this.showSearch;
},
handleQuery() {
this.queryParams.pageNum = 1;
this.hasMore = true;
this.refreshData();
},
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 10,
nodeName: '',
flowName: '',
flowCode: '',
createByIds: [],
};
this.userSelectCount = 0;
this.selectedUserIds = [];
this.hasMore = true;
this.refreshData();
},
handleView(item) {
this.routerJump({
businessId: item.businessId,
taskId: item.id,
type: 'view',
formCustom: item.formCustom,
formPath: item.formPath,
});
},
// 用户选择相关方法
openUserSelect() {
this.showUserSelector = true;
},
closeUserSelect() {
this.showUserSelector = false;
this.userSearchKeyword = '';
this.searchUsers = [];
},
async searchUsers() {
if (!this.userSearchKeyword.trim()) {
uni.showToast({ title: '请输入搜索关键词', icon: 'none' });
return;
}
// 这里应该调用用户搜索API,暂时模拟数据
this.searchUsers = [
{ userId: '1', userName: '张三', deptName: '技术部' },
{ userId: '2', userName: '李四', deptName: '财务部' },
{ userId: '3', userName: '王五', deptName: '人事部' },
];
},
toggleUserSelect(user) {
const index = this.selectedUserIds.findIndex((id) => id === user.userId);
if (index > -1) {
this.selectedUserIds.splice(index, 1);
} else {
this.selectedUserIds.push(user.userId);
}
},
confirmUserSelect() {
this.userSelectCount = this.selectedUserIds.length;
this.queryParams.createByIds = [...this.selectedUserIds];
this.closeUserSelect();
this.handleQuery();
},
clearUserFilter() {
this.userSelectCount = 0;
this.selectedUserIds = [];
this.queryParams.createByIds = [];
this.handleQuery();
},
getStatusText(status) {
const statusMap = {
draft: '草稿',
waiting: '待审批',
complete: '已完成',
cancel: '已取消',
back: '已退回',
suspend: '挂起',
terminate: '终止',
};
return statusMap[status] || '未知';
},
getTaskStatusText(status) {
const statusMap = {
agree: '同意',
reject: '驳回',
back: '退回',
delegate: '委托',
suspend: '挂起',
terminate: '终止',
};
return statusMap[status] || '未知';
},
getTaskStatusStyle(status) {
const styleMap = {
agree: { color: '#67c23a' },
reject: { color: '#f56c6c' },
back: { color: '#409eff' },
delegate: { color: '#e6a23c' },
suspend: { color: '#909399' },
terminate: { color: '#f56c6c' },
};
return styleMap[status] || { color: '#909399' };
},
getStatusStyle(status) {
return getStatusStyle(status);
},
formatTime(timeStr) {
return formatTime(timeStr);
},
routerJump(params) {
routerJump(params);
},
},
};
</script>
<style scoped>
.task-finish-container {
background: #f5f5f5;
min-height: 100vh;
padding-bottom: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.title {
font-size: 20px;
font-weight: bold;
color: #333;
}
.header-actions {
display: flex;
align-items: center;
}
.search-section {
background: #fff;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.search-input {
margin-bottom: 12px;
}
.search-actions {
display: flex;
gap: 12px;
}
.user-filter-section {
background: #fff;
padding: 12px 20px;
border-bottom: 1px solid #eee;
}
.filter-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.filter-text {
color: #007aff;
font-size: 14px;
}
.content {
flex: 1;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px;
}
.loading-text {
margin-top: 8px;
color: #999;
font-size: 14px;
}
.empty {
padding: 40px 20px;
}
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 14px;
color: #999;
margin-top: 12px;
}
.task-list {
padding: 16px 20px;
}
.task-item {
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
transition: box-shadow 0.2s;
}
.task-item:active {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.task-header {
margin-bottom: 12px;
}
.task-title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.task-name {
font-size: 16px;
font-weight: 500;
color: #333;
flex: 1;
margin-right: 12px;
}
.task-status {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.task-info {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
}
.task-code {
font-size: 13px;
color: #666;
}
.task-category {
font-size: 13px;
color: #666;
}
.task-version,
.task-node-name,
.task-applicant {
font-size: 13px;
color: #666;
margin-bottom: 4px;
}
.assignee-info {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.assignee-label {
font-size: 13px;
color: #666;
margin-right: 4px;
}
.assignee-name {
font-size: 13px;
color: #333;
font-weight: 500;
}
.task-status-info {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.task-status-label {
font-size: 13px;
color: #666;
margin-right: 4px;
}
.task-status-tag {
padding: 2px 6px;
border-radius: 8px;
font-size: 12px;
font-weight: 500;
}
.task-time {
font-size: 12px;
color: #999;
}
.task-actions {
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}
.pagination {
padding: 16px 20px;
}
.load-end {
text-align: center;
padding: 16px;
}
.load-end text {
font-size: 14px;
color: #999;
}
/* 弹窗样式 */
.user-selector-popup {
padding: 20px;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 18px;
font-weight: 500;
color: #333;
}
.popup-content {
margin-bottom: 16px;
}
.popup-tip {
font-size: 14px;
color: #666;
margin-bottom: 12px;
}
.search-user {
margin-bottom: 16px;
}
.user-results {
max-height: 200px;
overflow-y: auto;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border: 1px solid #e6e6e6;
border-radius: 8px;
margin-bottom: 8px;
transition: all 0.2s;
}
.user-item.selected {
border-color: #007aff;
background: #f0f8ff;
}
.user-name {
font-size: 14px;
font-weight: 500;
color: #333;
}
.user-dept {
font-size: 12px;
color: #666;
}
.popup-actions {
display: flex;
justify-content: space-between;
gap: 12px;
padding-top: 12px;
border-top: 1px solid #eee;
}
</style>
File diff suppressed because it is too large. Load diff
+16 -6
View File
@@ -71,8 +71,8 @@
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-item">
<text class="label">流程编码:</text>
<text class="value">{{ item.flowCode || '-' }}</text>
<text class="label">流程名称:</text>
<text class="value">{{ item.flowName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">流程分类:</text>
@@ -332,12 +332,17 @@ export default {
if (isLoadMore) {
this.taskList = [...this.taskList, ...newList];
// 判断是否还有更多数据
if (newList.length < this.queryParams.pageSize) {
// 判断是否还有更多数据:检查当前页数据量 AND 总数据量
if (
newList.length < this.queryParams.pageSize ||
this.taskList.length >= (res.total || this.total)
) {
this.loadmoreStatus = 'nomore';
} else {
this.loadmoreStatus = 'loadmore';
}
// 更新总数量(防止首次加载时total未正确设置)
this.total = res.total || this.total || 0;
} else {
this.taskList = newList;
this.total = res.total || 0;
@@ -565,12 +570,17 @@ export default {
if (isLoadMore) {
// 加载更多,追加数据
this.userList = [...this.userList, ...newUsers];
// 判断是否还有更多数据
if (newUsers.length < this.userListParams.pageSize) {
// 判断是否还有更多数据:检查当前页数据量 AND 总数据量
if (
newUsers.length < this.userListParams.pageSize ||
this.userList.length >= (res.total || this.userListParams.total)
) {
this.userLoadmoreStatus = 'nomore';
} else {
this.userLoadmoreStatus = 'loadmore';
}
// 更新总数量(防止首次加载时total未正确设置)
this.userListParams.total = res.total || this.userListParams.total || 0;
} else {
// 首次加载或搜索,替换数据
this.userList = newUsers;