chore: 新增 .gitignore 排除开发文档,同步代码与构建产物

Made-with: Cursor
This commit is contained in:
卡若
2026-03-16 09:21:39 +08:00
parent fa9903d235
commit 85ce2422d1
40 changed files with 2315 additions and 947 deletions

View File

@@ -10,7 +10,7 @@ import {
GitMerge,
} from 'lucide-react'
import { get, post } from '@/api/client'
import { clearAdminToken } from '@/api/auth'
import { clearAdminToken, getAdminToken } from '@/api/auth'
// 主菜单5 项平铺,按 Mycontent-temp 新规范)
const primaryMenuItems = [
@@ -35,22 +35,33 @@ export function AdminLayout() {
if (!mounted) return
setAuthChecked(false)
let cancelled = false
const token = getAdminToken()
if (!token) {
navigate('/login', { replace: true, state: { from: location.pathname } })
return () => {
cancelled = true
}
}
get<{ success?: boolean }>('/api/admin')
.then((data) => {
if (cancelled) return
if (data && (data as { success?: boolean }).success !== false) {
if (data?.success === true) {
setAuthChecked(true)
} else {
clearAdminToken()
navigate('/login', { replace: true })
}
})
.catch(() => {
if (!cancelled) navigate('/login', { replace: true })
if (!cancelled) {
clearAdminToken()
navigate('/login', { replace: true })
}
})
return () => {
cancelled = true
}
}, [mounted, navigate])
}, [location.pathname, mounted, navigate])
const handleLogout = async () => {
clearAdminToken()

View File

@@ -72,7 +72,7 @@ export function AdminUsersPage() {
pageSize: String(pageSize),
})
if (debouncedSearch.trim()) params.set('search', debouncedSearch.trim())
const data = await get<ListRes>(`/api/admin/users?${params}`)
const data = await get<ListRes>(`/api/admin/admin-users?${params}`)
if (data?.success) {
setRecords((data as ListRes).records || [])
setTotal((data as ListRes).total ?? 0)
@@ -130,7 +130,7 @@ export function AdminUsersPage() {
setSaving(true)
try {
if (editingUser) {
const data = await put<{ success?: boolean; error?: string }>('/api/admin/users', {
const data = await put<{ success?: boolean; error?: string }>('/api/admin/admin-users', {
id: editingUser.id,
password: formPassword || undefined,
name: formName.trim(),
@@ -144,7 +144,7 @@ export function AdminUsersPage() {
setError(data?.error || '保存失败')
}
} else {
const data = await post<{ success?: boolean; error?: string }>('/api/admin/users', {
const data = await post<{ success?: boolean; error?: string }>('/api/admin/admin-users', {
username: formUsername.trim(),
password: formPassword,
name: formName.trim(),
@@ -168,7 +168,7 @@ export function AdminUsersPage() {
const handleDelete = async (id: number) => {
if (!confirm('确定删除该管理员?')) return
try {
const data = await del<{ success?: boolean; error?: string }>(`/api/admin/users?id=${id}`)
const data = await del<{ success?: boolean; error?: string }>(`/api/admin/admin-users?id=${id}`)
if (data?.success) loadList()
else setError(data?.error || '删除失败')
} catch (e: unknown) {

View File

@@ -62,6 +62,13 @@ interface OrdersRes {
total?: number
}
function maskPhone(phone?: string) {
if (!phone) return ''
const digits = phone.replace(/\s+/g, '')
if (digits.length < 7) return digits
return `${digits.slice(0, 3)}****${digits.slice(-4)}`
}
export function DashboardPage() {
const navigate = useNavigate()
const [statsLoading, setStatsLoading] = useState(true)
@@ -355,7 +362,7 @@ export function DashboardPage() {
) : (
<RefreshCw className="w-3.5 h-3.5" />
)}
30
</button>
</CardHeader>
<CardContent>
@@ -382,6 +389,7 @@ export function DashboardPage() {
const buyer =
p.userNickname ||
users.find((u) => u.id === p.userId)?.nickname ||
maskPhone(users.find((u) => u.id === p.userId)?.phone) ||
'匿名用户'
return (
@@ -394,7 +402,7 @@ export function DashboardPage() {
<img
src={normalizeImageUrl(p.userAvatar)}
alt={buyer}
className="w-9 h-9 rounded-full object-cover flex-shrink-0 mt-0.5"
className="w-9 h-9 rounded-full object-cover shrink-0 mt-0.5"
onError={(e) => {
e.currentTarget.style.display = 'none'
const next = e.currentTarget.nextElementSibling as HTMLElement
@@ -403,7 +411,7 @@ export function DashboardPage() {
/>
) : null}
<div
className={`w-9 h-9 rounded-full bg-[#38bdac]/20 flex items-center justify-center text-sm font-medium text-[#38bdac] flex-shrink-0 mt-0.5 ${p.userAvatar ? 'hidden' : ''}`}
className={`w-9 h-9 rounded-full bg-[#38bdac]/20 flex items-center justify-center text-sm font-medium text-[#38bdac] shrink-0 mt-0.5 ${p.userAvatar ? 'hidden' : ''}`}
>
{buyer.charAt(0)}
</div>
@@ -418,7 +426,7 @@ export function DashboardPage() {
{buyer}
</button>
<span className="text-gray-600">·</span>
<span className="text-sm font-medium text-white truncate">
<span className="text-sm font-medium text-white truncate" title={product.title}>
{product.title}
</span>
</div>
@@ -443,7 +451,7 @@ export function DashboardPage() {
</div>
</div>
<div className="text-right ml-4 flex-shrink-0">
<div className="text-right ml-4 shrink-0">
<p className="text-sm font-bold text-[#38bdac]">
+¥{Number(p.amount).toFixed(2)}
</p>
@@ -482,13 +490,13 @@ export function DashboardPage() {
{users
.slice(0, 5)
.map((u) => (
<div
<div
key={u.id}
className="flex items-center justify-between p-4 bg-[#0a1628] rounded-lg border border-gray-700/30"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-[#38bdac]/20 flex items-center justify-center text-sm font-medium text-[#38bdac]">
{u.nickname?.charAt(0) || '?'}
{(u.nickname || maskPhone(u.phone) || '?').charAt(0)}
</div>
<div>
<button
@@ -496,9 +504,9 @@ export function DashboardPage() {
onClick={() => { setDetailUserId(u.id); setShowDetailModal(true) }}
className="text-sm font-medium text-[#38bdac] hover:text-[#2da396] hover:underline text-left"
>
{u.nickname || '匿名用户'}
{u.nickname || maskPhone(u.phone) || '匿名用户'}
</button>
<p className="text-xs text-gray-500">{u.phone || '-'}</p>
<p className="text-xs text-gray-500">{maskPhone(u.phone) || '未填写手机号'}</p>
</div>
</div>
<p className="text-xs text-gray-400">
@@ -580,7 +588,7 @@ export function DashboardPage() {
<span className="text-gray-300 truncate mr-2" title={`${item.action}: ${item.target}`}>
{item.target || item.action}
</span>
<div className="flex items-center gap-2 flex-shrink-0">
<div className="flex items-center gap-2 shrink-0">
<div className="w-16 h-1.5 bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-[#38bdac] rounded-full"

View File

@@ -1,10 +1,10 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Lock, User, ShieldCheck } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { post } from '@/api/client'
import { setAdminToken } from '@/api/auth'
import { getAdminToken, setAdminToken } from '@/api/auth'
export function LoginPage() {
const navigate = useNavigate()
@@ -13,6 +13,12 @@ export function LoginPage() {
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
useEffect(() => {
if (getAdminToken()) {
navigate('/dashboard', { replace: true })
}
}, [navigate])
const handleLogin = async () => {
setError('')
setLoading(true)
@@ -66,7 +72,10 @@ export function LoginPage() {
<Input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
onChange={(e) => {
setUsername(e.target.value)
if (error) setError('')
}}
placeholder="请输入用户名"
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500 focus:border-[#38bdac]"
/>
@@ -80,7 +89,10 @@ export function LoginPage() {
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => {
setPassword(e.target.value)
if (error) setError('')
}}
placeholder="请输入密码"
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500 focus:border-[#38bdac]"
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}