feat: 新增用户管理 & 论坛管理
This commit is contained in:
parent
8ad4263e2a
commit
8926d28a72
@ -27,12 +27,13 @@ export default [
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: '管理页',
|
||||
name: '后台管理',
|
||||
icon: 'crown',
|
||||
access: 'canAdmin',
|
||||
// access: 'canAdmin',
|
||||
routes: [
|
||||
{ path: '/admin', redirect: '/admin/sub-page' },
|
||||
{ path: '/admin/sub-page', name: '二级管理页', component: './Admin' },
|
||||
{ path: '/admin/sub-page', name: '论坛管理', component: './Admin' },
|
||||
{ path: '/admin/user-manage', name: '用户管理', component: './Admin_UserManage' },
|
||||
],
|
||||
},
|
||||
{ path: '/', redirect: '/welcome' },
|
||||
|
@ -1,44 +1,191 @@
|
||||
import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Alert, Card, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
const Admin: React.FC = () => {
|
||||
import { Table, Button, Modal, Form, Input, Tag, Space, Popconfirm, message, Avatar } from 'antd';
|
||||
import { listAllPostsUsingGet, updatePostUsingPost, deletePostUsingPost, addPostUsingPost } from '@/services/hebi/postController';
|
||||
|
||||
const ForumAdmin: React.FC = () => {
|
||||
const [posts, setPosts] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editingPost, setEditingPost] = useState<any>(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [search, setSearch] = useState<string>('');
|
||||
|
||||
// 获取帖子列表
|
||||
const fetchPosts = async () => {
|
||||
setLoading(true);
|
||||
const res = await listAllPostsUsingGet();
|
||||
if (res && res.code === 0 && Array.isArray(res.data)) {
|
||||
setPosts(res.data);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPosts();
|
||||
}, []);
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: any) => {
|
||||
setEditingPost(record);
|
||||
setModalVisible(true);
|
||||
form.setFieldsValue({
|
||||
...record,
|
||||
tags: record.tagList?.join(','),
|
||||
});
|
||||
};
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
setEditingPost(null);
|
||||
setModalVisible(true);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id: number) => {
|
||||
const res = await deletePostUsingPost({ id });
|
||||
if (res && res.code === 0) {
|
||||
message.success('删除成功');
|
||||
fetchPosts();
|
||||
} else {
|
||||
message.error(res?.message || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleOk = async () => {
|
||||
const values = await form.validateFields();
|
||||
const tags = values.tags ? values.tags.split(',').map((t: string) => t.trim()) : [];
|
||||
if (editingPost) {
|
||||
// 编辑
|
||||
const res = await updatePostUsingPost({ ...editingPost, ...values, tags });
|
||||
if (res && res.code === 0) {
|
||||
message.success('更新成功');
|
||||
setModalVisible(false);
|
||||
fetchPosts();
|
||||
} else {
|
||||
message.error(res?.message || '更新失败');
|
||||
}
|
||||
} else {
|
||||
// 新增
|
||||
const res = await addPostUsingPost({ ...values, tags });
|
||||
if (res && res.code === 0) {
|
||||
message.success('新增成功');
|
||||
setModalVisible(false);
|
||||
fetchPosts();
|
||||
} else {
|
||||
message.error(res?.message || '新增失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tagList',
|
||||
render: (tags: string[]) => (
|
||||
<>
|
||||
{tags?.map(tag => (
|
||||
<Tag color="blue" key={tag}>{tag}</Tag>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
dataIndex: ['user', 'userName'],
|
||||
width: 100,
|
||||
render: (_: any, record: any) => (
|
||||
<Space>
|
||||
{record.user?.userName}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 160,
|
||||
render: (_: any, record: any) => (
|
||||
<Space>
|
||||
<Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Popconfirm title="确定删除吗?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// 前端搜索过滤
|
||||
const filteredPosts = posts.filter(post => {
|
||||
const keyword = search.trim().toLowerCase();
|
||||
if (!keyword) return true;
|
||||
const titleMatch = post.title?.toLowerCase().includes(keyword);
|
||||
const tagsMatch = (post.tagList || []).some((tag: string) =>
|
||||
tag.toLowerCase().includes(keyword)
|
||||
);
|
||||
return titleMatch || tagsMatch;
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer content={' 这个页面只有 admin 权限才能查看'}>
|
||||
<Card>
|
||||
<Alert
|
||||
message={'更快更强的重型组件,已经发布。'}
|
||||
type="success"
|
||||
showIcon
|
||||
banner
|
||||
style={{
|
||||
margin: -12,
|
||||
marginBottom: 48,
|
||||
}}
|
||||
<PageContainer title="论坛管理">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<Button type="primary" onClick={handleAdd}>
|
||||
新增帖子
|
||||
</Button>
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder="搜索标题或标签"
|
||||
style={{ width: 300 }}
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
<Typography.Title
|
||||
level={2}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<SmileTwoTone /> 基于AIGC的智能数据分析系统 <HeartTwoTone twoToneColor="#eb2f96" />
|
||||
</Typography.Title>
|
||||
</Card>
|
||||
<p
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: 24,
|
||||
}}
|
||||
</div>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={filteredPosts}
|
||||
bordered
|
||||
pagination={{ pageSize: 8 }}
|
||||
/>
|
||||
<Modal
|
||||
title={editingPost ? '编辑帖子' : '新增帖子'}
|
||||
open={modalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
Want to add more pages? Please refer to{' '}
|
||||
<a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
|
||||
use block
|
||||
</a>
|
||||
。
|
||||
</p>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="title" label="标题" rules={[{ required: true, message: '请输入标题' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="content" label="内容" rules={[{ required: true, message: '请输入内容' }]}>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name="tags" label="标签(逗号分隔)">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default Admin;
|
||||
|
||||
export default ForumAdmin;
|
||||
|
255
src/pages/Admin_UserManage.tsx
Normal file
255
src/pages/Admin_UserManage.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Table, Button, Modal, Form, Input, Space, Popconfirm, message, Avatar, Select } from 'antd';
|
||||
import { listUserByPageUsingPost, updateUserUsingPost, deleteUserUsingPost, addUserUsingPost } from '@/services/hebi/userController';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const UserManage: React.FC = () => {
|
||||
const [allUsers, setAllUsers] = useState<any[]>([]);
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<any>(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [isAdd, setIsAdd] = useState(false); // 新增/编辑标志
|
||||
|
||||
// 拉取所有用户
|
||||
const fetchAllUsers = async () => {
|
||||
setLoading(true);
|
||||
const res = await listUserByPageUsingPost({
|
||||
userQueryRequest: {
|
||||
current: 1,
|
||||
pageSize: 10000, // 假设不会超过1万用户
|
||||
},
|
||||
});
|
||||
if (res && res.code === 0 && res.data) {
|
||||
setAllUsers(res.data.records || []);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// 前端过滤和分页
|
||||
const filterAndPaginate = (all: any[], keyword: string, page: number, pageSize: number) => {
|
||||
let filtered = all;
|
||||
if (keyword.trim()) {
|
||||
filtered = all.filter(user =>
|
||||
(user.userName || '').toLowerCase().includes(keyword.trim().toLowerCase())
|
||||
);
|
||||
}
|
||||
const total = filtered.length;
|
||||
const start = (page - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
return {
|
||||
users: filtered.slice(start, end),
|
||||
total,
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化和数据变动时处理
|
||||
useEffect(() => {
|
||||
fetchAllUsers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const { users, total } = filterAndPaginate(allUsers, search, pagination.current, pagination.pageSize);
|
||||
setUsers(users);
|
||||
setPagination(prev => ({ ...prev, total }));
|
||||
}, [allUsers, search, pagination.current, pagination.pageSize]);
|
||||
|
||||
// 搜索时重置到第一页
|
||||
const handleSearch = (value: string) => {
|
||||
setSearch(value);
|
||||
setPagination(prev => ({ ...prev, current: 1 }));
|
||||
};
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
setIsAdd(true);
|
||||
setEditingUser(null);
|
||||
setModalVisible(true);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: any) => {
|
||||
setIsAdd(false);
|
||||
setEditingUser(record);
|
||||
setModalVisible(true);
|
||||
form.setFieldsValue({
|
||||
...record,
|
||||
});
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleOk = async () => {
|
||||
const values = await form.validateFields();
|
||||
if (isAdd) {
|
||||
const res = await addUserUsingPost({ ...values });
|
||||
if (res && res.code === 0) {
|
||||
message.success('新增成功');
|
||||
setModalVisible(false);
|
||||
fetchAllUsers();
|
||||
} else {
|
||||
message.error(res?.message || '新增失败');
|
||||
}
|
||||
} else {
|
||||
const res = await updateUserUsingPost({ ...editingUser, ...values });
|
||||
if (res && res.code === 0) {
|
||||
message.success('更新成功');
|
||||
setModalVisible(false);
|
||||
fetchAllUsers();
|
||||
} else {
|
||||
message.error(res?.message || '更新失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id: number) => {
|
||||
const res = await deleteUserUsingPost({ id });
|
||||
if (res && res.code === 0) {
|
||||
message.success('删除成功');
|
||||
fetchAllUsers();
|
||||
} else {
|
||||
message.error(res?.message || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'userAvatar',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (avatar: string) => <Avatar src={avatar} />,
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'userName',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'userAccount',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'userRole',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (role: string) => {
|
||||
if (role === 'admin') return <span style={{ color: '#fa541c' }}>管理员</span>;
|
||||
if (role === 'ban') return <span style={{ color: '#bfbfbf' }}>禁用</span>;
|
||||
return <span>普通用户</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '简介',
|
||||
dataIndex: 'userProfile',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
render: (_: any, record: any) => (
|
||||
<Space>
|
||||
<Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Popconfirm title="确定删除该用户吗?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="用户管理">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<Button type="primary" onClick={handleAdd}>新增用户</Button>
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder="搜索用户名"
|
||||
style={{ width: 300 }}
|
||||
value={search}
|
||||
onChange={e => handleSearch(e.target.value)}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={users}
|
||||
bordered
|
||||
pagination={{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: pagination.total,
|
||||
showSizeChanger: true,
|
||||
onChange: (page, pageSize) => setPagination(prev => ({ ...prev, current: page, pageSize })),
|
||||
}}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
<Modal
|
||||
title={isAdd ? "新增用户" : "编辑用户"}
|
||||
open={modalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
destroyOnClose
|
||||
width={480}
|
||||
bodyStyle={{ padding: 24 }}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="userName" label="用户名" rules={[{ required: true, message: '请输入用户名' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="userAccount" label="账号" rules={isAdd ? [{ required: true, message: '请输入账号' }] : []}>
|
||||
<Input disabled={!isAdd} />
|
||||
</Form.Item>
|
||||
<Form.Item name="userAvatar" label="头像链接">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="userProfile" label="简介">
|
||||
<Input.TextArea rows={3} />
|
||||
</Form.Item>
|
||||
<Form.Item name="userRole" label="角色" rules={[{ required: true, message: '请选择角色' }]}>
|
||||
<Select>
|
||||
<Option value="user">普通用户</Option>
|
||||
<Option value="admin">管理员</Option>
|
||||
<Option value="ban">禁用</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{isAdd && (
|
||||
<Form.Item name="userPassword" label="密码" rules={[{ required: true, message: '请输入密码' }]}>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserManage;
|
Loading…
x
Reference in New Issue
Block a user