提交 00b5e549 编写于 作者: Q qujay

增加菜单CRUD,token删除,慢sql查询,个人信息更新,所属应用切换事件

上级 37de4de0
......@@ -12,7 +12,7 @@ import React, { useState } from 'react';
const SlowSqlLog: React.FC = () => {
const [params, setParams] = useState<Record<string, string | number>>();
const columns: ProColumns<SEARCH.AuditLog>[] = [
const columns: ProColumns<SEARCH.SlowSqlLog>[] = [
{
dataIndex: 'index',
valueType: 'indexBorder',
......@@ -75,20 +75,15 @@ const SlowSqlLog: React.FC = () => {
<ProFormSelect
name="searchKey"
label="搜索"
initialValue={['全文搜索']}
valueEnum={{
none: '全文搜索',
appName: '应用名',
classname: '类别',
methodName: '方法名',
userId: '用户ID',
userName: '用户名',
clientId: '租户ID',
operation: '操作信息',
query_str: '查询语句',
}}
/>
<ProFormText name="searchValue" />
</QueryFilter>
<ProTable<SEARCH.AuditLog>
<ProTable<SEARCH.SlowSqlLog>
className="audit-log"
rowKey="id"
headerTitle="慢查询日志"
......
import { menuOnes } from '@/services/system/api';
import {
ModalForm,
ProForm,
ProFormDigit,
ProFormRadio,
ProFormSelect,
ProFormText,
ProFormTreeSelect,
} from '@ant-design/pro-components';
import { treeify } from '@/util/treeify';
import { Form } from 'antd';
import React, { useEffect } from 'react';
export type FormValueType = {
roleIds?: number[];
} & Partial<SYSTEM.User>;
export type UpdateFormProps = {
onVisibleChange: (flag: boolean) => void;
onSubmit: (values: FormValueType) => Promise<void>;
updateModalVisible: boolean;
values?: SYSTEM.Menu;
};
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [form] = Form.useForm();
const { values } = props;
useEffect(() => {
form.setFieldsValue(values);
}, [form, values]);
return (
<ModalForm
title="修改菜单"
form={form}
// layout="horizontal"
visible={props.updateModalVisible}
// initialValues={formData}
onFinish={props.onSubmit}
onVisibleChange={props.onVisibleChange}
>
<ProForm.Group>
<ProFormTreeSelect
label="上级菜单"
name="parentId"
width="md"
request={async () => {
const menus = await menuOnes();
if (menus) {
let a = menus.map(item => {
return { ...item, key: item.id, title: item.name, value: item.id }
})
let treeData = treeify(a, {});
let root = {
title: '顶级目录',
value: -1,
key: -1,
};
treeData.unshift(root);
return treeData;
}
return [];
}}
fieldProps={{
fieldNames: {
label: 'title',
},
// treeCheckable: true,
// showCheckedStrategy: TreeSelect.SHOW_PARENT,
placeholder: '选择上级菜单名',
}}
/>
<ProFormText
rules={[
{
required: true,
message: '菜单名不为空',
},
]}
width="md"
name="name"
label="菜单名"
placeholder="输入菜单名"
/>
<ProFormText
width="md"
name="url"
label="菜单url"
placeholder="输入菜单url"
/>
<ProFormText
width="md"
name="path"
label="菜单path"
placeholder="输入菜单path"
/>
<ProFormText
rules={[
{
required: true,
message: '菜单图标不为空',
},
]}
width="md"
name="css"
label="菜单图标"
placeholder="输入菜单图标"
/>
<ProFormRadio.Group
name="hidden"
label="是否隐藏"
width="lg"
initialValue={false}
options={[
{
label: '',
value: false,
},
{
label: '',
value: true,
},
]}
rules={[{ required: true, message: '选择是否隐藏' }]}
/>
<ProFormRadio.Group
name="type"
label="是否为菜单"
width="lg"
initialValue={1}
options={[
{
label: '',
value: 1,
},
{
label: '',
value: 2,
},
]}
rules={[{ required: true, message: '选择是否为菜单' }]}
/>
<ProFormSelect
name="pathMethod"
label="请求方法"
width="md"
valueEnum={{
GET: 'GET',
POST: 'POST',
PUT: 'PUT',
DELETE: 'DELETE',
}}
placeholder="选择请求方法"
/>
<ProFormDigit
label="排序号"
name="sort"
width="md"
min={1}
fieldProps={{ precision: 0 }}
rules={[{ required: true, message: '选择排序号' }]}
/>
</ProForm.Group>
</ModalForm>
);
};
export default UpdateForm;
import { menu } from '@/services/system/api';
import { deleteMenus, menu, menuOnes, saveOrUpdateMenus } from '@/services/system/api';
import { treeify } from '@/util/treeify';
import { FolderOpenOutlined, MenuOutlined, PlusOutlined, ProfileOutlined } from '@ant-design/icons';
import type { ProColumns } from '@ant-design/pro-components';
import { ProFormSelect } from '@ant-design/pro-components';
import { PageContainer, ProFormText, ProTable, QueryFilter } from '@ant-design/pro-components';
import { Button, Space, Tag, Typography } from 'antd';
import React, { useEffect, useState } from 'react';
import { FolderOpenOutlined, MenuOutlined, PlusOutlined, ProfileOutlined, } from '@ant-design/icons';
import { idIDIntl, ProColumns, ProFormDigit } from '@ant-design/pro-components';
import { ProFormRadio, ProFormTreeSelect, ProForm, ProFormSelect } from '@ant-design/pro-components';
import { PageContainer, ProFormText, ProTable, QueryFilter, ModalForm } from '@ant-design/pro-components';
import { Button, Space, Tag, Typography, TreeSelect, message, Popconfirm } from 'antd';
import React, { useRef, useEffect, useState } from 'react';
import UpdateForm from './components/UpdateForm';
const { Link } = Typography;
const handleAdd = async (fields: SYSTEM.Menu) => {
const hide = message.loading('正在添加');
try {
const result = await saveOrUpdateMenus({ ...fields });
hide();
if (result.resp_code === 0) {
message.success('添加菜单成功');
return true;
} else {
message.error(result.resp_msg);
return false;
}
} catch (error) {
hide();
message.error('添加菜单失败');
return false;
}
};
const handleEdit = async (fields: SYSTEM.Menu) => {
const hide = message.loading('正在更新');
try {
const result = await saveOrUpdateMenus({ ...fields });
hide();
if (result.resp_code === 0) {
message.success('修改菜单成功');
return true;
} else {
message.error(result.resp_msg);
return false;
}
} catch (error) {
hide();
message.error('修改菜单失败');
return false;
}
};
const handleDelete = async (sysUser: SYSTEM.Menu) => {
const hide = message.loading('正在删除');
try {
const result = await deleteMenus(sysUser.id);
hide();
if (result.resp_code === 0) {
message.success('删除菜单成功');
return true;
} else {
message.error(result.resp_msg);
return false;
}
} catch (error) {
hide();
message.error('删除菜单失败');
return false;
}
};
const Generator: React.FC = () => {
const [params, setParams] = useState<Record<string, string | number>>({});
const actionRef = useRef<ActionType>();
const [createModalVisible, handleModalVisible] = useState<boolean>(false);
const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState<SYSTEM.Menu>();
const [data, setData] = useState<SYSTEM.Menu[]>();
const [keys, setKeys] = useState<(React.Key)[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<(React.Key)[]>([]);
const query = async (params: Record<string, string | number>) => {
const menus = await menu(params);
const treeData = treeify(menus, {});
menus.forEach((node) => {
if (node.children && node.children.length === 0) delete node.children;
});
// debugger;
setKeys(menus.map(m=>m.id));
setKeys(menus.map(m => m.id));
setData(treeData);
};
......@@ -102,10 +165,29 @@ const Generator: React.FC = () => {
key: 'action',
fixed: 'right',
width: 80,
render: () => (
render: (_, entity) => (
<Space>
<Link onClick={() => {}}>修改</Link>
<Link onClick={() => {}}>删除</Link>
<Link
onClick={() => {
setCurrentRow(entity);
handleUpdateModalVisible(true);
}}
>
修改
</Link>
<Popconfirm
title={`确认删除菜单[${entity.name}]?`}
onConfirm={async () => {
const success = await handleDelete(entity);
if (success) {
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
>
<Link>删除</Link>
</Popconfirm>
</Space>
),
},
......@@ -118,11 +200,14 @@ const Generator: React.FC = () => {
span={6}
className="query-filter"
onFinish={async (values) => query(values)}
// onReset={() => setParams({})}
// onReset={() => setParams({})}
>
<ProFormSelect
name="tenantId"
label="所属应用"
fieldProps={{
onChange: async (values) => query({ tenantId: values })
}}
valueEnum={{
webApp: 'pc端',
app: '移动端',
......@@ -134,6 +219,16 @@ const Generator: React.FC = () => {
<ProTable<SYSTEM.Menu>
rowKey="id"
headerTitle="菜单管理"
actionRef={actionRef}
request={async () => {
const menus = await menu(params);
const treeData = treeify(menus, {});
menus.forEach((node) => {
if (node.children && node.children.length === 0) delete node.children;
});
setKeys(menus.map(m => m.id));
setData(treeData);
}}
dataSource={data}
columns={columns}
search={false}
......@@ -144,7 +239,7 @@ const Generator: React.FC = () => {
}}
toolBarRender={() => [
<>
<Button
<Button
onClick={() => {
setExpandedRowKeys(keys);
}}
......@@ -161,7 +256,7 @@ const Generator: React.FC = () => {
<Button
type="primary"
onClick={() => {
//handleModalVisible(true);
handleModalVisible(true);
}}
>
<PlusOutlined /> 添加
......@@ -169,6 +264,162 @@ const Generator: React.FC = () => {
</>,
]}
/>
<ModalForm
title="添加菜单"
// layout="horizontal"
visible={createModalVisible}
onVisibleChange={handleModalVisible}
modalProps={{ destroyOnClose: true, okText: "保存" }}
onFinish={async (values) => {
const success = await handleAdd(values as SYSTEM.Menu);
if (success) {
handleModalVisible(false);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
>
<ProForm.Group>
<ProFormTreeSelect
label="上级菜单"
name="parentId"
width="md"
request={async () => {
const menus = await menuOnes();
if (menus) {
let a = menus.map(item => {
return { ...item, key: item.id, title: item.name, value: item.id }
})
let treeData = treeify(a, {});
let root = {
title: '顶级目录',
value: -1,
key: -1,
};
treeData.unshift(root);
return treeData;
}
return [];
}}
fieldProps={{
fieldNames: {
label: 'title',
},
// treeCheckable: true,
// showCheckedStrategy: TreeSelect.SHOW_PARENT,
placeholder: '选择上级菜单名',
}}
/>
<ProFormText
rules={[
{
required: true,
message: '菜单名不为空',
},
]}
width="md"
name="name"
label="菜单名"
placeholder="输入菜单名"
/>
<ProFormText
width="md"
name="url"
label="菜单url"
placeholder="输入菜单url"
/>
<ProFormText
width="md"
name="path"
label="菜单path"
placeholder="输入菜单path"
/>
<ProFormText
rules={[
{
required: true,
message: '菜单图标不为空',
},
]}
width="md"
name="css"
label="菜单图标"
placeholder="输入菜单图标"
/>
<ProFormRadio.Group
name="hidden"
label="是否隐藏"
width="lg"
initialValue={false}
options={[
{
label: '',
value: false,
},
{
label: '',
value: true,
},
]}
rules={[{ required: true, message: '选择是否隐藏' }]}
/>
<ProFormRadio.Group
name="type"
label="是否为菜单"
width="lg"
initialValue={1}
options={[
{
label: '',
value: 1,
},
{
label: '',
value: 2,
},
]}
rules={[{ required: true, message: '选择是否为菜单' }]}
/>
<ProFormSelect
name="pathMethod"
label="请求方法"
width="md"
valueEnum={{
GET: 'GET',
POST: 'POST',
PUT: 'PUT',
DELETE: 'DELETE',
}}
placeholder="选择请求方法"
/>
<ProFormDigit
label="排序号"
name="sort"
width="md"
min={1}
fieldProps={{ precision: 0 }}
rules={[{ required: true, message: '选择排序号' }]}
/>
</ProForm.Group>
</ModalForm>
<UpdateForm
onSubmit={async (values) => {
if (currentRow) {
const success = await handleEdit({ ...currentRow, ...values });
if (success) {
handleUpdateModalVisible(false);
if (actionRef.current) {
actionRef.current.reload();
}
}
setCurrentRow(undefined);
}
}}
onVisibleChange={handleUpdateModalVisible}
updateModalVisible={updateModalVisible}
values={currentRow}
/>
</PageContainer>
);
};
......
......@@ -163,6 +163,9 @@ const TableList: React.FC = () => {
<ProFormSelect
name="tenantId"
label="所属应用"
fieldProps={{
onChange:async (values) => setParams({tenantId: values})
}}
valueEnum={{
webApp: 'pc端',
app: '移动端',
......
import { token } from '@/services/system/api';
import type { ProColumns } from '@ant-design/pro-components';
import { deleteToken, token } from '@/services/system/api';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import {
ProFormSelect,
PageContainer,
......@@ -7,14 +7,33 @@ import {
ProTable,
QueryFilter,
} from '@ant-design/pro-components';
import { Typography, message } from 'antd';
import React, { useState } from 'react';
import { Typography, message, Popconfirm } from 'antd';
import React, { useRef, useState } from 'react';
const { Link } = Typography;
const handleDelete = async (token: SYSTEM.Token) => {
const hide = message.loading('正在删除');
try {
const result = await deleteToken(token.tokenValue);
hide();
if (result.resp_code === 0) {
message.success('删除Token成功');
return true;
} else {
message.error(result.resp_msg);
return false;
}
} catch (error) {
hide();
message.error('删除Token失败');
return false;
}
};
const TableList: React.FC = () => {
const actionRef = useRef<ActionType>();
const [params, setParams] = useState<Record<string, string | number>>({ tenantId: 'webApp' });
const columns: ProColumns<SYSTEM.Token>[] = [
{
dataIndex: 'index',
......@@ -48,7 +67,19 @@ const TableList: React.FC = () => {
{
title: '操作',
key: 'action',
render: () => <Link onClick={() => message.info('演示环境不支持该功能')}>删除</Link>,
render: (_, entity) => <Popconfirm
title={`确认删除Token?`}
onConfirm={async () => {
const success = await handleDelete(entity);
if (success) {
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
>
<Link>删除</Link>
</Popconfirm>,
},
];
......@@ -61,11 +92,14 @@ const TableList: React.FC = () => {
className="query-filter"
initialValues={params}
onFinish={async (values) => setParams(values)}
// onReset={() => setParams({})}
// onReset={() => setParams({})}
>
<ProFormSelect
name="tenantId"
label="所属应用"
fieldProps={{
onChange:async (values) => setParams({tenantId: values})
}}
valueEnum={{
webApp: 'pc端',
app: '移动端',
......@@ -79,6 +113,7 @@ const TableList: React.FC = () => {
rowKey="tokenValue"
headerTitle="Token管理"
request={token}
actionRef={actionRef}
columns={columns}
search={false}
params={params}
......
......@@ -23,7 +23,6 @@ export type UpdateFormProps = {
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [form] = Form.useForm();
const { values } = props;
useEffect(() => {
if (values?.roles) {
const roleIds = values.roles.map((r) => r.id);
......
......@@ -125,7 +125,6 @@ const handleDelete = async (sysUser: SYSTEM.User) => {
const handleImport = async (file: RcFile) => {
const hide = message.loading('正在导入');
debugger
try {
const formData = new FormData();
formData.append('file', file);
......
import { currentUser2 } from '@/services/login/api';
import { importImage, importUser, saveOrUpdateUser } from '@/services/system/api';
import { UploadOutlined } from '@ant-design/icons';
import type { ProFormInstance } from '@ant-design/pro-components';
import { PageContainer, ProFormText, ProForm, ProFormRadio } from '@ant-design/pro-components';
import { Button, Upload } from 'antd';
import { Button, message, Upload } from 'antd';
import { RcFile } from 'antd/lib/upload';
import React, { useRef } from 'react';
import { useModel } from 'umi';
import styles from './index.less';
const handleEdit = async (fields: SYSTEM.User) => {
const hide = message.loading('正在更新');
try {
const result = await saveOrUpdateUser({ ...fields });
hide();
if (result.resp_code === 0) {
message.success('修改个人信息成功');
return true;
} else {
message.error(result.resp_msg);
return false;
}
} catch (error) {
hide();
message.error('修改个人信息失败');
return false;
}
};
const handleImport = async (file: RcFile) => {
const hide = message.loading('正在导入');
try {
const formData = new FormData();
formData.append('file', file);
const result = await importImage(formData);
hide();
if (result.resp_code === 0) {
message.success('上传头像成功');
} else {
message.error(result.resp_msg);
}
} catch (error) {
hide();
message.error('上传头像失败');
}
};
const AvatarView = ({ avatar }: { avatar: string }) => (
<>
<div className={styles.avatar_title}>头像</div>
<div className={styles.avatar}>
<img src={avatar} alt="avatar" />
</div>
<Upload showUploadList={false}>
<Upload
maxCount={1}
action="/api-file/files-anon"
showUploadList={false}
beforeUpload={async (file) => {
await handleImport(file);
return false;
}}
>
<div className={styles.button_view}>
<Button>
<UploadOutlined />
......@@ -28,7 +75,6 @@ const UserInfo: React.FC = () => {
const formRef = useRef<ProFormInstance<API.CurrentUser>>();
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState;
return (
<PageContainer header={{ subTitle: '更新个人信息' }}>
<div className={styles.baseView}>
......@@ -37,8 +83,13 @@ const UserInfo: React.FC = () => {
formRef={formRef}
//layout="horizontal"
request={currentUser2}
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 16 }}
onFinish={async (values) => {
const user = { ...currentUser, ...values };
await handleEdit(user);
message.success('修改个人信息成功');
}}
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 16 }}
>
<ProFormText name="username" label="账号" readonly />
<ProFormText
......
......@@ -48,7 +48,7 @@ export async function auditlog(params?: { [key: string]: string | number }) {
export async function slowQueryLog(params?: { [key: string]: string | number }) {
const { current, pageSize, ...rest } = params ?? {};
const result = await request<SYSTEM.Page<SEARCH.TraceLog>>('/api-log/slowQueryLog', {
const result = await request<SYSTEM.Page<SEARCH.SlowSqlLog>>('/api-log/slowQueryLog', {
method: 'GET',
params: { page: current, limit: pageSize, ...rest },
});
......
......@@ -50,7 +50,12 @@ declare namespace SEARCH {
type SlowSqlLog = {
id?: string;
timestamp?: string;
query_str?: string;
query_time?: string;
lock_time?: string;
rows_sent?: string;
rows_examined?: string;
};
......
......@@ -69,6 +69,15 @@ export async function importUser(formData: FormData) {
return result;
}
export async function importImage(formData: FormData) {
const result = await request<API.Result<void>>("/api-file/files-anon", {
method: 'POST',
requestType: 'form',
data: formData,
});
return result;
}
export async function role() {
const result = await request<SYSTEM.Page<SYSTEM.Role>>('/api-user/roles', {
method: 'GET',
......@@ -164,6 +173,13 @@ export async function menu(params: { [key: string]: string | number }) {
return result.data;
}
export async function menuOnes() {
const result = await request<SYSTEM.Page<SYSTEM.Menu>>('/api-user/menus/findOnes', {
method: 'GET',
});
return result.data;
}
export async function assignMenu(assignMenu: SYSTEM.AssignMenu) {
const result = await request<API.Result<void>>("/api-user/menus/granted", {
method: 'POST',
......@@ -171,3 +187,28 @@ export async function assignMenu(assignMenu: SYSTEM.AssignMenu) {
});
return result;
}
export async function saveOrUpdateMenus(data: SYSTEM.Menu): Promise<API.Result<SYSTEM.Menu>> {
const result = await request<API.Result<SYSTEM.Menu>>('/api-user/menus/saveOrUpdate', {
method: 'POST',
data,
});
return result;
}
export async function deleteMenus(id: number) {
const url = `/api-user/menus/${id}`;
const result = await request<API.Result<void>>(url, {
method: 'DELETE',
});
return result;
}
export async function deleteToken(token: string) {
const url = `/api-uaa/oauth/remove/token`;
const result = await request<API.Result<void>>(url, {
method: 'DELETE',
params: {token}
});
return result;
}
\ No newline at end of file
......@@ -56,7 +56,7 @@ declare namespace SYSTEM {
clientId?: string;
expiration?: Date;
grantType?: string;
tokenValue?: string;
tokenValue: string;
username?: string;
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册