feat: 初始化项目所有界面

This commit is contained in:
JiaJun
2026-03-19 11:00:37 +08:00
parent 81f034b140
commit 906fa63870
26 changed files with 2435 additions and 148 deletions

View File

@@ -1,99 +1,300 @@
import { useState } from 'react'
import PageLayout from '@/components/layout'
import {Link} from 'react-router-dom'
import type {RecordButtonType} from "@/types";
import {useState} from "react";
import {cn} from "@/lib";
import { cn } from '@/lib'
import Modal from '@/components/modal'
import type { RecordButtonType } from '@/types'
import { Link } from 'react-router-dom'
type OrderRecord = {
id: string
date: string
time: string
category: string
title: string
trackingNumber?: string
status: string
points: string
}
type PointsRecord = {
id: string
title: string
date: string
time: string
amount: string
tone: 'positive' | 'negative'
}
const orderRecords: OrderRecord[] = [
{
id: 'order-1',
date: '2025-03-04',
time: '10:20',
category: 'Bonus',
title: 'Daily Rebate 50',
status: 'Issued',
points: '-500 points',
},
{
id: 'order-2',
date: '2025-03-03',
time: '14:00',
category: 'Physical',
title: 'Weekly Bonus 200',
trackingNumber: 'SF1234567890',
status: 'Shipped',
points: '-1200 points',
},
{
id: 'order-3',
date: '2025-03-02',
time: '09:15',
category: 'Withdrawal',
title: 'Wireless Earbuds',
status: 'Issued',
points: '-1000 points',
},
{
id: 'order-4',
date: '2025-03-01',
time: '16:30',
category: 'Bonus',
title: 'Fitness Tracker',
status: 'Pending',
points: '-1800 points',
},
{
id: 'order-5',
date: '2025-02-28',
time: '11:00',
category: 'Physical',
title: 'Withdraw 100',
status: 'Rejected',
points: '-2500 points',
},
]
const pointsRecords: PointsRecord[] = [
{
id: 'points-1',
title: 'Bonus Redemption - Daily Rewards 50',
date: '2025-03-04',
time: '10:20',
amount: '-500',
tone: 'negative',
},
{
id: 'points-2',
title: "Claim Yesterday's Protection Funds",
date: '2025-03-04',
time: '09:20',
amount: '+800',
tone: 'positive',
},
{
id: 'points-3',
title: 'Physical Item Redemption - Bluetooth Headphones',
date: '2025-03-03',
time: '14:00',
amount: '-1200',
tone: 'negative',
},
{
id: 'points-4',
title: "Claim Yesterday's Protection Funds",
date: '2025-03-03',
time: '09:00',
amount: '+700',
tone: 'positive',
},
{
id: 'points-5',
title: 'Withdraw to Platform - 100',
date: '2025-03-02',
time: '09:15',
amount: '-1000',
tone: 'negative',
},
]
const amountToneClassName: Record<PointsRecord['tone'], string> = {
positive: 'bg-[#9BFFC0] text-[#176640]',
negative: 'bg-[#FF9BA4] text-[#7B2634]',
}
function getOrderStatusClassName(status: string) {
switch (status.toLowerCase()) {
case 'issued':
return 'bg-[#9BFFC0] text-[#176640]'
case 'shipped':
return 'bg-[#95F0FF] text-[#116A79]'
case 'pending':
return 'bg-[#FFF18C] text-[#7F6A0D]'
case 'rejected':
return 'bg-[#FFB1C0] text-[#7C2941]'
default:
return 'bg-white/15 text-white/80'
}
}
type TabButtonProps = {
active: boolean
label: string
onClick: () => void
}
type OrderCardProps = {
record: OrderRecord
onOpenDetails: (record: OrderRecord) => void
}
function TabButton({ active, label, onClick }: TabButtonProps) {
return (
<button
type="button"
className={cn(
'min-w-[92px] cursor-pointer rounded-[6px] border px-[14px] py-[7px] text-[13px] transition-colors',
active
? 'border-[#F99A0B] bg-linear-to-r from-[#F96C02] to-[#FE9F00] text-white shadow-[0_0_16px_rgba(249,108,2,0.22)]'
: 'border-white/35 bg-white/3 text-[#B8B1AA] hover:bg-white/6',
)}
onClick={onClick}
>
{label}
</button>
)
}
function OrderCard({ record, onOpenDetails }: OrderCardProps) {
return (
<div className="overflow-hidden rounded-[10px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[8px] text-[14px] text-white">
{record.date} {record.time} {record.category}
</div>
<div className="liquid-glass-bg !rounded-t-none flex items-center justify-between px-[12px] py-[12px]">
<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">
Tracking Number&nbsp;&nbsp;{record.trackingNumber}
</div>
) : null}
<button
type="button"
className="mt-[8px] text-[13px] text-[#FA6A00]"
onClick={() => onOpenDetails(record)}
>
Check the details
</button>
</div>
<div className="flex shrink-0 flex-col items-end gap-[10px]">
<div
className={cn(
'rounded-[6px] px-[8px] py-[3px] text-[11px] leading-none',
getOrderStatusClassName(record.status),
)}
>
{record.status}
</div>
<div className="text-[13px] text-white/85">{record.points}</div>
</div>
</div>
</div>
)
}
function PointsCard({ record }: { record: PointsRecord }) {
return (
<div className="overflow-hidden rounded-[10px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[8px] text-[15px] text-white">
{record.title}
</div>
<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>
<div
className={cn(
'rounded-[6px] px-[10px] py-[3px] text-[12px] leading-none',
amountToneClassName[record.tone],
)}
>
{record.amount}
</div>
</div>
</div>
)
}
function RecordPage() {
const [tab, setTab] = useState<RecordButtonType>('order')
const [selectedOrder, setSelectedOrder] = useState<OrderRecord | null>(null)
const mockData = [
{
date: '2025-03-04 10:20',
name: ' Bonus',
content: 'Daily Rebate 50',
status: 'Issued',
points: '800'
},
{
date: '2025-03-04 10:20',
name: ' Bonus',
content: 'Daily Rebate 50',
status: 'Issued',
points: '800'
},
{
date: '2025-03-04 10:20',
name: ' Bonus',
content: 'Daily Rebate 50',
status: 'Issued',
points: '800'
},
{
date: '2025-03-04 10:20',
name: ' Bonus',
content: 'Daily Rebate 50',
status: 'Issued',
points: '800'
}
]
const [btnType, setBtnType] = useState<RecordButtonType>('record')
const handleCloseDetails = () => {
setSelectedOrder(null)
}
return (
<PageLayout contentClassName="min-h-screen">
<Link
to="/"
className={'relative text-[#F56E10] flex h-[40px] w-full items-center justify-center bg-[#08070E]/70'}
className="relative flex h-[40px] w-full items-center justify-center bg-[#08070E]/70 text-[#F56E10]"
>
<div className={'absolute left-[16px]'}> &lt; </div>
<div className="absolute left-[16px]">&lt;</div>
<div>Record</div>
</Link>
<div className={'w-[60%] mx-auto'}>
<div>
<div className={'flex gap-[10px] my-[15px]'}>
<div
className={cn('cursor-pointer text-[#B2ADAA] border-1 border-[#B2ADAA] rounded-[10px] p-[5px]', {
'text-[#ffffff] bg-[#FA6A00]': btnType === 'order',
})}
onClick={() => setBtnType('order')}
>My Orders
</div>
<div
className={cn('cursor-pointer text-[#B2ADAA] border-1 border-[#B2ADAA] rounded-[10px] p-[5px]', {
'text-[#ffffff] bg-[#FA6A00] border-[#FA6A00]': btnType === 'record',
})}
onClick={() => setBtnType('record')}
>Points Record
</div>
</div>
<hr className={'text-[#A4A4A4] border-[1px]'}></hr>
<div className="mx-auto w-[60%] pt-[14px] pb-[24px]">
<div className="flex gap-[8px]">
<TabButton active={tab === 'order'} label="My Orders" onClick={() => setTab('order')} />
<TabButton active={tab === 'record'} label="Points Record" onClick={() => setTab('record')} />
</div>
<div className={'flex flex-col mt-[10px]'}>
{
mockData.map((item, index) => (
<div key={index} className={'flex flex-col my-[10px]'}>
<div
className={'bg-linear-to-r from-[#F96C02] to-[#FE9F00] rounded-t-[10px] py-[5px] px-[10px]'}>{item.date} - {item.name} </div>
<div
className={'liquid-glass-bg !rounded-t-none px-[10px] py-[10px] pr-[20px] flex items-center justify-between'}>
<div className={'flex flex-col'}>
<div>{item.content}</div>
<div className={'text-[#FA6A00]'}>Check the Detail</div>
</div>
<div className={'flex flex-col items-end'}>
<div>{item.status}</div>
<div>{item.points}</div>
</div>
</div>
</div>
<div className="mt-[10px] border-t border-white/20"></div>
<div className="mt-[12px] flex flex-col gap-[12px]">
{tab === 'order'
? orderRecords.map((record) => (
<OrderCard key={record.id} record={record} onOpenDetails={setSelectedOrder} />
))
}
: pointsRecords.map((record) => <PointsCard key={record.id} record={record} />)}
</div>
</div>
<Modal
open={Boolean(selectedOrder)}
title="Order Details"
onClose={handleCloseDetails}
className="max-w-[380px]"
bodyClassName="pt-[0px]"
footer={
<button type="button" className="button-play h-[36px] min-w-[94px]" onClick={handleCloseDetails}>
Close
</button>
}
>
{selectedOrder ? (
<div className="rounded-[8px] bg-[#1C1818]/78 px-[12px] py-[6px]">
{[
{ label: 'Order Number', value: `ORD${selectedOrder.date.replaceAll('-', '')}${selectedOrder.time.replace(':', '')}001` },
{ label: 'Order Time', value: `${selectedOrder.date} ${selectedOrder.time}` },
{ label: 'Order Type', value: selectedOrder.category },
{ label: 'Item Name', value: selectedOrder.title },
{ label: 'Points', value: selectedOrder.points.replace('-', '') },
{ label: 'Status', value: selectedOrder.status },
].map((item) => (
<div key={item.label} className="border-b border-white/8 py-[10px] last:border-b-0">
<div className="text-[13px] text-white/48">{item.label}</div>
<div className="mt-[4px] text-[14px] text-white">{item.value}</div>
</div>
))}
</div>
) : null}
</Modal>
</PageLayout>
)
}
export default RecordPage;
export default RecordPage