diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index b57b4d2..0730d99 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -1801,6 +1801,128 @@ + + + + + + + + + + + + { } } +// 审批弹窗状态 +const approveModalVisible = ref(false) +const approveTaskData = ref(null) +const approveAssignType = ref<'auto' | 'manual'>('auto') +const approveTaskTypes = ref>([]) +const approveDesignerId = ref(undefined) +const designersList = ref([]) +const newTaskTypeId = ref(undefined) + +// 格式化相对时间 +const formatDeadlineRelative = (deadline: string) => { + if (!deadline) return '' + try { + const date = new Date(deadline) + const now = new Date() + const diff = date.getTime() - now.getTime() + const days = Math.floor(diff / (1000 * 60 * 60 * 24)) + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) + + if (diff < 0) return '已逾期' + if (days === 0) return `${hours}小时后` + return `${days}天${hours}小时后` + } catch { + return '' + } +} + // 显示指定设计师弹窗(接单部门审核时) const showAssignDesignerModal = async (task: Task) => { try { @@ -2556,338 +2705,83 @@ const showAssignDesignerModal = async (task: Task) => { message.warning('当前部门没有可用的设计师') return } - - let assignType = 'auto' // 'auto' 或 'manual' - let selectedDesignerId: number | null = null + + designersList.value = designers + approveTaskData.value = task + approveAssignType.value = 'auto' + approveDesignerId.value = undefined + newTaskTypeId.value = undefined // 解析任务类型 const taskTypeIdsStr = task.taskTypeIds || task.taskTypeId?.toString() || '' const taskTypeIdArray = taskTypeIdsStr.split(',').filter(id => id.trim()) - // 初始化任务类型列表(带数量) - let editableTaskTypes = taskTypeIdArray.map(id => ({ + // 初始化任务类型列表 + approveTaskTypes.value = taskTypeIdArray.map(id => ({ typeId: parseInt(id.trim()), quantity: task.quantity || 1 })) - - let newTaskTypeId: number | null = null - - const modalContent = h('div', { style: 'padding: 10px 0;' }, [ - // 任务类型编辑区域 - h('div', { - style: { - marginBottom: '24px', - padding: '16px', - background: '#fafafa', - borderRadius: '8px', - border: '1px solid #e8e8e8' - } - }, [ - h('div', { style: 'marginBottom: 16px; display: flex; alignItems: center;' }, [ - h('span', { - style: { - fontSize: '14px', - fontWeight: 600, - color: '#1890ff', - marginRight: '8px' - } - }, '📝'), - h('span', { - style: { - fontSize: '14px', - fontWeight: 600, - color: '#262626' - } - }, '编辑任务类型') - ]), - h(Space, { - direction: 'vertical', - size: 8, - style: { width: '100%', marginBottom: '12px' } - }, [ - ...editableTaskTypes.map((item, index) => - h('div', { - key: index, - style: { - display: 'flex', - alignItems: 'center', - padding: '12px', - background: 'white', - borderRadius: '6px', - border: '1px solid #e8e8e8', - transition: 'all 0.3s' - } - }, [ - h(Tag, { - color: 'blue', - style: { - marginRight: '12px', - fontSize: '13px', - padding: '4px 12px' - } - }, getTaskTypeName(item.typeId)), - h('span', { - style: { - flex: 1, - fontSize: '13px', - color: '#8c8c8c' - } - }), - h('span', { - style: { - marginRight: '8px', - fontSize: '13px', - color: '#595959' - } - }, '数量:'), - h(InputNumber, { - value: item.quantity, - min: 1, - size: 'small', - style: { width: '80px', marginRight: '8px' }, - onChange: (val: number) => { - item.quantity = val || 1 - } - }), - h(Button, { - type: 'text', - danger: true, - size: 'small', - onClick: () => { - editableTaskTypes.splice(index, 1) - } - }, '删除') - ]) - ) - ]), - h(Space, { size: 8, style: { width: '100%' } }, [ - h(Select, { - value: newTaskTypeId, - placeholder: '选择要添加的任务类型', - allowClear: true, - style: { flex: 1, minWidth: '200px' }, - onChange: (val: number) => { - newTaskTypeId = val - }, - options: demandTypes.value.map((type: any) => ({ - value: type.id, - label: type.name - })) - }), - h(Button, { - type: 'primary', - size: 'middle', - style: { background: '#52c41a', borderColor: '#52c41a' }, - onClick: () => { - if (newTaskTypeId && !editableTaskTypes.find(t => t.typeId === newTaskTypeId)) { - editableTaskTypes.push({ typeId: newTaskTypeId, quantity: 1 }) - newTaskTypeId = null - } else if (newTaskTypeId) { - message.warning('该任务类型已添加') - } else { - message.warning('请先选择任务类型') - } - } - }, '+ 添加类型') - ]) - ]), - - // 选择接单方式 - h('div', { - style: { - marginBottom: '24px' - } - }, [ - h('div', { - style: { - marginBottom: '12px', - fontSize: '14px', - fontWeight: 500, - color: '#262626' - } - }, '选择任务接单方式'), - h(RadioGroup, { - value: assignType, - onChange: (e: any) => { - assignType = e.target.value - }, - style: { width: '100%' } - }, [ - h(Radio, { - value: 'auto', - style: { - display: 'block', - marginBottom: '12px', - padding: '10px 12px', - border: '1px solid #d9d9d9', - borderRadius: '6px', - background: assignType === 'auto' ? '#e6f7ff' : 'white', - transition: 'all 0.3s' - } - }, '🏢 发布到任务大厅(设计师自由抢单)'), - h(Radio, { - value: 'manual', - style: { - display: 'block', - padding: '10px 12px', - border: '1px solid #d9d9d9', - borderRadius: '6px', - background: assignType === 'manual' ? '#e6f7ff' : 'white', - transition: 'all 0.3s' - } - }, '👤 指定设计师接单') - ]) - ]), - - // 设计师列表(仅在指定接单时显示) - h('div', { - style: { - display: assignType === 'manual' ? 'block' : 'none' - } - }, [ - h('div', { - style: { - marginBottom: '12px', - fontSize: '14px', - fontWeight: 500, - color: '#262626' - } - }, '选择设计师'), - h(Select, { - value: selectedDesignerId, - placeholder: '请选择要指派的设计师', - style: { width: '100%' }, - size: 'large', - onChange: (val: number) => { - selectedDesignerId = val - }, - options: designers.map(designer => { - const formatDeadline = (deadline: string) => { - if (!deadline) return '' - try { - const date = new Date(deadline) - const now = new Date() - const diff = date.getTime() - now.getTime() - const days = Math.floor(diff / (1000 * 60 * 60 * 24)) - const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) - - if (diff < 0) return '已逾期' - if (days === 0) return `${hours}小时后` - return `${days}天${hours}小时后` - } catch { - return '' - } - } - - const workloadColor = designer.inProgressCount > 5 ? '#ff4d4f' : - designer.inProgressCount > 3 ? '#faad14' : '#52c41a' - const workloadText = designer.inProgressCount === 0 ? '空闲' : - `进行中: ${designer.inProgressCount}个` - const deadlineText = designer.earliestDeadline ? - ` | 最早完成: ${formatDeadline(designer.earliestDeadline)}` : '' - - return { - value: designer.id, - label: h('div', { - style: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: '4px 0' - } - }, [ - h('span', { - style: { - fontSize: '14px', - fontWeight: 500, - flex: 1 - } - }, designer.realName), - h('span', { - style: { - fontSize: '12px', - color: workloadColor, - marginLeft: '12px' - } - }, `${workloadText}${deadlineText}`) - ]) - } - }) - }) - ]) - ]) - - Modal.confirm({ - title: '✅ 接单部门审核通过', - content: modalContent, - width: 650, - okText: '确认通过', - cancelText: '取消', - okButtonProps: { - size: 'large', - style: { minWidth: '100px' } - }, - cancelButtonProps: { - size: 'large', - style: { minWidth: '100px' } - }, - onOk: async () => { - try { - approvalLoading.value = true - - // 检查是否需要指定设计师 - if (assignType === 'manual' && !selectedDesignerId) { - message.error('请选择要指定的设计师') - return Promise.reject() - } - - // 检查任务类型是否为空 - if (editableTaskTypes.length === 0) { - message.error('至少需要一个任务类型') - return Promise.reject() - } - - // 准备任务类型更新数据 - const approvalData: TaskApprovalDTO = { - comment: assignType === 'manual' ? - `指定设计师接单` : '审核通过,发布到任务大厅', - assignedReceiverId: assignType === 'manual' && selectedDesignerId ? selectedDesignerId : undefined, - taskUpdates: { - taskTypes: editableTaskTypes.map(t => ({ - taskTypeId: t.typeId, - quantity: t.quantity - })) - } - } - - await taskApi.approveReceivingLevel(task.id!, approvalData) - - if (assignType === 'manual') { - const designer = designers.find(d => d.id === selectedDesignerId) - message.success(`已指定设计师 ${designer?.realName} 接单`) - } else { - message.success('审核通过,任务已发布到大厅') - } - - // 刷新列表和数量 - loadPendingApprovalTasks() - loadAllTabCounts() - loadDashboardStats() - } catch (error: any) { - console.error('审核失败:', error) - message.error(error.msg || error.message || '审核失败') - return Promise.reject(error) - } finally { - approvalLoading.value = false - } - } - }) + + approveModalVisible.value = true } catch (error: any) { console.error('获取设计师列表失败:', error) message.error('获取设计师列表失败') } } +// 确认审核通过 +const handleApproveConfirm = async () => { + if (!approveTaskData.value) return + + try { + approvalLoading.value = true + + // 检查是否需要指定设计师 + if (approveAssignType.value === 'manual' && !approveDesignerId.value) { + message.error('请选择要指定的设计师') + return + } + + // 检查任务类型是否为空 + if (approveTaskTypes.value.length === 0) { + message.error('至少需要一个任务类型') + return + } + + const approvalData: TaskApprovalDTO = { + comment: approveAssignType.value === 'manual' ? + `指定设计师接单` : '审核通过,发布到任务大厅', + assignedReceiverId: approveAssignType.value === 'manual' ? approveDesignerId.value : undefined, + taskUpdates: { + taskTypes: approveTaskTypes.value.map(t => ({ + taskTypeId: t.typeId, + quantity: t.quantity + })) + } + } + + await taskApi.approveReceivingLevel(approveTaskData.value.id!, approvalData) + + if (approveAssignType.value === 'manual') { + const designer = designersList.value.find(d => d.id === approveDesignerId.value) + message.success(`已指定设计师 ${designer?.realName} 接单`) + } else { + message.success('审核通过,任务已发布到大厅') + } + + approveModalVisible.value = false + // 刷新列表和数量 + loadPendingApprovalTasks() + loadAllTabCounts() + loadDashboardStats() + } catch (error: any) { + console.error('审核失败:', error) + message.error(error.msg || error.message || '审核失败') + } finally { + approvalLoading.value = false + } +} + // 两级审核:驳回任务 const handleRejectTwoLevelTask = async (task: Task) => { let rejectReason = '' @@ -4602,5 +4496,119 @@ onUnmounted(() => { } } } + +/* 审批弹窗暗黑主题适配 */ +:deep(.approve-modal) .ant-modal-content, +:deep(.approve-modal) .ant-modal-header { + background-color: #1f1f1f !important; + color: #fff !important; + border-bottom: 1px solid #303030; +} + +:deep(.approve-modal) .ant-modal-title { + color: #fff !important; +} + +:deep(.approve-modal) .ant-modal-close { + color: rgba(255, 255, 255, 0.45) !important; + + &:hover { + color: #fff !important; + } +} + +:deep(.approve-modal) .ant-radio-wrapper { + color: #fff !important; + transition: all 0.3s; + + &:hover .ant-radio-inner { + border-color: #31f08b !important; + } +} + +:deep(.approve-modal) .ant-radio-checked .ant-radio-inner { + border-color: #31f08b !important; + background-color: #31f08b !important; +} + +:deep(.approve-modal) .ant-radio-checked .ant-radio-inner::after { + background-color: #fff !important; +} + +/* 选中状态的 Radio 卡片边框颜色 */ +:deep(.approve-modal) .assign-option.ant-radio-wrapper-checked { + border-color: #31f08b !important; + background-color: rgba(49, 240, 139, 0.1) !important; +} + +/* 下拉框暗黑适配 */ +:deep(.approve-modal) .ant-select-selector { + background-color: #141414 !important; + border-color: #303030 !important; + color: #fff !important; +} + +:deep(.approve-modal) .ant-select:not(.ant-select-disabled):hover .ant-select-selector, +:deep(.approve-modal) .ant-select-focused .ant-select-selector { + border-color: #31f08b !important; + box-shadow: 0 0 0 2px rgba(49, 240, 139, 0.2) !important; +} + +:deep(.approve-modal) .ant-select-arrow { + color: rgba(255, 255, 255, 0.45) !important; +} + +:deep(.approve-modal) .ant-select-item-option-selected { + background-color: rgba(49, 240, 139, 0.1) !important; + color: #fff; + font-weight: 600; +} + +:deep(.approve-modal) .ant-select-item-option-active { + background-color: rgba(255, 255, 255, 0.08) !important; +} + +/* 输入框适配 */ +:deep(.approve-modal) .ant-input-number { + background-color: #141414; + border-color: #303030; + color: #fff; +} + +:deep(.approve-modal) .ant-input-number:hover, +:deep(.approve-modal) .ant-input-number-focused { + border-color: #31f08b; +} + +:deep(.approve-modal) .ant-input-number-handler-wrap { + background-color: #262626; + border-left-color: #303030; +} + +:deep(.approve-modal) .ant-input-number-handler { + border-top-color: #303030; + + &:hover .ant-input-number-handler-up-inner, + &:hover .ant-input-number-handler-down-inner { + color: #31f08b; + } +} + +/* 按钮适配 */ +:deep(.approve-modal) .ant-btn-primary { + background-color: #31f08b; + border-color: #31f08b; + color: #000; + font-weight: 500; + + &:hover { + background-color: #5cf2a3; + border-color: #5cf2a3; + } +} + +:deep(.approve-modal) .ant-btn-text.ant-btn-danger:hover { + background-color: rgba(255, 77, 79, 0.1); +}