feat(worker-platform): 优化打手我的寄卖卡片资料展示,前置展示打手自身上架账号资料
1. 后端 consignment-worker-service 在我的寄卖列表中解析并返回打手上架时填写的 sellerInfo(角色名、数字ID、区服系统、微信号/QQ、寄卖留言等); 2. 前端新增 ConsignmentSellerInfoBlock 卖家资料区块,提供角色名/ID高亮与一键复制功能; 3. 寄卖卡片布局升级为前置展示【我的寄卖账号资料】、后置展示【买家加好友资料】,多号上架时号主与买家清晰对应,彻底杜绝登错号加错人。
This commit is contained in:
@@ -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() {
|
export function getConsignmentPublishDepositFen() {
|
||||||
return getConfiguredConsignmentPublishDepositFen()
|
return getConfiguredConsignmentPublishDepositFen()
|
||||||
}
|
}
|
||||||
@@ -358,6 +423,7 @@ export async function listWorkerConsignments(workerId: number) {
|
|||||||
friendAddedAt: inv?.bound_work_order_id
|
friendAddedAt: inv?.bound_work_order_id
|
||||||
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
|
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
|
||||||
: null,
|
: null,
|
||||||
|
sellerInfo: extractConsignmentSellerInfoFromSubmission(sub),
|
||||||
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
|
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -397,6 +463,7 @@ export async function listWorkerConsignments(workerId: number) {
|
|||||||
friendAddedAt: inv.bound_work_order_id
|
friendAddedAt: inv.bound_work_order_id
|
||||||
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
|
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
|
||||||
: null,
|
: null,
|
||||||
|
sellerInfo: extractConsignmentSellerInfoFromInventory(inv),
|
||||||
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
|
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({
|
function ConsignmentBuyerInfoBlock({
|
||||||
buyerInfo,
|
buyerInfo,
|
||||||
}: {
|
}: {
|
||||||
@@ -946,8 +1059,8 @@ function ConsignmentBuyerInfoBlock({
|
|||||||
|
|
||||||
const copyAll = () => {
|
const copyAll = () => {
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
if (buyerInfo.gameNickname) parts.push(`角色名: ${buyerInfo.gameNickname}`)
|
if (buyerInfo.gameNickname) parts.push(`买家角色: ${buyerInfo.gameNickname}`)
|
||||||
if (buyerInfo.gameId) parts.push(`游戏ID: ${buyerInfo.gameId}`)
|
if (buyerInfo.gameId) parts.push(`角色ID: ${buyerInfo.gameId}`)
|
||||||
if (buyerInfo.serverZone) parts.push(`区服: ${buyerInfo.serverZone}`)
|
if (buyerInfo.serverZone) parts.push(`区服: ${buyerInfo.serverZone}`)
|
||||||
if (buyerInfo.system) parts.push(`系统: ${buyerInfo.system}`)
|
if (buyerInfo.system) parts.push(`系统: ${buyerInfo.system}`)
|
||||||
copyText(parts.join(' | '), '买家加好友资料')
|
copyText(parts.join(' | '), '买家加好友资料')
|
||||||
@@ -959,7 +1072,7 @@ function ConsignmentBuyerInfoBlock({
|
|||||||
<Space size={6}>
|
<Space size={6}>
|
||||||
<UserOutlined style={{ color: '#1677ff' }} />
|
<UserOutlined style={{ color: '#1677ff' }} />
|
||||||
<Typography.Text strong style={{ color: '#1677ff', fontSize: 13 }}>
|
<Typography.Text strong style={{ color: '#1677ff', fontSize: 13 }}>
|
||||||
买家加好友资料
|
买家加好友资料(请在游戏内添加此人)
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Button
|
<Button
|
||||||
@@ -1097,7 +1210,11 @@ function ConsignmentRecordCard({
|
|||||||
{record.reviewNote ? (
|
{record.reviewNote ? (
|
||||||
<Typography.Text type="danger">审核说明:{record.reviewNote}</Typography.Text>
|
<Typography.Text type="danger">审核说明:{record.reviewNote}</Typography.Text>
|
||||||
) : null}
|
) : 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 ? (
|
{record.hint ? (
|
||||||
<Typography.Text type="secondary" className="worker-consignment-record-hint">
|
<Typography.Text type="secondary" className="worker-consignment-record-hint">
|
||||||
{record.hint}
|
{record.hint}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import type {
|
|||||||
WorkerUser,
|
WorkerUser,
|
||||||
WorkerWalletLedger,
|
WorkerWalletLedger,
|
||||||
} from '@/types/worker-platform'
|
} from '@/types/worker-platform'
|
||||||
import type { ConsignmentBuyerInfo } from '@/types/consignment'
|
import type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
|
||||||
|
|
||||||
import { getWorkerDeviceId } from '@/utils/worker-auth'
|
import { getWorkerDeviceId } from '@/utils/worker-auth'
|
||||||
|
|
||||||
@@ -450,11 +450,13 @@ export type WorkerConsignmentRecord = {
|
|||||||
/** 赠送冷却截止时间,前端拿它做倒计时 */
|
/** 赠送冷却截止时间,前端拿它做倒计时 */
|
||||||
giftAvailableAt: string | null
|
giftAvailableAt: string | null
|
||||||
friendAddedAt: string | null
|
friendAddedAt: string | null
|
||||||
|
/** 卖家自己寄卖时填写的账号/角色资料 */
|
||||||
|
sellerInfo?: ConsignmentSellerInfo | null
|
||||||
/** 匹配工单的买家资料(用于加好友) */
|
/** 匹配工单的买家资料(用于加好友) */
|
||||||
buyerInfo?: ConsignmentBuyerInfo | null
|
buyerInfo?: ConsignmentBuyerInfo | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { ConsignmentBuyerInfo } from '@/types/consignment'
|
export type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
|
||||||
|
|
||||||
export type WorkerConsignmentListResponse = {
|
export type WorkerConsignmentListResponse = {
|
||||||
publishDepositFen: number
|
publishDepositFen: number
|
||||||
|
|||||||
@@ -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 {
|
.worker-consignment-buyer-box {
|
||||||
margin-top: 4px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #eff6ff;
|
background: #eff6ff;
|
||||||
border: 1px solid #bfdbfe;
|
border: 1px solid #bfdbfe;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-consignment-buyer-header {
|
.worker-consignment-buyer-header {
|
||||||
@@ -4498,16 +4566,18 @@ body {
|
|||||||
|
|
||||||
.buyer-field-label {
|
.buyer-field-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #64748b;
|
color: #1e40af;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buyer-field-highlight {
|
.buyer-field-highlight {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #1e293b;
|
color: #1e3a8a;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-consignment-buyer-shots-wrap {
|
.worker-consignment-buyer-shots-wrap {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
border-top: 1px dashed #cbd5e1;
|
border-top: 1px dashed #bfdbfe;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,16 @@ export type ConsignmentInventory = ConsignmentEvidenceFields & {
|
|||||||
buyerInfo?: ConsignmentBuyerInfo | null
|
buyerInfo?: ConsignmentBuyerInfo | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ConsignmentSellerInfo = {
|
||||||
|
characterName: string
|
||||||
|
characterId: string
|
||||||
|
sellerContact?: string
|
||||||
|
sellerPhone?: string
|
||||||
|
sellerNote?: string
|
||||||
|
system?: string
|
||||||
|
serverZone?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ConsignmentBuyerInfo = {
|
export type ConsignmentBuyerInfo = {
|
||||||
gameNickname: string
|
gameNickname: string
|
||||||
gameId: string
|
gameId: string
|
||||||
|
|||||||
Reference in New Issue
Block a user