| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836 |
- <template>
- <view class="task-waiting-container">
- <!-- 顶部导航 -->
- <u-navbar title="我的待办" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
- <view slot="left" @click="goBack">
- <u-icon name="arrow-left" color="#fff" size="22"></u-icon>
- </view>
- </u-navbar>
- <!-- 搜索区域 -->
- <view class="search-container">
- <u-search
- v-model="queryParams.flowName"
- placeholder="请输入流程定义名称"
- :show-action="true"
- action-text="搜索"
- :animation="true"
- @search="handleQuery"
- @custom="handleQuery"
- ></u-search>
- <!-- 申请人选择 -->
- <view class="filter-section">
- <view class="filter-item" @click="openUserSelect">
- <u-icon name="account" size="20" color="#3c9cff"></u-icon>
- <text class="filter-text">
- {{
- selectedUsers.length > 0
- ? `已选择${selectedUsers.length}个申请人`
- : '选择申请人'
- }}
- </text>
- <u-badge
- v-if="selectedUsers.length > 0"
- :count="selectedUsers.length"
- :max="99"
- ></u-badge>
- <u-icon name="arrow-right" size="16" color="#c0c4cc"></u-icon>
- </view>
- </view>
- </view>
- <!-- 加载状态 -->
- <u-loading-page
- :loading="loading && taskList.length === 0"
- loading-text="加载中..."
- ></u-loading-page>
- <!-- 待办任务列表 -->
- <view v-if="!loading || taskList.length > 0" class="content-wrapper">
- <!-- 空状态 -->
- <view v-if="taskList.length === 0 && !loading" class="empty-container">
- <u-empty text="暂无待办任务" mode="data"></u-empty>
- </view>
- <!-- 任务卡片列表 -->
- <view v-else class="task-list">
- <view v-for="(item, index) in taskList" :key="item.id || index" class="task-card">
- <!-- 卡片头部 -->
- <view class="card-header">
- <view class="task-title">{{ item.flowName || '-' }}</view>
- <view class="task-status">
- <u-tag
- :text="getFlowStatusText(item.flowStatus)"
- :type="getFlowStatusType(item.flowStatus)"
- size="mini"
- ></u-tag>
- </view>
- </view>
- <!-- 卡片内容 -->
- <view class="card-content">
- <view class="info-item">
- <text class="label">流程编码:</text>
- <text class="value">{{ item.flowCode || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="label">流程分类:</text>
- <text class="value">{{ item.categoryName || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="label">任务名称:</text>
- <text class="value">{{ item.nodeName || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="label">申请人:</text>
- <text class="value">{{ item.createByName || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="label">办理人:</text>
- <view class="assignee-tags">
- <template v-if="item.assigneeNames">
- <u-tag
- v-for="(name, idx) in item.assigneeNames.split(',')"
- :key="idx"
- :text="name"
- type="success"
- size="mini"
- style="margin-right: 8rpx; margin-bottom: 8rpx"
- ></u-tag>
- </template>
- <u-tag v-else text="无" type="info" size="mini"></u-tag>
- </view>
- </view>
- <view class="info-item">
- <text class="label">创建时间:</text>
- <text class="value">{{ formatTime(item.createTime) }}</text>
- </view>
- </view>
- <!-- 操作按钮 -->
- <view class="card-actions">
- <view class="action-btn primary-btn" @click="handleApproval(item)">
- <u-icon name="edit-pen" size="16" color="#3c9cff"></u-icon>
- <text class="btn-text">办理</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 滚动加载更多 -->
- <u-loadmore
- v-if="taskList.length > 0"
- :status="loadmoreStatus"
- :load-text="{
- loadmore: '轻轻上拉',
- loading: '努力加载中',
- nomore: '没有更多了',
- }"
- @loadmore="loadMore"
- ></u-loadmore>
- </view>
- <!-- 申请人选择弹窗 -->
- <u-popup
- :show="userSelectDialog.visible"
- mode="bottom"
- :round="14"
- @close="cancelUserSelect"
- >
- <view class="user-select-popup">
- <view class="popup-header">
- <text class="popup-title">选择申请人</text>
- <u-icon
- name="close"
- @click="cancelUserSelect"
- size="24"
- color="#909399"
- ></u-icon>
- </view>
- <view class="popup-content">
- <u-search
- v-model="userSearchKeyword"
- placeholder="搜索用户名或姓名"
- :show-action="false"
- @input="searchUsers"
- style="height: 60rpx !important"
- ></u-search>
- <scroll-view scroll-y class="user-list">
- <view
- v-for="user in userList"
- :key="user.userId"
- class="user-item"
- @click="toggleUserSelect(user)"
- >
- <view class="user-info">
- <u-avatar :text="getAvatarText(user.nickName)" size="40"></u-avatar>
- <view class="user-details">
- <text class="user-name">{{ user.nickName }}</text>
- <text class="user-username">{{ user.userName }}</text>
- </view>
- </view>
- <u-checkbox
- v-model="user.selected"
- @change="onCheckboxChange(user, $event)"
- @click.stop
- ></u-checkbox>
- </view>
- </scroll-view>
- </view>
- <view class="popup-footer">
- <u-button @click="cancelUserSelect" type="info" size="default">取消</u-button>
- <u-button @click="confirmUserSelect" type="primary" size="default">
- 确定
- </u-button>
- </view>
- </view>
- </u-popup>
- </view>
- </template>
- <script>
- import { request } from '@/common/request.js';
- import { getDict, getDictTagType } from '@/common/dict.js';
- export default {
- name: 'TaskWaiting',
- data() {
- return {
- loading: false,
- taskList: [],
- total: 0,
- loadmoreStatus: 'loadmore', // loadmore, loading, nomore
- // 查询参数
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- flowName: '', // 流程定义名称
- createByIds: [], // 申请人IDs
- },
- // 流程状态字典
- flowStatusDict: [],
- // 申请人选择相关
- userSelectDialog: {
- visible: false,
- },
- selectedUsers: [], // 已选择的申请人
- userList: [], // 用户列表
- userSearchKeyword: '', // 用户搜索关键词
- userSearchTimer: null, // 搜索防抖定时器
- };
- },
- async created() {
- await this.loadFlowStatusDict();
- await this.getList();
- },
- methods: {
- // 返回上一页
- goBack() {
- uni.navigateBack();
- },
- // 加载流程状态字典
- async loadFlowStatusDict() {
- try {
- this.flowStatusDict = await getDict('wf_business_status');
- console.log('流程状态字典加载完成:', this.flowStatusDict);
- } catch (error) {
- console.error('加载流程状态字典失败:', error);
- }
- },
- // 获取流程状态显示文本
- getFlowStatusText(status) {
- if (!this.flowStatusDict || this.flowStatusDict.length === 0) {
- // 字典未加载时的降级处理
- const statusMap = {
- 0: '待提交',
- 1: '审批中',
- 2: '已通过',
- 3: '已拒绝',
- 4: '已撤回',
- 5: '已终止',
- 6: '已作废',
- };
- return statusMap[status] || '未知';
- }
- const statusItem = this.flowStatusDict.find((item) => item.value === status);
- return statusItem ? statusItem.label : '未知';
- },
- // 获取流程状态标签类型
- getFlowStatusType(status) {
- const fallbackMap = {
- 0: 'info', // 待提交
- 1: 'warning', // 审批中
- 2: 'success', // 已通过
- 3: 'error', // 已拒绝
- 4: 'info', // 已撤回
- 5: 'error', // 已终止
- 6: 'error', // 已作废
- };
- return getDictTagType(this.flowStatusDict, status, fallbackMap);
- },
- // 获取待办任务列表
- async getList(isLoadMore = false) {
- if (!isLoadMore) {
- this.loading = true;
- this.queryParams.pageNum = 1;
- } else {
- this.loadmoreStatus = 'loading';
- }
- try {
- const res = await request({
- url: '/workflow/task/pageByTaskWait',
- method: 'GET',
- params: {
- pageNum: this.queryParams.pageNum,
- pageSize: this.queryParams.pageSize,
- flowName: this.queryParams.flowName || undefined,
- createByIds:
- this.queryParams.createByIds.length > 0
- ? this.queryParams.createByIds
- : undefined,
- },
- });
- if (res && res.code === 200) {
- const newList = res.rows || [];
- if (isLoadMore) {
- this.taskList = [...this.taskList, ...newList];
- // 判断是否还有更多数据
- if (newList.length < this.queryParams.pageSize) {
- this.loadmoreStatus = 'nomore';
- } else {
- this.loadmoreStatus = 'loadmore';
- }
- } else {
- this.taskList = newList;
- this.total = res.total || 0;
- // 重置加载更多状态
- if (this.taskList.length >= this.total) {
- this.loadmoreStatus = 'nomore';
- } else {
- this.loadmoreStatus = 'loadmore';
- }
- }
- } else {
- if (!isLoadMore) {
- this.taskList = [];
- this.total = 0;
- }
- uni.showToast({
- title: res?.msg || '获取数据失败',
- icon: 'none',
- });
- if (isLoadMore) {
- this.loadmoreStatus = 'loadmore';
- }
- }
- } catch (error) {
- console.error('获取待办任务列表失败:', error);
- if (!isLoadMore) {
- this.taskList = [];
- this.total = 0;
- }
- uni.showToast({
- title: '获取数据失败',
- icon: 'none',
- });
- if (isLoadMore) {
- this.loadmoreStatus = 'loadmore';
- }
- } finally {
- if (!isLoadMore) {
- this.loading = false;
- }
- }
- },
- // 搜索操作
- handleQuery() {
- this.getList();
- },
- // 加载更多
- loadMore() {
- if (this.loadmoreStatus === 'nomore' || this.loadmoreStatus === 'loading') {
- return;
- }
- this.queryParams.pageNum++;
- this.getList(true);
- },
- // 格式化时间
- formatTime(timeStr) {
- if (!timeStr) return '-';
- try {
- const date = new Date(timeStr);
- if (isNaN(date.getTime())) return timeStr;
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- const seconds = String(date.getSeconds()).padStart(2, '0');
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- } catch (error) {
- console.error('时间格式化失败:', error);
- return timeStr;
- }
- },
- // 办理任务
- handleApproval(row) {
- console.log('办理任务:', row);
- // 动态跳转到formPath对应的路径,携带businessId和taskId参数
- if (row.formPath) {
- // 确保路径以pages开头
- let fullPath = row.formPath;
- // 统一处理路径:去掉开头的/,然后添加pages前缀
- if (!fullPath.startsWith('pages/')) {
- // 如果以/开头,先去掉/
- if (fullPath.startsWith('/')) {
- fullPath = fullPath.substring(1);
- }
- // 添加pages前缀
- fullPath = `pages/${fullPath}`;
- }
- const url = `/${fullPath}?id=${row.businessId}&taskId=${row.id}&type=approval`;
- // 添加调试信息
- console.log('办理任务 - 原始formPath:', row.formPath);
- console.log('办理任务 - 处理后的fullPath:', fullPath);
- console.log('办理任务 - 最终跳转URL:', url);
- // 使用navigateTo进行跳转
- uni.navigateTo({
- url: url,
- fail: (err) => {
- console.error('办理页面跳转失败:', err);
- // 如果跳转失败,使用原有逻辑作为备选
- this.fallbackApproval(row);
- },
- });
- } else {
- // 如果没有formPath,使用原有逻辑作为备选
- this.fallbackApproval(row);
- }
- },
- // 备选办理方法(原有逻辑)
- fallbackApproval(row) {
- console.log('使用备选办理逻辑:', row);
- // 根据表单类型跳转到对应的审批页面
- if (row.formCustom === 'Y') {
- // 自定义表单
- uni.navigateTo({
- url: `/pages/workflow/form/custom?businessId=${row.businessId}&taskId=${
- row.id
- }&formPath=${encodeURIComponent(row.formPath || '')}`,
- });
- } else {
- // 默认表单或其他类型
- uni.navigateTo({
- url: `/pages/workflow/approval/index?businessId=${row.businessId}&taskId=${row.id}&type=approval`,
- });
- }
- },
- // 打开申请人选择
- openUserSelect() {
- this.userSelectDialog.visible = true;
- this.loadUserList();
- },
- // 取消申请人选择
- cancelUserSelect() {
- this.userSelectDialog.visible = false;
- this.userSearchKeyword = '';
- },
- // 确认申请人选择
- confirmUserSelect() {
- // 获取当前弹窗中选中的用户
- const currentSelected = this.userList.filter((user) => user.selected);
- console.log('确认选择 - 用户列表:', this.userList);
- console.log('确认选择 - 选中的用户:', currentSelected);
- if (currentSelected.length > 0) {
- // 如果有选中的用户,更新已选择的用户列表
- this.selectedUsers = currentSelected;
- this.queryParams.createByIds = currentSelected.map((user) => user.userId);
- console.log('更新选中用户:', this.selectedUsers);
- } else {
- // 如果没有选中任何用户,清空已选择的用户列表
- this.selectedUsers = [];
- this.queryParams.createByIds = [];
- console.log('清空选中用户');
- }
- this.userSelectDialog.visible = false;
- this.handleQuery();
- },
- // 切换用户选择状态
- toggleUserSelect(user) {
- this.$set(user, 'selected', !user.selected);
- console.log('切换用户选择:', user.nickName, '选中状态:', user.selected);
- },
- // checkbox变化处理
- onCheckboxChange(user, value) {
- console.log('Checkbox变化:', user.nickName, '新值:', value);
- this.$set(user, 'selected', value);
- },
- // 加载用户列表
- async loadUserList() {
- try {
- const res = await request({
- url: '/system/user/list',
- method: 'GET',
- params: {
- pageNum: 1,
- pageSize: 100,
- userName: this.userSearchKeyword || undefined,
- nickName: this.userSearchKeyword || undefined,
- },
- });
- if (res && res.code === 200) {
- // 每次加载用户列表时,所有用户默认都不选中
- this.userList = (res.rows || []).map((user) => ({
- ...user,
- selected: false,
- }));
- console.log('加载用户列表完成,用户数量:', this.userList.length);
- } else {
- this.userList = [];
- uni.showToast({
- title: res?.msg || '获取用户列表失败',
- icon: 'none',
- });
- }
- } catch (error) {
- console.error('获取用户列表失败:', error);
- this.userList = [];
- uni.showToast({
- title: '获取用户列表失败',
- icon: 'none',
- });
- }
- },
- // 搜索用户(防抖处理)
- searchUsers() {
- if (this.userSearchTimer) {
- clearTimeout(this.userSearchTimer);
- }
- this.userSearchTimer = setTimeout(() => {
- this.loadUserList();
- }, 500);
- },
- // 获取头像显示文字(取前两个字符)
- getAvatarText(nickName) {
- if (!nickName) return '';
- // 取前两个字符,如果只有一个字符则返回该字符
- return nickName.length > 1 ? nickName.substring(0, 1) : nickName;
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .task-waiting-container {
- background-color: #f5f6fa;
- min-height: 100vh;
- }
- .search-container {
- padding: 20rpx;
- background-color: #fff;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
- }
- .filter-section {
- margin-top: 20rpx;
- }
- .filter-item {
- display: flex;
- align-items: center;
- padding: 24rpx;
- background-color: #f8f9fa;
- border-radius: 12rpx;
- border: 1px solid #e4e7ed;
- &:active {
- background-color: #f0f0f0;
- }
- }
- .filter-text {
- flex: 1;
- margin-left: 16rpx;
- font-size: 28rpx;
- color: #303133;
- }
- .content-wrapper {
- padding: 20rpx;
- }
- .empty-container {
- padding: 100rpx 20rpx;
- }
- .task-list {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- }
- .task-card {
- background-color: #fff;
- border-radius: 16rpx;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
- overflow: hidden;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24rpx;
- border-bottom: 1px solid #f0f0f0;
- }
- .task-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #303133;
- flex: 1;
- }
- .task-status {
- margin-left: 16rpx;
- }
- .card-content {
- padding: 24rpx;
- }
- .info-item {
- display: flex;
- align-items: flex-start;
- margin-bottom: 16rpx;
- &:last-child {
- margin-bottom: 0;
- }
- }
- .label {
- font-size: 28rpx;
- color: #606266;
- min-width: 140rpx;
- margin-right: 16rpx;
- }
- .value {
- font-size: 28rpx;
- color: #303133;
- flex: 1;
- word-break: break-all;
- }
- .assignee-tags {
- flex: 1;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- }
- .card-actions {
- display: flex;
- justify-content: flex-end;
- padding: 20rpx;
- border-top: 1px solid #f0f0f0;
- }
- .action-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 16rpx 40rpx;
- border-radius: 12rpx;
- background-color: #f8f9fa;
- min-width: 120rpx;
- transition: all 0.3s ease;
- &:active {
- transform: scale(0.95);
- opacity: 0.8;
- }
- }
- .btn-text {
- font-size: 24rpx;
- margin-top: 8rpx;
- color: #303133;
- font-weight: 500;
- }
- .primary-btn {
- background-color: rgba(60, 156, 255, 0);
- .btn-text {
- color: #3c9cff;
- }
- }
- // 申请人选择弹窗样式
- .user-select-popup {
- background-color: #fff;
- border-radius: 14rpx 14rpx 0 0;
- overflow: hidden;
- height: 80vh;
- display: flex;
- flex-direction: column;
- }
- .popup-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24rpx;
- border-bottom: 1px solid #f0f0f0;
- flex-shrink: 0; /* 防止头部被压缩 */
- }
- .popup-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #303133;
- }
- .popup-content {
- flex: 1;
- padding: 24rpx 24rpx 0 24rpx; /* 底部不需要padding,由footer提供 */
- display: flex;
- flex-direction: column;
- overflow: hidden; /* 防止内容溢出 */
- }
- .user-list {
- margin-top: 20rpx;
- flex: 1; /* 使用弹性布局,自动占满剩余空间 */
- overflow-y: auto; /* 添加垂直滚动条 */
- min-height: 0; /* 确保flex子元素可以收缩 */
- }
- .user-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx 0;
- border-bottom: 1px solid #f8f9fa;
- &:last-child {
- border-bottom: none;
- }
- &:active {
- background-color: #f8f9fa;
- }
- }
- .user-info {
- display: flex;
- align-items: center;
- flex: 1;
- }
- .user-details {
- margin-left: 20rpx;
- flex: 1;
- }
- .user-name {
- display: block;
- font-size: 28rpx;
- color: #303133;
- font-weight: 500;
- }
- .user-username {
- display: block;
- font-size: 24rpx;
- color: #909399;
- margin-top: 4rpx;
- }
- .popup-footer {
- padding: 24rpx;
- border-top: 1px solid #f0f0f0;
- display: flex;
- gap: 20rpx;
- flex-shrink: 0; /* 防止底部按钮区域被压缩 */
- background-color: #fff; /* 确保背景色一致 */
- }
- // uview组件样式调整
- ::v-deep .u-search {
- background-color: #f5f6fa;
- /* 清除默认的 flex: 1 */
- flex: none;
- }
- // 弹窗内搜索框样式优化
- .popup-content ::v-deep .u-search {
- background-color: #f8f9fa;
- border-radius: 8rpx;
- margin-bottom: 0; /* 移除默认margin */
- }
- .popup-content ::v-deep .u-search__content {
- height: 60rpx; /* 设置搜索框高度 */
- border-radius: 8rpx;
- }
- .popup-content ::v-deep .u-input {
- font-size: 28rpx; /* 调整输入框字体大小 */
- }
- ::v-deep .u-tag {
- margin: 0;
- }
- ::v-deep .u-loadmore {
- padding: 40rpx 0;
- }
- </style>
|