feat(worker-hall): 接单大厅支持内部打手可见订单已发布时长标签

This commit is contained in:
yml2213
2026-09-13 11:14:16 +08:00
parent 85272cde46
commit 18ac95ca27
5 changed files with 128 additions and 1 deletions
@@ -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 />}>
+18
View File
@@ -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;
+31
View File
@@ -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`
}
+43
View File
@@ -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',
)
})