新增打手改昵称卡功能默认一张且一年冷却

This commit is contained in:
yml2213
2026-08-22 10:20:10 +08:00
parent 985e61209d
commit ba35d27976
13 changed files with 315 additions and 2 deletions
@@ -1,6 +1,7 @@
import {
Alert,
Button,
Checkbox,
Descriptions,
Form,
Image,
@@ -24,6 +25,11 @@ export type PasswordFormValues = {
confirmPassword?: string
}
export type RenameFormValues = {
displayName?: string
confirmed?: boolean
}
export function WorkerProfileAccountModals({
isMobile,
worker,
@@ -41,6 +47,11 @@ export function WorkerProfileAccountModals({
submitPasswordChange,
contactOpen,
setContactOpen,
renameOpen,
setRenameOpen,
submittingRename,
renameForm,
submitRename,
}: {
isMobile: boolean
worker: WorkerUser | undefined
@@ -58,6 +69,11 @@ export function WorkerProfileAccountModals({
submitPasswordChange: (values: PasswordFormValues) => Promise<void>
contactOpen: boolean
setContactOpen: (open: boolean) => void
renameOpen: boolean
setRenameOpen: (open: boolean) => void
submittingRename: boolean
renameForm: FormInstance<RenameFormValues>
submitRename: (values: RenameFormValues) => Promise<void>
}) {
return (
<>
@@ -220,6 +236,74 @@ export function WorkerProfileAccountModals({
</Typography.Paragraph>
</Space>
</Modal>
<Modal
title="修改昵称"
open={renameOpen}
width={isMobile ? '92%' : 560}
destroyOnHidden
footer={null}
onCancel={() => setRenameOpen(false)}
>
<Alert
showIcon
type="warning"
message="修改昵称后将锁定一年"
description={
<Space direction="vertical" size={0}>
<Typography.Text type="danger" strong>
</Typography.Text>
<Typography.Text type="secondary">
{worker?.renameCards ?? 1} 1
</Typography.Text>
</Space>
}
/>
<Form<RenameFormValues>
form={renameForm}
layout="vertical"
onFinish={(values) => void submitRename(values)}
style={{ marginTop: 16 }}
>
<Form.Item label="当前昵称">
<Typography.Text strong>
{worker?.displayName || worker?.username || '-'}
</Typography.Text>
</Form.Item>
<Form.Item
name="displayName"
label="新昵称"
rules={[
{ required: true, whitespace: true, message: '请输入新的昵称' },
{ max: 32, message: '昵称最长 32 个字符' },
{
validator: (_, value) =>
value && value.trim() === String(worker?.displayName || '').trim()
? Promise.reject(new Error('新昵称不能与当前昵称一致'))
: Promise.resolve(),
},
]}
>
<Input allowClear placeholder="最长 32 个字符,全平台不可重复" autoComplete="off" />
</Form.Item>
<Form.Item
name="confirmed"
valuePropName="checked"
rules={[
{
validator: (_, value) =>
value ? Promise.resolve() : Promise.reject(new Error('请先勾选确认')),
},
]}
>
<Checkbox> 1 </Checkbox>
</Form.Item>
<Button type="primary" block loading={submittingRename} htmlType="submit">
</Button>
</Form>
</Modal>
</>
)
}
@@ -5,9 +5,10 @@ import {
GlobalOutlined,
LockOutlined,
LogoutOutlined,
UserOutlined,
WalletOutlined,
} from '@ant-design/icons'
import { Avatar, Button, Card, Descriptions, Progress, Space, Tag, Typography } from 'antd'
import { Avatar, Button, Card, Descriptions, Progress, Space, Tag, Tooltip, Typography } from 'antd'
import type {
WorkerLevelProgress,
@@ -124,6 +125,9 @@ export function WorkerProfileDesktopActions({
onAddWithdrawalAccount,
onAddAlipayQrCode,
onChangePassword,
onRename,
renameDisabled,
renameHint,
onManageDevices,
onContact,
onLogout,
@@ -136,6 +140,9 @@ export function WorkerProfileDesktopActions({
onAddWithdrawalAccount: () => void
onAddAlipayQrCode: () => void
onChangePassword: () => void
onRename: () => void
renameDisabled: boolean
renameHint: string
onManageDevices: () => void
onContact: () => void
onLogout: () => void
@@ -177,6 +184,11 @@ export function WorkerProfileDesktopActions({
</Button>
) : null}
<Tooltip title={renameHint}>
<Button icon={<UserOutlined />} disabled={renameDisabled} onClick={onRename}>
</Button>
</Tooltip>
<Button icon={<LockOutlined />} onClick={onChangePassword}>
</Button>
@@ -6,6 +6,7 @@ import {
LockOutlined,
LogoutOutlined,
ReloadOutlined,
UserSwitchOutlined,
WalletOutlined,
} from '@ant-design/icons'
import { Alert, Avatar, Button, Progress, Tag } from 'antd'
@@ -37,6 +38,8 @@ type WorkerProfileMobileViewProps = {
onRecharge: () => void
onWithdraw: () => void
onChangePassword: () => void
onRename: () => void
renameDisabled: boolean
onManageDevices: () => void
onContact: () => void
onAddAlipayQrCode: () => void
@@ -59,6 +62,8 @@ export function WorkerProfileMobileView({
onRecharge,
onWithdraw,
onChangePassword,
onRename,
renameDisabled,
onManageDevices,
onContact,
onAddAlipayQrCode,
@@ -190,6 +195,14 @@ export function WorkerProfileMobileView({
</div>
<div className="worker-profile-mobile-tools-card">
<MobileTool
label="修改昵称"
icon={<UserSwitchOutlined />}
color="#4338ca"
background="#eef2ff"
disabled={renameDisabled}
onClick={onRename}
/>
<MobileTool
label="修改密码"
icon={<LockOutlined />}
@@ -15,12 +15,18 @@ import {
deleteWorkerSession,
fetchWorkerWalletLedgers,
logoutWorker,
renameWorkerAccount,
saveWorkerAlipayWithdrawalQrCode,
} from '@/services/worker'
import type { UploadedFile, WorkerSessionDevice } from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
import { useIsMobile } from '@/utils/use-is-mobile'
import { clearWorkerSession } from '@/utils/worker-auth'
import { WorkerProfileAccountModals, type PasswordFormValues } from './WorkerProfileAccountModals'
import {
WorkerProfileAccountModals,
type PasswordFormValues,
type RenameFormValues,
} from './WorkerProfileAccountModals'
import { WorkerProfileFinanceModals } from './WorkerProfileFinanceModals'
import { WorkerProfileMobileView } from './WorkerProfileMobileView'
import {
@@ -80,6 +86,9 @@ export default function WorkerProfilePage() {
const [withdrawalAccountOpen, setWithdrawalAccountOpen] = useState(false)
const [alipayQrCodeOpen, setAlipayQrCodeOpen] = useState(false)
const [passwordOpen, setPasswordOpen] = useState(false)
const [renameOpen, setRenameOpen] = useState(false)
const [submittingRename, setSubmittingRename] = useState(false)
const [renameForm] = Form.useForm<RenameFormValues>()
const [contactOpen, setContactOpen] = useState(false)
const [devicesOpen, setDevicesOpen] = useState(false)
const [submittingRecharge, setSubmittingRecharge] = useState(false)
@@ -119,6 +128,15 @@ export default function WorkerProfilePage() {
(account) => account.accountChannel === 'alipay',
)
const canUseFinanceActions = worker?.status === 'active'
const renameCards = worker?.renameCards ?? 1
const renameLocked =
Boolean(worker?.renameUnlockedAt) && Date.parse(worker?.renameUnlockedAt || '') > Date.now()
const canRename = renameCards > 0 && !renameLocked
const renameHint = renameLocked
? `修改昵称后一年内不可再次修改,${formatDateTime(worker?.renameUnlockedAt || null)} 后可改`
: renameCards <= 0
? '改名卡已用完'
: `剩余改名卡 ${renameCards} 张,修改昵称后一年内不可再次修改`
async function copyInviteCode() {
if (!worker?.inviteCode) return
@@ -326,6 +344,23 @@ export default function WorkerProfilePage() {
}
}
async function submitRename(values: RenameFormValues) {
setSubmittingRename(true)
try {
const response = await renameWorkerAccount({
displayName: String(values.displayName || '').trim(),
})
message.success(`昵称已修改为「${response.data.worker.displayName}」,一年内无法再次修改`)
setRenameOpen(false)
renameForm.resetFields()
await refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '修改昵称失败')
} finally {
setSubmittingRename(false)
}
}
async function submitPasswordChange(values: PasswordFormValues) {
setSubmittingPassword(true)
try {
@@ -440,6 +475,8 @@ export default function WorkerProfilePage() {
onRecharge={openRechargeModal}
onWithdraw={openWithdrawModal}
onChangePassword={() => setPasswordOpen(true)}
onRename={() => setRenameOpen(true)}
renameDisabled={!canRename}
onManageDevices={() => setDevicesOpen(true)}
onContact={() => setContactOpen(true)}
onAddAlipayQrCode={openAlipayQrCodeModal}
@@ -493,6 +530,9 @@ export default function WorkerProfilePage() {
onAddWithdrawalAccount={openWithdrawalAccountModal}
onAddAlipayQrCode={openAlipayQrCodeModal}
onChangePassword={() => setPasswordOpen(true)}
onRename={() => setRenameOpen(true)}
renameDisabled={!canRename}
renameHint={renameHint}
onManageDevices={() => setDevicesOpen(true)}
onContact={() => setContactOpen(true)}
onLogout={submitLogout}
@@ -627,6 +667,11 @@ export default function WorkerProfilePage() {
submitPasswordChange={submitPasswordChange}
contactOpen={contactOpen}
setContactOpen={setContactOpen}
renameOpen={renameOpen}
setRenameOpen={setRenameOpen}
submittingRename={submittingRename}
renameForm={renameForm}
submitRename={submitRename}
/>
</>
)
+7
View File
@@ -194,6 +194,13 @@ export function changeWorkerPassword(payload: { currentPassword: string; newPass
)
}
export function renameWorkerAccount(payload: { displayName: string }) {
return apiPost<{ worker: WorkerUser; reloginRequired: boolean }>(
'/api/v1/worker/profile/rename',
payload,
)
}
export function fetchWorkerHallOrders(params?: Record<string, unknown>) {
return apiGet<WorkerHallOrdersResponse>('/api/v1/worker/hall/orders', params)
}
@@ -37,6 +37,11 @@ export type WorkerUser = {
createdAt: string
updatedAt: string
reviewedAt: string | null
/** 剩余改名卡数量,注册默认 1 张 */
renameCards: number
lastRenamedAt: string | null
/** 上次改名后的一年锁定期结束时间 */
renameUnlockedAt: string | null
}
export type WorkerSessionDevice = {