diff --git a/backend/src/main/java/com/designhub/service/SystemConfigService.java b/backend/src/main/java/com/designhub/service/SystemConfigService.java
index 1ae496f..6cfc484 100644
--- a/backend/src/main/java/com/designhub/service/SystemConfigService.java
+++ b/backend/src/main/java/com/designhub/service/SystemConfigService.java
@@ -262,6 +262,9 @@ public class SystemConfigService {
createConfigIfNotExists("task.release_type.default", "true", "task", "显示默认模式按钮", "是否显示「默认模式」发布类型按钮", "boolean", 1);
createConfigIfNotExists("task.release_type.recruitment", "true", "task", "显示招募任务按钮", "是否显示「招募任务」发布类型按钮", "boolean", 1);
createConfigIfNotExists("task.release_type.assigned", "true", "task", "显示指定任务按钮", "是否显示「指定任务」发布类型按钮", "boolean", 1);
+
+ // 盲盒模式配置(可接任务页面任务卡片显示方式)
+ createConfigIfNotExists("task.blind_box_mode", "false", "task", "盲盒模式", "开启后普通部门成员在可接任务页面看到的任务卡片变成盲盒(只显示排队序号),部门负责人不受影响", "boolean", 1);
}
/**
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
index 50df289..9ed25d3 100644
--- a/frontend/src/views/Settings.vue
+++ b/frontend/src/views/Settings.vue
@@ -1710,6 +1710,28 @@
任务数据管理
+
+
+
+
+
+
+ 盲盒模式
+
+
+ 开启后,普通部门成员在可接任务页面看到的任务卡片变成盲盒样式(只显示排队序号),隐藏所有任务信息。
+
+ 部门负责人不受此设置影响,始终可以看到完整任务信息。
+
+
+
+
+
+
{
+ try {
+ const response = await getAllSystemConfigs()
+ if (response.code === 200 && response.data) {
+ const config = response.data.find((c: any) => c.configKey === 'task.blind_box_mode')
+ if (config) {
+ blindBoxMode.value = config.configValue === 'true'
+ }
+ }
+ } catch (error) {
+ console.error('加载盲盒模式配置失败:', error)
+ }
+}
+
+// 保存盲盒模式配置
+const handleBlindBoxModeChange = async (checked: boolean) => {
+ savingBlindBoxMode.value = true
+ try {
+ await batchUpdateSystemConfigs({
+ 'task.blind_box_mode': String(checked)
+ })
+ message.success(checked ? '盲盒模式已开启' : '盲盒模式已关闭')
+ } catch (error: any) {
+ message.error('保存失败: ' + (error.message || '未知错误'))
+ // 回滚状态
+ blindBoxMode.value = !checked
+ } finally {
+ savingBlindBoxMode.value = false
+ }
+}
+
// 删除任务
const handleDeleteTask = async () => {
if (!taskDeleteForm.taskNo) {
@@ -5522,6 +5581,8 @@ onMounted(() => {
loadDesignerTargetList()
// 加载进度检查配置
loadProgressCheckConfig()
+ // 加载盲盒模式配置
+ loadBlindBoxMode()
})
// S3配置相关方法
@@ -7190,6 +7251,54 @@ watch(activeTab, (value) => {
}
}
+// 盲盒模式设置卡片样式
+.blind-box-card {
+ max-width: 600px;
+ background: rgba(0, 0, 0, 0.2) !important;
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
+ border-radius: 8px;
+
+ :deep(.ant-card-head) {
+ background: transparent !important;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
+
+ .ant-card-head-title {
+ color: rgba(255, 255, 255, 0.85);
+ font-weight: 500;
+ }
+ }
+
+ :deep(.ant-card-body) {
+ background: transparent !important;
+ }
+
+ .blind-box-setting {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+
+ .setting-info {
+ flex: 1;
+ margin-right: 24px;
+
+ .setting-title {
+ display: flex;
+ align-items: center;
+ font-size: 16px;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.85);
+ margin-bottom: 8px;
+ }
+
+ .setting-desc {
+ font-size: 13px;
+ color: rgba(255, 255, 255, 0.65);
+ line-height: 1.6;
+ }
+ }
+ }
+}
+
// 任务删除卡片样式
.task-delete-card {
max-width: 600px;
diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue
index 76c7d33..94d4a63 100644
--- a/frontend/src/views/TaskHall.vue
+++ b/frontend/src/views/TaskHall.vue
@@ -103,11 +103,34 @@
+
+
+
+
+
+
+
+ 排队序号
+ #{{ index + 1 }}
+
+
+
+ 任务信息已隐藏
+
+
+
+ 抢单
+
+
+
+
+
+
+
@@ -2450,7 +2474,7 @@ import * as userApi from '@/api/user'
import * as configApi from '@/api/config'
import * as authApi from '@/api/auth'
import * as departmentApi from '@/api/department'
-import { getWorkHoursConfig, getSystemConfig } from '@/api/system'
+import { getWorkHoursConfig, getSystemConfig, getAllSystemConfigs } from '@/api/system'
import type { Task, TaskApprovalDTO, TaskProduct, ExportTaskHallParams } from '@/api/task'
import { exportTaskHall } from '@/api/task'
import type { DesignerWithWorkload } from '@/api/user'
@@ -2630,6 +2654,35 @@ const total = ref(0)
const detailDrawerVisible = ref(false)
const selectedTask = ref(null)
+// 盲盒模式相关
+const blindBoxMode = ref(false)
+const isDeptLeader = computed(() => {
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
+ // 部门负责人或管理员不受盲盒模式影响
+ return userInfo.isDepartmentLeader === 1 || userInfo.isAdmin === 1 || userInfo.isAdmin === true
+})
+
+// 是否显示盲盒(开启盲盒模式且不是部门负责人/管理员)
+const showBlindBox = computed(() => {
+ return blindBoxMode.value && !isDeptLeader.value
+})
+
+// 加载盲盒模式配置
+const loadBlindBoxMode = async () => {
+ try {
+ const response = await getAllSystemConfigs()
+ if (response.code === 200 && response.data) {
+ const config = response.data.find((c: any) => c.configKey === 'task.blind_box_mode')
+ if (config) {
+ blindBoxMode.value = config.configValue === 'true'
+ console.log('盲盒模式:', blindBoxMode.value, '是否显示盲盒:', showBlindBox.value)
+ }
+ }
+ } catch (error) {
+ console.error('加载盲盒模式配置失败:', error)
+ }
+}
+
// 评分弹窗
const ratingModalVisible = ref(false)
const ratingTask = ref(null)
@@ -5926,6 +5979,50 @@ const handleGrabFromDetail = async () => {
detailDrawerVisible.value = false
}
+// 盲盒模式抢单
+const handleGrabTask = async (task: TaskOrSubTask) => {
+ const isSubTask = task.isSubTask
+ const taskType = isSubTask ? '子任务' : '任务'
+
+ // 先检查权限
+ const { canGrab, reason } = canGrabTask(task)
+ if (!canGrab) {
+ message.warning(reason)
+ return
+ }
+
+ Modal.confirm({
+ title: '确认抢单',
+ content: h('div', {}, [
+ h('p', { style: { marginBottom: '8px' } }, '您正在盲盒模式下抢单'),
+ h('p', { style: { color: '#fa8c16', fontSize: '14px' } }, '任务信息将在抢单成功后显示'),
+ h('p', { style: { color: '#888', fontSize: '12px' } }, '确定要抢单这个任务吗?')
+ ]),
+ okText: '确认抢单',
+ cancelText: '取消',
+ okButtonProps: { style: { background: '#fa8c16', borderColor: '#fa8c16' } },
+ onOk: async () => {
+ try {
+ let response
+ if (isSubTask) {
+ response = await taskApi.grabTaskItem(task.itemId || task.id!)
+ } else {
+ response = await taskApi.grabTask(task.id!)
+ }
+
+ if (response.code === 200) {
+ message.success(`${taskType}抢单成功!任务已添加到您的进行中任务`)
+ loadTasks(true)
+ loadAllTabCounts()
+ }
+ } catch (error: any) {
+ console.error('抢单失败:', error)
+ message.error(error.message || '抢单失败')
+ }
+ }
+ })
+}
+
// ==================== 商品ID管理 ====================
// 加载任务商品ID列表
@@ -6404,6 +6501,7 @@ onMounted(async () => {
loadDashboardStats()
loadAllTabCounts() // 加载所有Tab的数量统计
loadTasks(true) // 初始加载时强制获取数据
+ loadBlindBoxMode() // 加载盲盒模式配置
// 监听配置更新事件
window.addEventListener('configUpdated', handleConfigUpdate as EventListener)
})
@@ -6778,6 +6876,85 @@ onUnmounted(() => {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
+ // 盲盒模式卡片样式
+ &.blind-box-card {
+ background: linear-gradient(135deg, rgba(250, 140, 22, 0.1) 0%, rgba(250, 173, 20, 0.05) 100%) !important;
+ border: 2px dashed rgba(250, 140, 22, 0.4) !important;
+ min-height: 200px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ border-color: rgba(250, 140, 22, 0.8) !important;
+ background: linear-gradient(135deg, rgba(250, 140, 22, 0.15) 0%, rgba(250, 173, 20, 0.1) 100%) !important;
+ }
+
+ .blind-box-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ padding: 24px;
+
+ .blind-box-icon {
+ margin-bottom: 16px;
+ animation: pulse 2s ease-in-out infinite;
+ }
+
+ .blind-box-queue {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-bottom: 12px;
+
+ .queue-label {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.5);
+ margin-bottom: 4px;
+ }
+
+ .queue-number {
+ font-size: 32px;
+ font-weight: bold;
+ color: #fa8c16;
+ text-shadow: 0 2px 8px rgba(250, 140, 22, 0.3);
+ }
+ }
+
+ .blind-box-hint {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.45);
+ margin-bottom: 16px;
+ }
+
+ .blind-box-grab-btn {
+ background: linear-gradient(135deg, #fa8c16 0%, #faad14 100%);
+ border: none;
+ padding: 8px 24px;
+ font-weight: 500;
+
+ &:hover {
+ background: linear-gradient(135deg, #d87a00 0%, #d89614 100%);
+ }
+ }
+ }
+ }
+
+ @keyframes pulse {
+ 0%, 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 50% {
+ transform: scale(1.1);
+ opacity: 0.8;
+ }
+ }
+
// 带预览图的卡片布局
.card-with-preview {
display: flex;