ux: 热点问题管理改为列表式编辑,支持逐条增删改+批量粘贴
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Sparkles, Loader2, RefreshCw, Pencil, Calendar } from 'lucide-react'
|
||||
import { Sparkles, Loader2, RefreshCw, Pencil, Calendar, Plus, X } from 'lucide-react'
|
||||
import { PageLayout } from '@/components/common/PageLayout'
|
||||
import { EmptyState } from '@/components/common/EmptyState'
|
||||
import { LoadingState } from '@/components/common/LoadingState'
|
||||
@@ -8,7 +8,7 @@ import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
@@ -160,7 +160,7 @@ export function AdminDailyTopics() {
|
||||
)
|
||||
}
|
||||
|
||||
/** 编辑/创建热点问题弹窗 */
|
||||
/** 编辑/创建热点问题弹窗:列表式编辑,每条可单独修改/删除 */
|
||||
function EditTopicsDialog({
|
||||
topic,
|
||||
onClose,
|
||||
@@ -172,23 +172,43 @@ function EditTopicsDialog({
|
||||
}) {
|
||||
const isCreate = !topic
|
||||
const [scene, setScene] = useState(topic?.scene || 'legal')
|
||||
const [questionsText, setQuestionsText] = useState(
|
||||
topic?.questions.join('\n') || '',
|
||||
)
|
||||
const [questions, setQuestions] = useState<string[]>(topic?.questions || [''])
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
/** 更新某行问题 */
|
||||
const handleUpdate = (index: number, value: string) => {
|
||||
setQuestions(prev => prev.map((q, i) => i === index ? value : q))
|
||||
}
|
||||
|
||||
/** 删除某行问题 */
|
||||
const handleDelete = (index: number) => {
|
||||
setQuestions(prev => prev.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
/** 添加空行 */
|
||||
const handleAdd = () => {
|
||||
setQuestions(prev => [...prev, ''])
|
||||
}
|
||||
|
||||
/** 批量粘贴:从剪贴板按行解析 */
|
||||
const handlePaste = (e: React.ClipboardEvent) => {
|
||||
const text = e.clipboardData.getData('text')
|
||||
if (text.includes('\n') && text.trim().length > 50) {
|
||||
e.preventDefault()
|
||||
const lines = text.split('\n').map(s => s.trim()).filter(Boolean)
|
||||
setQuestions(prev => [...prev.filter(s => s.trim()), ...lines])
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
const questions = questionsText
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
if (questions.length === 0) {
|
||||
const valid = questions.map(s => s.trim()).filter(Boolean)
|
||||
if (valid.length === 0) {
|
||||
toast.error('至少输入一个问题')
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
await api.adminUpdateDailyTopics({ scene, questions })
|
||||
await api.adminUpdateDailyTopics({ scene, questions: valid })
|
||||
toast.success(isCreate ? '热点问题已添加' : '热点问题已更新')
|
||||
onSuccess()
|
||||
} catch (e) {
|
||||
@@ -200,17 +220,17 @@ function EditTopicsDialog({
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isCreate ? '添加热点问题' : `编辑 - ${SCENE_LABELS[topic!.scene]}`}</DialogTitle>
|
||||
<DialogDescription>每行一个问题,保存后用户端实时生效</DialogDescription>
|
||||
<DialogDescription>保存后用户端实时生效。可逐条编辑,支持批量粘贴。</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{isCreate && (
|
||||
<div className="space-y-1.5">
|
||||
<Label>场景</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{Object.entries(SCENE_LABELS).map(([value, label]) => (
|
||||
<Button
|
||||
key={value}
|
||||
@@ -226,17 +246,38 @@ function EditTopicsDialog({
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="questions">问题列表(每行一个)</Label>
|
||||
<Textarea
|
||||
id="questions"
|
||||
value={questionsText}
|
||||
onChange={(e) => setQuestionsText(e.target.value)}
|
||||
placeholder={'每行一个问题,例如:\n新公司法下股东出资加速到期,我要补缴吗?\n配偶要求离婚,公司股权怎么分割损失最小?'}
|
||||
className="min-h-[300px] scrollbar-none"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{questionsText.split('\n').filter(s => s.trim()).length} 个问题
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>问题列表({questions.filter(s => s.trim()).length} 条)</Label>
|
||||
<Button size="sm" variant="ghost" onClick={handleAdd} className="h-7 text-xs">
|
||||
<Plus className="mr-1 h-3.5 w-3.5" /> 添加
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="max-h-[50vh] space-y-2 overflow-y-auto scrollbar-none rounded-md border p-2"
|
||||
onPaste={handlePaste}
|
||||
>
|
||||
{questions.map((q, i) => (
|
||||
<div key={i} className="flex items-start gap-2">
|
||||
<span className="mt-2 shrink-0 text-xs tabular-nums text-muted-foreground">{i + 1}</span>
|
||||
<Input
|
||||
value={q}
|
||||
onChange={(e) => handleUpdate(i, e.target.value)}
|
||||
placeholder="输入问题..."
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleDelete(i)}
|
||||
className="mt-1 flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
aria-label="删除"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{questions.length === 0 && (
|
||||
<p className="py-4 text-center text-xs text-muted-foreground">暂无问题,点击"添加"或粘贴</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user