feat: 新增用户管理 & 论坛管理
This commit is contained in:
parent
8ad4263e2a
commit
8926d28a72
@ -27,12 +27,13 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
name: '管理页',
|
name: '后台管理',
|
||||||
icon: 'crown',
|
icon: 'crown',
|
||||||
access: 'canAdmin',
|
// access: 'canAdmin',
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/admin', redirect: '/admin/sub-page' },
|
{ 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' },
|
{ 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 { PageContainer } from '@ant-design/pro-components';
|
||||||
import '@umijs/max';
|
import { Table, Button, Modal, Form, Input, Tag, Space, Popconfirm, message, Avatar } from 'antd';
|
||||||
import { Alert, Card, Typography } from 'antd';
|
import { listAllPostsUsingGet, updatePostUsingPost, deletePostUsingPost, addPostUsingPost } from '@/services/hebi/postController';
|
||||||
import React from 'react';
|
|
||||||
const Admin: React.FC = () => {
|
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 (
|
return (
|
||||||
<PageContainer content={' 这个页面只有 admin 权限才能查看'}>
|
<PageContainer title="论坛管理">
|
||||||
<Card>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
<Alert
|
<Button type="primary" onClick={handleAdd}>
|
||||||
message={'更快更强的重型组件,已经发布。'}
|
新增帖子
|
||||||
type="success"
|
</Button>
|
||||||
showIcon
|
<Input.Search
|
||||||
banner
|
allowClear
|
||||||
style={{
|
placeholder="搜索标题或标签"
|
||||||
margin: -12,
|
style={{ width: 300 }}
|
||||||
marginBottom: 48,
|
value={search}
|
||||||
}}
|
onChange={e => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Typography.Title
|
</div>
|
||||||
level={2}
|
<Table
|
||||||
style={{
|
rowKey="id"
|
||||||
textAlign: 'center',
|
loading={loading}
|
||||||
}}
|
columns={columns}
|
||||||
>
|
dataSource={filteredPosts}
|
||||||
<SmileTwoTone /> 基于AIGC的智能数据分析系统 <HeartTwoTone twoToneColor="#eb2f96" />
|
bordered
|
||||||
</Typography.Title>
|
pagination={{ pageSize: 8 }}
|
||||||
</Card>
|
/>
|
||||||
<p
|
<Modal
|
||||||
style={{
|
title={editingPost ? '编辑帖子' : '新增帖子'}
|
||||||
textAlign: 'center',
|
open={modalVisible}
|
||||||
marginTop: 24,
|
onOk={handleOk}
|
||||||
}}
|
onCancel={() => setModalVisible(false)}
|
||||||
|
destroyOnClose
|
||||||
>
|
>
|
||||||
Want to add more pages? Please refer to{' '}
|
<Form form={form} layout="vertical">
|
||||||
<a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
|
<Form.Item name="title" label="标题" rules={[{ required: true, message: '请输入标题' }]}>
|
||||||
use block
|
<Input />
|
||||||
</a>
|
</Form.Item>
|
||||||
。
|
<Form.Item name="content" label="内容" rules={[{ required: true, message: '请输入内容' }]}>
|
||||||
</p>
|
<Input.TextArea rows={4} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="tags" label="标签(逗号分隔)">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
</PageContainer>
|
</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