1813 lines
44 KiB
Vue
1813 lines
44 KiB
Vue
<template>
|
||
<crud-layout title="工作代办" :bg="$fileUrl(filePath(SERVICE_IMAGE.blueBg))">
|
||
<view class="todo-page">
|
||
<view class="todo-tabs">
|
||
<view :class="['tab-item', activeTab === 0 ? 'active' : '']" @click="switchTab(0)">我的待办</view>
|
||
<view :class="['tab-item', activeTab === 1 ? 'active' : '']" @click="switchTab(1)">我的已办</view>
|
||
</view>
|
||
<view class="todo-list">
|
||
<view v-if="todoList.length === 0 && !loading" class="todo-empty">
|
||
<image src="/static/icon/empty-todo.png" class="empty-img" />
|
||
<view class="empty-text">暂无数据</view>
|
||
</view>
|
||
<view v-else>
|
||
<view v-for="item in todoList" :key="item.taskId || item.id" class="todo-card">
|
||
<view class="card-bar"></view>
|
||
<view class="todo-card-content">
|
||
<view class="todo-card-title">
|
||
<text class="group">{{ item.groupName }}</text>
|
||
<text class="process">{{ item.processName }}</text>
|
||
</view>
|
||
<view class="todo-card-info">
|
||
<view class="info-row">
|
||
<text class="label">发起人:</text>
|
||
<text class="value">{{ item.rootUserName }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="label">发起时间:</text>
|
||
<text class="value">{{ item.startTime }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="todo-card-actions">
|
||
<button v-if="activeTab === 0" class="todo-btn process" @click="startProcess(item)">开始处理</button>
|
||
<button v-if="activeTab === 1" class="todo-btn view" @click="viewProcess(item)">查看</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="loading" class="loading-tip">加载中...</view>
|
||
<view v-else-if="finished" class="no-more-tip">没有更多了</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 审核意见弹窗 -->
|
||
<view v-if="showCommentModal" class="modal-overlay comment-modal" @tap="hideCommentModal">
|
||
<view class="modal-content" @tap.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">提交审核</text>
|
||
<text class="modal-close" @tap="hideCommentModal">×</text>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<view class="comment-section">
|
||
<text class="comment-label">审核意见</text>
|
||
<textarea v-model="approvalComment" class="comment-textarea" placeholder="请输入审核意见..."
|
||
maxlength="100"></textarea>
|
||
<view class="char-count">{{ approvalComment.length }}/100</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<view class="modal-btn cancel-btn" @tap="hideCommentModal">取消</view>
|
||
<view class="modal-btn confirm-btn" @tap="confirmApproval">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 转办人员选择弹窗 -->
|
||
<view v-if="showDelegateModal" class="modal-overlay delegate-modal" @tap="hideDelegateModal">
|
||
<view class="modal-content delegate-modal" @tap.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">选择转办人员</text>
|
||
<text class="modal-close" @tap="hideDelegateModal">×</text>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<!-- 搜索框 -->
|
||
<view class="search-section">
|
||
<input v-model="searchKeyword" class="search-input" placeholder="搜索" @input="filterUsers" />
|
||
</view>
|
||
|
||
<!-- 面包屑导航 -->
|
||
<view v-if="breadcrumbPath.length > 1" class="breadcrumb-section">
|
||
<view class="breadcrumb">
|
||
<view v-for="(item, index) in breadcrumbPath" :key="item.id" class="breadcrumb-item"
|
||
@tap="navigateToBreadcrumb(index)">
|
||
<text class="breadcrumb-text">{{ item.name }}</text>
|
||
<text v-if="index < breadcrumbPath.length - 1" class="breadcrumb-separator">></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户列表 -->
|
||
<view class="user-list">
|
||
<!-- 部门列表 -->
|
||
<view class="section-title">部门</view>
|
||
<view v-for="dept in filteredDepartments" :key="dept.id" class="user-item" @tap="selectUser(dept)">
|
||
<view class="user-checkbox" :class="{ checked: selectedUser?.id === dept.id }">
|
||
<text v-if="selectedUser?.id === dept.id" class="check-icon">✓</text>
|
||
</view>
|
||
<view class="user-icon dept-icon">🏢</view>
|
||
<view class="user-info">
|
||
<text class="user-name">{{ dept.name }}</text>
|
||
<text class="user-type">部门</text>
|
||
</view>
|
||
<text class="subordinate-link">下级</text>
|
||
</view>
|
||
|
||
<!-- 员工列表 -->
|
||
<view class="section-title">员工</view>
|
||
<view v-for="employee in filteredEmployees" :key="employee.id" class="user-item"
|
||
@tap="selectUser(employee)">
|
||
<view class="user-checkbox" :class="{ checked: selectedUser?.id === employee.id }">
|
||
<text v-if="selectedUser?.id === employee.id" class="check-icon">✓</text>
|
||
</view>
|
||
<view class="user-icon employee-icon">
|
||
<image v-if="employee.avatar" :src="$fileUrl(employee.avatar)" class="avatar-img" @error="$fileRetry(employee.avatar)" />
|
||
<text v-else class="avatar-text">{{ employee.name.charAt(0) }}</text>
|
||
</view>
|
||
<view class="user-info">
|
||
<text class="user-name">{{ employee.name }}</text>
|
||
<text class="user-type">员工</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<view class="modal-btn cancel-btn" @tap="hideDelegateModal">取消</view>
|
||
<view class="modal-btn confirm-btn" @tap="confirmDelegateUser">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 转办确认弹窗 -->
|
||
<view v-if="showDelegateConfirmModal" class="modal-overlay delegate-confirm-modal"
|
||
@tap="hideDelegateConfirmModal">
|
||
<view class="modal-content" @tap.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">转办确认</text>
|
||
<text class="modal-close" @tap="hideDelegateConfirmModal">×</text>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<view class="delegate-info">
|
||
<view class="info-item">
|
||
<text class="info-label">转办给:</text>
|
||
<text class="info-value">{{ selectedUser?.name }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="comment-section">
|
||
<text class="comment-label">审核意见 *</text>
|
||
<textarea v-model="delegateComment" class="comment-textarea" placeholder="请输入审核意见 (2-100字)"
|
||
maxlength="100"></textarea>
|
||
<view class="char-count">{{ delegateComment.length }}/100</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<view class="modal-btn cancel-btn" @tap="hideDelegateConfirmModal">取消</view>
|
||
<view class="modal-btn confirm-btn" @tap="confirmDelegate">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 审批弹窗 -->
|
||
<view v-if="showApprovalModal" class="modal-overlay" @tap="hideApprovalModal">
|
||
<view class="modal-content" @tap.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">审批处理</text>
|
||
<text class="modal-close" @tap="hideApprovalModal">×</text>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<!-- 动态字段渲染 - 根据 formItems 渲染 -->
|
||
<view v-if="currentTask.formItems && currentTask.formItems.length > 0">
|
||
<view v-for="field in currentTask.formItems" :key="field.id" class="form-item">
|
||
<text class="form-label">{{ field.name }}{{ field.required ? ' *' : '' }}</text>
|
||
<view v-if="field.type === 'Textarea'" class="form-textarea">
|
||
{{ field.props?.value || '' }}
|
||
</view>
|
||
<view v-else-if="field.type === 'Sequence'" class="form-sequence">
|
||
{{ field.props?.value || field.placeholder || '未知' }}
|
||
</view>
|
||
<view v-else class="form-input">{{ field.props?.value || '未知' }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 如果没有动态字段,显示默认字段 -->
|
||
<view v-else>
|
||
<!-- 流水号 -->
|
||
<view class="form-item">
|
||
<text class="form-label">流水号</text>
|
||
<view class="form-value">{{ currentTask.serialNumber || '未知流水号' }}</view>
|
||
</view>
|
||
|
||
<!-- 会议室 -->
|
||
<view class="form-item">
|
||
<text class="form-label">* 会议室</text>
|
||
<view class="form-input">{{ currentTask.roomName || '未知会议室' }}</view>
|
||
</view>
|
||
|
||
<!-- 预约人 -->
|
||
<view class="form-item">
|
||
<text class="form-label">* 预约人</text>
|
||
<view class="form-input">{{ currentTask.applicantName || '未知申请人' }}</view>
|
||
</view>
|
||
|
||
<!-- 预约时间 -->
|
||
<view class="form-item">
|
||
<text class="form-label">* 预约时间</text>
|
||
<view class="form-input">{{ currentTask.meetingTime || '未知时间' }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
<!-- 动态审批流程 - 根据 approvalFlow 渲染 -->
|
||
<view class="approval-flow">
|
||
<view v-if="currentTask.approvalFlow && currentTask.approvalFlow.length > 0">
|
||
<view v-for="(flow, index) in currentTask.approvalFlow" :key="flow.id" class="flow-item">
|
||
<view class="flow-icon" :class="flow.status === 2 ? 'completed' : 'pending'">
|
||
{{ flow.status === 2 ? '✓' : '⟲' }}
|
||
</view>
|
||
<view class="flow-content">
|
||
<text class="flow-label">{{ flow.name }}{{ flow.placeholder ? ` [${flow.placeholder}]` : ''
|
||
}}</text>
|
||
<view v-if="flow.userVoList && flow.userVoList.length > 0" class="flow-users">
|
||
<view v-for="user in flow.userVoList" :key="user.id" class="flow-user">
|
||
<view class="user-avatar">
|
||
<image v-if="user.avatar" :src="$fileUrl(user.avatar)" class="avatar-img" @error="$fileRetry(user.avatar)" />
|
||
<text v-else class="avatar-text">{{ user.name?.charAt(0) || '?' }}</text>
|
||
</view>
|
||
<text class="user-name">{{ user.name || '未知用户' }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else class="flow-user">
|
||
<view class="user-avatar"></view>
|
||
<text class="user-name">待分配</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="index < currentTask.approvalFlow.length - 1" class="flow-line"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 如果没有动态审批流程,显示默认流程 -->
|
||
<view v-else>
|
||
<view class="flow-item">
|
||
<view class="flow-icon completed">✓</view>
|
||
<view class="flow-content">
|
||
<text class="flow-label">发起人</text>
|
||
<view class="flow-user">
|
||
<view class="user-avatar"></view>
|
||
<text class="user-name">{{ currentTask.applicantName || '未知申请人' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="flow-line"></view>
|
||
<view class="flow-item">
|
||
<view class="flow-icon pending">⟲</view>
|
||
<view class="flow-content">
|
||
<text class="flow-label">审批人</text>
|
||
<view class="flow-user">
|
||
<view class="user-avatar"></view>
|
||
<text class="user-name">当前用户</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<!-- 查看模式下只显示关闭按钮 -->
|
||
<view v-if="currentTask.isViewMode" class="modal-btn close-btn" @tap="hideApprovalModal">
|
||
<text class="btn-icon">×</text>
|
||
<text class="btn-text">关闭</text>
|
||
</view>
|
||
|
||
<!-- 审批模式下显示操作按钮 -->
|
||
<template v-else>
|
||
<view class="modal-btn reject-btn" @tap="handleReject">
|
||
<text class="btn-icon">✕</text>
|
||
<text class="btn-text">拒绝</text>
|
||
</view>
|
||
<view class="modal-btn submit-btn" @tap="handleSubmit">
|
||
<text class="btn-icon">✓</text>
|
||
<text class="btn-text">同意</text>
|
||
</view>
|
||
<view class="modal-btn transfer-btn" @tap="handleTransfer">
|
||
<text class="btn-icon">↗</text>
|
||
<text class="btn-text">转办</text>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 转办表单弹窗 -->
|
||
<view v-if="showDelegateFormModal" class="modal-overlay delegate-form-modal" @tap="hideDelegateFormModal">
|
||
<view class="modal-content delegate-form-content" @tap.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">转办</text>
|
||
<view class="close-btn" @tap="hideDelegateFormModal">
|
||
<text class="iconfont icon-guanbi"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="delegate-form">
|
||
<!-- 选择转办人员 -->
|
||
<view class="form-section">
|
||
<view class="section-title">
|
||
<text class="title-text">选择转办人员</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<view class="user-selector" @tap="showUserSelection">
|
||
<view v-if="selectedUser" class="selected-user-tag">
|
||
<text class="user-tag-name">{{ selectedUser.name }}</text>
|
||
<text class="user-tag-remove" @tap.stop="removeSelectedUser">×</text>
|
||
</view>
|
||
<view class="add-user-btn">
|
||
<text class="add-icon">+</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="userSelectError" class="error-message">{{ userSelectError }}</view>
|
||
</view>
|
||
|
||
<!-- 审核意见 -->
|
||
<view class="form-section">
|
||
<view class="section-title">
|
||
<text class="title-text">审核意见</text>
|
||
<text class="required">*</text>
|
||
</view>
|
||
<view class="comment-input-wrapper">
|
||
<textarea v-model="delegateFormComment" class="comment-textarea" placeholder="请输入审核意见 (2-100字)"
|
||
:maxlength="100" auto-height />
|
||
<view class="char-count">{{ delegateFormComment.length }}/100</view>
|
||
</view>
|
||
<view v-if="commentError" class="error-message">{{ commentError }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<view class="modal-btn cancel-btn" @tap="hideDelegateFormModal">
|
||
<text class="btn-text">取消</text>
|
||
</view>
|
||
<view class="modal-btn confirm-btn" @tap="confirmDelegateForm">
|
||
<text class="btn-text">确定</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</crud-layout>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||
|
||
import { filePath } from "@@/utils/file"
|
||
import { SERVICE_IMAGE } from "@@/constant/index"
|
||
|
||
import { taskApi, integratedApi } from "@/sheep/api/index"
|
||
|
||
const todoList = ref<any[]>([])
|
||
const current = ref(1)
|
||
const size = 10
|
||
const total = ref(0)
|
||
const loading = ref(false)
|
||
const finished = ref(false)
|
||
const activeTab = ref(0)
|
||
|
||
// 审批弹窗相关
|
||
const showApprovalModal = ref(false)
|
||
const currentTask = ref<any>({})
|
||
const isProcessing = ref(false)
|
||
|
||
// 审核意见弹窗相关
|
||
const showCommentModal = ref(false)
|
||
const approvalComment = ref('')
|
||
const pendingApproval = ref<boolean | null>(null) // true: 同意, false: 拒绝
|
||
|
||
// 转办相关
|
||
const showDelegateModal = ref(false)
|
||
const showDelegateConfirmModal = ref(false)
|
||
const selectedUser = ref<any>(null)
|
||
const delegateComment = ref('')
|
||
const searchKeyword = ref('')
|
||
|
||
// 用户选择数据
|
||
const userSelectionData = ref<any>({})
|
||
const filteredDepartments = ref<any[]>([])
|
||
const filteredEmployees = ref<any[]>([])
|
||
|
||
// 转办表单弹窗
|
||
const showDelegateFormModal = ref(false)
|
||
const delegateFormComment = ref('')
|
||
|
||
// 面包屑导航
|
||
const breadcrumbPath = ref<any[]>([])
|
||
|
||
// 表单验证错误提示
|
||
const userSelectError = ref('')
|
||
const commentError = ref('')
|
||
|
||
function fetchTodoList(isRefresh = false) {
|
||
if (loading.value) return
|
||
loading.value = true
|
||
const apiFn = activeTab.value === 0 ? integratedApi.getTodoList : integratedApi.getDoneList
|
||
apiFn({ current: current.value, size }).then((res: any) => {
|
||
if (res.code === 0 && res.data) {
|
||
total.value = res.data.total || 0
|
||
const records = res.data.records || []
|
||
if (isRefresh) {
|
||
todoList.value = records
|
||
} else {
|
||
todoList.value = [...todoList.value, ...records]
|
||
}
|
||
finished.value = todoList.value.length >= total.value
|
||
}
|
||
loading.value = false
|
||
if (isRefresh) uni.stopPullDownRefresh()
|
||
}).catch(() => {
|
||
loading.value = false
|
||
if (isRefresh) uni.stopPullDownRefresh()
|
||
})
|
||
}
|
||
|
||
function switchTab(tabIdx: number) {
|
||
if (activeTab.value === tabIdx) return
|
||
activeTab.value = tabIdx
|
||
current.value = 1
|
||
finished.value = false
|
||
fetchTodoList(true)
|
||
}
|
||
|
||
|
||
onMounted(() => {
|
||
fetchTodoList(true)
|
||
})
|
||
|
||
onPullDownRefresh(() => {
|
||
current.value = 1
|
||
finished.value = false
|
||
fetchTodoList(true)
|
||
})
|
||
|
||
onReachBottom(() => {
|
||
if (finished.value || loading.value) return
|
||
current.value += 1
|
||
fetchTodoList()
|
||
})
|
||
|
||
// 查看已办任务
|
||
async function viewProcess(item: any) {
|
||
if (isProcessing.value) return
|
||
|
||
isProcessing.value = true
|
||
uni.showLoading({ title: '加载中...', mask: true })
|
||
|
||
try {
|
||
// 调用第一个接口:查询任务详情 (view=true)
|
||
const taskRes = await taskApi.queryTask(item.taskId, true)
|
||
|
||
// 调用第二个接口:格式化开始节点显示
|
||
const formatData = {
|
||
flowId: item.flowId,
|
||
processInstanceId: item.processInstanceId,
|
||
paramMap: {
|
||
mcmxfktv1b0zg: null,
|
||
roomName: item.roomName,
|
||
applicantName: item.applicantName,
|
||
meetingTime: item.meetingTime
|
||
},
|
||
taskId: item.taskId
|
||
}
|
||
|
||
|
||
const formatRes = await taskApi.formatStartNodeShow(formatData)
|
||
|
||
// 从接口返回的数据中提取动态字段和审批流程
|
||
const taskData = taskRes as any
|
||
const formatData_result = formatRes as any
|
||
|
||
let formItems = []
|
||
let approvalFlow = []
|
||
|
||
if (taskData.code === 0 && taskData.data) {
|
||
// 从 queryTask 接口获取表单字段
|
||
formItems = taskData.data.formItems || []
|
||
}
|
||
|
||
if (formatData_result.code === 0 && formatData_result.data) {
|
||
// 从 formatStartNodeShow 接口获取审批流程
|
||
approvalFlow = formatData_result.data || []
|
||
}
|
||
|
||
// 设置当前任务数据 - 使用实际的任务数据和接口返回的动态数据
|
||
currentTask.value = {
|
||
...item,
|
||
serialNumber: item.serialNumber || item.taskId?.substring(0, 13) || '未知流水号',
|
||
roomName: item.roomName || '未知会议室',
|
||
applicantName: item.applicantName || item.rootUserName || '未知申请人',
|
||
meetingTime: item.meetingTime || item.startTime || '未知时间',
|
||
flowId: item.flowId,
|
||
processInstanceId: item.processInstanceId,
|
||
taskId: item.taskId,
|
||
// 添加动态数据
|
||
formItems: formItems,
|
||
approvalFlow: approvalFlow,
|
||
// 标记为查看模式
|
||
isViewMode: true
|
||
}
|
||
|
||
|
||
// 显示审批弹窗
|
||
showApprovalModal.value = true
|
||
|
||
} catch (error) {
|
||
console.error('加载查看信息失败:', error)
|
||
uni.showToast({
|
||
title: '加载失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
isProcessing.value = false
|
||
}
|
||
}
|
||
|
||
// 开始处理审批
|
||
async function startProcess(item: any) {
|
||
if (isProcessing.value) return
|
||
|
||
isProcessing.value = true
|
||
uni.showLoading({ title: '加载中...', mask: true })
|
||
|
||
try {
|
||
// 调用第一个接口:查询任务详情
|
||
const taskRes = await taskApi.queryTask(item.taskId, false)
|
||
|
||
// 调用第二个接口:格式化开始节点显示
|
||
const formatData = {
|
||
flowId: item.flowId,
|
||
processInstanceId: item.processInstanceId,
|
||
paramMap: {
|
||
mcmxfktv1b0zg: null,
|
||
roomName: item.roomName,
|
||
applicantName: item.applicantName,
|
||
meetingTime: item.meetingTime
|
||
},
|
||
taskId: item.taskId
|
||
}
|
||
|
||
|
||
const formatRes = await taskApi.formatStartNodeShow(formatData)
|
||
|
||
// 从接口返回的数据中提取动态字段和审批流程
|
||
const taskData = taskRes as any
|
||
const formatData_result = formatRes as any
|
||
|
||
let formItems = []
|
||
let approvalFlow = []
|
||
|
||
if (taskData.code === 0 && taskData.data) {
|
||
// 从 queryTask 接口获取表单字段
|
||
formItems = taskData.data.formItems || []
|
||
}
|
||
|
||
if (formatData_result.code === 0 && formatData_result.data) {
|
||
// 从 formatStartNodeShow 接口获取审批流程
|
||
approvalFlow = formatData_result.data || []
|
||
}
|
||
|
||
// 设置当前任务数据 - 使用实际的任务数据和接口返回的动态数据
|
||
currentTask.value = {
|
||
...item,
|
||
serialNumber: item.serialNumber || item.taskId?.substring(0, 13) || '未知流水号',
|
||
roomName: item.roomName || '未知会议室',
|
||
applicantName: item.applicantName || item.rootUserName || '未知申请人',
|
||
meetingTime: item.meetingTime || item.startTime || '未知时间',
|
||
flowId: item.flowId,
|
||
processInstanceId: item.processInstanceId,
|
||
taskId: item.taskId,
|
||
// 添加动态数据
|
||
formItems: formItems,
|
||
approvalFlow: approvalFlow
|
||
}
|
||
|
||
|
||
// 显示审批弹窗
|
||
showApprovalModal.value = true
|
||
|
||
} catch (error) {
|
||
console.error('加载审批信息失败:', error)
|
||
uni.showToast({
|
||
title: '加载失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
isProcessing.value = false
|
||
}
|
||
}
|
||
|
||
// 隐藏审批弹窗
|
||
function hideApprovalModal() {
|
||
showApprovalModal.value = false
|
||
currentTask.value = {}
|
||
}
|
||
|
||
// 处理拒绝
|
||
function handleReject() {
|
||
pendingApproval.value = false
|
||
approvalComment.value = ''
|
||
showCommentModal.value = true
|
||
}
|
||
|
||
// 处理提交(同意)
|
||
function handleSubmit() {
|
||
pendingApproval.value = true
|
||
approvalComment.value = ''
|
||
showCommentModal.value = true
|
||
}
|
||
|
||
// 处理转办
|
||
async function handleTransfer() {
|
||
// 先显示转办表单弹窗
|
||
delegateFormComment.value = ''
|
||
selectedUser.value = null
|
||
showDelegateFormModal.value = true
|
||
}
|
||
|
||
// 提交审批结果
|
||
async function submitApproval(approved: boolean) {
|
||
|
||
if (isProcessing.value) return
|
||
|
||
isProcessing.value = true
|
||
uni.showLoading({
|
||
title: approved ? '提交中...' : '拒绝中...',
|
||
mask: true
|
||
})
|
||
|
||
try {
|
||
// 检查任务数据
|
||
|
||
if (!currentTask.value.taskId) {
|
||
throw new Error('任务ID不存在')
|
||
}
|
||
|
||
// 构建审批参数
|
||
const paramMap = {
|
||
mcmxfktv1b0zg: null,
|
||
roomName: currentTask.value.roomName,
|
||
applicantName: currentTask.value.applicantName,
|
||
meetingTime: currentTask.value.meetingTime,
|
||
mcmxoxqvzau0u_approve_condition: approved
|
||
}
|
||
|
||
|
||
const taskLocalParamMap = {
|
||
approveDesc: approvalComment.value || (approved ? '同意申请' : '拒绝申请')
|
||
}
|
||
|
||
const requestData = {
|
||
taskId: currentTask.value.taskId,
|
||
paramMap: paramMap,
|
||
taskLocalParamMap: taskLocalParamMap
|
||
}
|
||
|
||
|
||
const res = await taskApi.completeTask(requestData)
|
||
|
||
|
||
uni.showToast({
|
||
title: approved ? '提交成功' : '拒绝成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 关闭弹窗并刷新列表
|
||
hideApprovalModal()
|
||
setTimeout(() => {
|
||
fetchTodoList(true)
|
||
}, 1000)
|
||
|
||
} catch (error) {
|
||
console.error('审批失败:', error)
|
||
uni.showToast({
|
||
title: '操作失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
isProcessing.value = false
|
||
}
|
||
}
|
||
|
||
// 审核意见弹窗相关方法
|
||
function hideCommentModal() {
|
||
showCommentModal.value = false
|
||
approvalComment.value = ''
|
||
pendingApproval.value = null
|
||
}
|
||
|
||
function confirmApproval() {
|
||
if (!approvalComment.value.trim()) {
|
||
uni.showToast({
|
||
title: '请输入审核意见',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
|
||
// 先保存 pendingApproval 的值,因为 hideCommentModal 会重置它
|
||
const approvalValue = pendingApproval.value
|
||
|
||
hideCommentModal()
|
||
submitApproval(approvalValue!)
|
||
}
|
||
|
||
// 转办表单弹窗相关方法
|
||
function hideDelegateFormModal() {
|
||
showDelegateFormModal.value = false
|
||
delegateFormComment.value = ''
|
||
selectedUser.value = null
|
||
}
|
||
|
||
async function showUserSelection() {
|
||
try {
|
||
// 获取用户选择列表 - 使用抽取的API
|
||
const res = await taskApi.getDeptOrgUsers(0)
|
||
|
||
const responseData = res as any
|
||
if (responseData.code === 0 && responseData.data) {
|
||
// 处理接口返回的数据结构
|
||
const apiData = responseData.data
|
||
userSelectionData.value = {
|
||
titleDepartments: apiData.titleDepartments || [],
|
||
employees: apiData.employees || [],
|
||
childDepartments: apiData.childDepartments || []
|
||
}
|
||
filteredDepartments.value = apiData.childDepartments || []
|
||
filteredEmployees.value = apiData.employees || []
|
||
searchKeyword.value = ''
|
||
// 初始化面包屑导航
|
||
breadcrumbPath.value = [{ id: 0, name: '全部', type: 'root' }]
|
||
showDelegateModal.value = true
|
||
} else {
|
||
uni.showToast({
|
||
title: '获取用户列表失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取用户列表失败:', error)
|
||
uni.showToast({
|
||
title: '获取用户列表失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
function removeSelectedUser() {
|
||
selectedUser.value = null
|
||
}
|
||
|
||
function confirmDelegateForm() {
|
||
|
||
// 清除之前的错误提示
|
||
userSelectError.value = ''
|
||
commentError.value = ''
|
||
|
||
let hasError = false
|
||
|
||
// 验证表单
|
||
if (!selectedUser.value) {
|
||
userSelectError.value = '请选择转办人员'
|
||
hasError = true
|
||
}
|
||
|
||
if (!delegateFormComment.value || delegateFormComment.value.length < 2) {
|
||
commentError.value = '请输入审核意见(至少2字)'
|
||
hasError = true
|
||
} else if (delegateFormComment.value.length > 100) {
|
||
commentError.value = '审核意见不能超过100字'
|
||
hasError = true
|
||
}
|
||
|
||
if (hasError) {
|
||
return
|
||
}
|
||
|
||
// 设置转办数据并提交
|
||
delegateComment.value = delegateFormComment.value
|
||
// 先提交转办,再关闭弹窗
|
||
submitDelegate()
|
||
}
|
||
|
||
// 转办相关方法
|
||
function hideDelegateModal() {
|
||
showDelegateModal.value = false
|
||
// 不要清空 selectedUser,因为用户可能已经选择了
|
||
searchKeyword.value = ''
|
||
}
|
||
|
||
function hideDelegateConfirmModal() {
|
||
showDelegateConfirmModal.value = false
|
||
delegateComment.value = ''
|
||
}
|
||
|
||
async function selectUser(user: any) {
|
||
// 如果是部门类型,请求下级数据
|
||
if (user.type === 'dept') {
|
||
try {
|
||
uni.showLoading({ title: '加载中...', mask: true })
|
||
|
||
const res = await taskApi.getDeptOrgUsers(user.id)
|
||
|
||
const responseData = res as any
|
||
if (responseData.code === 0 && responseData.data) {
|
||
const apiData = responseData.data
|
||
|
||
// 更新数据,显示下级部门和员工
|
||
userSelectionData.value = {
|
||
titleDepartments: [...(userSelectionData.value.titleDepartments || []), user],
|
||
employees: apiData.employees || [],
|
||
childDepartments: apiData.childDepartments || []
|
||
}
|
||
filteredDepartments.value = apiData.childDepartments || []
|
||
filteredEmployees.value = apiData.employees || []
|
||
searchKeyword.value = ''
|
||
|
||
// 更新面包屑导航
|
||
breadcrumbPath.value.push(user)
|
||
} else {
|
||
uni.showToast({
|
||
title: '获取下级数据失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取下级数据失败:', error)
|
||
uni.showToast({
|
||
title: '获取下级数据失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
} else {
|
||
// 如果是员工,直接选择并关闭弹窗
|
||
selectedUser.value = user
|
||
hideDelegateModal()
|
||
// 返回到转办表单弹窗
|
||
showDelegateFormModal.value = true
|
||
}
|
||
}
|
||
|
||
function confirmDelegateUser() {
|
||
if (!selectedUser.value) {
|
||
uni.showToast({
|
||
title: '请选择转办人员',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
hideDelegateModal()
|
||
// 返回到转办表单弹窗
|
||
showDelegateFormModal.value = true
|
||
}
|
||
|
||
// 面包屑导航点击
|
||
async function navigateToBreadcrumb(index: number) {
|
||
if (index === breadcrumbPath.value.length - 1) {
|
||
// 点击当前层级,不执行任何操作
|
||
return
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({ title: '加载中...', mask: true })
|
||
|
||
// 截取面包屑路径到指定层级
|
||
breadcrumbPath.value = breadcrumbPath.value.slice(0, index + 1)
|
||
|
||
// 获取目标层级的数据
|
||
const targetItem = breadcrumbPath.value[index]
|
||
const res = await taskApi.getDeptOrgUsers(targetItem.id)
|
||
|
||
const responseData = res as any
|
||
if (responseData.code === 0 && responseData.data) {
|
||
const apiData = responseData.data
|
||
|
||
// 更新数据
|
||
filteredDepartments.value = apiData.childDepartments || []
|
||
filteredEmployees.value = apiData.employees || []
|
||
searchKeyword.value = ''
|
||
} else {
|
||
uni.showToast({
|
||
title: '获取数据失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取数据失败:', error)
|
||
uni.showToast({
|
||
title: '获取数据失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
function confirmDelegate() {
|
||
if (!delegateComment.value.trim()) {
|
||
uni.showToast({
|
||
title: '请输入审核意见',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (delegateComment.value.length < 2) {
|
||
uni.showToast({
|
||
title: '审核意见至少2个字符',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
submitDelegate()
|
||
}
|
||
|
||
async function submitDelegate() {
|
||
|
||
if (isProcessing.value) {
|
||
return
|
||
}
|
||
|
||
isProcessing.value = true
|
||
uni.showLoading({ title: '转办中...', mask: true })
|
||
|
||
try {
|
||
// 使用抽取的转办接口
|
||
const res = await taskApi.setAssignee({
|
||
paramMap: {
|
||
mcmxfktv1b0zg: null,
|
||
roomName: currentTask.value.roomName,
|
||
applicantName: currentTask.value.applicantName,
|
||
meetingTime: currentTask.value.meetingTime,
|
||
mcmxoxqvzau0u_approve_condition: true
|
||
},
|
||
taskId: currentTask.value.taskId,
|
||
targetUserIdList: [selectedUser.value.id],
|
||
taskLocalParamMap: {
|
||
approveDesc: delegateComment.value
|
||
}
|
||
})
|
||
|
||
|
||
const responseData = res as any
|
||
if (responseData.code === 0) {
|
||
uni.showToast({
|
||
title: '转办成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 关闭弹窗并刷新列表
|
||
hideDelegateFormModal()
|
||
hideApprovalModal()
|
||
setTimeout(() => {
|
||
fetchTodoList(true)
|
||
}, 1000)
|
||
} else {
|
||
uni.showToast({
|
||
title: responseData.msg || '转办失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('转办失败:', error)
|
||
uni.showToast({
|
||
title: '转办失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
isProcessing.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索过滤
|
||
function filterUsers() {
|
||
const keyword = searchKeyword.value.toLowerCase()
|
||
|
||
if (!keyword) {
|
||
filteredDepartments.value = userSelectionData.value.childDepartments || []
|
||
filteredEmployees.value = userSelectionData.value.employees || []
|
||
return
|
||
}
|
||
|
||
filteredDepartments.value = (userSelectionData.value.childDepartments || []).filter((dept: any) =>
|
||
dept.name.toLowerCase().includes(keyword)
|
||
)
|
||
|
||
filteredEmployees.value = (userSelectionData.value.employees || []).filter((emp: any) =>
|
||
emp.name.toLowerCase().includes(keyword)
|
||
)
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.todo-page {
|
||
background: linear-gradient(180deg, #f5f7fa 60%, #e3eafc 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.todo-tabs {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 0 0 20rpx 20rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
|
||
margin-bottom: 12rpx;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 9;
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 30rpx;
|
||
color: #888;
|
||
padding: 24rpx 0 18rpx 0;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: color 0.2s, border 0.2s;
|
||
border-bottom: 4rpx solid transparent;
|
||
}
|
||
|
||
.tab-item.active {
|
||
color: #1976d2;
|
||
border-bottom: 4rpx solid #1976d2;
|
||
background: linear-gradient(90deg, #e3eafc 0%, #fff 100%);
|
||
}
|
||
|
||
.todo-list {
|
||
padding: 32rpx 24rpx 0 24rpx;
|
||
}
|
||
|
||
.todo-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #bbb;
|
||
font-size: 32rpx;
|
||
padding: 120rpx 0 60rpx 0;
|
||
}
|
||
|
||
.empty-img {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
margin-bottom: 32rpx;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 32rpx;
|
||
color: #bbb;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.todo-card {
|
||
display: flex;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(25, 118, 210, 0.08);
|
||
overflow: hidden;
|
||
position: relative;
|
||
min-height: 120rpx;
|
||
}
|
||
|
||
.card-bar {
|
||
width: 12rpx;
|
||
background: linear-gradient(180deg, #1976d2 0%, #42a5f5 100%);
|
||
border-radius: 0 12rpx 12rpx 0;
|
||
}
|
||
|
||
.todo-card-content {
|
||
flex: 1;
|
||
padding: 16rpx 20rpx 36rpx 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
.todo-card-title {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 12rpx;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.group {
|
||
font-size: 24rpx;
|
||
color: #1976d2;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.process {
|
||
font-size: 26rpx;
|
||
color: #222;
|
||
font-weight: bold;
|
||
margin-top: 0;
|
||
}
|
||
|
||
.todo-card-info {
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
margin-bottom: 2rpx;
|
||
}
|
||
|
||
.label {
|
||
color: #888;
|
||
font-size: 24rpx;
|
||
min-width: 80rpx;
|
||
}
|
||
|
||
.value {
|
||
color: #333;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.todo-card-actions {
|
||
position: absolute;
|
||
right: 20rpx;
|
||
bottom: 16rpx;
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.todo-btn {
|
||
font-size: 24rpx;
|
||
padding: 6rpx 28rpx;
|
||
border-radius: 20rpx;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: background 0.2s, box-shadow 0.2s;
|
||
box-shadow: 0 2rpx 8rpx rgba(25, 118, 210, 0.08);
|
||
}
|
||
|
||
.todo-btn.process {
|
||
background: linear-gradient(90deg, #1976d2 60%, #42a5f5 100%);
|
||
color: #fff;
|
||
}
|
||
|
||
.todo-btn.view {
|
||
background: linear-gradient(90deg, #4caf50 60%, #66bb6a 100%);
|
||
color: #fff;
|
||
}
|
||
|
||
.loading-tip,
|
||
.no-more-tip {
|
||
text-align: center;
|
||
color: #bbb;
|
||
font-size: 28rpx;
|
||
padding: 40rpx 0 0 0;
|
||
}
|
||
|
||
/* 审批弹窗样式 */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
/* 审核意见弹窗 - 更高层级 */
|
||
.modal-overlay.comment-modal {
|
||
z-index: 1100;
|
||
}
|
||
|
||
/* 转办人员选择弹窗 - 最高层级 */
|
||
.modal-overlay.delegate-modal {
|
||
z-index: 1500;
|
||
}
|
||
|
||
/* 转办确认弹窗 - 更高层级 */
|
||
.modal-overlay.delegate-confirm-modal {
|
||
z-index: 1300;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 90%;
|
||
max-width: 600rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
max-height: 80vh;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
background: linear-gradient(90deg, #1976d2 0%, #42a5f5 100%);
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
}
|
||
|
||
.modal-close {
|
||
font-size: 40rpx;
|
||
color: #fff;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 32rpx;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-value {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
padding: 12rpx 0;
|
||
}
|
||
|
||
.form-input {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
padding: 16rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
border: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.form-textarea {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
padding: 16rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
border: 1px solid #e0e0e0;
|
||
min-height: 120rpx;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.form-sequence {
|
||
font-size: 26rpx;
|
||
color: #1976d2;
|
||
padding: 16rpx;
|
||
background: #f0f8ff;
|
||
border-radius: 8rpx;
|
||
border: 1px solid #d0e7ff;
|
||
font-weight: 500;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.approval-flow {
|
||
margin-top: 32rpx;
|
||
padding: 24rpx;
|
||
background: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.flow-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.flow-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.flow-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
margin-right: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.flow-icon.completed {
|
||
background: #4caf50;
|
||
color: #fff;
|
||
}
|
||
|
||
.flow-icon.pending {
|
||
background: #ff9800;
|
||
color: #fff;
|
||
}
|
||
|
||
.flow-line {
|
||
width: 2rpx;
|
||
height: 32rpx;
|
||
background: #e0e0e0;
|
||
margin-left: 23rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.flow-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.flow-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.flow-user {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.flow-users {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
background: #e0e0e0;
|
||
border-radius: 50%;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.modal-btn {
|
||
flex: 1;
|
||
padding: 24rpx;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.reject-btn {
|
||
color: #f44336;
|
||
background: #fff;
|
||
}
|
||
|
||
.submit-btn {
|
||
color: #4caf50;
|
||
background: #fff;
|
||
}
|
||
|
||
.transfer-btn {
|
||
color: #666;
|
||
background: #fff;
|
||
}
|
||
|
||
.close-btn {
|
||
color: #666;
|
||
background: #fff;
|
||
flex: none;
|
||
width: 200rpx;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.btn-icon {
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
/* 审核意见弹窗样式 */
|
||
.comment-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.comment-label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 12rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.comment-textarea {
|
||
width: 100%;
|
||
min-height: 200rpx;
|
||
padding: 16rpx;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
background: #fff;
|
||
resize: none;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.char-count {
|
||
text-align: right;
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
/* 转办人员选择弹窗样式 */
|
||
.delegate-modal {
|
||
max-height: 80vh;
|
||
}
|
||
|
||
.search-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.breadcrumb-section {
|
||
margin-bottom: 24rpx;
|
||
padding: 16rpx 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.breadcrumb-item {
|
||
display: flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.breadcrumb-text {
|
||
font-size: 26rpx;
|
||
color: #1976d2;
|
||
padding: 4rpx 8rpx;
|
||
border-radius: 4rpx;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.breadcrumb-text:active {
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.breadcrumb-separator {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin: 0 8rpx;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 16rpx;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
background: #fff;
|
||
}
|
||
|
||
.user-list {
|
||
max-height: 400rpx;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
padding: 8rpx 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.user-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16rpx 0;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.user-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.user-checkbox {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border: 2rpx solid #e0e0e0;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.user-checkbox.checked {
|
||
background: #1976d2;
|
||
border-color: #1976d2;
|
||
}
|
||
|
||
.check-icon {
|
||
color: #fff;
|
||
font-size: 20rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.user-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.dept-icon {
|
||
background: #e3f2fd;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.employee-icon {
|
||
background: #f3e5f5;
|
||
position: relative;
|
||
}
|
||
|
||
.avatar-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-text {
|
||
font-size: 20rpx;
|
||
color: #1976d2;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.user-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.user-type {
|
||
font-size: 22rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.subordinate-link {
|
||
font-size: 22rpx;
|
||
color: #1976d2;
|
||
padding: 8rpx 16rpx;
|
||
border: 1px solid #1976d2;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
/* 转办确认弹窗样式 */
|
||
.delegate-info {
|
||
margin-bottom: 24rpx;
|
||
padding: 16rpx;
|
||
background: #f8f9fa;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 转办表单弹窗样式 */
|
||
.delegate-form-modal {
|
||
z-index: 1400;
|
||
}
|
||
|
||
.delegate-form-content {
|
||
width: 600rpx;
|
||
max-height: 80vh;
|
||
padding: 0;
|
||
}
|
||
|
||
.delegate-form {
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.form-section {
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.form-section:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.title-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.required {
|
||
color: #f44336;
|
||
font-size: 28rpx;
|
||
margin-left: 4rpx;
|
||
}
|
||
|
||
.user-selector {
|
||
border: 1px solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.user-selector:active {
|
||
border-color: #1976d2;
|
||
}
|
||
|
||
.selected-user-tag {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #ff9800;
|
||
color: #fff;
|
||
padding: 8rpx 12rpx;
|
||
border-radius: 16rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.user-tag-name {
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.user-tag-remove {
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
padding: 2rpx;
|
||
border-radius: 50%;
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.user-tag-remove:active {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.add-user-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border: 1px solid #ddd;
|
||
border-radius: 50%;
|
||
background: #f5f5f5;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.add-user-btn:active {
|
||
background: #e0e0e0;
|
||
border-color: #1976d2;
|
||
}
|
||
|
||
.add-icon {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.comment-input-wrapper {
|
||
position: relative;
|
||
}
|
||
|
||
.comment-textarea {
|
||
width: 100%;
|
||
min-height: 120rpx;
|
||
padding: 16rpx;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
background: #fff;
|
||
resize: none;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.comment-textarea:focus {
|
||
border-color: #1976d2;
|
||
outline: none;
|
||
}
|
||
|
||
.char-count {
|
||
position: absolute;
|
||
bottom: 8rpx;
|
||
right: 16rpx;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.cancel-btn {
|
||
color: #666;
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.confirm-btn {
|
||
color: #fff;
|
||
background: #1976d2;
|
||
}
|
||
|
||
.error-message {
|
||
font-size: 24rpx;
|
||
color: #f44336;
|
||
margin-top: 8rpx;
|
||
padding-left: 4rpx;
|
||
}
|
||
</style>
|