更新个人资料页实现评估会议记录,明确展示与编辑页字段一致性要求,补充技能字段的展示与编辑需求。优化小程序页面,增加联系方式完善弹窗,确保用户在使用找伙伴功能前填写手机号或微信号。调整相关文档以反映最新进展,提升用户体验与功能一致性。
This commit is contained in:
@@ -16,6 +16,8 @@ import { QRCodesPage } from './pages/qrcodes/QRCodesPage'
|
||||
import { MatchPage } from './pages/match/MatchPage'
|
||||
import { MatchRecordsPage } from './pages/match-records/MatchRecordsPage'
|
||||
import { VipRolesPage } from './pages/vip-roles/VipRolesPage'
|
||||
import { MentorsPage } from './pages/mentors/MentorsPage'
|
||||
import { MentorConsultationsPage } from './pages/mentor-consultations/MentorConsultationsPage'
|
||||
import { ApiDocPage } from './pages/api-doc/ApiDocPage'
|
||||
import { NotFoundPage } from './pages/not-found/NotFoundPage'
|
||||
|
||||
@@ -34,6 +36,8 @@ function App() {
|
||||
<Route path="chapters" element={<ChaptersPage />} />
|
||||
<Route path="referral-settings" element={<ReferralSettingsPage />} />
|
||||
<Route path="vip-roles" element={<VipRolesPage />} />
|
||||
<Route path="mentors" element={<MentorsPage />} />
|
||||
<Route path="mentor-consultations" element={<MentorConsultationsPage />} />
|
||||
<Route path="settings" element={<SettingsPage />} />
|
||||
<Route path="payment" element={<PaymentPage />} />
|
||||
<Route path="site" element={<SitePage />} />
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
BookOpen,
|
||||
GitMerge,
|
||||
Crown,
|
||||
GraduationCap,
|
||||
Calendar,
|
||||
} from 'lucide-react'
|
||||
import { get, post } from '@/api/client'
|
||||
import { clearAdminToken } from '@/api/auth'
|
||||
@@ -19,6 +21,8 @@ const menuItems = [
|
||||
{ icon: BookOpen, label: '内容管理', href: '/content' },
|
||||
{ icon: Users, label: '用户管理', href: '/users' },
|
||||
{ icon: Crown, label: 'VIP 角色', href: '/vip-roles' },
|
||||
{ icon: GraduationCap, label: '导师管理', href: '/mentors' },
|
||||
{ icon: Calendar, label: '导师预约', href: '/mentor-consultations' },
|
||||
{ icon: Wallet, label: '交易中心', href: '/distribution' },
|
||||
{ icon: GitMerge, label: '匹配记录', href: '/match-records' },
|
||||
{ icon: CreditCard, label: '推广设置', href: '/referral-settings' },
|
||||
|
||||
@@ -49,6 +49,7 @@ interface SectionListItem {
|
||||
title: string
|
||||
price: number
|
||||
isFree?: boolean
|
||||
isNew?: boolean
|
||||
partId?: string
|
||||
partTitle?: string
|
||||
chapterId?: string
|
||||
@@ -62,6 +63,7 @@ interface Section {
|
||||
price: number
|
||||
filePath?: string
|
||||
isFree?: boolean
|
||||
isNew?: boolean
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
@@ -83,6 +85,7 @@ interface EditingSection {
|
||||
content?: string
|
||||
filePath?: string
|
||||
isFree?: boolean
|
||||
isNew?: boolean
|
||||
}
|
||||
|
||||
function buildTree(sections: SectionListItem[]): Part[] {
|
||||
@@ -108,6 +111,7 @@ function buildTree(sections: SectionListItem[]): Part[] {
|
||||
price: s.price ?? 1,
|
||||
filePath: s.filePath,
|
||||
isFree: s.isFree,
|
||||
isNew: s.isNew,
|
||||
})
|
||||
}
|
||||
return Array.from(partMap.values()).map((p) => ({
|
||||
@@ -201,6 +205,7 @@ export function ContentPage() {
|
||||
`/api/db/book?action=read&id=${encodeURIComponent(section.id)}`,
|
||||
)
|
||||
if (data?.success && data.section) {
|
||||
const sec = data.section as { isNew?: boolean }
|
||||
setEditingSection({
|
||||
id: section.id,
|
||||
title: data.section.title ?? section.title,
|
||||
@@ -208,6 +213,7 @@ export function ContentPage() {
|
||||
content: data.section.content ?? '',
|
||||
filePath: section.filePath,
|
||||
isFree: section.isFree || section.price === 0,
|
||||
isNew: sec.isNew ?? section.isNew,
|
||||
})
|
||||
} else {
|
||||
setEditingSection({
|
||||
@@ -217,6 +223,7 @@ export function ContentPage() {
|
||||
content: '',
|
||||
filePath: section.filePath,
|
||||
isFree: section.isFree,
|
||||
isNew: section.isNew,
|
||||
})
|
||||
if (data && !(data as { success?: boolean }).success) {
|
||||
alert('无法读取文件内容: ' + ((data as { error?: string }).error || '未知错误'))
|
||||
@@ -256,6 +263,7 @@ export function ContentPage() {
|
||||
price: editingSection.isFree ? 0 : editingSection.price,
|
||||
content,
|
||||
isFree: editingSection.isFree || editingSection.price === 0,
|
||||
isNew: editingSection.isNew,
|
||||
saveToFile: true,
|
||||
})
|
||||
if (res && (res as { success?: boolean }).success !== false) {
|
||||
@@ -557,6 +565,25 @@ export function ContentPage() {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">最新新增</Label>
|
||||
<div className="flex items-center h-10">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editingSection.isNew ?? false}
|
||||
onChange={(e) =>
|
||||
setEditingSection({
|
||||
...editingSection,
|
||||
isNew: e.target.checked,
|
||||
})
|
||||
}
|
||||
className="w-5 h-5 rounded border-gray-600 bg-[#0a1628] text-[#38bdac] focus:ring-[#38bdac]"
|
||||
/>
|
||||
<span className="ml-2 text-gray-400 text-sm">标记 NEW</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">章节标题</Label>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Calendar, RefreshCw } from 'lucide-react'
|
||||
import { get } from '@/api/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
interface Consultation {
|
||||
id: number
|
||||
userId: number
|
||||
mentorId: number
|
||||
consultationType: string
|
||||
amount: number
|
||||
status: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export function MentorConsultationsPage() {
|
||||
const [list, setList] = useState<Consultation[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [statusFilter, setStatusFilter] = useState('')
|
||||
|
||||
async function load() {
|
||||
setLoading(true)
|
||||
try {
|
||||
const url = statusFilter ? `/api/db/mentor-consultations?status=${statusFilter}` : '/api/db/mentor-consultations'
|
||||
const data = await get<{ success?: boolean; data?: Consultation[] }>(url)
|
||||
if (data?.success && data.data) setList(data.data)
|
||||
} catch (e) {
|
||||
console.error('Load consultations error:', e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load()
|
||||
}, [statusFilter])
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
created: '已创建',
|
||||
pending_pay: '待支付',
|
||||
paid: '已支付',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
}
|
||||
const typeMap: Record<string, string> = {
|
||||
single: '单次',
|
||||
half_year: '半年',
|
||||
year: '年度',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 w-full">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-[#38bdac]" />
|
||||
导师预约列表
|
||||
</h2>
|
||||
<p className="text-gray-400 mt-1">
|
||||
stitch_soul 导师咨询预约记录
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="bg-[#0f2137] border border-gray-700 rounded-lg px-3 py-2 text-gray-300 text-sm"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
{Object.entries(statusMap).map(([k, v]) => (
|
||||
<option key={k} value={k}>{v}</option>
|
||||
))}
|
||||
</select>
|
||||
<Button onClick={load} disabled={loading} variant="outline" className="border-gray-600 text-gray-300">
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="bg-[#0f2137] border-gray-700/50">
|
||||
<CardContent className="p-0">
|
||||
{loading ? (
|
||||
<div className="py-12 text-center text-gray-400">加载中...</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-[#0a1628] border-gray-700">
|
||||
<TableHead className="text-gray-400">ID</TableHead>
|
||||
<TableHead className="text-gray-400">用户ID</TableHead>
|
||||
<TableHead className="text-gray-400">导师ID</TableHead>
|
||||
<TableHead className="text-gray-400">类型</TableHead>
|
||||
<TableHead className="text-gray-400">金额</TableHead>
|
||||
<TableHead className="text-gray-400">状态</TableHead>
|
||||
<TableHead className="text-gray-400">创建时间</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{list.map((r) => (
|
||||
<TableRow key={r.id} className="border-gray-700/50">
|
||||
<TableCell className="text-gray-300">{r.id}</TableCell>
|
||||
<TableCell className="text-gray-400">{r.userId}</TableCell>
|
||||
<TableCell className="text-gray-400">{r.mentorId}</TableCell>
|
||||
<TableCell className="text-gray-400">{typeMap[r.consultationType] || r.consultationType}</TableCell>
|
||||
<TableCell className="text-white">¥{r.amount}</TableCell>
|
||||
<TableCell className="text-gray-400">{statusMap[r.status] || r.status}</TableCell>
|
||||
<TableCell className="text-gray-500 text-sm">{r.createdAt}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{list.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center py-12 text-gray-500">
|
||||
暂无预约记录
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
429
soul-admin/src/pages/mentors/MentorsPage.tsx
Normal file
429
soul-admin/src/pages/mentors/MentorsPage.tsx
Normal file
@@ -0,0 +1,429 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Users, Plus, Edit3, Trash2, X, Save } from 'lucide-react'
|
||||
import { get, post, put, del } from '@/api/client'
|
||||
|
||||
interface Mentor {
|
||||
id: number
|
||||
name: string
|
||||
avatar?: string
|
||||
intro?: string
|
||||
tags?: string
|
||||
priceSingle?: number
|
||||
priceHalfYear?: number
|
||||
priceYear?: number
|
||||
quote?: string
|
||||
whyFind?: string
|
||||
offering?: string
|
||||
judgmentStyle?: string
|
||||
sort: number
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export function MentorsPage() {
|
||||
const [mentors, setMentors] = useState<Mentor[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [editing, setEditing] = useState<Mentor | null>(null)
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
avatar: '',
|
||||
intro: '',
|
||||
tags: '',
|
||||
priceSingle: '',
|
||||
priceHalfYear: '',
|
||||
priceYear: '',
|
||||
quote: '',
|
||||
whyFind: '',
|
||||
offering: '',
|
||||
judgmentStyle: '',
|
||||
sort: 0,
|
||||
enabled: true,
|
||||
})
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
async function load() {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await get<{ success?: boolean; data?: Mentor[] }>('/api/db/mentors')
|
||||
if (data?.success && data.data) setMentors(data.data)
|
||||
} catch (e) {
|
||||
console.error('Load mentors error:', e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load()
|
||||
}, [])
|
||||
|
||||
const resetForm = () => {
|
||||
setForm({
|
||||
name: '',
|
||||
avatar: '',
|
||||
intro: '',
|
||||
tags: '',
|
||||
priceSingle: '',
|
||||
priceHalfYear: '',
|
||||
priceYear: '',
|
||||
quote: '',
|
||||
whyFind: '',
|
||||
offering: '',
|
||||
judgmentStyle: '',
|
||||
sort: mentors.length > 0 ? Math.max(...mentors.map((m) => m.sort)) + 1 : 0,
|
||||
enabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditing(null)
|
||||
resetForm()
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleEdit = (m: Mentor) => {
|
||||
setEditing(m)
|
||||
setForm({
|
||||
name: m.name,
|
||||
avatar: m.avatar || '',
|
||||
intro: m.intro || '',
|
||||
tags: m.tags || '',
|
||||
priceSingle: m.priceSingle != null ? String(m.priceSingle) : '',
|
||||
priceHalfYear: m.priceHalfYear != null ? String(m.priceHalfYear) : '',
|
||||
priceYear: m.priceYear != null ? String(m.priceYear) : '',
|
||||
quote: m.quote || '',
|
||||
whyFind: m.whyFind || '',
|
||||
offering: m.offering || '',
|
||||
judgmentStyle: m.judgmentStyle || '',
|
||||
sort: m.sort,
|
||||
enabled: m.enabled ?? true,
|
||||
})
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.name.trim()) {
|
||||
alert('导师姓名不能为空')
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
const num = (s: string) => (s === '' ? undefined : parseFloat(s))
|
||||
const payload = {
|
||||
name: form.name.trim(),
|
||||
avatar: form.avatar.trim() || undefined,
|
||||
intro: form.intro.trim() || undefined,
|
||||
tags: form.tags.trim() || undefined,
|
||||
priceSingle: num(form.priceSingle),
|
||||
priceHalfYear: num(form.priceHalfYear),
|
||||
priceYear: num(form.priceYear),
|
||||
quote: form.quote.trim() || undefined,
|
||||
whyFind: form.whyFind.trim() || undefined,
|
||||
offering: form.offering.trim() || undefined,
|
||||
judgmentStyle: form.judgmentStyle.trim() || undefined,
|
||||
sort: form.sort,
|
||||
enabled: form.enabled,
|
||||
}
|
||||
if (editing) {
|
||||
const data = await put<{ success?: boolean; error?: string }>('/api/db/mentors', {
|
||||
id: editing.id,
|
||||
...payload,
|
||||
})
|
||||
if (data?.success) {
|
||||
setShowModal(false)
|
||||
load()
|
||||
} else {
|
||||
alert('更新失败: ' + (data as { error?: string })?.error)
|
||||
}
|
||||
} else {
|
||||
const data = await post<{ success?: boolean; error?: string }>('/api/db/mentors', payload)
|
||||
if (data?.success) {
|
||||
setShowModal(false)
|
||||
load()
|
||||
} else {
|
||||
alert('新增失败: ' + (data as { error?: string })?.error)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Save error:', e)
|
||||
alert('保存失败')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
if (!confirm('确定删除该导师?')) return
|
||||
try {
|
||||
const data = await del<{ success?: boolean; error?: string }>(`/api/db/mentors?id=${id}`)
|
||||
if (data?.success) load()
|
||||
else alert('删除失败: ' + (data as { error?: string })?.error)
|
||||
} catch (e) {
|
||||
console.error('Delete error:', e)
|
||||
alert('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
const fmt = (v?: number) => (v != null ? `¥${v}` : '-')
|
||||
|
||||
return (
|
||||
<div className="p-8 w-full">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
|
||||
<Users className="w-5 h-5 text-[#38bdac]" />
|
||||
导师管理
|
||||
</h2>
|
||||
<p className="text-gray-400 mt-1">
|
||||
stitch_soul 导师列表,支持每个导师独立配置单次/半年/年度价格
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleAdd} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
新增导师
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card className="bg-[#0f2137] border-gray-700/50">
|
||||
<CardContent className="p-0">
|
||||
{loading ? (
|
||||
<div className="py-12 text-center text-gray-400">加载中...</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-[#0a1628] border-gray-700">
|
||||
<TableHead className="text-gray-400">ID</TableHead>
|
||||
<TableHead className="text-gray-400">姓名</TableHead>
|
||||
<TableHead className="text-gray-400">简介</TableHead>
|
||||
<TableHead className="text-gray-400">单次</TableHead>
|
||||
<TableHead className="text-gray-400">半年</TableHead>
|
||||
<TableHead className="text-gray-400">年度</TableHead>
|
||||
<TableHead className="text-gray-400">排序</TableHead>
|
||||
<TableHead className="text-right text-gray-400">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{mentors.map((m) => (
|
||||
<TableRow key={m.id} className="border-gray-700/50">
|
||||
<TableCell className="text-gray-300">{m.id}</TableCell>
|
||||
<TableCell className="text-white">{m.name}</TableCell>
|
||||
<TableCell className="text-gray-400 max-w-[200px] truncate">{m.intro || '-'}</TableCell>
|
||||
<TableCell className="text-gray-400">{fmt(m.priceSingle)}</TableCell>
|
||||
<TableCell className="text-gray-400">{fmt(m.priceHalfYear)}</TableCell>
|
||||
<TableCell className="text-gray-400">{fmt(m.priceYear)}</TableCell>
|
||||
<TableCell className="text-gray-400">{m.sort}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(m)}
|
||||
className="text-gray-400 hover:text-[#38bdac]"
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(m.id)}
|
||||
className="text-gray-400 hover:text-red-400"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{mentors.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center py-12 text-gray-500">
|
||||
暂无导师,点击「新增导师」添加
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={showModal} onOpenChange={setShowModal}>
|
||||
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">
|
||||
{editing ? '编辑导师' : '新增导师'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">姓名 *</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="如:卡若"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">排序</Label>
|
||||
<Input
|
||||
type="number"
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
value={form.sort}
|
||||
onChange={(e) => setForm((f) => ({ ...f, sort: parseInt(e.target.value, 10) || 0 }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">头像 URL</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="https://..."
|
||||
value={form.avatar}
|
||||
onChange={(e) => setForm((f) => ({ ...f, avatar: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">简介</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="如:结构判断型咨询 · Decision > Execution"
|
||||
value={form.intro}
|
||||
onChange={(e) => setForm((f) => ({ ...f, intro: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">技能标签(逗号分隔)</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="如:项目结构判断、风险止损、人×项目匹配"
|
||||
value={form.tags}
|
||||
onChange={(e) => setForm((f) => ({ ...f, tags: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-gray-700 pt-4">
|
||||
<Label className="text-gray-300 block mb-2">价格配置(每个导师独立)</Label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-500 text-xs">单次咨询 ¥</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="980"
|
||||
value={form.priceSingle}
|
||||
onChange={(e) => setForm((f) => ({ ...f, priceSingle: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-500 text-xs">半年咨询 ¥</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="19800"
|
||||
value={form.priceHalfYear}
|
||||
onChange={(e) => setForm((f) => ({ ...f, priceHalfYear: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-500 text-xs">年度咨询 ¥</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="29800"
|
||||
value={form.priceYear}
|
||||
onChange={(e) => setForm((f) => ({ ...f, priceYear: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">引言</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="如:大多数人失败,不是因为不努力..."
|
||||
value={form.quote}
|
||||
onChange={(e) => setForm((f) => ({ ...f, quote: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">为什么找(文本)</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder=""
|
||||
value={form.whyFind}
|
||||
onChange={(e) => setForm((f) => ({ ...f, whyFind: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">提供什么(文本)</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder=""
|
||||
value={form.offering}
|
||||
onChange={(e) => setForm((f) => ({ ...f, offering: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">判断风格(逗号分隔)</Label>
|
||||
<Input
|
||||
className="bg-[#0a1628] border-gray-700 text-white"
|
||||
placeholder="如:冷静、克制、偏风险视角"
|
||||
value={form.judgmentStyle}
|
||||
onChange={(e) => setForm((f) => ({ ...f, judgmentStyle: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
checked={form.enabled}
|
||||
onChange={(e) => setForm((f) => ({ ...f, enabled: e.target.checked }))}
|
||||
className="rounded border-gray-600 bg-[#0a1628]"
|
||||
/>
|
||||
<Label htmlFor="enabled" className="text-gray-300 cursor-pointer">上架(小程序可见)</Label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowModal(false)}
|
||||
className="border-gray-600 text-gray-300"
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="bg-[#38bdac] hover:bg-[#2da396] text-white"
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{saving ? '保存中...' : '保存'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user