初步优化手机端ui

This commit is contained in:
yml2213
2026-08-16 10:33:45 +08:00
parent ad84388450
commit 10ae3d01ee
6 changed files with 1316 additions and 338 deletions
+46 -199
View File
@@ -1,4 +1,4 @@
import { ClockCircleOutlined, EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { ClockCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
Alert,
@@ -6,7 +6,6 @@ import {
Button,
Card,
Descriptions,
Drawer,
Empty,
Input,
InputNumber,
@@ -29,8 +28,8 @@ import {
grabWorkerOrder,
joinWorkerSharingOrder,
} from '@/services/worker'
import type { CollectField, WorkOrder, WorkerUser } from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
import type { WorkOrder, WorkerUser } from '@/types/worker-platform'
import { useIsMobile } from '@/utils/use-is-mobile'
type HallSummary = {
pendingAcceptanceCount: number
@@ -38,6 +37,7 @@ type HallSummary = {
}
export default function WorkerHallPage() {
const isMobile = useIsMobile()
const { message } = App.useApp()
const queryClient = useQueryClient()
const [keywordInput, setKeywordInput] = useState('')
@@ -46,7 +46,6 @@ export default function WorkerHallPage() {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [grabbingId, setGrabbingId] = useState(0)
const [detailOrder, setDetailOrder] = useState<WorkOrder | null>(null)
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
const [sharingQuantity, setSharingQuantity] = useState(1)
const [submittingSharing, setSubmittingSharing] = useState(false)
@@ -118,9 +117,6 @@ export default function WorkerHallPage() {
maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
const shouldWarn =
(remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
const detailDisabledReason = detailOrder
? resolveGrabDisabledReason(detailOrder, worker, remainingSlots)
: ''
async function refreshAll() {
await Promise.all([
@@ -137,7 +133,6 @@ export default function WorkerHallPage() {
try {
await grabWorkerOrder(order.workOrderId)
message.success('抢单成功,已进入我的订单')
setDetailOrder(null)
await refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '抢单失败')
@@ -160,7 +155,6 @@ export default function WorkerHallPage() {
})
message.success('拼单成功,已进入我的订单')
setSharingOrder(null)
setDetailOrder(null)
await refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '拼单失败')
@@ -211,32 +205,40 @@ export default function WorkerHallPage() {
return (
<section className="page-stack">
<div className="worker-page-head">
<Typography.Title level={3}></Typography.Title>
<Space wrap className="worker-hall-toolbar">
<Button
icon={<ReloadOutlined />}
loading={
ordersQuery.isFetching ||
profileQuery.isFetching ||
hallSummaryQuery.isFetching
}
onClick={() => refreshAll()}
>
</Button>
<Tooltip
title={
autoRefresh
? '已开启自动刷新,每 5 秒刷新一次,点击关闭'
: '开启后每 5 秒自动刷新订单列表'
}
>
<Switch
size="small"
checked={autoRefresh}
onChange={toggleAutoRefresh}
/>
</Tooltip>
<div className="worker-page-head-title-row">
<Typography.Title level={3}></Typography.Title>
<div className="worker-hall-head-controls">
<Tooltip
title={
autoRefresh
? '已开启自动刷新,每 5 秒刷新一次'
: '开启后每 5 秒自动刷新订单列表'
}
>
<div className="worker-hall-autorefresh-box">
<span className="worker-hall-autorefresh-label"></span>
<Switch
size="small"
checked={autoRefresh}
onChange={toggleAutoRefresh}
/>
</div>
</Tooltip>
<Button
icon={<ReloadOutlined />}
loading={
ordersQuery.isFetching ||
profileQuery.isFetching ||
hallSummaryQuery.isFetching
}
onClick={() => refreshAll()}
>
</Button>
</div>
</div>
<div className="worker-hall-search-bar">
<Space.Compact className="worker-hall-search">
<Input
allowClear
@@ -262,7 +264,7 @@ export default function WorkerHallPage() {
</Button>
</Space.Compact>
</Space>
</div>
</div>
<Card className="worker-hall-board-card" bordered={false}>
@@ -340,13 +342,12 @@ export default function WorkerHallPage() {
return (
<article key={order.workOrderId} className="worker-hall-card">
<div className="worker-hall-card-title">
<Typography.Link
className="worker-hall-card-link"
<strong
className="worker-hall-card-title-text"
title={order.productName || '未命名商品'}
onClick={() => setDetailOrder(order)}
>
{order.productName || '未命名商品'}
</Typography.Link>
</strong>
<Typography.Text type="secondary">
{order.categoryName || '默认分类'}
</Typography.Text>
@@ -397,16 +398,6 @@ export default function WorkerHallPage() {
) : null}
<div className="worker-hall-card-actions">
<div className="worker-hall-card-actions-row">
<Button
size="small"
type="text"
icon={<EyeOutlined />}
onClick={() => setDetailOrder(order)}
>
</Button>
</div>
<div className="worker-hall-card-grab-row">
{order.sharing?.enabled && !disabledReason ? (
<Button
@@ -443,9 +434,10 @@ export default function WorkerHallPage() {
current={pagination?.page || page}
pageSize={pagination?.pageSize || pageSize}
total={pagination?.total || 0}
showSizeChanger
showSizeChanger={!isMobile}
simple={isMobile}
pageSizeOptions={[20, 50, 100]}
showTotal={(total) => `${total}`}
showTotal={isMobile ? undefined : (total) => `${total}`}
onChange={(nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
@@ -457,87 +449,9 @@ export default function WorkerHallPage() {
)}
</Card>
<Drawer
title={
detailOrder
? `${detailOrder.productName || '工单详情'} · ${getDisplayOrderNo(detailOrder)}`
: '工单详情'
}
width={760}
open={Boolean(detailOrder)}
destroyOnHidden
onClose={() => setDetailOrder(null)}
extra={
detailOrder ? (
<Button
type="primary"
disabled={Boolean(detailDisabledReason)}
loading={grabbingId === detailOrder.workOrderId}
onClick={() => submitGrab(detailOrder)}
>
</Button>
) : null
}
>
{detailOrder ? (
<div className="worker-hall-detail-stack">
<Alert
showIcon
type={detailDisabledReason ? 'warning' : 'success'}
message={detailDisabledReason || buildGrabReadyHint(detailOrder)}
/>
<Card title="基础信息" size="small">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="商品">
{detailOrder.productName || '-'}
</Descriptions.Item>
<Descriptions.Item label="分类">
{detailOrder.categoryName || '-'}
</Descriptions.Item>
<Descriptions.Item label="平台订单号">
{getDisplayOrderNo(detailOrder)}
</Descriptions.Item>
<Descriptions.Item label="接单奖励">
{formatMoney(detailOrder.rewardAmount)}
</Descriptions.Item>
<Descriptions.Item label="冻结押金">
{formatMoney(detailOrder.freezeDepositAmount)}
</Descriptions.Item>
<Descriptions.Item label="任务时限">
{Number(detailOrder.timeoutMinutes || 0) > 0 ? (
<Tag color="orange" icon={<ClockCircleOutlined />}>
{detailOrder.timeoutMinutes}
</Tag>
) : (
'不限时'
)}
</Descriptions.Item>
<Descriptions.Item label="发布时间">
{formatDateTime(resolvePublishedTime(detailOrder))}
</Descriptions.Item>
<Descriptions.Item label="更新时间">
{formatDateTime(detailOrder.updatedAt)}
</Descriptions.Item>
<Descriptions.Item label="资料要求">
{formatRequirementShortLabel(getRequirementFields(detailOrder))}
</Descriptions.Item>
<Descriptions.Item label="接单资格">
{detailDisabledReason || '当前可直接抢单'}
</Descriptions.Item>
</Descriptions>
</Card>
<Card title="资料要求" size="small">
{renderRequirementFieldValues(detailOrder)}
</Card>
</div>
) : null}
</Drawer>
<Modal
title={`拼单参与 · ${sharingOrder?.productName || ''}`}
width={isMobile ? '92%' : 520}
open={Boolean(sharingOrder)}
confirmLoading={submittingSharing}
onCancel={() => setSharingOrder(null)}
@@ -581,47 +495,6 @@ export default function WorkerHallPage() {
)
}
function getRequirementFields(order: WorkOrder | null): CollectField[] {
if (!order) return []
const rawFields = Array.isArray(order.requirement?.fields)
? order.requirement.fields
: []
return rawFields
.map((item) => {
const source = asRecord(item)
const key = String(source.key || '').trim()
if (!key) return null
return {
key,
label: String(source.label || key).trim(),
required: source.required !== false,
}
})
.filter((item): item is CollectField => Boolean(item))
}
function renderRequirementFieldValues(order: WorkOrder) {
const fields = getRequirementFields(order)
if (fields.length === 0) {
return <Typography.Text type="secondary"></Typography.Text>
}
return (
<Descriptions column={1} bordered size="small">
{fields.map((field) => (
<Descriptions.Item key={field.key} label={field.label}>
<Space size={6} wrap>
<Typography.Text code>{field.key}</Typography.Text>
<Tag color={field.required ? 'orange' : 'default'}>
{field.required ? '必填' : '选填'}
</Tag>
</Space>
</Descriptions.Item>
))}
</Descriptions>
)
}
function resolveGrabDisabledReason(
order: WorkOrder,
worker: WorkerUser | undefined,
@@ -644,36 +517,10 @@ function resolveGrabDisabledReason(
return ''
}
function buildGrabReadyHint(order: WorkOrder) {
const freezeAmount = Number(order.freezeDepositAmount || 0)
if (freezeAmount <= 0) {
return '当前工单无需冻结押金,可直接抢单'
}
return `抢单后会冻结 ${formatMoney(freezeAmount)}`
}
function formatRequirementShortLabel(fields: CollectField[]) {
if (fields.length === 0) {
return '资料简单'
}
const requiredCount = fields.filter((field) => field.required).length
return `${requiredCount} 项必填 / 共 ${fields.length}`
}
function resolvePublishedTime(order: WorkOrder) {
return order.publishedAt || order.createdAt || order.updatedAt
}
function getDisplayOrderNo(order: Pick<WorkOrder, 'platformOrderId'> | null | undefined): string {
return String(order?.platformOrderId || '').trim() || '-'
}
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: {}
}
const AUTO_REFRESH_STORAGE_KEY = 'worker-hall-auto-refresh'
function loadAutoRefreshPreference(): boolean {
@@ -10,6 +10,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
AutoComplete,
App,
Alert,
Button,
Card,
Descriptions,
@@ -18,7 +19,9 @@ import {
Form,
Input,
Modal,
Pagination,
Space,
Spin,
Table,
Tabs,
Tag,
@@ -45,6 +48,7 @@ import type {
} from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
import { getImageIdentity } from '@/utils/image-identity'
import { useIsMobile } from '@/utils/use-is-mobile'
const STATUS_OPTIONS = [
{ value: '', label: '全部订单' },
@@ -72,6 +76,7 @@ type DetailFieldItem = {
}
export default function WorkerOrdersPage() {
const isMobile = useIsMobile()
const { message } = App.useApp()
const queryClient = useQueryClient()
const [status, setStatus] = useState('')
@@ -449,50 +454,42 @@ export default function WorkerOrdersPage() {
]
return (
<section className="page-stack">
<div className="worker-page-head">
<Typography.Title level={3}></Typography.Title>
<Button
icon={<ReloadOutlined />}
loading={ordersQuery.isFetching || overviewQuery.isFetching}
onClick={() => refreshAll()}
>
</Button>
</div>
<Card bordered={false}>
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Tabs
activeKey={status || 'all'}
items={STATUS_OPTIONS.map((item) => {
<>
{isMobile ? (
<div className="worker-orders-mobile-page">
{/* 顶部水平滑动状态切换胶囊条 */}
<div className="worker-orders-mobile-status-bar">
{STATUS_OPTIONS.map((item) => {
const countKey = item.value || 'all'
const count = Number(overviewQuery.data?.[countKey] || 0)
return {
key: item.value || 'all',
label: (
<span>
{item.label}
{count > 0 ? (
<span className="worker-order-tab-count">{count}</span>
) : null}
</span>
),
}
const isActive = (status || '') === item.value
return (
<button
key={item.value || 'all'}
type="button"
className={`worker-orders-status-pill ${isActive ? 'active' : ''}`}
onClick={() => {
setStatus(item.value)
setPage(1)
}}
>
<span>{item.label}</span>
{count > 0 ? (
<span className="worker-orders-status-pill-count">{count}</span>
) : null}
</button>
)
})}
onChange={(nextKey) => {
const nextStatus = nextKey === 'all' ? '' : nextKey
setStatus(nextStatus)
setPage(1)
}}
/>
</div>
<Space.Compact style={{ width: '100%', maxWidth: 460 }}>
{/* 搜索与刷新栏 */}
<div className="worker-orders-mobile-search-row">
<Input
allowClear
value={keywordInput}
placeholder="搜索订单号 / 商品名"
prefix={<SearchOutlined />}
className="worker-orders-mobile-search-input"
onChange={(event) => {
const nextValue = event.target.value
setKeywordInput(nextValue)
@@ -504,55 +501,361 @@ export default function WorkerOrdersPage() {
onPressEnter={applyKeywordSearch}
/>
<Button
type="primary"
icon={<SearchOutlined />}
loading={ordersQuery.isFetching}
onClick={applyKeywordSearch}
>
</Button>
</Space.Compact>
/>
<Button
icon={<ReloadOutlined />}
loading={ordersQuery.isFetching || overviewQuery.isFetching}
onClick={() => refreshAll()}
title="刷新"
/>
</div>
<Table<WorkOrder>
rowKey="workOrderId"
size="small"
loading={ordersQuery.isLoading}
dataSource={orders}
columns={columns}
locale={{
emptyText: ordersQuery.error ? (
<Empty
description={
ordersQuery.error instanceof Error
? ordersQuery.error.message
: '读取我的订单失败'
}
{/* 移动端紧凑卡片列表 */}
<div className="worker-orders-mobile-list">
{ordersQuery.isLoading ? (
<div className="worker-hall-loading">
<Spin size="large" />
</div>
) : ordersQuery.error ? (
<Empty
description={
ordersQuery.error instanceof Error
? ordersQuery.error.message
: '读取我的订单失败'
}
/>
) : orders.length === 0 ? (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
}
/>
) : (
orders.map((order) => {
const items = getMaterialDetailItems(order).filter((item) =>
String(item.value || '').trim(),
)
const screenshots = getMaterialScreenshots(order)
const canCopyInfo = getCopyOrderInfoItems(order).length > 0
const submittedAt = getAcceptanceSubmittedAt(order)
const acceptanceFiles = getUniqueAcceptanceFiles(getWorkerAcceptance(order))
const hasAcceptance = acceptanceFiles.length > 0
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(order))
const draftCount = draftFiles.length
const noteText = getWorkerAcceptance(order)?.note
const workerNote = String(order.workerNote || '').trim()
return (
<article key={order.workOrderId} className="worker-order-mobile-card">
{/* 卡片顶栏:分类 + 订单号 + 状态/倒计时 */}
<div className="worker-order-mobile-header">
<div className="worker-order-mobile-header-left">
{order.categoryName ? (
<Tag color="blue" className="worker-order-mobile-tag">
{order.categoryName}
</Tag>
) : null}
{order.myShare ? (
<Tag color="purple" className="worker-order-mobile-tag">
</Tag>
) : null}
<span className="worker-order-mobile-orderno-text">
{getDisplayOrderNo(order)}
</span>
</div>
<div className="worker-order-mobile-header-right">
<Tag color={resolveStatusColor(order.status)}>
{formatStatus(order.status)}
</Tag>
{order.status === 'in_progress' && order.deadlineAt ? (
<DeadlineCountdown deadlineAt={order.deadlineAt} />
) : null}
</div>
</div>
{/* 商品标题与金额 */}
<div className="worker-order-mobile-main-row">
<div className="worker-order-mobile-product-title">
{order.productName || '未命名商品'}
</div>
<div className="worker-order-mobile-price-badge">
<span className="worker-order-mobile-price-label"></span>
<strong className="worker-order-mobile-price-num">
{formatMoney(order.rewardAmount)}
</strong>
</div>
</div>
{/* 辅助金额与接单时间 */}
<div className="worker-order-mobile-sub-row">
<span className="worker-order-mobile-sub-item">
: {formatMoney(order.freezeDepositAmount)}
</span>
{order.myShare ? (
<span className="worker-order-mobile-sub-item">
{order.myShare.quantity} ({formatMoney(order.myShare.shareReward)})
</span>
) : null}
<span className="worker-order-mobile-sub-time">
{formatDateTime(order.assignedAt)}
</span>
</div>
{/* 问题单提示 */}
{order.status === 'problem' && String(order.problemNote || '').trim() ? (
<div className="worker-order-mobile-problem-box">
<strong></strong>
<span>{order.problemNote}</span>
</div>
) : null}
{/* 买家资料与截图 */}
{(items.length > 0 || screenshots.length > 0) && (
<div className="worker-order-mobile-credentials-box">
{items.length > 0 ? (
<div className="worker-order-mobile-credentials-grid">
{items.map((item) => (
<div key={item.key} className="worker-order-mobile-credential-line">
<span className="worker-order-mobile-credential-label">{item.label}:</span>
<span className="worker-order-mobile-credential-val">{item.value}</span>
</div>
))}
</div>
) : null}
{screenshots.length > 0 ? (
<div className="worker-order-mobile-screenshots-row">
<span className="worker-order-mobile-credential-label">:</span>
<ImagePreviewList files={screenshots} size={34} />
</div>
) : null}
<div className="worker-order-mobile-copy-toolbar">
<Button
size="small"
icon={<CopyOutlined />}
disabled={!canCopyInfo}
onClick={() => copyOrderInfo(order)}
>
</Button>
{screenshots.length > 0 ? (
<Button
size="small"
icon={<PictureOutlined />}
onClick={() => copyMaterialImages(order)}
>
</Button>
) : null}
</div>
</div>
)}
{/* 验收凭据与暂存状态 */}
{(hasAcceptance || (draftCount > 0 && !hasSubmittedAcceptance(order)) || noteText) && (
<div className="worker-order-mobile-acceptance-strip">
{hasAcceptance ? (
<div className="worker-order-mobile-acceptance-files">
<span className="worker-order-mobile-acceptance-tag">
({acceptanceFiles.length})
</span>
<ImagePreviewList files={acceptanceFiles} size={32} maxVisible={3} />
</div>
) : null}
{draftCount > 0 && !hasSubmittedAcceptance(order) ? (
<div className="worker-order-mobile-draft-files">
<Tag color="processing" icon={<PictureOutlined />}>
{draftCount}
</Tag>
<ImagePreviewList files={draftFiles} size={32} maxVisible={3} />
</div>
) : null}
{noteText ? (
<div className="worker-order-mobile-acceptance-text">
{noteText}
</div>
) : null}
</div>
)}
{/* 底部备注与操作 */}
<div className="worker-order-mobile-footer-row">
<div className="worker-order-mobile-note-block">
{workerNote ? (
<div className="worker-note-badge" onClick={() => openNoteModal(order)}>
<span className="worker-note-text" title={workerNote}>
{workerNote}
</span>
<EditOutlined className="worker-note-edit-icon" />
</div>
) : (
<button
type="button"
className="worker-order-mobile-add-note-btn"
onClick={() => openNoteModal(order)}
>
<EditOutlined />
</button>
)}
</div>
<div className="worker-order-mobile-btn-group">
<Button
size="small"
icon={<EyeOutlined />}
onClick={() => setDetailOrder(order)}
>
</Button>
{canEditAcceptanceImages(order) ? (
<Button
size="small"
type={canSubmitAcceptance(order) ? 'primary' : 'default'}
icon={<PictureOutlined />}
className="worker-order-mobile-action-submit"
onClick={() => openAcceptanceModal(order)}
>
{hasSubmittedAcceptance(order) ? '补充图片' : '提交验收'}
</Button>
) : null}
</div>
</div>
</article>
)
})
)}
{(pagination?.total || 0) > 0 ? (
<div className="worker-orders-mobile-pagination">
<Pagination
current={pagination?.page || page}
pageSize={pagination?.pageSize || pageSize}
total={pagination?.total || 0}
simple
onChange={(nextPage: number, nextPageSize: number) => {
setPage(nextPage)
setPageSize(nextPageSize)
}}
/>
) : (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
</div>
) : null}
</div>
</div>
) : (
<section className="page-stack">
<div className="worker-page-head">
<Typography.Title level={3}></Typography.Title>
<Button
icon={<ReloadOutlined />}
loading={ordersQuery.isFetching || overviewQuery.isFetching}
onClick={() => refreshAll()}
>
</Button>
</div>
<Card bordered={false}>
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Tabs
activeKey={status || 'all'}
items={STATUS_OPTIONS.map((item) => {
const countKey = item.value || 'all'
const count = Number(overviewQuery.data?.[countKey] || 0)
return {
key: item.value || 'all',
label: (
<span>
{item.label}
{count > 0 ? (
<span className="worker-order-tab-count">{count}</span>
) : null}
</span>
),
}
})}
onChange={(nextKey) => {
const nextStatus = nextKey === 'all' ? '' : nextKey
setStatus(nextStatus)
setPage(1)
}}
/>
<Space.Compact style={{ width: '100%', maxWidth: 460 }}>
<Input
allowClear
value={keywordInput}
placeholder="搜索订单号 / 商品名"
prefix={<SearchOutlined />}
onChange={(event) => {
const nextValue = event.target.value
setKeywordInput(nextValue)
if (!nextValue.trim()) {
setKeyword('')
setPage(1)
}
}}
onPressEnter={applyKeywordSearch}
/>
),
}}
pagination={{
current: pagination?.page || page,
pageSize: pagination?.pageSize || pageSize,
total: pagination?.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50, 100],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
}}
scroll={{ x: 1440 }}
/>
</Space>
</Card>
<Button
icon={<SearchOutlined />}
loading={ordersQuery.isFetching}
onClick={applyKeywordSearch}
>
</Button>
</Space.Compact>
<Table<WorkOrder>
rowKey="workOrderId"
size="small"
loading={ordersQuery.isLoading}
dataSource={orders}
columns={columns}
locale={{
emptyText: ordersQuery.error ? (
<Empty
description={
ordersQuery.error instanceof Error
? ordersQuery.error.message
: '读取我的订单失败'
}
/>
) : (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
}
/>
),
}}
pagination={{
current: pagination?.page || page,
pageSize: pagination?.pageSize || pageSize,
total: pagination?.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50, 100],
showTotal: (total) => `${total}`,
onChange: (nextPage: number, nextPageSize: number) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
}}
scroll={{ x: 1440 }}
/>
</Space>
</Card>
</section>
)}
<Drawer
title={
@@ -560,7 +863,7 @@ export default function WorkerOrdersPage() {
? `${detailOrder.productName || '订单详情'} · ${getDisplayOrderNo(detailOrder)}`
: '订单详情'
}
width={820}
width={isMobile ? '100%' : 820}
open={Boolean(detailOrder)}
destroyOnHidden
onClose={() => setDetailOrder(null)}
@@ -698,7 +1001,7 @@ export default function WorkerOrdersPage() {
<Modal
title={`订单备注 · ${getDisplayOrderNo(noteOrder)}`}
open={Boolean(noteOrder)}
width={460}
width={isMobile ? '92%' : 460}
onCancel={() => {
setNoteOrder(null)
noteForm.resetFields()
@@ -740,6 +1043,7 @@ export default function WorkerOrdersPage() {
: '验收图片'
}
open={Boolean(editingOrder)}
width={isMobile ? '96%' : 620}
onCancel={() => {
setEditingOrder(null)
setAcceptanceFiles([])
@@ -789,7 +1093,7 @@ export default function WorkerOrdersPage() {
</Form.Item>
</Form>
</Modal>
</section>
</>
)
}
@@ -22,9 +22,11 @@ import {
Input,
InputNumber,
Modal,
Pagination,
Progress,
Select,
Space,
Spin,
Table,
Tabs,
Tag,
@@ -52,6 +54,7 @@ import type {
WorkerWalletLedger,
} from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
import { useIsMobile } from '@/utils/use-is-mobile'
import { clearWorkerSession } from '@/utils/worker-auth'
type RechargeFormValues = {
@@ -76,6 +79,7 @@ type PasswordFormValues = {
}
export default function WorkerProfilePage() {
const isMobile = useIsMobile()
const { message } = App.useApp()
const navigate = useNavigate()
const queryClient = useQueryClient()
@@ -673,33 +677,93 @@ export default function WorkerProfilePage() {
</Space>
}
>
<Table<WorkerWalletLedger>
rowKey="ledgerId"
size="small"
loading={ledgersQuery.isLoading}
dataSource={ledgersQuery.data?.data.items || []}
columns={ledgerColumns}
locale={{
emptyText: ledgersQuery.error
? ledgersQuery.error instanceof Error
? ledgersQuery.error.message
: '读取钱包流水失败'
: '暂无钱包流水',
}}
pagination={{
current: ledgersQuery.data?.data.pagination.page || ledgerPage,
pageSize: ledgersQuery.data?.data.pagination.pageSize || ledgerPageSize,
total: ledgersQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setLedgerPage(nextPage)
setLedgerPageSize(nextPageSize)
},
}}
scroll={{ x: 920 }}
/>
{isMobile ? (
<div className="worker-profile-mobile-records">
{ledgersQuery.isLoading ? (
<div className="worker-hall-loading"><Spin size="large" /></div>
) : (ledgersQuery.data?.data.items || []).length === 0 ? (
<Empty description="暂无钱包流水" />
) : (
(ledgersQuery.data?.data.items || []).map((row) => (
<div key={row.ledgerId} className="worker-profile-record-card">
<div className="worker-profile-record-row-head">
<Tag color={resolveLedgerTagColor(row.ledgerType)}>
{formatLedgerType(row.ledgerType)}
</Tag>
<strong
className={
row.amount >= 0
? 'worker-amount-positive'
: 'worker-amount-negative'
}
style={{ fontSize: 16 }}
>
{row.amount >= 0 ? '+' : ''}
{formatMoney(row.amount)}
</strong>
</div>
<div className="worker-profile-record-row-meta">
<span>: {formatMoney(row.balanceAfter)}</span>
<span>: {formatMoney(row.frozenAfter)}</span>
</div>
{row.note ? (
<div className="worker-profile-record-note">{row.note}</div>
) : null}
{row.relatedWorkOrderId ? (
<div className="worker-profile-record-sub">
#{row.relatedWorkOrderId}
</div>
) : null}
<div className="worker-profile-record-time">
{formatDateTime(String(row.createdAt || ''))}
</div>
</div>
))
)}
{(ledgersQuery.data?.data.pagination.total || 0) > 0 ? (
<div className="worker-orders-mobile-pagination">
<Pagination
current={ledgersQuery.data?.data.pagination.page || ledgerPage}
pageSize={ledgersQuery.data?.data.pagination.pageSize || ledgerPageSize}
total={ledgersQuery.data?.data.pagination.total || 0}
simple
onChange={(nextPage: number, nextPageSize: number) => {
setLedgerPage(nextPage)
setLedgerPageSize(nextPageSize)
}}
/>
</div>
) : null}
</div>
) : (
<Table<WorkerWalletLedger>
rowKey="ledgerId"
size="small"
loading={ledgersQuery.isLoading}
dataSource={ledgersQuery.data?.data.items || []}
columns={ledgerColumns}
locale={{
emptyText: ledgersQuery.error
? ledgersQuery.error instanceof Error
? ledgersQuery.error.message
: '读取钱包流水失败'
: '暂无钱包流水',
}}
pagination={{
current: ledgersQuery.data?.data.pagination.page || ledgerPage,
pageSize: ledgersQuery.data?.data.pagination.pageSize || ledgerPageSize,
total: ledgersQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setLedgerPage(nextPage)
setLedgerPageSize(nextPageSize)
},
}}
scroll={{ x: 920 }}
/>
)}
</Card>
),
},
@@ -749,33 +813,93 @@ export default function WorkerProfilePage() {
</Space>
}
>
<Table<WorkerFinanceRequest>
rowKey="requestId"
size="small"
loading={requestsQuery.isLoading}
dataSource={requestsQuery.data?.data.items || []}
columns={requestColumns}
locale={{
emptyText: requestsQuery.error
? requestsQuery.error instanceof Error
? requestsQuery.error.message
: '读取申请记录失败'
: '暂无申请记录',
}}
pagination={{
current: requestsQuery.data?.data.pagination.page || requestPage,
pageSize: requestsQuery.data?.data.pagination.pageSize || requestPageSize,
total: requestsQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setRequestPage(nextPage)
setRequestPageSize(nextPageSize)
},
}}
scroll={{ x: 980 }}
/>
{isMobile ? (
<div className="worker-profile-mobile-records">
{requestsQuery.isLoading ? (
<div className="worker-hall-loading"><Spin size="large" /></div>
) : (requestsQuery.data?.data.items || []).length === 0 ? (
<Empty description="暂无申请记录" />
) : (
(requestsQuery.data?.data.items || []).map((row) => (
<div key={row.requestId} className="worker-profile-record-card">
<div className="worker-profile-record-row-head">
<Space size={4}>
<Tag color={row.requestType === 'withdraw' ? 'green' : 'blue'}>
{formatRequestType(row.requestType)}
</Tag>
<Tag color={resolveRequestStatusColor(row.status)}>
{formatRequestStatus(row.status)}
</Tag>
</Space>
<strong style={{ fontSize: 16 }}>{formatMoney(row.amount)}</strong>
</div>
{row.requestType === 'withdraw' ? (
<div className="worker-profile-record-row-meta">
: {formatWithdrawChannel(row.accountChannel)} / {row.accountName || '-'} ({maskAccountNo(row.accountNo)})
</div>
) : (
<div className="worker-profile-record-row-meta">
{renderRechargeRequestSummary(row)}
</div>
)}
{row.note ? (
<div className="worker-profile-record-note">: {row.note}</div>
) : null}
{row.reviewedNote ? (
<div className="worker-profile-record-note" style={{ color: '#d97706' }}>
: {row.reviewedNote}
</div>
) : null}
<div className="worker-profile-record-time">
{formatDateTime(String(row.createdAt || ''))}
</div>
</div>
))
)}
{(requestsQuery.data?.data.pagination.total || 0) > 0 ? (
<div className="worker-orders-mobile-pagination">
<Pagination
current={requestsQuery.data?.data.pagination.page || requestPage}
pageSize={requestsQuery.data?.data.pagination.pageSize || requestPageSize}
total={requestsQuery.data?.data.pagination.total || 0}
simple
onChange={(nextPage: number, nextPageSize: number) => {
setRequestPage(nextPage)
setRequestPageSize(nextPageSize)
}}
/>
</div>
) : null}
</div>
) : (
<Table<WorkerFinanceRequest>
rowKey="requestId"
size="small"
loading={requestsQuery.isLoading}
dataSource={requestsQuery.data?.data.items || []}
columns={requestColumns}
locale={{
emptyText: requestsQuery.error
? requestsQuery.error instanceof Error
? requestsQuery.error.message
: '读取申请记录失败'
: '暂无申请记录',
}}
pagination={{
current: requestsQuery.data?.data.pagination.page || requestPage,
pageSize: requestsQuery.data?.data.pagination.pageSize || requestPageSize,
total: requestsQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setRequestPage(nextPage)
setRequestPageSize(nextPageSize)
},
}}
scroll={{ x: 980 }}
/>
)}
</Card>
),
},
@@ -787,6 +911,7 @@ export default function WorkerProfilePage() {
<Modal
title="充值申请"
open={rechargeOpen}
width={isMobile ? '92%' : 540}
destroyOnHidden
confirmLoading={submittingRecharge}
onCancel={() => {
@@ -884,6 +1009,7 @@ export default function WorkerProfilePage() {
<Modal
title="提现申请"
open={withdrawOpen}
width={isMobile ? '92%' : 540}
destroyOnHidden
confirmLoading={submittingWithdraw}
onCancel={() => {
@@ -965,6 +1091,7 @@ export default function WorkerProfilePage() {
<Modal
title="修改密码"
open={passwordOpen}
width={isMobile ? '92%' : 480}
destroyOnHidden
confirmLoading={submittingPassword}
onCancel={() => {
@@ -1015,6 +1142,7 @@ export default function WorkerProfilePage() {
<Modal
title="联系管理员"
open={contactOpen}
width={isMobile ? '92%' : 500}
destroyOnHidden
footer={
<Button type="primary" onClick={() => setContactOpen(false)}>