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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理后台 - Soul创业派对</title>
<script type="module" crossorigin src="/assets/index-A95wVqFr.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BHf8KXmF.css">
<script type="module" crossorigin src="/assets/index-BI5eNUOf.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DpIZ55qK.css">
</head>
<body>
<div id="root"></div>

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()}