feat(worker-hall): 接单大厅支持内部打手可见订单已发布时长标签
This commit is contained in:
@@ -77,11 +77,16 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work
|
||||
if (share.status === 'joined') progress.pendingSubmissionCount += 1
|
||||
sharingProgressByOrderId.set(orderId, progress)
|
||||
}
|
||||
const isInternalWorker = String(worker.worker_type || '').trim() === 'internal'
|
||||
return {
|
||||
items: items.map((item) => {
|
||||
const myShare = myShareByOrderId.get(Number(item.id))
|
||||
const mappedOrder = mapWorkOrderForWorker(item, permissions)
|
||||
return {
|
||||
...mapWorkOrderForWorker(item, permissions),
|
||||
...mappedOrder,
|
||||
publishedAt: isInternalWorker
|
||||
? item.published_at || item.hall_queued_at || item.created_at || null
|
||||
: null,
|
||||
myShare: myShare ? mapWorkOrderShare(myShare) : null,
|
||||
sharingProgress: sharingProgressByOrderId.get(Number(item.id)) || {
|
||||
joinedQuantity: 0,
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
resolveHallRemainingQuantity,
|
||||
resolveHallRewardAmount,
|
||||
} from '@/utils/worker-product-name'
|
||||
import { formatDateTime, formatPublishedElapsedTime } from '@/utils/date-time'
|
||||
|
||||
type HallSummary = {
|
||||
pendingAcceptanceCount: number
|
||||
@@ -88,8 +89,16 @@ export default function WorkerHallPage() {
|
||||
const [failedImageKeys, setFailedImageKeys] = useState<Record<string | number, boolean>>({})
|
||||
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||
const [grabConfirmEnabled, setGrabConfirmEnabled] = useState(isGrabConfirmEnabled)
|
||||
const [nowMs, setNowMs] = useState(() => Date.now())
|
||||
const loadMoreRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setNowMs(Date.now())
|
||||
}, 30_000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
const categoriesQuery = useQuery({
|
||||
queryKey: ['worker-hall-categories'],
|
||||
queryFn: () => fetchWorkerHallCategories(),
|
||||
@@ -227,6 +236,7 @@ export default function WorkerHallPage() {
|
||||
setFailedImageKeys({})
|
||||
}, [allCategories.length, allIconUrl])
|
||||
const worker = profileQuery.data?.data.worker
|
||||
const isInternalWorker = worker?.workerType === 'internal'
|
||||
const activeCount = Number(profileQuery.data?.data.summary.activeOrderCount || 0)
|
||||
const pendingAcceptanceCount = hallSummaryQuery.data?.pendingAcceptanceCount || 0
|
||||
const problemCount = hallSummaryQuery.data?.problemCount || 0
|
||||
@@ -623,6 +633,16 @@ export default function WorkerHallPage() {
|
||||
: order.categoryName}
|
||||
</Tag>
|
||||
) : null}
|
||||
{isInternalWorker && order.publishedAt ? (
|
||||
<Tooltip title={`发布时间: ${formatDateTime(order.publishedAt)}`}>
|
||||
<Tag
|
||||
icon={<ClockCircleOutlined />}
|
||||
className="worker-hall-published-tag"
|
||||
>
|
||||
{formatPublishedElapsedTime(order.publishedAt, nowMs)}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{order.sharing?.enabled ? (
|
||||
<Tag color="purple" className="worker-hall-mobile-tag">
|
||||
拼单 {order.sharingProgress?.joinedQuantity || 0}/
|
||||
@@ -1047,6 +1067,16 @@ export default function WorkerHallPage() {
|
||||
? `${order.parentCategoryName} · ${order.categoryName}`
|
||||
: order.categoryName || '默认分类'}
|
||||
</Tag>
|
||||
{isInternalWorker && order.publishedAt ? (
|
||||
<Tooltip title={`发布时间: ${formatDateTime(order.publishedAt)}`}>
|
||||
<Tag
|
||||
icon={<ClockCircleOutlined />}
|
||||
className="worker-hall-published-tag"
|
||||
>
|
||||
{formatPublishedElapsedTime(order.publishedAt, nowMs)}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{order.sharing?.enabled ? <Tag color="purple">拼单</Tag> : null}
|
||||
{Number(order.timeoutMinutes || 0) > 0 ? (
|
||||
<Tag color="orange" icon={<ClockCircleOutlined />}>
|
||||
|
||||
@@ -1122,6 +1122,24 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.worker-hall-published-tag {
|
||||
background: #fff7ed !important;
|
||||
color: #ea580c !important;
|
||||
border: 1px solid #fed7aa !important;
|
||||
font-weight: 500;
|
||||
font-size: 11px !important;
|
||||
line-height: 18px !important;
|
||||
border-radius: 4px;
|
||||
margin: 0 !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.worker-hall-published-tag .anticon {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.worker-hall-card-orderno {
|
||||
font-size: 11px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
|
||||
@@ -52,3 +52,34 @@ export function normalizeDateTimeValue(value: unknown): unknown {
|
||||
export function stringifyDisplayJson(value: unknown, space = 2) {
|
||||
return JSON.stringify(normalizeDateTimeValue(value), null, space)
|
||||
}
|
||||
|
||||
/** 抢单大厅发布耗时格式化(仅内部打手可见):发布 <1m / 发布 5m / 发布 1h 20m / 发布 1d 4h */
|
||||
export function formatPublishedElapsedTime(
|
||||
value: string | null | undefined,
|
||||
nowMs = Date.now(),
|
||||
): string {
|
||||
const normalized = String(value || '').trim()
|
||||
if (!normalized) return ''
|
||||
|
||||
const date = new Date(normalized)
|
||||
const time = date.getTime()
|
||||
if (Number.isNaN(time)) return ''
|
||||
|
||||
const diffSeconds = Math.max(0, Math.floor((nowMs - time) / 1000))
|
||||
|
||||
if (diffSeconds < 60) {
|
||||
return '发布 <1m'
|
||||
}
|
||||
const diffMinutes = Math.floor(diffSeconds / 60)
|
||||
if (diffMinutes < 60) {
|
||||
return `发布 ${diffMinutes}m`
|
||||
}
|
||||
const diffHours = Math.floor(diffMinutes / 60)
|
||||
const remMinutes = diffMinutes % 60
|
||||
if (diffHours < 24) {
|
||||
return remMinutes > 0 ? `发布 ${diffHours}h ${remMinutes}m` : `发布 ${diffHours}h`
|
||||
}
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
const remHours = diffHours % 24
|
||||
return remHours > 0 ? `发布 ${diffDays}d ${remHours}h` : `发布 ${diffDays}d`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { formatDateTime, formatPublishedElapsedTime } from '../src/utils/date-time.ts'
|
||||
|
||||
test('formatPublishedElapsedTime formats elapsed time for worker hall', () => {
|
||||
const baseTime = new Date('2026-09-13T10:00:00.000Z').getTime()
|
||||
|
||||
// Invalid or empty
|
||||
assert.equal(formatPublishedElapsedTime(null, baseTime), '')
|
||||
assert.equal(formatPublishedElapsedTime('', baseTime), '')
|
||||
assert.equal(formatPublishedElapsedTime('invalid-date', baseTime), '')
|
||||
|
||||
// <1m
|
||||
assert.equal(
|
||||
formatPublishedElapsedTime(new Date(baseTime - 30 * 1000).toISOString(), baseTime),
|
||||
'发布 <1m',
|
||||
)
|
||||
|
||||
// 5m
|
||||
assert.equal(
|
||||
formatPublishedElapsedTime(new Date(baseTime - 5 * 60 * 1000).toISOString(), baseTime),
|
||||
'发布 5m',
|
||||
)
|
||||
|
||||
// 1h
|
||||
assert.equal(
|
||||
formatPublishedElapsedTime(new Date(baseTime - 60 * 60 * 1000).toISOString(), baseTime),
|
||||
'发布 1h',
|
||||
)
|
||||
|
||||
// 1h 25m
|
||||
assert.equal(
|
||||
formatPublishedElapsedTime(new Date(baseTime - 85 * 60 * 1000).toISOString(), baseTime),
|
||||
'发布 1h 25m',
|
||||
)
|
||||
|
||||
// 2d 3h
|
||||
assert.equal(
|
||||
formatPublishedElapsedTime(new Date(baseTime - (48 + 3) * 3600 * 1000).toISOString(), baseTime),
|
||||
'发布 2d 3h',
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user