feat(worker-platform): 优化打手我的寄卖卡片资料展示,前置展示打手自身上架账号资料

1. 后端 consignment-worker-service 在我的寄卖列表中解析并返回打手上架时填写的 sellerInfo(角色名、数字ID、区服系统、微信号/QQ、寄卖留言等);
2. 前端新增 ConsignmentSellerInfoBlock 卖家资料区块,提供角色名/ID高亮与一键复制功能;
3. 寄卖卡片布局升级为前置展示【我的寄卖账号资料】、后置展示【买家加好友资料】,多号上架时号主与买家清晰对应,彻底杜绝登错号加错人。
This commit is contained in:
yml2213
2026-09-19 14:19:35 +08:00
parent 0b4ee964ed
commit d40b5a4c1c
5 changed files with 277 additions and 11 deletions
@@ -70,6 +70,71 @@ function extractConsignmentGiftEvidenceFromWorkOrder(boundWo?: WorkOrderRow | nu
}
}
export type ConsignmentSellerInfo = {
characterName: string
characterId: string
sellerContact?: string
sellerPhone?: string
sellerNote?: string
system?: string
serverZone?: string
}
function extractConsignmentSellerInfoFromSubmission(
sub: Awaited<ReturnType<typeof listConsignmentSubmissions>>['items'][number],
): ConsignmentSellerInfo {
const content = toRecord(sub.content_json) || {}
let characterName = String(content.characterName || '').trim()
let characterId = String(content.characterId || '').trim()
if (!characterName && sub.seller_game_id) {
const raw = String(sub.seller_game_id).trim()
const match = raw.match(/^(.*?)\s*\((.*?)\)$/)
if (match) {
characterName = match[1]?.trim() || raw
if (!characterId) characterId = match[2]?.trim() || ''
} else {
characterName = raw
}
}
return {
characterName,
characterId,
sellerContact: String(sub.contact_info || '').trim(),
sellerPhone: String(sub.seller_phone || '').trim(),
sellerNote: String(content.sellerNote || '').trim(),
system: sub.system,
serverZone: sub.server_zone,
}
}
function extractConsignmentSellerInfoFromInventory(
inv: Awaited<ReturnType<typeof listConsignmentInventories>>['items'][number],
): ConsignmentSellerInfo {
let characterName = ''
let characterId = ''
if (inv.seller_game_id) {
const raw = String(inv.seller_game_id).trim()
const match = raw.match(/^(.*?)\s*\((.*?)\)$/)
if (match) {
characterName = match[1]?.trim() || raw
characterId = match[2]?.trim() || ''
} else {
characterName = raw
}
}
return {
characterName,
characterId,
sellerContact: String(inv.seller_contact || '').trim(),
sellerPhone: String(inv.seller_phone || '').trim(),
sellerNote: String(inv.remark || '').trim(),
system: inv.system,
serverZone: inv.server_zone,
}
}
export function getConsignmentPublishDepositFen() {
return getConfiguredConsignmentPublishDepositFen()
}
@@ -358,6 +423,7 @@ export async function listWorkerConsignments(workerId: number) {
friendAddedAt: inv?.bound_work_order_id
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
: null,
sellerInfo: extractConsignmentSellerInfoFromSubmission(sub),
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
}
}),
@@ -397,6 +463,7 @@ export async function listWorkerConsignments(workerId: number) {
friendAddedAt: inv.bound_work_order_id
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
: null,
sellerInfo: extractConsignmentSellerInfoFromInventory(inv),
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
}
}),
@@ -929,6 +929,119 @@ function SpecTags({
)
}
function ConsignmentSellerInfoBlock({
sellerInfo,
record,
}: {
sellerInfo?: WorkerConsignmentRecord['sellerInfo']
record: WorkerConsignmentRecord
}) {
const { message } = App.useApp()
const copyText = (text: string, label: string) => {
if (!text) return
navigator.clipboard
.writeText(text)
.then(() => message.success(`已复制${label}:${text}`))
.catch(() => message.error('复制失败,请手动长按复制'))
}
const characterName = sellerInfo?.characterName || ''
const characterId = sellerInfo?.characterId || ''
const serverZone = sellerInfo?.serverZone || record.serverZone || ''
const system = sellerInfo?.system || record.system || ''
const sellerContact = sellerInfo?.sellerContact || ''
const sellerNote = sellerInfo?.sellerNote || ''
const copyAll = () => {
const parts: string[] = []
if (characterName) parts.push(`卖家角色: ${characterName}`)
if (characterId) parts.push(`角色ID: ${characterId}`)
if (serverZone) parts.push(`区服: ${serverZone}`)
if (system) parts.push(`系统: ${system}`)
if (sellerContact) parts.push(`联系方式: ${sellerContact}`)
copyText(parts.join(' | '), '我的寄卖账号资料')
}
return (
<div className="worker-consignment-seller-box">
<div className="worker-consignment-seller-header">
<Space size={6}>
<UserOutlined style={{ color: '#d97706' }} />
<Typography.Text strong style={{ color: '#b45309', fontSize: 13 }}>
我的寄卖账号 / 角色资料
</Typography.Text>
</Space>
<Button
type="link"
size="small"
icon={<CopyOutlined />}
style={{ padding: 0, height: 'auto', fontSize: 12, color: '#d97706' }}
onClick={copyAll}
>
一键复制资料
</Button>
</div>
<div className="worker-consignment-seller-grid">
{characterName ? (
<div className="seller-field-cell">
<span className="seller-field-label">我的游戏角色名</span>
<Typography.Text
strong
copyable={{ text: characterName }}
className="seller-field-highlight"
>
{characterName}
</Typography.Text>
</div>
) : null}
{characterId ? (
<div className="seller-field-cell">
<span className="seller-field-label">角色数字ID</span>
<Typography.Text
strong
copyable={{ text: characterId }}
className="seller-field-highlight"
>
{characterId}
</Typography.Text>
</div>
) : null}
{serverZone || system ? (
<div className="seller-field-cell">
<span className="seller-field-label">上架区服与系统</span>
<Space size={4} style={{ marginTop: 2 }}>
{system ? <Tag color="gold">{system}</Tag> : null}
{serverZone ? <Tag color="orange">{serverZone}</Tag> : null}
</Space>
</div>
) : null}
{sellerContact ? (
<div className="seller-field-cell">
<span className="seller-field-label">微信号/QQ</span>
<Typography.Text
copyable={{ text: sellerContact }}
style={{ fontSize: 13, color: '#78350f' }}
>
{sellerContact}
</Typography.Text>
</div>
) : null}
</div>
{sellerNote ? (
<div className="worker-consignment-seller-note-wrap">
<span>寄卖备注:{sellerNote}</span>
</div>
) : null}
</div>
)
}
function ConsignmentBuyerInfoBlock({
buyerInfo,
}: {
@@ -946,8 +1059,8 @@ function ConsignmentBuyerInfoBlock({
const copyAll = () => {
const parts: string[] = []
if (buyerInfo.gameNickname) parts.push(`角色名: ${buyerInfo.gameNickname}`)
if (buyerInfo.gameId) parts.push(`游戏ID: ${buyerInfo.gameId}`)
if (buyerInfo.gameNickname) parts.push(`买家角色: ${buyerInfo.gameNickname}`)
if (buyerInfo.gameId) parts.push(`角色ID: ${buyerInfo.gameId}`)
if (buyerInfo.serverZone) parts.push(`区服: ${buyerInfo.serverZone}`)
if (buyerInfo.system) parts.push(`系统: ${buyerInfo.system}`)
copyText(parts.join(' | '), '买家加好友资料')
@@ -959,7 +1072,7 @@ function ConsignmentBuyerInfoBlock({
<Space size={6}>
<UserOutlined style={{ color: '#1677ff' }} />
<Typography.Text strong style={{ color: '#1677ff', fontSize: 13 }}>
买家加好友资料
买家加好友资料(请在游戏内添加此人)
</Typography.Text>
</Space>
<Button
@@ -1097,7 +1210,11 @@ function ConsignmentRecordCard({
{record.reviewNote ? (
<Typography.Text type="danger">审核说明:{record.reviewNote}</Typography.Text>
) : null}
{record.buyerInfo ? <ConsignmentBuyerInfoBlock buyerInfo={record.buyerInfo} /> : null}
<div className={`worker-consignment-info-deck ${record.buyerInfo ? 'is-dual' : ''}`}>
<ConsignmentSellerInfoBlock sellerInfo={record.sellerInfo} record={record} />
{record.buyerInfo ? <ConsignmentBuyerInfoBlock buyerInfo={record.buyerInfo} /> : null}
</div>
{record.hint ? (
<Typography.Text type="secondary" className="worker-consignment-record-hint">
{record.hint}
+4 -2
View File
@@ -23,7 +23,7 @@ import type {
WorkerUser,
WorkerWalletLedger,
} from '@/types/worker-platform'
import type { ConsignmentBuyerInfo } from '@/types/consignment'
import type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
import { getWorkerDeviceId } from '@/utils/worker-auth'
@@ -450,11 +450,13 @@ export type WorkerConsignmentRecord = {
/** 赠送冷却截止时间,前端拿它做倒计时 */
giftAvailableAt: string | null
friendAddedAt: string | null
/** 卖家自己寄卖时填写的账号/角色资料 */
sellerInfo?: ConsignmentSellerInfo | null
/** 匹配工单的买家资料(用于加好友) */
buyerInfo?: ConsignmentBuyerInfo | null
}
export type { ConsignmentBuyerInfo } from '@/types/consignment'
export type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
export type WorkerConsignmentListResponse = {
publishDepositFen: number
+75 -5
View File
@@ -4467,13 +4467,81 @@ body {
}
}
.worker-consignment-info-deck {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 6px;
margin-bottom: 6px;
}
@media (min-width: 992px) {
.worker-consignment-info-deck.is-dual {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
align-items: stretch;
}
}
.worker-consignment-seller-box {
padding: 10px 12px;
border-radius: 8px;
background: #fffbeb;
border: 1px solid #fde68a;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.worker-consignment-seller-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.worker-consignment-seller-grid {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
margin-bottom: 6px;
}
.seller-field-cell {
display: flex;
flex-direction: column;
gap: 2px;
}
.seller-field-label {
font-size: 11px;
color: #92400e;
font-weight: 500;
}
.seller-field-highlight {
font-size: 14px;
color: #78350f;
font-weight: 600;
}
.worker-consignment-seller-note-wrap {
margin-top: 6px;
padding-top: 6px;
border-top: 1px dashed #fcd34d;
font-size: 12px;
color: #92400e;
}
.worker-consignment-buyer-box {
margin-top: 4px;
margin-bottom: 4px;
padding: 10px 12px;
border-radius: 8px;
background: #eff6ff;
border: 1px solid #bfdbfe;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.worker-consignment-buyer-header {
@@ -4498,16 +4566,18 @@ body {
.buyer-field-label {
font-size: 11px;
color: #64748b;
color: #1e40af;
font-weight: 500;
}
.buyer-field-highlight {
font-size: 14px;
color: #1e293b;
color: #1e3a8a;
font-weight: 600;
}
.worker-consignment-buyer-shots-wrap {
margin-top: 6px;
padding-top: 6px;
border-top: 1px dashed #cbd5e1;
border-top: 1px dashed #bfdbfe;
}
+10
View File
@@ -80,6 +80,16 @@ export type ConsignmentInventory = ConsignmentEvidenceFields & {
buyerInfo?: ConsignmentBuyerInfo | null
}
export type ConsignmentSellerInfo = {
characterName: string
characterId: string
sellerContact?: string
sellerPhone?: string
sellerNote?: string
system?: string
serverZone?: string
}
export type ConsignmentBuyerInfo = {
gameNickname: string
gameId: string