feat:项目界面补充

This commit is contained in:
JiaJun
2026-04-10 17:29:49 +08:00
parent af3ed15ba2
commit 8fcf0355b3
19 changed files with 292 additions and 407 deletions

View File

@@ -1,7 +1,6 @@
import {useState} from 'react'
import PageLayout from '@/components/layout'
import BorderlessTable from '@/components/table'
import Modal from '@/components/modal'
import Button from '@/components/button'
import {Link} from 'react-router-dom'
@@ -10,16 +9,6 @@ import {useAddressBook} from '@/features/addressBook'
import {GoodsRedeemModal} from '@/features/goods'
import {notifySuccess} from '@/features/notifications'
import type {AddressListItem} from '@/types/address.type.ts'
import type {TableColumn} from '@/types'
type AddressTableRow = {
id: string
name: string
phone: string
address: string
action: string
setting: string
}
function AccountPage() {
const addressBook = useAddressBook({autoLoad: true})
@@ -27,15 +16,6 @@ function AccountPage() {
const [editingAddress, setEditingAddress] = useState<AddressListItem | null>(null)
const [deleteTarget, setDeleteTarget] = useState<AddressListItem | null>(null)
const rows: AddressTableRow[] = addressBook.addresses.map((item) => ({
id: String(item.id),
name: item.receiver_name,
phone: item.phone,
address: addressBook.addressOptions.find((option) => option.id === String(item.id))?.address ?? '',
action: 'Edit',
setting: item.default_setting === 1 ? 'Default' : 'Optional',
}))
const isAddressFormValid = addressBook.isAddressFormValid
const handleOpenAddAddress = () => {
@@ -76,63 +56,6 @@ function AccountPage() {
}
}
const columns: TableColumn<AddressTableRow>[] = [
{
label: 'Name',
key: 'name',
render: (value: string) => <div className="font-medium text-white">{value}</div>,
},
{
label: 'Phone / Mobile',
key: 'phone',
render: (value: string) => <div className="text-white/72">{value}</div>,
},
{
label: 'Address',
key: 'address',
render: (value: string) => <div className="max-w-[280px] text-white/72">{value}</div>,
},
{
label: 'Action',
key: 'action',
render: (_value: string, _record: AddressTableRow, index: number) => (
<div className="flex items-center gap-[8px]">
<button
type="button"
className="inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => handleOpenEditAddress(addressBook.addresses[index])}
>
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
Edit
</button>
<button
type="button"
className="inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => setDeleteTarget(addressBook.addresses[index])}
>
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
Delete
</button>
</div>
),
},
{
label: 'Default Setting',
key: 'setting',
render: (value: string) => (
<div
className={`inline-flex rounded-full px-[10px] py-[5px] text-[12px] ${
value === 'Default'
? 'bg-[#FA6A00]/14 text-[#FFB36D]'
: 'bg-white/6 text-white/62'
}`}
>
{value}
</div>
),
},
]
return (
<PageLayout contentClassName="mx-auto flex min-h-screen w-full max-w-[1120px] flex-col px-4 pb-8 sm:px-6 lg:px-8">
<Link
@@ -173,61 +96,62 @@ function AccountPage() {
</div>
) : !addressBook.addresses.length ? (
<div className="liquid-glass-bg px-[16px] py-[18px] text-[14px] text-white/60">
No shipping address found. Add one to start redeeming physical rewards.
No shipping address found. Add one.
</div>
) : (
<>
<div className="space-y-[12px] lg:hidden">
{rows.map((item, index) => (
<div key={item.id} className="liquid-glass-bg p-[14px]">
<div className="grid grid-cols-1 gap-[12px]">
{addressBook.addresses.map((address) => {
const addressId = String(address.id)
const addressText = addressBook.addressOptions.find((option) => option.id === addressId)?.address ?? ''
const isDefault = address.default_setting === 1
return (
<div key={addressId} className="liquid-glass-bg p-[14px]">
<div className="flex items-start justify-between gap-[12px]">
<div>
<div className="text-[16px] font-semibold text-white">{item.name}</div>
<div className="mt-[4px] text-[13px] text-white/62">{item.phone}</div>
<div className="text-[16px] font-semibold text-white">{address.receiver_name}</div>
<div className="mt-[4px] text-[13px] text-white/62">{address.phone}</div>
</div>
<div
className={`inline-flex rounded-full px-[10px] py-[5px] text-[12px] ${
item.setting === 'Default'
isDefault
? 'bg-[#FA6A00]/14 text-[#FFB36D]'
: 'bg-white/6 text-white/62'
}`}
>
{item.setting === 'Default' ? (
{isDefault ? (
<span className="inline-flex items-center gap-[5px]">
<BadgeCheck className="h-[12px] w-[12px]" aria-hidden="true" />
{item.setting}
Default
</span>
) : item.setting}
) : 'Optional'}
</div>
</div>
<div className="mt-[12px] rounded-[10px] bg-black/12 p-[12px]">
<div className="text-[12px] uppercase tracking-[0.08em] text-white/44">Address</div>
<div className="mt-[6px] text-[13px] leading-[1.6] text-white/78">{item.address}</div>
<div className="mt-[6px] text-[13px] leading-[1.6] text-white/78">{addressText}</div>
</div>
<div className="mt-[12px] flex justify-end gap-[10px]">
<button
type="button"
className="inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => handleOpenEditAddress(address)}
>
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
Edit
</button>
<button
type="button"
className="inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => setDeleteTarget(address)}
>
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
Delete
</button>
</div>
<button
type="button"
className="mt-[12px] inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => handleOpenEditAddress(addressBook.addresses[index])}
>
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
Edit
</button>
<button
type="button"
className="mt-[10px] inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
onClick={() => setDeleteTarget(addressBook.addresses[index])}
>
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
Delete
</button>
</div>
))}
</div>
<div className="hidden lg:block">
<BorderlessTable columns={columns} dataSource={rows} />
</div>
</>
)})}
</div>
)}
</div>

View File

@@ -132,11 +132,11 @@ function HomePage() {
<div className="mt-[4px]">
<div className="flex flex-col gap-3 lg:flex-row lg:items-stretch">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:w-[544px] lg:shrink-0">
<div className="grid grid-cols-2 gap-3 lg:w-[544px] lg:shrink-0">
<div className="liquid-glass-bg flex min-h-[167px] flex-col justify-between p-[14px]">
<div className="flex items-start justify-between gap-[12px]">
<div>
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Claimable
<div className="text-[13px] tracking-[0.16em] text-white/58">Claimable
Points
</div>
<div
@@ -154,7 +154,7 @@ function HomePage() {
<div className="liquid-glass-bg flex min-h-[167px] flex-col justify-around p-[14px]">
<div className="flex items-start justify-between gap-[12px]">
<div>
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Daily Claim
<div className="text-[13px] tracking-[0.16em] text-white/58">Daily Claim
Limit
</div>
<div
@@ -178,20 +178,25 @@ function HomePage() {
></div>
</div>
<div
className="mt-[10px] text-[13px] text-white/68">Claimed: <span className={'text-[#FE9C00]'}>{assetsInfo?.today_claimed || 0}</span> / {assetsInfo?.today_limit || 0}</div>
className="mt-[10px] text-[13px] text-white/68">Claimed: <span
className={'text-[#FE9C00]'}>{assetsInfo?.today_claimed || 0}</span> / {assetsInfo?.today_limit || 0}
</div>
</div>
</div>
<div className="flex min-w-0 flex-1 flex-col gap-3">
<div
className="liquid-glass-bg flex min-h-[109px] flex-col justify-between p-[14px] sm:p-[16px]">
<div className="flex items-start justify-between gap-[12px]">
className="liquid-glass-bg flex min-h-[100px] flex-col justify-between p-[14px] sm:p-[16px]">
<div className="flex items-center justify-between gap-[12px]">
<div>
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Available for
Withdrawal
<div className="text-[13px] tracking-[0.16em] text-white/58">Available for
Withdrawal (Cash)
</div>
<div
className="mt-[10px] text-[32px] font-semibold leading-none text-white">{assetsInfo?.withdrawable_cash || 0} CNY</div>
className="mt-[10px] text-[32px] font-semibold leading-none text-white">{assetsInfo?.withdrawable_cash || 0}
<span
className="text-[13px] tracking-[0.16em] text-white/58 ml-[10px]">CNY</span>
</div>
</div>
<div
className="flex h-[38px] w-[38px] items-center justify-center rounded-[12px] bg-[#FA6A00]/16 text-[#FE9F00]">
@@ -266,7 +271,8 @@ function HomePage() {
>
<div
className="rounded-[12px] bg-[#1C1818]/82 px-[14px] py-[18px] text-[17px] leading-[1.65] text-white/92 shadow-[0_10px_30px_rgba(0,0,0,0.2)]">
After converting the points to be collected into usable points, they can be redeemed or withdrawn. Are you sure to claim it?
After converting the points to be collected into usable points, they can be redeemed or withdrawn.
Are you sure to claim it?
</div>
</Modal>
</PageLayout>

View File

@@ -1,4 +1,4 @@
import {useEffect, useState} from 'react'
import {useState} from 'react'
import {useQuery} from '@tanstack/react-query'
import PageLayout from '@/components/layout'
@@ -274,11 +274,11 @@ function TabButton({ active, label, icon: Icon, onClick }: TabButtonProps) {
function OrderCard({ record, onOpenDetails }: OrderCardProps) {
return (
<div className="overflow-hidden rounded-[12px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[13px] text-white sm:text-[14px]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[14px] text-white">
{record.date} {record.time} {record.category}
</div>
<div className="liquid-glass-bg !rounded-t-none flex flex-col gap-[14px] px-[12px] py-[14px] sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 flex-1">
<div className="liquid-glass-bg !rounded-t-none flex items-center justify-between gap-[14px] px-[12px] py-[14px]">
<div className="min-w-0 flex-1 pr-[16px]">
<div className="text-[16px] font-medium text-white">{record.title}</div>
{record.trackingNumber ? (
<div className="mt-[4px] text-[13px] text-white/45">
@@ -295,7 +295,7 @@ function OrderCard({ record, onOpenDetails }: OrderCardProps) {
</button>
</div>
<div className="flex shrink-0 items-center justify-between gap-[10px] sm:flex-col sm:items-end">
<div className="flex shrink-0 flex-col items-end gap-[10px]">
<div
className={cn(
'rounded-[6px] px-[8px] py-[3px] text-[11px] leading-none',
@@ -314,10 +314,10 @@ function OrderCard({ record, onOpenDetails }: OrderCardProps) {
function PointsCard({ record }: PointsCardProps) {
return (
<div className="overflow-hidden rounded-[12px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[14px] text-white sm:text-[15px]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[15px] text-white">
{record.title}
</div>
<div className="liquid-glass-bg !rounded-t-none flex flex-col gap-[10px] px-[12px] py-[18px] sm:flex-row sm:items-center sm:justify-between sm:py-[22px]">
<div className="liquid-glass-bg !rounded-t-none flex items-center justify-between px-[12px] py-[22px]">
<div className="text-[13px] text-white/40">
{record.date} &nbsp; {record.time}
</div>
@@ -427,10 +427,6 @@ function RecordPage() {
setSelectedOrder(null)
}
useEffect(() => {
setSelectedOrder(null)
}, [tab])
return (
<PageLayout contentClassName="mx-auto flex min-h-screen w-full max-w-[980px] flex-col px-4 pb-8 sm:px-6 lg:px-8">
<Link
@@ -452,13 +448,19 @@ function RecordPage() {
active={tab === 'order'}
icon={PackageSearch}
label="My Orders"
onClick={() => setTab('order')}
onClick={() => {
setSelectedOrder(null)
setTab('order')
}}
/>
<TabButton
active={tab === 'record'}
icon={Coins}
label="Points Record"
onClick={() => setTab('record')}
onClick={() => {
setSelectedOrder(null)
setTab('record')
}}
/>
</div>
</div>
@@ -487,7 +489,7 @@ function RecordPage() {
}
>
{selectedOrder ? (
<div className="rounded-[10px] bg-[#1C1818]/78 px-[12px] py-[6px]">
<div className="mt-[10px] rounded-[10px] bg-[#1C1818]/78 px-[12px] py-[6px]">
{[
{ label: 'Order Number', value: selectedOrder.orderNumber ?? '--' },
{ label: 'Order Time', value: `${selectedOrder.date} ${selectedOrder.time}` },