feat: 完成TikTok短剧管理后台基础功能开发
本次提交搭建了完整的后台管理系统,包含: 1. 登录认证与权限校验体系 2. 工作台、短剧、用户、角色、人员、日志等全业务模块 3. 响应式布局与通用样式组件 4. 菜单动态过滤与路由权限控制 5. 移除了README中冗余的权限管理菜单项
This commit is contained in:
65
src/Profile/index.css
Normal file
65
src/Profile/index.css
Normal file
@@ -0,0 +1,65 @@
|
||||
.profile-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 420px;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.profile-grid .section-card:first-child {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.profile-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.profile-summary h2 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.profile-summary p {
|
||||
margin: 0;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.profile-info-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.profile-info-list div {
|
||||
padding: 14px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
|
||||
.profile-info-list span {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #86909c;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.profile-info-list strong {
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.profile-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.profile-info-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
112
src/Profile/index.tsx
Normal file
112
src/Profile/index.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Avatar, Button, Card, Form, Input, Message } from '@arco-design/web-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { ApiResponse, AdminProfile } from '../api/types';
|
||||
import { TOKEN_KEY, api } from '../api/client';
|
||||
import './index.css';
|
||||
|
||||
function ProfilePage() {
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
const [passwordForm] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [changingPassword, setChangingPassword] = useState(false);
|
||||
const [profile, setProfile] = useState<AdminProfile | null>(null);
|
||||
|
||||
const loadProfile = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await api.get<ApiResponse<AdminProfile>>('/api/admin/v1/auth/profile');
|
||||
setProfile(response.data.data);
|
||||
form.setFieldsValue({
|
||||
nickname: response.data.data.nickname,
|
||||
email: response.data.data.email,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadProfile();
|
||||
}, []);
|
||||
|
||||
const updateProfile = async (values: { nickname?: string; email?: string }) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const response = await api.patch<ApiResponse<AdminProfile>>('/api/admin/v1/auth/profile', values);
|
||||
setProfile(response.data.data);
|
||||
Message.success('个人信息已更新');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async (values: { oldPassword: string; newPassword: string; confirmPassword: string }) => {
|
||||
if (values.newPassword !== values.confirmPassword) {
|
||||
Message.error('两次输入的新密码不一致');
|
||||
return;
|
||||
}
|
||||
setChangingPassword(true);
|
||||
try {
|
||||
await api.patch('/api/admin/v1/auth/password', {
|
||||
oldPassword: values.oldPassword,
|
||||
newPassword: values.newPassword,
|
||||
});
|
||||
Message.success('密码修改成功,请重新登录');
|
||||
passwordForm.resetFields();
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
navigate('/login', { replace: true });
|
||||
} finally {
|
||||
setChangingPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="profile-grid">
|
||||
<Card className="section-card" title="个人中心" loading={loading}>
|
||||
<div className="profile-summary">
|
||||
<Avatar size={56}>{profile?.username?.slice(0, 1).toUpperCase() || 'A'}</Avatar>
|
||||
<div>
|
||||
<h2>{profile?.nickname || profile?.username || '管理员'}</h2>
|
||||
<p>{profile?.email || '未设置邮箱'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="profile-info-list">
|
||||
<div><span>用户名</span><strong>{profile?.username || '-'}</strong></div>
|
||||
<div><span>状态</span><strong>{profile?.status === 'active' ? '启用' : '禁用'}</strong></div>
|
||||
<div><span>角色</span><strong>{profile?.isSuperAdmin ? '超级管理员' : profile?.roles.map((role) => role.name).join('、') || '-'}</strong></div>
|
||||
<div><span>创建时间</span><strong>{profile?.createdAt || '-'}</strong></div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="section-card" title="修改个人信息">
|
||||
<Form form={form} layout="vertical" onSubmit={updateProfile}>
|
||||
<Form.Item label="姓名" field="nickname">
|
||||
<Input placeholder="请输入姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item label="邮箱" field="email" rules={[{ type: 'email', message: '请输入正确的邮箱地址' }]}>
|
||||
<Input placeholder="请输入邮箱" />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={saving}>保存修改</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
<Card className="section-card" title="修改密码">
|
||||
<Form form={passwordForm} layout="vertical" onSubmit={changePassword}>
|
||||
<Form.Item label="旧密码" field="oldPassword" rules={[{ required: true }]}>
|
||||
<Input.Password placeholder="请输入旧密码" />
|
||||
</Form.Item>
|
||||
<Form.Item label="新密码" field="newPassword" rules={[{ required: true }, { minLength: 6, message: '新密码至少 6 位' }]}>
|
||||
<Input.Password placeholder="请输入新密码" />
|
||||
</Form.Item>
|
||||
<Form.Item label="确认新密码" field="confirmPassword" rules={[{ required: true }]}>
|
||||
<Input.Password placeholder="请再次输入新密码" />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={changingPassword}>确认修改</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePage;
|
||||
Reference in New Issue
Block a user