feat: 添加可接任务页面盲盒模式功能
- 后端:在SystemConfigService添加task.blind_box_mode配置项 - 前端:在Settings.vue任务数据管理中添加盲盒模式开关 - 前端:在TaskHall.vue实现盲盒卡片显示 - 开启后普通部门成员看到的任务卡片变成盲盒样式 - 盲盒显示排队序号,隐藏所有任务信息 - 部门负责人和管理员不受盲盒模式影响
This commit is contained in:
parent
47f028513f
commit
c592ba2234
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1710,6 +1710,28 @@
|
||||
<h2 class="section-title">任务数据管理</h2>
|
||||
</div>
|
||||
|
||||
<!-- 盲盒模式设置 -->
|
||||
<a-card title="可接任务显示设置" class="blind-box-card" style="margin-bottom: 24px;">
|
||||
<div class="blind-box-setting">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">
|
||||
<Icon name="gift" size="sm" color="#fa8c16" style="margin-right: 8px;" />
|
||||
盲盒模式
|
||||
</div>
|
||||
<div class="setting-desc">
|
||||
开启后,普通部门成员在可接任务页面看到的任务卡片变成盲盒样式(只显示排队序号),隐藏所有任务信息。
|
||||
<br />
|
||||
<span style="color: #52c41a;">部门负责人不受此设置影响,始终可以看到完整任务信息。</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-switch
|
||||
v-model:checked="blindBoxMode"
|
||||
:loading="savingBlindBoxMode"
|
||||
@change="handleBlindBoxModeChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-alert
|
||||
message="危险操作"
|
||||
description="删除任务将永久删除该任务及其所有子任务数据,此操作不可恢复,请谨慎操作!"
|
||||
@ -2772,6 +2794,7 @@ import {
|
||||
import {
|
||||
getSystemConfigsByGroup,
|
||||
getSystemConfig,
|
||||
getAllSystemConfigs,
|
||||
batchUpdateSystemConfigs,
|
||||
createSystemConfig,
|
||||
updateSystemConfig,
|
||||
@ -2968,6 +2991,42 @@ const taskDeleteForm = reactive({
|
||||
})
|
||||
const deletingTask = ref(false)
|
||||
|
||||
// 盲盒模式相关
|
||||
const blindBoxMode = ref(false)
|
||||
const savingBlindBoxMode = ref(false)
|
||||
|
||||
// 加载盲盒模式配置
|
||||
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'
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
|
||||
@ -103,11 +103,34 @@
|
||||
|
||||
<div v-else class="task-grid">
|
||||
<div
|
||||
v-for="task in taskList"
|
||||
v-for="(task, index) in taskList"
|
||||
:key="task.id"
|
||||
class="task-card chart-card"
|
||||
:class="['task-card', 'chart-card', { 'blind-box-card': showBlindBox }]"
|
||||
@click="handleTaskClick(task)"
|
||||
>
|
||||
<!-- 盲盒模式显示 -->
|
||||
<template v-if="showBlindBox">
|
||||
<div class="blind-box-content">
|
||||
<div class="blind-box-icon">
|
||||
<Icon name="gift" size="xl" color="#fa8c16" />
|
||||
</div>
|
||||
<div class="blind-box-queue">
|
||||
<span class="queue-label">排队序号</span>
|
||||
<span class="queue-number">#{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="blind-box-hint">
|
||||
<Icon name="lock" size="sm" color="#666" style="margin-right: 4px;" />
|
||||
任务信息已隐藏
|
||||
</div>
|
||||
<a-button type="primary" size="small" class="blind-box-grab-btn" @click.stop="handleGrabTask(task)">
|
||||
<Icon name="zap" size="sm" style="margin-right: 4px;" />
|
||||
抢单
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 正常模式显示 -->
|
||||
<template v-else>
|
||||
<!-- 任务标题 -->
|
||||
<div class="task-header">
|
||||
<h3 class="task-title">{{ task.title }}</h3>
|
||||
@ -310,6 +333,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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<TaskOrSubTask | null>(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<TaskOrSubTask | null>(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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user