chore: 新增 .gitignore 排除开发文档,同步代码与构建产物
Made-with: Cursor
This commit is contained in:
1
soul-admin/dist/assets/index-BHf8KXmF.css
vendored
1
soul-admin/dist/assets/index-BHf8KXmF.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
soul-admin/dist/assets/index-DpIZ55qK.css
vendored
Normal file
1
soul-admin/dist/assets/index-DpIZ55qK.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
soul-admin/dist/index.html
vendored
4
soul-admin/dist/index.html
vendored
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()}
|
||||
|
||||
Reference in New Issue
Block a user