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 @@
+
+
+
+
+
+
+
+
+
{{ getTaskTypeName(item.typeId) }}
+
+
+
+
+
+
+
+
+ {{ type.name }}
+
+
+
{
+ if (newTaskTypeId && !approveTaskTypes.find(t => t.typeId === newTaskTypeId)) {
+ approveTaskTypes.push({ typeId: newTaskTypeId, quantity: 1 })
+ newTaskTypeId = undefined
+ } else if (newTaskTypeId) {
+ message.warning('该任务类型已添加')
+ }
+ }">
+ 添加
+
+
+
+
+
+
+
+
+
+
+
+ 发布到任务大厅
+ 所有设计师均可抢单
+
+
+
+
+ 指定设计师接单
+ 直接分配给特定设计师
+
+
+
+
+
+
+
+
+
+
+
+
{{ designer.realName }}
+
+
+ 进行中: {{ designer.inProgressCount }}
+
+
+ 最早完成: {{ formatDeadlineRelative(designer.earliestDeadline) }}
+
+
+
+
+
+
+
+
{
}
}
+// 审批弹窗状态
+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);
+}