feat(creator): 完成达人端前端页面开发
- 新增申诉中心页面(列表、详情、新建申诉) - 新增申诉次数管理页面(按任务显示配额,支持向代理商申请) - 新增个人中心页面(达人ID复制、菜单导航) - 新增个人信息编辑、账户设置、消息通知设置页面 - 新增帮助中心和历史记录页面 - 新增脚本提交和视频提交页面 - 优化消息中心页面(消息详情跳转) - 优化任务详情页面布局和交互 - 更新 ResponsiveLayout、Sidebar、ReviewSteps 通用组件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
1011e73643
commit
2f9b7f05fd
@@ -0,0 +1,383 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Key,
|
||||
ShieldCheck,
|
||||
Smartphone,
|
||||
ChevronRight,
|
||||
Check,
|
||||
X,
|
||||
Eye,
|
||||
EyeOff,
|
||||
} from 'lucide-react'
|
||||
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Modal } from '@/components/ui/Modal'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// 模拟登录设备数据
|
||||
const mockDevices = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'iPhone 15 Pro',
|
||||
type: 'mobile',
|
||||
location: '北京',
|
||||
lastActive: '当前设备',
|
||||
isCurrent: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'MacBook Pro',
|
||||
type: 'desktop',
|
||||
location: '北京',
|
||||
lastActive: '2小时前',
|
||||
isCurrent: false,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'iPad Air',
|
||||
type: 'tablet',
|
||||
location: '上海',
|
||||
lastActive: '3天前',
|
||||
isCurrent: false,
|
||||
},
|
||||
]
|
||||
|
||||
// 设置项组件
|
||||
function SettingItem({
|
||||
icon: Icon,
|
||||
iconColor,
|
||||
title,
|
||||
description,
|
||||
badge,
|
||||
onClick,
|
||||
showArrow = true,
|
||||
}: {
|
||||
icon: React.ElementType
|
||||
iconColor: string
|
||||
title: string
|
||||
description: string
|
||||
badge?: React.ReactNode
|
||||
onClick?: () => void
|
||||
showArrow?: boolean
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="flex items-center justify-between py-5 px-5 w-full text-left hover:bg-bg-elevated/30 transition-colors border-b border-border-subtle last:border-b-0"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', `${iconColor}/15`)}>
|
||||
<Icon size={20} className={iconColor} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[15px] font-medium text-text-primary">{title}</span>
|
||||
<span className="text-[13px] text-text-tertiary">{description}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{badge}
|
||||
{showArrow && <ChevronRight size={20} className="text-text-tertiary" />}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// 修改密码弹窗
|
||||
function ChangePasswordModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
const [step, setStep] = useState(1)
|
||||
const [showPasswords, setShowPasswords] = useState({
|
||||
current: false,
|
||||
new: false,
|
||||
confirm: false,
|
||||
})
|
||||
const [formData, setFormData] = useState({
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsSaving(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
setIsSaving(false)
|
||||
setStep(2)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setStep(1)
|
||||
setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={handleClose} title="修改密码">
|
||||
{step === 1 ? (
|
||||
<div className="flex flex-col gap-5">
|
||||
{/* 当前密码 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-text-primary">当前密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPasswords.current ? 'text' : 'password'}
|
||||
value={formData.currentPassword}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, currentPassword: e.target.value }))}
|
||||
placeholder="请输入当前密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPasswords(prev => ({ ...prev, current: !prev.current }))}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showPasswords.current ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 新密码 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-text-primary">新密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPasswords.new ? 'text' : 'password'}
|
||||
value={formData.newPassword}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, newPassword: e.target.value }))}
|
||||
placeholder="请输入新密码(8-20位)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPasswords(prev => ({ ...prev, new: !prev.new }))}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showPasswords.new ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-text-tertiary">密码需包含字母和数字,长度8-20位</p>
|
||||
</div>
|
||||
|
||||
{/* 确认密码 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-text-primary">确认新密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPasswords.confirm ? 'text' : 'password'}
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))}
|
||||
placeholder="请再次输入新密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPasswords(prev => ({ ...prev, confirm: !prev.confirm }))}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showPasswords.confirm ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 按钮 */}
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button variant="secondary" size="lg" className="flex-1" onClick={handleClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="flex-1"
|
||||
onClick={handleSubmit}
|
||||
disabled={!formData.currentPassword || !formData.newPassword || !formData.confirmPassword || isSaving}
|
||||
>
|
||||
{isSaving ? '提交中...' : '确认修改'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-4 py-6">
|
||||
<div className="w-16 h-16 rounded-full bg-accent-green/15 flex items-center justify-center">
|
||||
<Check size={32} className="text-accent-green" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-semibold text-text-primary">密码修改成功</p>
|
||||
<p className="text-sm text-text-secondary mt-1">下次登录请使用新密码</p>
|
||||
</div>
|
||||
<Button variant="primary" size="lg" className="w-full mt-2" onClick={handleClose}>
|
||||
完成
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
// 设备管理弹窗
|
||||
function DeviceManageModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState(mockDevices)
|
||||
|
||||
const handleRemoveDevice = (deviceId: string) => {
|
||||
setDevices(prev => prev.filter(d => d.id !== deviceId))
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="登录设备管理">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-text-secondary">管理已登录此账号的设备,可移除不再使用的设备以保障账户安全。</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{devices.map((device) => (
|
||||
<div
|
||||
key={device.id}
|
||||
className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-bg-card flex items-center justify-center">
|
||||
<Smartphone size={20} className="text-text-secondary" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[15px] font-medium text-text-primary">{device.name}</span>
|
||||
{device.isCurrent && (
|
||||
<span className="px-2 py-0.5 rounded-full bg-accent-green/15 text-accent-green text-xs font-medium">
|
||||
当前
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[13px] text-text-tertiary">
|
||||
{device.location} · {device.lastActive}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!device.isCurrent && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveDevice(device.id)}
|
||||
className="text-accent-coral hover:text-accent-coral/80 text-sm font-medium"
|
||||
>
|
||||
移除
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button variant="secondary" size="lg" className="w-full mt-2" onClick={onClose}>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AccountSettingsPage() {
|
||||
const router = useRouter()
|
||||
const [showPasswordModal, setShowPasswordModal] = useState(false)
|
||||
const [showDeviceModal, setShowDeviceModal] = useState(false)
|
||||
const [twoFactorEnabled, setTwoFactorEnabled] = useState(true)
|
||||
|
||||
return (
|
||||
<ResponsiveLayout role="creator">
|
||||
<div className="flex flex-col gap-6 h-full">
|
||||
{/* 顶部栏 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="w-10 h-10 rounded-xl bg-bg-elevated flex items-center justify-center hover:bg-bg-elevated/80 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={20} className="text-text-secondary" />
|
||||
</button>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl lg:text-[28px] font-bold text-text-primary">账户设置</h1>
|
||||
<p className="text-sm lg:text-[15px] text-text-secondary">管理密码和账户安全选项</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容区 */}
|
||||
<div className="flex flex-col gap-5 flex-1 min-h-0 overflow-y-auto lg:max-w-2xl">
|
||||
{/* 账户安全 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-text-primary px-1">账户安全</h2>
|
||||
<div className="bg-bg-card rounded-2xl card-shadow overflow-hidden">
|
||||
<SettingItem
|
||||
icon={Key}
|
||||
iconColor="text-accent-indigo"
|
||||
title="修改密码"
|
||||
description="定期更新密码以保障账户安全"
|
||||
onClick={() => setShowPasswordModal(true)}
|
||||
/>
|
||||
<SettingItem
|
||||
icon={ShieldCheck}
|
||||
iconColor="text-accent-green"
|
||||
title="两步验证"
|
||||
description="启用双因素认证增强安全性"
|
||||
badge={
|
||||
<span
|
||||
className={cn(
|
||||
'px-2 py-1 rounded text-xs font-medium',
|
||||
twoFactorEnabled
|
||||
? 'bg-accent-green/15 text-accent-green'
|
||||
: 'bg-bg-elevated text-text-tertiary'
|
||||
)}
|
||||
>
|
||||
{twoFactorEnabled ? '已开启' : '未开启'}
|
||||
</span>
|
||||
}
|
||||
onClick={() => setTwoFactorEnabled(!twoFactorEnabled)}
|
||||
/>
|
||||
<SettingItem
|
||||
icon={Smartphone}
|
||||
iconColor="text-accent-blue"
|
||||
title="登录设备管理"
|
||||
description="查看和管理已登录的设备"
|
||||
onClick={() => setShowDeviceModal(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 危险区域 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-accent-coral px-1">危险区域</h2>
|
||||
<div className="bg-bg-card rounded-2xl card-shadow p-5">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[15px] font-medium text-text-primary">注销账户</span>
|
||||
<span className="text-[13px] text-text-tertiary">
|
||||
永久删除您的账户和所有相关数据,此操作不可撤销
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
className="border-accent-coral text-accent-coral hover:bg-accent-coral/10 lg:flex-shrink-0"
|
||||
>
|
||||
注销账户
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 弹窗 */}
|
||||
<ChangePasswordModal isOpen={showPasswordModal} onClose={() => setShowPasswordModal(false)} />
|
||||
<DeviceManageModal isOpen={showDeviceModal} onClose={() => setShowDeviceModal(false)} />
|
||||
</ResponsiveLayout>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Mail,
|
||||
Bell,
|
||||
MessageSquare,
|
||||
Clock,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react'
|
||||
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// 开关组件
|
||||
function Toggle({
|
||||
enabled,
|
||||
onChange,
|
||||
}: {
|
||||
enabled: boolean
|
||||
onChange: (enabled: boolean) => void
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(!enabled)}
|
||||
className={cn(
|
||||
'w-12 h-7 rounded-full p-0.5 transition-colors',
|
||||
enabled ? 'bg-accent-indigo' : 'bg-bg-elevated'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'w-6 h-6 rounded-full bg-white shadow-sm transition-transform',
|
||||
enabled ? 'translate-x-5' : 'translate-x-0'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// 设置项组件
|
||||
function NotificationSettingItem({
|
||||
icon: Icon,
|
||||
iconColor,
|
||||
title,
|
||||
description,
|
||||
enabled,
|
||||
onChange,
|
||||
isLast = false,
|
||||
}: {
|
||||
icon: React.ElementType
|
||||
iconColor: string
|
||||
title: string
|
||||
description: string
|
||||
enabled: boolean
|
||||
onChange: (enabled: boolean) => void
|
||||
isLast?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between py-5 px-5',
|
||||
!isLast && 'border-b border-border-subtle'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', `${iconColor}/15`)}>
|
||||
<Icon size={20} className={iconColor} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[15px] font-medium text-text-primary">{title}</span>
|
||||
<span className="text-[13px] text-text-tertiary">{description}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Toggle enabled={enabled} onChange={onChange} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 时段选择组件
|
||||
function TimeRangeSelector({
|
||||
startTime,
|
||||
endTime,
|
||||
onChange,
|
||||
disabled,
|
||||
}: {
|
||||
startTime: string
|
||||
endTime: string
|
||||
onChange: (start: string, end: string) => void
|
||||
disabled: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-3', disabled && 'opacity-50')}>
|
||||
<select
|
||||
value={startTime}
|
||||
onChange={(e) => onChange(e.target.value, endTime)}
|
||||
disabled={disabled}
|
||||
className="px-3 py-2 rounded-lg bg-bg-elevated border border-border-default text-text-primary text-sm focus:outline-none focus:border-accent-indigo"
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<option key={i} value={`${i.toString().padStart(2, '0')}:00`}>
|
||||
{i.toString().padStart(2, '0')}:00
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="text-text-tertiary">至</span>
|
||||
<select
|
||||
value={endTime}
|
||||
onChange={(e) => onChange(startTime, e.target.value)}
|
||||
disabled={disabled}
|
||||
className="px-3 py-2 rounded-lg bg-bg-elevated border border-border-default text-text-primary text-sm focus:outline-none focus:border-accent-indigo"
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<option key={i} value={`${i.toString().padStart(2, '0')}:00`}>
|
||||
{i.toString().padStart(2, '0')}:00
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function NotificationSettingsPage() {
|
||||
const router = useRouter()
|
||||
|
||||
// 通知设置状态
|
||||
const [settings, setSettings] = useState({
|
||||
emailNotification: true,
|
||||
pushNotification: true,
|
||||
smsNotification: false,
|
||||
reviewResult: true,
|
||||
taskReminder: true,
|
||||
urgentAlert: true,
|
||||
quietMode: false,
|
||||
quietStart: '22:00',
|
||||
quietEnd: '08:00',
|
||||
})
|
||||
|
||||
const handleToggle = (key: keyof typeof settings) => {
|
||||
setSettings(prev => ({ ...prev, [key]: !prev[key] }))
|
||||
}
|
||||
|
||||
const handleTimeChange = (start: string, end: string) => {
|
||||
setSettings(prev => ({ ...prev, quietStart: start, quietEnd: end }))
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveLayout role="creator">
|
||||
<div className="flex flex-col gap-6 h-full">
|
||||
{/* 顶部栏 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="w-10 h-10 rounded-xl bg-bg-elevated flex items-center justify-center hover:bg-bg-elevated/80 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={20} className="text-text-secondary" />
|
||||
</button>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl lg:text-[28px] font-bold text-text-primary">消息设置</h1>
|
||||
<p className="text-sm lg:text-[15px] text-text-secondary">自定义通知方式和提醒偏好</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容区 */}
|
||||
<div className="flex flex-col gap-5 flex-1 min-h-0 overflow-y-auto lg:max-w-2xl">
|
||||
{/* 通知渠道 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-text-primary px-1">通知渠道</h2>
|
||||
<div className="bg-bg-card rounded-2xl card-shadow overflow-hidden">
|
||||
<NotificationSettingItem
|
||||
icon={Mail}
|
||||
iconColor="text-accent-indigo"
|
||||
title="邮件通知"
|
||||
description="接收审核结果和系统通知的邮件提醒"
|
||||
enabled={settings.emailNotification}
|
||||
onChange={() => handleToggle('emailNotification')}
|
||||
/>
|
||||
<NotificationSettingItem
|
||||
icon={Bell}
|
||||
iconColor="text-accent-blue"
|
||||
title="推送通知"
|
||||
description="接收移动端实时推送消息"
|
||||
enabled={settings.pushNotification}
|
||||
onChange={() => handleToggle('pushNotification')}
|
||||
/>
|
||||
<NotificationSettingItem
|
||||
icon={MessageSquare}
|
||||
iconColor="text-accent-green"
|
||||
title="短信通知"
|
||||
description="接收重要消息的短信提醒"
|
||||
enabled={settings.smsNotification}
|
||||
onChange={() => handleToggle('smsNotification')}
|
||||
isLast
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 通知类型 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-text-primary px-1">通知类型</h2>
|
||||
<div className="bg-bg-card rounded-2xl card-shadow overflow-hidden">
|
||||
<NotificationSettingItem
|
||||
icon={Bell}
|
||||
iconColor="text-accent-indigo"
|
||||
title="审核结果通知"
|
||||
description="视频审核通过或驳回时收到通知"
|
||||
enabled={settings.reviewResult}
|
||||
onChange={() => handleToggle('reviewResult')}
|
||||
/>
|
||||
<NotificationSettingItem
|
||||
icon={Clock}
|
||||
iconColor="text-accent-coral"
|
||||
title="任务提醒"
|
||||
description="任务截止日期临近时收到提醒"
|
||||
enabled={settings.taskReminder}
|
||||
onChange={() => handleToggle('taskReminder')}
|
||||
/>
|
||||
<NotificationSettingItem
|
||||
icon={AlertTriangle}
|
||||
iconColor="text-status-error"
|
||||
title="紧急通知"
|
||||
description="紧急事项和系统公告即时推送"
|
||||
enabled={settings.urgentAlert}
|
||||
onChange={() => handleToggle('urgentAlert')}
|
||||
isLast
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 免打扰模式 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-text-primary px-1">免打扰模式</h2>
|
||||
<div className="bg-bg-card rounded-2xl card-shadow p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[15px] font-medium text-text-primary">开启免打扰</span>
|
||||
<span className="text-[13px] text-text-tertiary">在指定时段内静音所有通知</span>
|
||||
</div>
|
||||
<Toggle enabled={settings.quietMode} onChange={() => handleToggle('quietMode')} />
|
||||
</div>
|
||||
|
||||
{/* 时间段选择 */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col lg:flex-row lg:items-center gap-3 pt-3 border-t border-border-subtle',
|
||||
!settings.quietMode && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
<span className="text-sm text-text-secondary whitespace-nowrap">免打扰时段</span>
|
||||
<TimeRangeSelector
|
||||
startTime={settings.quietStart}
|
||||
endTime={settings.quietEnd}
|
||||
onChange={handleTimeChange}
|
||||
disabled={!settings.quietMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 提示卡片 */}
|
||||
<div className="bg-accent-indigo/10 rounded-2xl p-5 flex gap-4">
|
||||
<div className="w-10 h-10 rounded-xl bg-accent-indigo/15 flex items-center justify-center flex-shrink-0">
|
||||
<Bell size={20} className="text-accent-indigo" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[15px] font-medium text-text-primary">关于通知</span>
|
||||
<span className="text-[13px] text-text-secondary leading-relaxed">
|
||||
建议保持审核结果和紧急通知开启,以便及时了解任务进度和重要信息。您可以随时调整通知偏好。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ResponsiveLayout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user