更新管理端用户详情弹窗,新增 VIP 手动设置功能,支持到期日、展示名、项目、联系方式和简介的编辑。优化 VIP 相关接口,确保用户状态和资料更新功能正常,增强用户体验。调整文档,明确 VIP 设置的必填项和格式要求。

This commit is contained in:
Alex-larget
2026-02-26 18:03:01 +08:00
parent ab27acdb21
commit a5e2cfaa61
40 changed files with 1520 additions and 993 deletions

View File

@@ -10,7 +10,6 @@ import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Switch } from '@/components/ui/switch'
import {
User,
Phone,
@@ -25,7 +24,6 @@ import {
Save,
X,
Tag,
Crown,
} from 'lucide-react'
import { get, put, post } from '@/api/client'
@@ -91,12 +89,6 @@ export function UserDetailModal({
const [editNickname, setEditNickname] = useState('')
const [editTags, setEditTags] = useState<string[]>([])
const [newTag, setNewTag] = useState('')
const [editIsVip, setEditIsVip] = useState(false)
const [editVipExpireDate, setEditVipExpireDate] = useState('')
const [editVipName, setEditVipName] = useState('')
const [editVipProject, setEditVipProject] = useState('')
const [editVipContact, setEditVipContact] = useState('')
const [editVipBio, setEditVipBio] = useState('')
useEffect(() => {
if (open && userId) loadUserDetail()
@@ -115,12 +107,6 @@ export function UserDetailModal({
setEditPhone(u.phone || '')
setEditNickname(u.nickname || '')
setEditTags(typeof u.tags === 'string' ? (JSON.parse(u.tags || '[]') as string[]) : [])
setEditIsVip(!!u.isVip)
setEditVipExpireDate(u.vipExpireDate ? String(u.vipExpireDate).slice(0, 10) : '')
setEditVipName(u.vipName || '')
setEditVipProject(u.vipProject || '')
setEditVipContact(u.vipContact || '')
setEditVipBio(u.vipBio || '')
}
try {
const trackData = await get<{ success?: boolean; tracks?: UserTrack[] }>(
@@ -173,17 +159,6 @@ export function UserDetailModal({
async function handleSave() {
if (!user) return
if (editIsVip && !editVipExpireDate.trim()) {
alert('开启 VIP 时请填写有效到期日')
return
}
if (editIsVip && editVipExpireDate.trim()) {
const d = new Date(editVipExpireDate)
if (isNaN(d.getTime())) {
alert('到期日格式无效,请使用 YYYY-MM-DD')
return
}
}
setSaving(true)
try {
const payload: Record<string, unknown> = {
@@ -191,12 +166,6 @@ export function UserDetailModal({
phone: editPhone || undefined,
nickname: editNickname || undefined,
tags: JSON.stringify(editTags),
isVip: editIsVip,
vipExpireDate: editVipExpireDate || undefined,
vipName: editVipName || undefined,
vipProject: editVipProject || undefined,
vipContact: editVipContact || undefined,
vipBio: editVipBio || undefined,
}
const data = await put<{ success?: boolean; error?: string }>('/api/db/users', payload)
if (data?.success) {
@@ -331,70 +300,6 @@ export function UserDetailModal({
/>
</div>
</div>
<div className="p-4 bg-[#0a1628] rounded-lg border border-amber-500/20">
<div className="flex items-center gap-2 mb-3">
<Crown className="w-4 h-4 text-amber-400" />
<span className="text-white font-medium">VIP </span>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center justify-between">
<Label className="text-gray-300">VIP </Label>
<Switch
checked={editIsVip}
onCheckedChange={setEditIsVip}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300">
(YYYY-MM-DD)
{editIsVip && <span className="text-amber-400 ml-1">*</span>}
</Label>
<Input
type="date"
className="bg-[#162840] border-gray-700 text-white"
value={editVipExpireDate}
onChange={(e) => setEditVipExpireDate(e.target.value)}
required={editIsVip}
/>
</div>
<div className="space-y-2 col-span-2">
<Label className="text-gray-300">VIP </Label>
<Input
className="bg-[#162840] border-gray-700 text-white"
placeholder="创业老板排行展示名"
value={editVipName}
onChange={(e) => setEditVipName(e.target.value)}
/>
</div>
<div className="space-y-2 col-span-2">
<Label className="text-gray-300">/</Label>
<Input
className="bg-[#162840] border-gray-700 text-white"
placeholder="项目名称"
value={editVipProject}
onChange={(e) => setEditVipProject(e.target.value)}
/>
</div>
<div className="space-y-2 col-span-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#162840] border-gray-700 text-white"
placeholder="微信号或手机"
value={editVipContact}
onChange={(e) => setEditVipContact(e.target.value)}
/>
</div>
<div className="space-y-2 col-span-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#162840] border-gray-700 text-white"
placeholder="简要描述业务"
value={editVipBio}
onChange={(e) => setEditVipBio(e.target.value)}
/>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="p-4 bg-[#0a1628] rounded-lg">
<p className="text-gray-400 text-sm"></p>