精简接单端页面样式
This commit is contained in:
@@ -140,7 +140,7 @@ export default function WorkerHallPage() {
|
||||
{
|
||||
label: '当前余额',
|
||||
value: worker ? formatMoney(worker.wallet.availableAmount) : '-',
|
||||
note: '可用于冻结押金',
|
||||
note: worker ? `冻结押金 ${formatMoney(worker.wallet.frozenDepositAmount)}` : '可用于抢单冻结',
|
||||
},
|
||||
{
|
||||
label: '接单上限',
|
||||
@@ -151,17 +151,7 @@ export default function WorkerHallPage() {
|
||||
note:
|
||||
remainingSlots === null
|
||||
? '当前等级待配置'
|
||||
: `还能再接 ${remainingSlots} 单`,
|
||||
},
|
||||
{
|
||||
label: '冻结押金',
|
||||
value: worker ? formatMoney(worker.wallet.frozenDepositAmount) : '-',
|
||||
note: '当前已占用金额',
|
||||
},
|
||||
{
|
||||
label: '问题单',
|
||||
value: `${problemCount} 单`,
|
||||
note: problemCount ? '建议优先处理' : '当前暂无问题单',
|
||||
: `待验收 ${pendingAcceptanceCount} 单 / 问题单 ${problemCount} 单`,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -257,72 +247,50 @@ export default function WorkerHallPage() {
|
||||
<>
|
||||
<div className="worker-hall-card-grid">
|
||||
{orders.map((order) => {
|
||||
const requirementFields = getRequirementFields(order)
|
||||
const disabledReason = resolveGrabDisabledReason(
|
||||
order,
|
||||
worker,
|
||||
remainingSlots,
|
||||
)
|
||||
const zeroDeposit = Number(order.freezeDepositAmount || 0) <= 0
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-hall-card">
|
||||
<div className="worker-hall-card-head">
|
||||
<div className="worker-hall-card-title">
|
||||
<Typography.Link onClick={() => setDetailOrder(order)}>
|
||||
{order.productName || order.workOrderNo}
|
||||
</Typography.Link>
|
||||
<Typography.Text type="secondary">
|
||||
{order.categoryName || '默认分类'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="worker-hall-card-badges">
|
||||
<Tag color={zeroDeposit ? 'green' : 'gold'}>
|
||||
{zeroDeposit ? '免押金' : '需押金'}
|
||||
</Tag>
|
||||
<Tag color="blue">
|
||||
{formatRequirementShortLabel(requirementFields)}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-order">
|
||||
<div className="worker-hall-card-title">
|
||||
<Typography.Link
|
||||
className="worker-hall-card-link"
|
||||
title={order.productName || order.workOrderNo}
|
||||
onClick={() => setDetailOrder(order)}
|
||||
>
|
||||
{order.productName || order.workOrderNo}
|
||||
</Typography.Link>
|
||||
<Typography.Text type="secondary">
|
||||
工单号:{order.workOrderNo}
|
||||
{order.categoryName || '默认分类'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
订单号:{order.platformOrderId || '-'}
|
||||
订单号:{order.platformOrderId || order.workOrderNo}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-main">
|
||||
<div className="worker-hall-card-amount-row">
|
||||
<div className="worker-hall-card-amount">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
</div>
|
||||
<div className="worker-hall-card-side">
|
||||
<span>所需保证金:{formatMoney(order.freezeDepositAmount)}</span>
|
||||
<span>发布时间:{formatDateTime(resolvePublishedTime(order))}</span>
|
||||
</div>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
className="worker-hall-card-deposit"
|
||||
>
|
||||
所需保证金:{formatMoney(order.freezeDepositAmount)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-meta">
|
||||
<div className="worker-hall-card-meta-row">
|
||||
<span>资料要求</span>
|
||||
<strong>{formatRequirementPreview(requirementFields)}</strong>
|
||||
</div>
|
||||
<div className="worker-hall-card-meta-row">
|
||||
<span>抢单判断</span>
|
||||
<strong
|
||||
className={
|
||||
disabledReason
|
||||
? 'worker-hall-card-danger'
|
||||
: 'worker-hall-card-safe'
|
||||
}
|
||||
>
|
||||
{disabledReason || buildGrabReadyHint(order)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
{disabledReason ? (
|
||||
<Typography.Text
|
||||
type="danger"
|
||||
className="worker-hall-card-hint"
|
||||
>
|
||||
{disabledReason}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
|
||||
<div className="worker-hall-card-actions">
|
||||
<Button icon={<EyeOutlined />} onClick={() => setDetailOrder(order)}>
|
||||
@@ -517,24 +485,6 @@ function formatRequirementShortLabel(fields: CollectField[]) {
|
||||
return `${requiredCount} 项必填 / 共 ${fields.length} 项`
|
||||
}
|
||||
|
||||
function formatRequirementPreview(fields: CollectField[]) {
|
||||
if (fields.length === 0) {
|
||||
return '当前工单未配置额外资料字段'
|
||||
}
|
||||
|
||||
const preview = fields
|
||||
.slice(0, 3)
|
||||
.map((field) => field.label)
|
||||
.filter(Boolean)
|
||||
.join('、')
|
||||
|
||||
if (fields.length <= 3) {
|
||||
return preview
|
||||
}
|
||||
|
||||
return `${preview} 等 ${fields.length} 项`
|
||||
}
|
||||
|
||||
function resolvePublishedTime(order: WorkOrder) {
|
||||
return order.publishedAt || order.createdAt || order.updatedAt
|
||||
}
|
||||
|
||||
@@ -9,13 +9,17 @@ import {
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
Row,
|
||||
Segmented,
|
||||
Space,
|
||||
Statistic,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
@@ -38,37 +42,13 @@ const STATUS_OPTIONS = [
|
||||
{ value: 'cancelled', label: '已取消' },
|
||||
] as const
|
||||
|
||||
const STATUS_OVERVIEW = [
|
||||
{
|
||||
key: 'in_progress',
|
||||
label: '代练中',
|
||||
note: '正在执行中的订单',
|
||||
tone: 'blue',
|
||||
},
|
||||
{
|
||||
key: 'pending_acceptance',
|
||||
label: '待验收',
|
||||
note: '已经提交,等待审核',
|
||||
tone: 'gold',
|
||||
},
|
||||
{
|
||||
key: 'problem',
|
||||
label: '问题单',
|
||||
note: '需要补充或重新处理',
|
||||
tone: 'red',
|
||||
},
|
||||
{
|
||||
key: 'accepted',
|
||||
label: '已验收',
|
||||
note: '已完成并结算的订单',
|
||||
tone: 'green',
|
||||
},
|
||||
{
|
||||
key: 'cancelled',
|
||||
label: '已取消',
|
||||
note: '已结束且不再继续',
|
||||
tone: 'slate',
|
||||
},
|
||||
const STATUS_SUMMARY_ITEMS = [
|
||||
{ key: 'all', label: '全部订单', note: '当前账号历史接单总数' },
|
||||
{ key: 'in_progress', label: '代练中', note: '正在执行中的订单' },
|
||||
{ key: 'pending_acceptance', label: '待验收', note: '已提交,等待审核' },
|
||||
{ key: 'problem', label: '问题单', note: '需要补充或重新处理' },
|
||||
{ key: 'accepted', label: '已验收', note: '已完成并结算的订单' },
|
||||
{ key: 'cancelled', label: '已取消', note: '已结束且不再继续' },
|
||||
] as const
|
||||
|
||||
type AcceptanceFormValues = {
|
||||
@@ -248,7 +228,11 @@ export default function WorkerOrdersPage() {
|
||||
详情
|
||||
</Button>
|
||||
{canSubmitAcceptance(row) ? (
|
||||
<Button icon={<CheckOutlined />} type="primary" onClick={() => openAcceptanceModal(row)}>
|
||||
<Button
|
||||
icon={<CheckOutlined />}
|
||||
type="primary"
|
||||
onClick={() => openAcceptanceModal(row)}
|
||||
>
|
||||
提交验收
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -270,119 +254,114 @@ export default function WorkerOrdersPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="worker-orders-overview-grid">
|
||||
<Card className="worker-orders-overview-card total" bordered={false}>
|
||||
<span className="worker-orders-overview-label">全部订单</span>
|
||||
<strong className="worker-orders-overview-value">
|
||||
{Number(overviewQuery.data?.all || 0)}
|
||||
</strong>
|
||||
<span className="worker-orders-overview-note">当前账号历史接单总数</span>
|
||||
</Card>
|
||||
{STATUS_OVERVIEW.map((item) => (
|
||||
<Card
|
||||
key={item.key}
|
||||
className={`worker-orders-overview-card tone-${item.tone}`}
|
||||
bordered={false}
|
||||
>
|
||||
<span className="worker-orders-overview-label">{item.label}</span>
|
||||
<strong className="worker-orders-overview-value">
|
||||
{Number(overviewQuery.data?.[item.key] || 0)}
|
||||
</strong>
|
||||
<span className="worker-orders-overview-note">{item.note}</span>
|
||||
</Card>
|
||||
<Row gutter={[16, 16]}>
|
||||
{STATUS_SUMMARY_ITEMS.map((item) => (
|
||||
<Col key={item.key} xs={24} sm={12} xl={8} xxl={4}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={item.label}
|
||||
value={Number(overviewQuery.data?.[item.key] || 0)}
|
||||
suffix="单"
|
||||
loading={overviewQuery.isLoading}
|
||||
/>
|
||||
<Typography.Text type="secondary">{item.note}</Typography.Text>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<Card bordered={false} className="worker-orders-table-card">
|
||||
<div className="worker-orders-toolbar">
|
||||
<Space.Compact className="worker-orders-search">
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索工单号 / 订单号 / 商品名"
|
||||
prefix={<SearchOutlined />}
|
||||
onChange={(event) => {
|
||||
const nextValue = event.target.value
|
||||
setKeywordInput(nextValue)
|
||||
if (!nextValue.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
<Card bordered={false}>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||
<Space wrap size={12}>
|
||||
<Space.Compact style={{ width: '100%', maxWidth: 520 }}>
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索工单号 / 订单号 / 商品名"
|
||||
prefix={<SearchOutlined />}
|
||||
onChange={(event) => {
|
||||
const nextValue = event.target.value
|
||||
setKeywordInput(nextValue)
|
||||
if (!nextValue.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={applyKeywordSearch}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Typography.Text type="secondary">
|
||||
当前筛选:
|
||||
{STATUS_OPTIONS.find((item) => item.value === status)?.label || '全部订单'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
|
||||
<Segmented
|
||||
block
|
||||
value={status || 'all'}
|
||||
options={STATUS_OPTIONS.map((item) => {
|
||||
const countKey = item.value || 'all'
|
||||
return {
|
||||
value: item.value || 'all',
|
||||
label: `${item.label} (${Number(overviewQuery.data?.[countKey] || 0)})`,
|
||||
}
|
||||
})}
|
||||
onChange={(value) => {
|
||||
const nextStatus = String(value) === 'all' ? '' : String(value)
|
||||
setStatus(nextStatus)
|
||||
setPage(1)
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={applyKeywordSearch}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Typography.Text type="secondary">
|
||||
当前筛选:{STATUS_OPTIONS.find((item) => item.value === status)?.label || '全部订单'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Space>
|
||||
|
||||
<div className="worker-orders-status-tabs">
|
||||
{STATUS_OPTIONS.map((item) => {
|
||||
const active = status === item.value
|
||||
const countKey = item.value || 'all'
|
||||
return (
|
||||
<button
|
||||
key={item.value || 'all'}
|
||||
type="button"
|
||||
className={`worker-orders-status-tab${active ? ' is-active' : ''}`}
|
||||
onClick={() => {
|
||||
setStatus(item.value)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<strong>{Number(overviewQuery.data?.[countKey] || 0)}</strong>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Table<WorkOrder>
|
||||
rowKey="workOrderId"
|
||||
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, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
},
|
||||
}}
|
||||
scroll={{ x: 1160 }}
|
||||
/>
|
||||
<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, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
},
|
||||
}}
|
||||
scroll={{ x: 1160 }}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Drawer
|
||||
@@ -402,7 +381,11 @@ export default function WorkerOrdersPage() {
|
||||
{formatStatus(detailOrder.status)}
|
||||
</Tag>
|
||||
{canSubmitAcceptance(detailOrder) ? (
|
||||
<Button type="primary" icon={<CheckOutlined />} onClick={() => openAcceptanceModal(detailOrder)}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckOutlined />}
|
||||
onClick={() => openAcceptanceModal(detailOrder)}
|
||||
>
|
||||
提交验收
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
@@ -12,22 +12,27 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { useRef, useState, type ReactNode, type RefObject } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
|
||||
import {
|
||||
@@ -39,13 +44,14 @@ import {
|
||||
fetchWorkerWalletLedgers,
|
||||
logoutWorker,
|
||||
} from '@/services/worker'
|
||||
import { clearWorkerSession } from '@/utils/worker-auth'
|
||||
import type {
|
||||
WorkerFinanceRequest,
|
||||
WorkerProfileSummary,
|
||||
WorkerUser,
|
||||
WorkerWalletLedger,
|
||||
} from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
import { clearWorkerSession } from '@/utils/worker-auth'
|
||||
|
||||
type RechargeFormValues = {
|
||||
amount?: number
|
||||
@@ -66,13 +72,21 @@ type PasswordFormValues = {
|
||||
confirmPassword?: string
|
||||
}
|
||||
|
||||
type MetricCardItem = {
|
||||
label: string
|
||||
value: number
|
||||
note: string
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
precision?: number
|
||||
}
|
||||
|
||||
export default function WorkerProfilePage() {
|
||||
const { message } = App.useApp()
|
||||
const navigate = useNavigate()
|
||||
const queryClient = useQueryClient()
|
||||
const ledgerSectionRef = useRef<HTMLDivElement | null>(null)
|
||||
const requestSectionRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const [activeTab, setActiveTab] = useState('account')
|
||||
const [ledgerPage, setLedgerPage] = useState(1)
|
||||
const [ledgerPageSize, setLedgerPageSize] = useState(10)
|
||||
const [ledgerType, setLedgerType] = useState('')
|
||||
@@ -152,21 +166,17 @@ export default function WorkerProfilePage() {
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToSection(ref: RefObject<HTMLDivElement | null>) {
|
||||
ref.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
|
||||
function jumpToLedgerSection(nextLedgerType = '') {
|
||||
setLedgerType(nextLedgerType)
|
||||
setLedgerPage(1)
|
||||
scrollToSection(ledgerSectionRef)
|
||||
setActiveTab('ledger')
|
||||
}
|
||||
|
||||
function jumpToRequestSection(nextRequestType = '', nextStatus = '') {
|
||||
setRequestType(nextRequestType)
|
||||
setRequestStatus(nextStatus)
|
||||
setRequestPage(1)
|
||||
scrollToSection(requestSectionRef)
|
||||
setActiveTab('requests')
|
||||
}
|
||||
|
||||
async function submitRecharge(values: RechargeFormValues) {
|
||||
@@ -232,7 +242,7 @@ export default function WorkerProfilePage() {
|
||||
title: '时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 180,
|
||||
render: (value) => String(value || '-'),
|
||||
render: (value) => formatDateTime(String(value || '')),
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
@@ -287,7 +297,7 @@ export default function WorkerProfilePage() {
|
||||
title: '申请时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 180,
|
||||
render: (value) => String(value || '-'),
|
||||
render: (value) => formatDateTime(String(value || '')),
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
@@ -385,7 +395,7 @@ export default function WorkerProfilePage() {
|
||||
{!worker && !profileQuery.isLoading ? <Empty description="暂无打手信息" /> : null}
|
||||
|
||||
{worker ? (
|
||||
<>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
{!canUseFinanceActions ? (
|
||||
<Alert
|
||||
showIcon
|
||||
@@ -394,285 +404,280 @@ export default function WorkerProfilePage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Card className="worker-profile-hero" bordered={false}>
|
||||
<div className="worker-profile-hero-head">
|
||||
<div className="worker-profile-identity">
|
||||
<div className="worker-profile-avatar">
|
||||
{String(worker.displayName || worker.username || '打')
|
||||
.trim()
|
||||
.slice(0, 1)
|
||||
.toUpperCase()}
|
||||
</div>
|
||||
<div className="worker-profile-identity-copy">
|
||||
<div className="worker-profile-identity-title">
|
||||
<Typography.Title level={4}>
|
||||
{worker.displayName || worker.username}
|
||||
</Typography.Title>
|
||||
<Space size={[6, 6]} wrap>
|
||||
<Tag className="worker-profile-tag">
|
||||
{worker.level?.name || '未分级'}
|
||||
</Tag>
|
||||
<Tag color={resolveWorkerStatusColor(worker.status)}>
|
||||
{formatWorkerStatus(worker.status)}
|
||||
</Tag>
|
||||
</Space>
|
||||
</div>
|
||||
<Typography.Text>
|
||||
账号:{worker.username}
|
||||
</Typography.Text>
|
||||
<Typography.Text>
|
||||
手机号:{worker.phone || '-'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-profile-hero-side">
|
||||
<Space direction="vertical" size={4}>
|
||||
<Typography.Text>累计充值</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{formatMoney(worker.wallet.totalCreditedAmount)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Typography.Text>累计结算</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{formatMoney(worker.wallet.totalSettledAmount)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-profile-metrics">
|
||||
{buildMetricCards(worker, summary).map((item) => (
|
||||
<div key={item.label} className="worker-profile-metric">
|
||||
<span className="worker-profile-metric-label">{item.label}</span>
|
||||
<strong className="worker-profile-metric-value">{item.value}</strong>
|
||||
<span className="worker-profile-metric-note">{item.note}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="worker-profile-actions">
|
||||
<ProfileActionCard
|
||||
icon={<WalletOutlined />}
|
||||
title="充值申请"
|
||||
description="线下充值后提交金额与备注,等待管理员入账。"
|
||||
tone="blue"
|
||||
disabled={!canUseFinanceActions}
|
||||
onClick={() => setRechargeOpen(true)}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<BankOutlined />}
|
||||
title="提现申请"
|
||||
description="填写收款账号并发起提现,系统会保留申请记录。"
|
||||
tone="green"
|
||||
disabled={!canUseFinanceActions}
|
||||
onClick={() => setWithdrawOpen(true)}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<HistoryOutlined />}
|
||||
title="资金流水"
|
||||
description="查看充值、押金冻结、结算奖励等钱包变动明细。"
|
||||
tone="slate"
|
||||
onClick={() => jumpToLedgerSection()}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<SafetyCertificateOutlined />}
|
||||
title="提现记录"
|
||||
description="快速查看提现申请状态、审核备注和收款信息。"
|
||||
tone="orange"
|
||||
onClick={() => jumpToRequestSection('withdraw')}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<LockOutlined />}
|
||||
title="修改密码"
|
||||
description="更新接单账号密码,修改后会要求重新登录。"
|
||||
tone="violet"
|
||||
onClick={() => setPasswordOpen(true)}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<CustomerServiceOutlined />}
|
||||
title="联系管理员"
|
||||
description="查看处理说明和当前账号资料,沟通充值、提现或审核问题。"
|
||||
tone="amber"
|
||||
onClick={() => setContactOpen(true)}
|
||||
/>
|
||||
<ProfileActionCard
|
||||
icon={<LogoutOutlined />}
|
||||
title="退出登录"
|
||||
description="当前设备退出接单端登录,会清理本地登录态。"
|
||||
tone="rose"
|
||||
onClick={submitLogout}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card title="账号信息" bordered={false}>
|
||||
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
||||
<Descriptions.Item label="账号">{worker.username}</Descriptions.Item>
|
||||
<Descriptions.Item label="昵称">{worker.displayName}</Descriptions.Item>
|
||||
<Descriptions.Item label="手机号">{worker.phone || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="审核状态">
|
||||
<Card size="small">
|
||||
<Space size={16} wrap align="start">
|
||||
<Avatar size={64}>
|
||||
{resolveWorkerInitial(worker)}
|
||||
</Avatar>
|
||||
<Space direction="vertical" size={2}>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||
{worker.displayName || worker.username}
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
账号:{worker.username}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
手机号:{worker.phone || '-'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Space size={[8, 8]} wrap>
|
||||
<Tag color={resolveWorkerStatusColor(worker.status)}>
|
||||
{formatWorkerStatus(worker.status)}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="当前等级">
|
||||
{worker.level?.name || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="免押额度">
|
||||
{formatMoney(worker.level?.permissions.depositFreeAmount || 0)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最大同时接单">
|
||||
{worker.level?.permissions.maxActiveOrders || 0} 单
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核备注">
|
||||
{worker.reviewNote || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="注册时间">
|
||||
{worker.createdAt || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最近更新时间">
|
||||
{worker.updatedAt || '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Tag>{worker.level?.name || '未分级'}</Tag>
|
||||
</Space>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<div ref={ledgerSectionRef}>
|
||||
<Card
|
||||
title="资金流水"
|
||||
bordered={false}
|
||||
extra={
|
||||
<Space>
|
||||
<Select
|
||||
value={ledgerType}
|
||||
style={{ width: 170 }}
|
||||
options={[
|
||||
{ value: '', label: '全部流水' },
|
||||
{ value: 'manual_credit', label: '人工充值' },
|
||||
{ value: 'deposit_freeze', label: '冻结押金' },
|
||||
{ value: 'deposit_release', label: '释放押金' },
|
||||
{ value: 'deposit_deduction', label: '扣除押金' },
|
||||
{ value: 'reward_settlement', label: '结算报酬' },
|
||||
]}
|
||||
onChange={(nextType) => {
|
||||
setLedgerType(nextType)
|
||||
setLedgerPage(1)
|
||||
}}
|
||||
<Row gutter={[16, 16]}>
|
||||
{buildMetricCards(worker, summary).map((item) => (
|
||||
<Col key={item.label} xs={24} sm={12} xl={8}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={item.label}
|
||||
value={item.value}
|
||||
prefix={item.prefix}
|
||||
suffix={item.suffix}
|
||||
precision={item.precision}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={ledgersQuery.isFetching}
|
||||
onClick={() => ledgersQuery.refetch()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</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 }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<Typography.Text type="secondary">{item.note}</Typography.Text>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<div ref={requestSectionRef}>
|
||||
<Card
|
||||
title="申请记录"
|
||||
bordered={false}
|
||||
extra={
|
||||
<Space>
|
||||
<Select
|
||||
value={requestType}
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ value: '', label: '全部类型' },
|
||||
{ value: 'recharge', label: '充值申请' },
|
||||
{ value: 'withdraw', label: '提现申请' },
|
||||
]}
|
||||
onChange={(nextType) => {
|
||||
setRequestType(nextType)
|
||||
setRequestPage(1)
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={requestStatus}
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ value: '', label: '全部状态' },
|
||||
{ value: 'pending', label: '待处理' },
|
||||
{ value: 'approved', label: '已通过' },
|
||||
{ value: 'rejected', label: '已拒绝' },
|
||||
{ value: 'cancelled', label: '已取消' },
|
||||
]}
|
||||
onChange={(nextStatus) => {
|
||||
setRequestStatus(nextStatus)
|
||||
setRequestPage(1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={requestsQuery.isFetching}
|
||||
onClick={() => requestsQuery.refetch()}
|
||||
<Card title="快捷操作" size="small">
|
||||
<Space wrap size={[12, 12]}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<WalletOutlined />}
|
||||
disabled={!canUseFinanceActions}
|
||||
onClick={() => setRechargeOpen(true)}
|
||||
>
|
||||
充值申请
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<BankOutlined />}
|
||||
disabled={!canUseFinanceActions}
|
||||
onClick={() => setWithdrawOpen(true)}
|
||||
>
|
||||
提现申请
|
||||
</Button>
|
||||
<Button
|
||||
icon={<HistoryOutlined />}
|
||||
onClick={() => jumpToLedgerSection()}
|
||||
>
|
||||
查看资金流水
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SafetyCertificateOutlined />}
|
||||
onClick={() => jumpToRequestSection('withdraw')}
|
||||
>
|
||||
查看提现记录
|
||||
</Button>
|
||||
<Button icon={<LockOutlined />} onClick={() => setPasswordOpen(true)}>
|
||||
修改密码
|
||||
</Button>
|
||||
<Button
|
||||
icon={<CustomerServiceOutlined />}
|
||||
onClick={() => setContactOpen(true)}
|
||||
>
|
||||
联系管理员
|
||||
</Button>
|
||||
<Button danger icon={<LogoutOutlined />} onClick={submitLogout}>
|
||||
退出登录
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={[
|
||||
{
|
||||
key: 'account',
|
||||
label: '账号信息',
|
||||
children: (
|
||||
<Card size="small">
|
||||
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
||||
<Descriptions.Item label="账号">{worker.username}</Descriptions.Item>
|
||||
<Descriptions.Item label="昵称">
|
||||
{worker.displayName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="手机号">
|
||||
{worker.phone || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核状态">
|
||||
<Tag color={resolveWorkerStatusColor(worker.status)}>
|
||||
{formatWorkerStatus(worker.status)}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="当前等级">
|
||||
{worker.level?.name || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="免押额度">
|
||||
{formatMoney(worker.level?.permissions.depositFreeAmount || 0)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最大同时接单">
|
||||
{worker.level?.permissions.maxActiveOrders || 0} 单
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核备注">
|
||||
{worker.reviewNote || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="注册时间">
|
||||
{formatDateTime(worker.createdAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最近更新时间">
|
||||
{formatDateTime(worker.updatedAt)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ledger',
|
||||
label: '资金流水',
|
||||
children: (
|
||||
<Card
|
||||
size="small"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Select
|
||||
value={ledgerType}
|
||||
style={{ width: 170 }}
|
||||
options={[
|
||||
{ value: '', label: '全部流水' },
|
||||
{ value: 'manual_credit', label: '人工充值' },
|
||||
{ value: 'deposit_freeze', label: '冻结押金' },
|
||||
{ value: 'deposit_release', label: '释放押金' },
|
||||
{ value: 'deposit_deduction', label: '扣除押金' },
|
||||
{ value: 'reward_settlement', label: '结算报酬' },
|
||||
]}
|
||||
onChange={(nextType) => {
|
||||
setLedgerType(nextType)
|
||||
setLedgerPage(1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={ledgersQuery.isFetching}
|
||||
onClick={() => ledgersQuery.refetch()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</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 }}
|
||||
/>
|
||||
</Card>
|
||||
</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>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'requests',
|
||||
label: '申请记录',
|
||||
children: (
|
||||
<Card
|
||||
size="small"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Select
|
||||
value={requestType}
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ value: '', label: '全部类型' },
|
||||
{ value: 'recharge', label: '充值申请' },
|
||||
{ value: 'withdraw', label: '提现申请' },
|
||||
]}
|
||||
onChange={(nextType) => {
|
||||
setRequestType(nextType)
|
||||
setRequestPage(1)
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={requestStatus}
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ value: '', label: '全部状态' },
|
||||
{ value: 'pending', label: '待处理' },
|
||||
{ value: 'approved', label: '已通过' },
|
||||
{ value: 'rejected', label: '已拒绝' },
|
||||
{ value: 'cancelled', label: '已取消' },
|
||||
]}
|
||||
onChange={(nextStatus) => {
|
||||
setRequestStatus(nextStatus)
|
||||
setRequestPage(1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={requestsQuery.isFetching}
|
||||
onClick={() => requestsQuery.refetch()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</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 }}
|
||||
/>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
) : null}
|
||||
|
||||
<Modal
|
||||
@@ -697,12 +702,7 @@ export default function WorkerProfilePage() {
|
||||
name="amount"
|
||||
rules={[{ required: true, message: '请输入充值金额' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={0.01}
|
||||
step={1}
|
||||
addonAfter="元"
|
||||
className="full-width"
|
||||
/>
|
||||
<InputNumber min={0.01} step={1} addonAfter="元" className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item label="备注说明" name="note">
|
||||
<Input.TextArea
|
||||
@@ -740,12 +740,7 @@ export default function WorkerProfilePage() {
|
||||
name="amount"
|
||||
rules={[{ required: true, message: '请输入提现金额' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={0.01}
|
||||
step={1}
|
||||
addonAfter="元"
|
||||
className="full-width"
|
||||
/>
|
||||
<InputNumber min={0.01} step={1} addonAfter="元" className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="提现方式"
|
||||
@@ -794,11 +789,7 @@ export default function WorkerProfilePage() {
|
||||
}}
|
||||
onOk={() => passwordForm.submit()}
|
||||
>
|
||||
<Form
|
||||
form={passwordForm}
|
||||
layout="vertical"
|
||||
onFinish={submitPasswordChange}
|
||||
>
|
||||
<Form form={passwordForm} layout="vertical" onFinish={submitPasswordChange}>
|
||||
<Form.Item
|
||||
label="当前密码"
|
||||
name="currentPassword"
|
||||
@@ -880,56 +871,48 @@ export default function WorkerProfilePage() {
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileActionCard({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
tone,
|
||||
disabled = false,
|
||||
onClick,
|
||||
}: {
|
||||
icon: ReactNode
|
||||
title: string
|
||||
description: string
|
||||
tone: 'blue' | 'green' | 'slate' | 'orange' | 'violet' | 'amber' | 'rose'
|
||||
disabled?: boolean
|
||||
onClick: () => void
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={`worker-profile-action-card is-${tone}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="worker-profile-action-icon">{icon}</span>
|
||||
<span className="worker-profile-action-title">{title}</span>
|
||||
<span className="worker-profile-action-description">{description}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function buildMetricCards(worker: WorkerUser, summary?: WorkerProfileSummary) {
|
||||
function buildMetricCards(worker: WorkerUser, summary?: WorkerProfileSummary): MetricCardItem[] {
|
||||
return [
|
||||
{
|
||||
label: '账户余额',
|
||||
value: formatMoney(worker.wallet.availableAmount),
|
||||
value: toYuan(worker.wallet.availableAmount),
|
||||
note: '可用于抢单冻结或提现申请',
|
||||
prefix: '¥',
|
||||
precision: 2,
|
||||
},
|
||||
{
|
||||
label: '冻结押金',
|
||||
value: formatMoney(worker.wallet.frozenDepositAmount),
|
||||
value: toYuan(worker.wallet.frozenDepositAmount),
|
||||
note: '抢单后冻结,验收通过会自动释放',
|
||||
prefix: '¥',
|
||||
precision: 2,
|
||||
},
|
||||
{
|
||||
label: '累计充值',
|
||||
value: toYuan(worker.wallet.totalCreditedAmount),
|
||||
note: '管理员已处理的累计充值金额',
|
||||
prefix: '¥',
|
||||
precision: 2,
|
||||
},
|
||||
{
|
||||
label: '累计结算',
|
||||
value: toYuan(worker.wallet.totalSettledAmount),
|
||||
note: '已经结算入账的报酬金额',
|
||||
prefix: '¥',
|
||||
precision: 2,
|
||||
},
|
||||
{
|
||||
label: '提现中金额',
|
||||
value: formatMoney(summary?.pendingWithdrawAmount),
|
||||
value: toYuan(summary?.pendingWithdrawAmount),
|
||||
note: '待管理员处理的提现申请金额',
|
||||
prefix: '¥',
|
||||
precision: 2,
|
||||
},
|
||||
{
|
||||
label: '累计成单',
|
||||
value: `${Number(summary?.acceptedOrderCount || 0)} 单`,
|
||||
value: Number(summary?.acceptedOrderCount || 0),
|
||||
note: '已完成并验收通过的工单数量',
|
||||
suffix: '单',
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -945,10 +928,21 @@ function resolveAvailableForWithdraw(
|
||||
)
|
||||
}
|
||||
|
||||
function resolveWorkerInitial(worker: WorkerUser | undefined) {
|
||||
return String(worker?.displayName || worker?.username || '打')
|
||||
.trim()
|
||||
.slice(0, 1)
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
function formatMoney(value: number | undefined) {
|
||||
return `¥${((Number(value || 0) || 0) / 100).toFixed(2)}`
|
||||
}
|
||||
|
||||
function toYuan(value: number | undefined) {
|
||||
return Number(((Number(value || 0) || 0) / 100).toFixed(2))
|
||||
}
|
||||
|
||||
function formatWorkerStatus(status: string) {
|
||||
if (status === 'active') return '已审核'
|
||||
if (status === 'pending_review') return '待审核'
|
||||
|
||||
@@ -1476,10 +1476,6 @@ select {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.worker-profile-hero-head {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-hall-toolbar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
@@ -1713,20 +1709,25 @@ select {
|
||||
|
||||
.worker-hall-summary-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.worker-hall-summary-item {
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
padding: 14px 16px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-summary-item + .worker-hall-summary-item {
|
||||
border-left: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.worker-hall-summary-label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
@@ -1762,25 +1763,18 @@ select {
|
||||
|
||||
.worker-hall-card {
|
||||
min-width: 0;
|
||||
padding: 18px 18px 16px;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #edf0f5;
|
||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.04);
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.worker-hall-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-title {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -1788,65 +1782,32 @@ select {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.worker-hall-card-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
.worker-hall-card-link {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount-row {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.worker-hall-card-order {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-card-main {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount {
|
||||
color: #f97316;
|
||||
font-size: 34px;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.worker-hall-card-side {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: #94a3b8;
|
||||
.worker-hall-card-deposit,
|
||||
.worker-hall-card-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row span {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row strong {
|
||||
color: #334155;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.worker-hall-card-danger {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
.worker-hall-card-safe {
|
||||
color: #16a34a !important;
|
||||
}
|
||||
|
||||
.worker-hall-card-actions {
|
||||
@@ -1862,332 +1823,6 @@ select {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-orders-overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card {
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.worker-orders-overview-card .ant-card-body {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.total {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(20, 50, 74, 0.96), rgba(27, 93, 115, 0.92)),
|
||||
#14324a;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.total .worker-orders-overview-label,
|
||||
.worker-orders-overview-card.total .worker-orders-overview-value,
|
||||
.worker-orders-overview-card.total .worker-orders-overview-note {
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.tone-blue {
|
||||
border-top: 3px solid #2563eb;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.tone-gold {
|
||||
border-top: 3px solid #d97706;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.tone-red {
|
||||
border-top: 3px solid #dc2626;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.tone-green {
|
||||
border-top: 3px solid #059669;
|
||||
}
|
||||
|
||||
.worker-orders-overview-card.tone-slate {
|
||||
border-top: 3px solid #64748b;
|
||||
}
|
||||
|
||||
.worker-orders-overview-label {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.worker-orders-overview-value {
|
||||
color: #0f172a;
|
||||
font-size: 28px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.worker-orders-overview-note {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.worker-orders-table-card .ant-card-body {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.worker-orders-toolbar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.worker-orders-search {
|
||||
width: min(520px, 100%);
|
||||
}
|
||||
|
||||
.worker-orders-status-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.worker-orders-status-tab {
|
||||
padding: 10px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #dbe4ee;
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.18s ease,
|
||||
background 0.18s ease,
|
||||
color 0.18s ease,
|
||||
transform 0.18s ease;
|
||||
}
|
||||
|
||||
.worker-orders-status-tab:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: #90b7cf;
|
||||
}
|
||||
|
||||
.worker-orders-status-tab strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.worker-orders-status-tab.is-active {
|
||||
border-color: transparent;
|
||||
background: linear-gradient(135deg, #1b5d73, #237b77);
|
||||
color: #f8fafc;
|
||||
box-shadow: 0 10px 24px rgba(27, 93, 115, 0.24);
|
||||
}
|
||||
|
||||
.worker-profile-hero {
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(25, 94, 125, 0.14);
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(116, 227, 214, 0.18), transparent 30%),
|
||||
linear-gradient(135deg, #14324a 0%, #1b5d73 55%, #237b77 100%);
|
||||
}
|
||||
|
||||
.worker-profile-hero .ant-card-body {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.worker-profile-hero-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.worker-profile-identity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.worker-profile-avatar {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 22px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.worker-profile-identity-copy {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.worker-profile-identity-copy .ant-typography {
|
||||
color: rgba(239, 248, 255, 0.88);
|
||||
}
|
||||
|
||||
.worker-profile-identity-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.worker-profile-identity-title .ant-typography {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.worker-profile-tag {
|
||||
border-color: transparent;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.worker-profile-hero-side {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.worker-profile-hero-side .ant-space {
|
||||
padding: 10px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.worker-profile-hero-side .ant-typography {
|
||||
color: rgba(239, 248, 255, 0.88);
|
||||
}
|
||||
|
||||
.worker-profile-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.worker-profile-metric {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 16px 18px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.worker-profile-metric-label,
|
||||
.worker-profile-metric-note {
|
||||
color: rgba(239, 248, 255, 0.75);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-profile-metric-value {
|
||||
color: #ffffff;
|
||||
font-size: 26px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.worker-profile-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-profile-action-card {
|
||||
width: 100%;
|
||||
padding: 18px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #ffffff;
|
||||
text-align: left;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
box-shadow 0.18s ease,
|
||||
border-color 0.18s ease;
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.worker-profile-action-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.worker-profile-action-card:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.58;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.worker-profile-action-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.worker-profile-action-title {
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.worker-profile-action-description {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-blue .worker-profile-action-icon {
|
||||
background: #dbeafe;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-green .worker-profile-action-icon {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-slate .worker-profile-action-icon {
|
||||
background: #e2e8f0;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-orange .worker-profile-action-icon {
|
||||
background: #ffedd5;
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-violet .worker-profile-action-icon {
|
||||
background: #ede9fe;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-amber .worker-profile-action-icon {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.worker-profile-action-card.is-rose .worker-profile-action-icon {
|
||||
background: #ffe4e6;
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
.worker-amount-positive {
|
||||
color: #047857;
|
||||
}
|
||||
@@ -2253,12 +1888,7 @@ select {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-hall-summary-strip {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.worker-hall-toolbar,
|
||||
.worker-orders-toolbar {
|
||||
.worker-hall-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -2275,23 +1905,20 @@ select {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.worker-hall-card-head,
|
||||
.worker-hall-card-actions {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-orders-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.worker-hall-summary-strip {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.worker-orders-status-tab {
|
||||
flex: 1 1 calc(50% - 10px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.worker-profile-hero-side {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
.worker-hall-summary-item + .worker-hall-summary-item {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user