feat(worker-hall): 抢单大厅UI视觉与布局全面优化升级
1. 桌面端顶部整合:将标题、实时派单呼吸标签、居中搜索栏与操作按钮整合在同一行,消除右侧大面积空白。 2. 4栏KPI仪表盘:重构大厅顶部数据指标为账户资金、接单配额进度条、在办工单状态(支持直达工单)、实时资源池4张高质感卡片,消除原横条留白与空洞感。 3. 游戏分类卡片视觉升级:无图标时根据分类自动生成多色高质感渐变背景与白色微拟物首字,优化选中与悬浮阴影态。 4. 工单卡片与保证金排版升级:优化卡片头部标签、订单号排版,金额与保证金突出展示(0保证金呈现绿色'免保证金'微标),强化立即抢单按钮渐变。
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
ArrowRightOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
FireOutlined,
|
||||
PushpinOutlined,
|
||||
QuestionCircleOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
ThunderboltOutlined,
|
||||
TrophyOutlined,
|
||||
WalletOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
@@ -28,6 +33,7 @@ import {
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import {
|
||||
fetchWorkerHallOrders,
|
||||
@@ -159,6 +165,9 @@ export default function WorkerHallPage() {
|
||||
const maxActiveOrders = Number(worker?.level?.permissions.maxActiveOrders || 0)
|
||||
const remainingSlots = maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
|
||||
const shouldWarn = (remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
|
||||
const totalHallOrders = Number(ordersQuery.data?.pages[0]?.data.pagination.total || orders.length)
|
||||
const quotaPercent =
|
||||
maxActiveOrders > 0 ? Math.min(100, Math.round((activeCount / maxActiveOrders) * 100)) : 0
|
||||
|
||||
useEffect(() => {
|
||||
const target = loadMoreRef.current
|
||||
@@ -363,11 +372,18 @@ export default function WorkerHallPage() {
|
||||
className={`worker-hall-game-card ${primaryCategoryId === 0 ? 'active' : ''}`}
|
||||
onClick={() => handleSelectPrimaryCategory(0)}
|
||||
>
|
||||
<div className="worker-hall-game-icon-box">
|
||||
<div
|
||||
className="worker-hall-game-icon-box"
|
||||
style={
|
||||
primaryCategoryId === 0
|
||||
? { background: 'linear-gradient(135deg, #ff6a00 0%, #ff8e3c 100%)' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<AppstoreOutlined
|
||||
style={{
|
||||
fontSize: 20,
|
||||
color: primaryCategoryId === 0 ? 'var(--theme-primary)' : '#64748b',
|
||||
color: primaryCategoryId === 0 ? '#ffffff' : '#64748b',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -380,7 +396,14 @@ export default function WorkerHallPage() {
|
||||
className={`worker-hall-game-card ${primaryCategoryId === category.categoryId ? 'active' : ''}`}
|
||||
onClick={() => handleSelectPrimaryCategory(category.categoryId)}
|
||||
>
|
||||
<div className="worker-hall-game-icon-box">
|
||||
<div
|
||||
className="worker-hall-game-icon-box"
|
||||
style={
|
||||
!category.iconUrl
|
||||
? { background: getCategoryGradient(category.name, category.categoryId) }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{category.iconUrl ? (
|
||||
<img
|
||||
src={category.iconUrl}
|
||||
@@ -595,54 +618,25 @@ export default function WorkerHallPage() {
|
||||
</div>
|
||||
) : (
|
||||
<section className="page-stack">
|
||||
<div className="worker-page-head">
|
||||
<div className="worker-page-head-title-row">
|
||||
<Typography.Title level={3}>抢单大厅</Typography.Title>
|
||||
<div className="worker-hall-page-actions">
|
||||
<div className="worker-hall-head-controls">
|
||||
<Tooltip title="开启后点击抢单会先弹窗确认,防止误点">
|
||||
<span className="worker-hall-grab-confirm-toggle">
|
||||
抢单确认
|
||||
<Switch
|
||||
size="small"
|
||||
checked={grabConfirmEnabled}
|
||||
onChange={toggleGrabConfirm}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={
|
||||
ordersQuery.isFetching ||
|
||||
profileQuery.isFetching ||
|
||||
hallSummaryQuery.isFetching
|
||||
}
|
||||
onClick={() => refreshAll()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
<div className="worker-hall-leaderboard-trigger">
|
||||
<Tooltip title="接单排行榜">
|
||||
<Button
|
||||
aria-label="接单排行榜"
|
||||
icon={<TrophyOutlined />}
|
||||
onClick={() => setLeaderboardOpen(true)}
|
||||
>
|
||||
接单排行榜
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/* 桌面端整合头部:标题 + 实时派单标签 + 居中全局搜索 + 快捷操作 */}
|
||||
<div className="worker-hall-desktop-header">
|
||||
<div className="worker-hall-header-left">
|
||||
<Typography.Title level={3} className="worker-hall-heading">
|
||||
抢单大厅
|
||||
</Typography.Title>
|
||||
<span className="worker-hall-live-badge">
|
||||
<span className="worker-hall-pulse-dot" />
|
||||
实时派单
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-search-bar">
|
||||
<Space.Compact className="worker-hall-search">
|
||||
<div className="worker-hall-header-center">
|
||||
<Space.Compact className="worker-hall-desktop-search">
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索商品名称 / 订单号 / 游戏ID / 游戏昵称"
|
||||
prefix={<SearchOutlined />}
|
||||
prefix={<SearchOutlined style={{ color: '#94a3b8' }} />}
|
||||
onChange={(event) => {
|
||||
const nextKeyword = event.target.value
|
||||
setKeywordInput(nextKeyword)
|
||||
@@ -662,33 +656,163 @@ export default function WorkerHallPage() {
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-header-right">
|
||||
<Tooltip title="开启后点击抢单会先弹窗确认,防止误触">
|
||||
<label className="worker-hall-header-switch-wrap">
|
||||
<span>抢单确认</span>
|
||||
<Switch size="small" checked={grabConfirmEnabled} onChange={toggleGrabConfirm} />
|
||||
</label>
|
||||
</Tooltip>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={
|
||||
ordersQuery.isFetching || profileQuery.isFetching || hallSummaryQuery.isFetching
|
||||
}
|
||||
onClick={() => refreshAll()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button icon={<TrophyOutlined />} onClick={() => setLeaderboardOpen(true)}>
|
||||
接单排行榜
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 桌面端 4 栏 KPI 仪表盘 */}
|
||||
<div className="worker-hall-kpi-grid">
|
||||
{/* 卡片 1: 账户资产 */}
|
||||
<div className="worker-hall-kpi-card">
|
||||
<div className="worker-hall-kpi-top">
|
||||
<div className="worker-hall-kpi-icon-wrap kpi-wallet">
|
||||
<WalletOutlined />
|
||||
</div>
|
||||
<div className="worker-hall-kpi-info">
|
||||
<span className="worker-hall-kpi-title">可用余额</span>
|
||||
<strong className="worker-hall-kpi-val">
|
||||
{worker ? formatMoney(worker.wallet.availableAmount) : '-'}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-hall-kpi-bottom">
|
||||
<div className="worker-hall-kpi-sub-item">
|
||||
<span className="kpi-sub-label">冻结押金:</span>
|
||||
<span className="kpi-sub-val">
|
||||
{worker ? formatMoney(worker.wallet.frozenDepositAmount) : '¥0.00'}
|
||||
</span>
|
||||
</div>
|
||||
{worker?.level?.name ? (
|
||||
<span className="worker-hall-kpi-level-badge">{worker.level.name}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 卡片 2: 接单配额 */}
|
||||
<div className="worker-hall-kpi-card">
|
||||
<div className="worker-hall-kpi-top">
|
||||
<div className="worker-hall-kpi-icon-wrap kpi-quota">
|
||||
<ThunderboltOutlined />
|
||||
</div>
|
||||
<div className="worker-hall-kpi-info">
|
||||
<span className="worker-hall-kpi-title">接单配额</span>
|
||||
<strong className="worker-hall-kpi-val">
|
||||
{remainingSlots === null ? '未限制' : `${activeCount} / ${maxActiveOrders}`}
|
||||
<span className="worker-hall-kpi-unit"> 单</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-hall-kpi-bottom">
|
||||
<div className="worker-hall-kpi-progress-track">
|
||||
<div
|
||||
className={`worker-hall-kpi-progress-bar ${
|
||||
quotaPercent > 85 ? 'danger' : quotaPercent > 60 ? 'warning' : 'safe'
|
||||
}`}
|
||||
style={{ width: `${quotaPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="worker-hall-kpi-sub-text">
|
||||
{remainingSlots === null
|
||||
? '等级未限'
|
||||
: remainingSlots > 0
|
||||
? `还可接 ${remainingSlots} 单`
|
||||
: '已达上限'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 卡片 3: 进行中任务 */}
|
||||
<div className="worker-hall-kpi-card">
|
||||
<div className="worker-hall-kpi-top">
|
||||
<div className="worker-hall-kpi-icon-wrap kpi-tasks">
|
||||
<ClockCircleOutlined />
|
||||
</div>
|
||||
<div className="worker-hall-kpi-info">
|
||||
<span className="worker-hall-kpi-title">进行中任务</span>
|
||||
<strong className="worker-hall-kpi-val">
|
||||
{activeCount}
|
||||
<span className="worker-hall-kpi-unit"> 单代练中</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-hall-kpi-bottom">
|
||||
<div className="worker-hall-kpi-stat-tags">
|
||||
<span className="worker-hall-kpi-tag blue">待验收 {pendingAcceptanceCount}</span>
|
||||
<span className={`worker-hall-kpi-tag ${problemCount > 0 ? 'red' : 'neutral'}`}>
|
||||
问题单 {problemCount}
|
||||
</span>
|
||||
</div>
|
||||
<Link to="/orders" className="worker-hall-kpi-link">
|
||||
工单管理 <ArrowRightOutlined style={{ fontSize: 10 }} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 卡片 4: 实时订单池 */}
|
||||
<div className="worker-hall-kpi-card">
|
||||
<div className="worker-hall-kpi-top">
|
||||
<div className="worker-hall-kpi-icon-wrap kpi-pool">
|
||||
<FireOutlined />
|
||||
</div>
|
||||
<div className="worker-hall-kpi-info">
|
||||
<span className="worker-hall-kpi-title">大厅可抢订单</span>
|
||||
<strong className="worker-hall-kpi-val">
|
||||
{totalHallOrders}
|
||||
<span className="worker-hall-kpi-unit"> 单在池</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-hall-kpi-bottom">
|
||||
<span className="worker-hall-kpi-pool-hint">
|
||||
<span className="worker-hall-kpi-dot-online" />
|
||||
极速响应 · 秒级派发
|
||||
</span>
|
||||
<span className="worker-hall-kpi-pool-sub">支持拼单</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="worker-hall-board-card" bordered={false}>
|
||||
<div className="worker-hall-summary-strip">
|
||||
{summaryItems.map((item) => (
|
||||
<div key={item.label} className="worker-hall-summary-item">
|
||||
<span className="worker-hall-summary-label">{item.label}</span>
|
||||
<strong className="worker-hall-summary-value">{item.value}</strong>
|
||||
<small className="worker-hall-summary-note">{item.note}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 桌面端一级分类卡片与二级联动 */}
|
||||
{primaryCategories.length > 0 ? (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div className="worker-hall-game-cards-scroll">
|
||||
<button
|
||||
type="button"
|
||||
className={`worker-hall-game-card ${primaryCategoryId === 0 ? 'active' : ''}`}
|
||||
onClick={() => handleSelectPrimaryCategory(0)}
|
||||
>
|
||||
<div className="worker-hall-game-icon-box">
|
||||
<div
|
||||
className="worker-hall-game-icon-box"
|
||||
style={
|
||||
primaryCategoryId === 0
|
||||
? { background: 'linear-gradient(135deg, #ff6a00 0%, #ff8e3c 100%)' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<AppstoreOutlined
|
||||
style={{
|
||||
fontSize: 20,
|
||||
color: primaryCategoryId === 0 ? 'var(--theme-primary)' : '#64748b',
|
||||
color: primaryCategoryId === 0 ? '#ffffff' : '#64748b',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -701,7 +825,16 @@ export default function WorkerHallPage() {
|
||||
className={`worker-hall-game-card ${primaryCategoryId === category.categoryId ? 'active' : ''}`}
|
||||
onClick={() => handleSelectPrimaryCategory(category.categoryId)}
|
||||
>
|
||||
<div className="worker-hall-game-icon-box">
|
||||
<div
|
||||
className="worker-hall-game-icon-box"
|
||||
style={
|
||||
!category.iconUrl
|
||||
? {
|
||||
background: getCategoryGradient(category.name, category.categoryId),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{category.iconUrl ? (
|
||||
<img
|
||||
src={category.iconUrl}
|
||||
@@ -722,8 +855,8 @@ export default function WorkerHallPage() {
|
||||
</div>
|
||||
|
||||
{shouldShowSubcategories ? (
|
||||
<div className="worker-hall-subcat-bar" style={{ marginTop: 8 }}>
|
||||
<span className="worker-hall-subcat-label">业务类型:</span>
|
||||
<div className="worker-hall-subcat-bar" style={{ marginTop: 10 }}>
|
||||
<span className="worker-hall-subcat-label">业务分类:</span>
|
||||
<button
|
||||
type="button"
|
||||
className={`worker-hall-mobile-cat-pill ${subCategoryId === 0 ? 'active' : ''}`}
|
||||
@@ -786,35 +919,50 @@ export default function WorkerHallPage() {
|
||||
const disabledReason = resolveGrabDisabledReason(order, worker, remainingSlots)
|
||||
const productName = resolveHallProductName(order) || '未命名商品'
|
||||
const sharingRemaining = resolveSharingRemaining(order)
|
||||
const depositAmount = resolveHallDepositAmount(order)
|
||||
const rewardAmount = resolveHallRewardAmount(order)
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-hall-card">
|
||||
<div className="worker-hall-card-title">
|
||||
{order.pinnedAt ? (
|
||||
<Tag
|
||||
color="gold"
|
||||
icon={<PushpinOutlined />}
|
||||
className="worker-hall-pinned-tag"
|
||||
>
|
||||
置顶
|
||||
<div className="worker-hall-card-header">
|
||||
<div className="worker-hall-card-tags">
|
||||
{order.pinnedAt ? (
|
||||
<Tag
|
||||
color="gold"
|
||||
icon={<PushpinOutlined />}
|
||||
className="worker-hall-pinned-tag"
|
||||
>
|
||||
置顶
|
||||
</Tag>
|
||||
) : null}
|
||||
<Tag className="worker-hall-category-tag">
|
||||
{order.parentCategoryName
|
||||
? `${order.parentCategoryName} · ${order.categoryName}`
|
||||
: order.categoryName || '默认分类'}
|
||||
</Tag>
|
||||
) : null}
|
||||
{order.sharing?.enabled ? <Tag color="purple">拼单</Tag> : null}
|
||||
{Number(order.timeoutMinutes || 0) > 0 ? (
|
||||
<Tag color="orange" icon={<ClockCircleOutlined />}>
|
||||
限时 {order.timeoutMinutes}m
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
<span
|
||||
className="worker-hall-card-orderno"
|
||||
title={`订单号:${getDisplayOrderNo(order)}`}
|
||||
>
|
||||
{getDisplayOrderNo(order)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-body">
|
||||
<strong className="worker-hall-card-title-text" title={productName}>
|
||||
{productName}
|
||||
</strong>
|
||||
<Typography.Text type="secondary">
|
||||
{order.parentCategoryName
|
||||
? `${order.parentCategoryName} · ${order.categoryName}`
|
||||
: order.categoryName || '默认分类'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
订单号:{getDisplayOrderNo(order)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{order.sharing?.enabled ? (
|
||||
<div className="worker-hall-card-sharing">
|
||||
<Tag color="purple">拼单</Tag>
|
||||
<Typography.Text type="secondary">
|
||||
单价 {formatMoney(order.sharing.unitReward)} · 进度{' '}
|
||||
{order.sharingProgress?.joinedQuantity || 0}/
|
||||
@@ -823,21 +971,24 @@ export default function WorkerHallPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{Number(order.timeoutMinutes || 0) > 0 ? (
|
||||
<div className="worker-hall-card-sharing">
|
||||
<Tag color="orange" icon={<ClockCircleOutlined />}>
|
||||
限时 {order.timeoutMinutes} 分钟
|
||||
</Tag>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="worker-hall-card-amount-row">
|
||||
<div className="worker-hall-card-amount">
|
||||
{formatMoney(resolveHallRewardAmount(order))}
|
||||
<div className="worker-hall-card-amount-group">
|
||||
<span className="worker-hall-card-amount-label">接单报酬</span>
|
||||
<div className="worker-hall-card-amount">
|
||||
{formatMoney(rewardAmount)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-hall-card-deposit-group">
|
||||
{depositAmount <= 0 ? (
|
||||
<span className="worker-hall-deposit-free-badge">
|
||||
<CheckCircleOutlined /> 免保证金
|
||||
</span>
|
||||
) : (
|
||||
<span className="worker-hall-deposit-badge">
|
||||
保证金 {formatMoney(depositAmount)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Typography.Text type="secondary" className="worker-hall-card-deposit">
|
||||
所需保证金:{formatMoney(resolveHallDepositAmount(order))}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{disabledReason ? (
|
||||
@@ -867,6 +1018,7 @@ export default function WorkerHallPage() {
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
className="worker-hall-card-grab-btn"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => requestGrab(order)}
|
||||
@@ -1013,6 +1165,22 @@ export default function WorkerHallPage() {
|
||||
)
|
||||
}
|
||||
|
||||
function getCategoryGradient(name: string, id: number): string {
|
||||
const gradients = [
|
||||
'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
'linear-gradient(135deg, #0ea5e9 0%, #2563eb 100%)',
|
||||
'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
||||
'linear-gradient(135deg, #f59e0b 0%, #ea580c 100%)',
|
||||
'linear-gradient(135deg, #ec4899 0%, #f43f5e 100%)',
|
||||
'linear-gradient(135deg, #8b5cf6 0%, #d946ef 100%)',
|
||||
'linear-gradient(135deg, #14b8a6 0%, #0d9488 100%)',
|
||||
'linear-gradient(135deg, #f97316 0%, #c2410c 100%)',
|
||||
]
|
||||
const charCode = (name && name.charCodeAt(0)) || 0
|
||||
const index = Math.abs((id * 31 + charCode) % gradients.length)
|
||||
return gradients[index]
|
||||
}
|
||||
|
||||
function resolveGrabDisabledReason(
|
||||
order: WorkOrder,
|
||||
worker: WorkerUser | undefined,
|
||||
|
||||
@@ -645,6 +645,354 @@
|
||||
width: min(460px, 100%);
|
||||
}
|
||||
|
||||
/* ===== 桌面端抢单大厅全新整合头部 ===== */
|
||||
.worker-hall-desktop-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
background: #ffffff;
|
||||
padding: 14px 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.worker-hall-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-heading.ant-typography {
|
||||
margin: 0 !important;
|
||||
font-size: 20px !important;
|
||||
font-weight: 700 !important;
|
||||
color: #0f172a !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.worker-hall-live-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
padding: 2px 10px;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.worker-hall-pulse-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
||||
animation: worker-hall-pulse 2s infinite cubic-bezier(0.4, 0, 0.6, 1);
|
||||
}
|
||||
|
||||
@keyframes worker-hall-pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.worker-hall-header-center {
|
||||
flex: 1;
|
||||
max-width: 520px;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.worker-hall-desktop-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.worker-hall-desktop-search .ant-input {
|
||||
border-radius: 8px 0 0 8px;
|
||||
padding: 6px 14px;
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
|
||||
.worker-hall-desktop-search .ant-input:hover,
|
||||
.worker-hall-desktop-search .ant-input:focus {
|
||||
border-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.worker-hall-desktop-search .ant-btn {
|
||||
border-radius: 0 8px 8px 0;
|
||||
padding: 6px 18px;
|
||||
height: auto;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-header-switch-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ===== 抢单大厅 4 栏 KPI 仪表盘 ===== */
|
||||
.worker-hall-kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.worker-hall-kpi-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.worker-hall-kpi-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.worker-hall-kpi-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.worker-hall-kpi-card:hover {
|
||||
border-color: #cbd5e1;
|
||||
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.worker-hall-kpi-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-icon-wrap {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 19px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-icon-wrap.kpi-wallet {
|
||||
background: #fff7ed;
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-icon-wrap.kpi-quota {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-icon-wrap.kpi-tasks {
|
||||
background: #faf5ff;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-icon-wrap.kpi-pool {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-title {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-val {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-unit {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #64748b;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-sub-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-sub-item .kpi-sub-val {
|
||||
color: #334155;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-level-badge {
|
||||
font-size: 11px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: #fff7ed;
|
||||
color: #c2410c;
|
||||
border: 1px solid #fed7aa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-progress-track {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-progress-bar.safe {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
}
|
||||
|
||||
.worker-hall-kpi-progress-bar.warning {
|
||||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
||||
}
|
||||
|
||||
.worker-hall-kpi-progress-bar.danger {
|
||||
background: linear-gradient(90deg, #ef4444, #dc2626);
|
||||
}
|
||||
|
||||
.worker-hall-kpi-sub-text {
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-stat-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-tag {
|
||||
font-size: 11px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-tag.blue {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-tag.red {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-tag.neutral {
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
color: var(--theme-primary);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-pool-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #059669;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-dot-online {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.worker-hall-kpi-pool-sub {
|
||||
color: #94a3b8;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.worker-hall-board-card .ant-card-body {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
@@ -732,13 +1080,130 @@
|
||||
|
||||
.worker-hall-card {
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.04);
|
||||
display: grid;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.worker-hall-card:hover {
|
||||
border-color: #cbd5e1;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.worker-hall-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.worker-hall-card-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-category-tag {
|
||||
background: #f1f5f9 !important;
|
||||
color: #334155 !important;
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.worker-hall-card-orderno {
|
||||
font-size: 11px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-card-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.worker-hall-card-title-text {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f1f5f9;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount-label {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount {
|
||||
color: #ea580c;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.worker-hall-card-deposit-group {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.worker-hall-deposit-free-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.worker-hall-deposit-badge {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
background: #f1f5f9;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.worker-hall-card-grab-btn {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.worker-hall-card-title {
|
||||
@@ -1896,11 +2361,9 @@ body {
|
||||
.worker-hall-game-initial {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.worker-hall-game-card.active .worker-hall-game-initial {
|
||||
color: var(--theme-primary);
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.worker-hall-game-title {
|
||||
|
||||
Reference in New Issue
Block a user