第一次

This commit is contained in:
PC-202306242200\Administrator
2024-09-10 16:47:49 +08:00
parent 4988491b1c
commit d4720a32e1
419 changed files with 59630 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
import React, { useRef, useEffect, useState } from 'react';
import { history } from '@umijs/max';
import { Button, Space, Image, Popconfirm } from 'antd';
import {
PageContainer,
EditableProTable,
ActionType
} from '@ant-design/pro-components';
import { userlevelPage, userlevelDelete, userlevelUpdate, userlevelAdd } from "@/services/user/grade"
export default () => {
const actionRef = useRef<ActionType>();
const initRow = {
id: '',
name: '',
level: '',
num: '',
note: '',
fee: '',
team_prize: ''
};
const [row, setRow] = useState(initRow);
const [editableKeys, setEditableRowKeys] = useState([]);
const handleDelete = async (fields) => {
const { success } = await userlevelDelete(fields);
if (success) {
actionRef.current.clearSelected();
actionRef.current?.reload();
}
};
const handleCreate = async (fields) => {
setRow(fields);
const { success } = await userlevelAdd({ ...fields });
if (success) {
setRow(initRow);
actionRef.current?.reload();
}
};
const handleUpdate = async (fields) => {
const { success } = await userlevelUpdate({ ...fields });
if (success) {
actionRef.current?.reload();
}
};
const columns = [
{
title: '名称',
dataIndex: 'name',
search: false,
},
{
title: '等级',
dataIndex: 'level',
search: false,
},
{
title: '分成比例',
dataIndex: 'ratio',
search: false,
},
// {
// title: '服务费',
// dataIndex: 'fee',
// search: false,
// },
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (text, record, _, action) => [
<Button
key="editable"
type="link"
onClick={() => {
setRow(record);
action?.startEditable?.(record.id);
}}
>
</Button>,
<Popconfirm
title="确认删除?"
onConfirm={() => {
handleDelete([record.id]);
}}
>
<Button
key="delete"
type="link"
danger
>
</Button>
</Popconfirm>
],
},
];
return (
<PageContainer
ghost
>
<>
<EditableProTable
actionRef={actionRef}
rowKey="id"
search={false}
request={async (params, sorter, filter) => {
const { data, success } = await userlevelPage({
...params
});
return {
data: data.records || [],
total: 0,
success,
};
}}
recordCreatorProps={
{
position: 'bottom',
record: () => ({ id: 0 }),
}
}
editable={{
type: 'multiple',
editableKeys,
onSave: async (rowKey, data, row) => {
if (rowKey) {
handleUpdate(data)
} else {
handleCreate(data)
}
},
onChange: setEditableRowKeys,
}}
columns={columns}
pagination={false}
/>
</>
</PageContainer>
);
};

View File

@@ -0,0 +1,7 @@
.ant-pro-layout-content {
padding: 0;
padding-block: 0 !important;
padding-inline: 0 !important;
height: 100vh !important;
}

View File

@@ -0,0 +1,212 @@
import { Footer } from '@/components';
import { login } from '@/services/user/index';
import {
AlipayCircleOutlined,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
import {
LoginForm,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
} from '@ant-design/pro-components';
import { FormattedMessage, Helmet, SelectLang, useIntl, useModel } from '@umijs/max';
import { Alert, message, Tabs } from 'antd';
import { createStyles } from 'antd-style';
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
import Settings from '../../../../config/defaultSettings';
import "./index.css";
const useStyles = createStyles(({ token }) => {
return {
action: {
marginLeft: '8px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
transition: 'color 0.3s',
'&:hover': {
color: token.colorPrimaryActive,
},
},
lang: {
width: 42,
height: 42,
lineHeight: '42px',
position: 'fixed',
right: 16,
borderRadius: token.borderRadius,
':hover': {
backgroundColor: token.colorBgTextHover,
},
},
container: {
display: 'flex',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
backgroundImage:
"url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
backgroundSize: '100% 100%',
},
};
});
const ActionIcons = () => {
const { styles } = useStyles();
return (
<>
<AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.action} />
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={styles.action} />
<WeiboCircleOutlined key="WeiboCircleOutlined" className={styles.action} />
</>
);
};
const Lang = () => {
const { styles } = useStyles();
return (
<div className={styles.lang} data-lang>
{SelectLang && <SelectLang />}
</div>
);
};
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => {
return (
<Alert
style={{
marginBottom: 24,
}}
message={content}
type="error"
showIcon
/>
);
};
const Login: React.FC = () => {
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
const { initialState, setInitialState } = useModel('@@initialState');
const { styles } = useStyles();
const intl = useIntl();
const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
console.log(userInfo, 'userInfo');
if (userInfo) {
flushSync(() => {
setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
});
}
};
const handleSubmit = async (values: API.LoginParams) => {
try {
// 登录
const msg = await login({ ...values });
console.log(msg);
if (msg.success || msg.status == 'ok') {
localStorage.setItem('token', msg.data?.token)
const defaultLoginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
message.success(defaultLoginSuccessMessage);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
window.location.href = urlParams.get('redirect') || '/';
return;
}
setUserLoginState(msg);
} catch (error) {
}
};
return (
<div className={styles.container}>
<Helmet>
<title>
{intl.formatMessage({
id: 'menu.login',
defaultMessage: '登录页',
})}
- {Settings.title}
</title>
</Helmet>
{/* <Lang /> */}
<div
style={{
flex: '1',
padding: '32px 0',
}}
>
<LoginForm
contentStyle={{
minWidth: 280,
maxWidth: '75vw',
}}
logo={<img alt="logo" src="/logo.svg" />}
title="云充电 管理后台"
subTitle={'云充电 管理后台'}
initialValues={{
autoLogin: true,
}}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
placeholder={'请输入用户名'}
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
placeholder={'请输入密码'}
rules={[
{
required: true,
message: '请输入密码!',
},
]}
/>
</>
</LoginForm>
</div>
<Footer />
</div>
);
};
export default Login;

View File

@@ -0,0 +1,96 @@
import { TestBrowser } from '@@/testBrowser';
import { act, fireEvent, render } from '@testing-library/react';
import React from 'react';
// @ts-ignore
import { startMock } from '@@/requestRecordMock';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
let server: {
close: () => void;
};
describe('Login Page', () => {
beforeAll(async () => {
server = await startMock({
port: 8000,
scene: 'login',
});
});
afterAll(() => {
server?.close();
});
it('should show login form', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
act(() => {
historyRef.current?.push('/user/login');
});
expect(rootContainer.baseElement?.querySelector('.ant-pro-form-login-desc')?.textContent).toBe(
'Ant Design is the most influential web design specification in Xihu district',
);
expect(rootContainer.asFragment()).toMatchSnapshot();
rootContainer.unmount();
});
it('should login success', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
const userNameInput = await rootContainer.findByPlaceholderText('Username: admin or user');
act(() => {
fireEvent.change(userNameInput, { target: { value: 'admin' } });
});
const passwordInput = await rootContainer.findByPlaceholderText('Password: ant.design');
act(() => {
fireEvent.change(passwordInput, { target: { value: 'ant.design' } });
});
await (await rootContainer.findByText('Login')).click();
// 等待接口返回结果
await waitTime(5000);
await rootContainer.findAllByText('Ant Design Pro');
expect(rootContainer.asFragment()).toMatchSnapshot();
await waitTime(2000);
rootContainer.unmount();
});
});

View File

@@ -0,0 +1,38 @@
import { Link, useSearchParams } from '@umijs/max';
import { Button, Result } from 'antd';
import React from 'react';
import useStyles from './style.style';
const RegisterResult: React.FC<Record<string, unknown>> = () => {
const { styles } = useStyles();
const [params] = useSearchParams();
const actions = (
<div className={styles.actions}>
<a href="">
<Button size="large" type="primary">
<span></span>
</Button>
</a>
<Link to="/">
<Button size="large"></Button>
</Link>
</div>
);
const email = params?.get('account') || 'AntDesign@example.com';
return (
<Result
className={styles.registerResult}
status="success"
title={
<div className={styles.title}>
<span>{email} </span>
</div>
}
subTitle="激活邮件已发送到你的邮箱中邮件有效期为24小时。请及时登录邮箱点击邮件中的链接激活帐户。"
extra={actions}
/>
);
};
export default RegisterResult;

View File

@@ -0,0 +1,26 @@
import { createStyles } from 'antd-style';
const useStyles = createStyles(() => {
return {
registerResult: {
width: '800px',
minHeight: '400px',
margin: 'auto',
padding: '80px',
background: 'none',
},
anticon: {
fontSize: '64px',
},
title: {
marginTop: '32px',
fontSize: '20px',
lineHeight: '28px',
},
actions: {
marginTop: '40px',
'a + a': { marginLeft: '8px' },
},
};
});
export default useStyles;

View File

@@ -0,0 +1,9 @@
import type { Request, Response } from 'express';
export default {
'POST /api/register': (_: Request, res: Response) => {
res.send({
data: { status: 'ok', currentAuthority: 'user' },
});
},
};

View File

@@ -0,0 +1,292 @@
import { history, Link, useRequest } from '@umijs/max';
import { Button, Col, Form, Input, message, Popover, Progress, Row, Select, Space } from 'antd';
import type { Store } from 'antd/es/form/interface';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import type { StateType } from './service';
import { fakeRegister } from './service';
import useStyles from './style.style';
const FormItem = Form.Item;
const { Option } = Select;
const passwordProgressMap: {
ok: 'success';
pass: 'normal';
poor: 'exception';
} = {
ok: 'success',
pass: 'normal',
poor: 'exception',
};
const Register: FC = () => {
const { styles } = useStyles();
const [count, setCount]: [number, any] = useState(0);
const [open, setVisible]: [boolean, any] = useState(false);
const [prefix, setPrefix]: [string, any] = useState('86');
const [popover, setPopover]: [boolean, any] = useState(false);
const confirmDirty = false;
let interval: number | undefined;
const passwordStatusMap = {
ok: (
<div className={styles.success}>
<span></span>
</div>
),
pass: (
<div className={styles.warning}>
<span></span>
</div>
),
poor: (
<div className={styles.error}>
<span></span>
</div>
),
};
const [form] = Form.useForm();
useEffect(
() => () => {
clearInterval(interval);
},
[interval],
);
const onGetCaptcha = () => {
let counts = 59;
setCount(counts);
interval = window.setInterval(() => {
counts -= 1;
setCount(counts);
if (counts === 0) {
clearInterval(interval);
}
}, 1000);
};
const getPasswordStatus = () => {
const value = form.getFieldValue('password');
if (value && value.length > 9) {
return 'ok';
}
if (value && value.length > 5) {
return 'pass';
}
return 'poor';
};
const { loading: submitting, run: register } = useRequest<{
data: StateType;
}>(fakeRegister, {
manual: true,
onSuccess: (data, params) => {
if (data.status === 'ok') {
message.success('注册成功!');
history.push({
pathname: `/user/register-result?account=${params[0].email}`,
});
}
},
});
const onFinish = (values: Store) => {
register(values);
};
const checkConfirm = (_: any, value: string) => {
const promise = Promise;
if (value && value !== form.getFieldValue('password')) {
return promise.reject('两次输入的密码不匹配!');
}
return promise.resolve();
};
const checkPassword = (_: any, value: string) => {
const promise = Promise;
// 没有值的情况
if (!value) {
setVisible(!!value);
return promise.reject('请输入密码!');
}
// 有值的情况
if (!open) {
setVisible(!!value);
}
setPopover(!popover);
if (value.length < 6) {
return promise.reject('');
}
if (value && confirmDirty) {
form.validateFields(['confirm']);
}
return promise.resolve();
};
const changePrefix = (value: string) => {
setPrefix(value);
};
const renderPasswordProgress = () => {
const value = form.getFieldValue('password');
const passwordStatus = getPasswordStatus();
return value && value.length ? (
<div className={styles[`progress-${passwordStatus}`]}>
<Progress
status={passwordProgressMap[passwordStatus]}
strokeWidth={6}
percent={value.length * 10 > 100 ? 100 : value.length * 10}
showInfo={false}
/>
</div>
) : null;
};
return (
<div className={styles.main}>
<h3></h3>
<Form form={form} name="UserRegister" onFinish={onFinish}>
<FormItem
name="email"
rules={[
{
required: true,
message: '请输入邮箱地址!',
},
{
type: 'email',
message: '邮箱地址格式错误!',
},
]}
>
<Input size="large" placeholder="邮箱" />
</FormItem>
<Popover
getPopupContainer={(node) => {
if (node && node.parentNode) {
return node.parentNode as HTMLElement;
}
return node;
}}
content={
open && (
<div
style={{
padding: '4px 0',
}}
>
{passwordStatusMap[getPasswordStatus()]}
{renderPasswordProgress()}
<div
style={{
marginTop: 10,
}}
>
<span> 6 使</span>
</div>
</div>
)
}
overlayStyle={{
width: 240,
}}
placement="right"
open={open}
>
<FormItem
name="password"
className={
form.getFieldValue('password') &&
form.getFieldValue('password').length > 0 &&
styles.password
}
rules={[
{
validator: checkPassword,
},
]}
>
<Input size="large" type="password" placeholder="至少6位密码区分大小写" />
</FormItem>
</Popover>
<FormItem
name="confirm"
rules={[
{
required: true,
message: '确认密码',
},
{
validator: checkConfirm,
},
]}
>
<Input size="large" type="password" placeholder="确认密码" />
</FormItem>
<FormItem
name="mobile"
rules={[
{
required: true,
message: '请输入手机号!',
},
{
pattern: /^\d{11}$/,
message: '手机号格式错误!',
},
]}
>
<Space.Compact style={{ width: '100%' }}>
<Select
size="large"
value={prefix}
onChange={changePrefix}
style={{
width: '30%',
}}
>
<Option value="86">+86</Option>
<Option value="87">+87</Option>
</Select>
<Input size="large" placeholder="手机号" />
</Space.Compact>
</FormItem>
{/* <Row gutter={8}>
<Col span={16}>
<FormItem
name="captcha"
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
>
<Input size="large" placeholder="验证码" />
</FormItem>
</Col>
<Col span={8}>
<Button
size="large"
disabled={!!count}
className={styles.getCaptcha}
onClick={onGetCaptcha}
>
{count ? `${count} s` : '获取验证码'}
</Button>
</Col>
</Row> */}
<FormItem>
<div className={styles.footer}>
<Button
size="large"
loading={submitting}
className={styles.submit}
type="primary"
htmlType="submit"
>
<span></span>
</Button>
<Link to="/user/login">
<span>使</span>
</Link>
</div>
</FormItem>
</Form>
</div>
);
};
export default Register;

View File

@@ -0,0 +1,22 @@
import { request } from '@umijs/max';
export interface StateType {
status?: 'ok' | 'error';
currentAuthority?: 'user' | 'guest' | 'admin';
}
export interface UserRegisterParams {
mail: string;
password: string;
confirm: string;
mobile: string;
captcha: string;
prefix: string;
}
export async function fakeRegister(params: UserRegisterParams) {
return request('/api/register', {
method: 'POST',
data: params,
});
}

View File

@@ -0,0 +1,46 @@
import { createStyles } from 'antd-style';
const useStyles = createStyles(({ token }) => {
return {
main: {
width: '368px',
margin: '0 auto',
h3: { marginBottom: '20px', fontSize: '16px' },
},
password: {
marginBottom: '24px',
'.ant-form-item-explain': { display: 'none' },
},
getCaptcha: {
display: 'block',
width: '100%',
},
footer: {
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
submit: {
width: '50%',
},
success: {
transition: 'color 0.3s',
color: token.colorSuccess,
},
warning: {
transition: 'color 0.3s',
color: token.colorWarning,
},
error: {
transition: 'color 0.3s',
color: token.colorError,
},
'progress-pass > .progress': {
'.ant-progress-bg': { backgroundColor: token.colorWarning },
},
};
});
export default useStyles;

View File

@@ -0,0 +1,86 @@
/*
* @Note:
* @Author: 2058827620@qq.com
* @Date: 2022-04-03 17:02:15
*/
import { ProForm, ModalForm, ProFormSelect, ProFormText, ProFormTextArea, ProFormInstance } from '@ant-design/pro-components';
import { Tree } from 'antd';
import { useEffect, useRef } from 'react';
import FilesManager from '@/components/FilesManage/index';
import { userlevelPage } from '@/services/user/grade'
export default ({ modalOpenState, onModalOpenState, onSubmit }) => {
const restFormRef = useRef<ProFormInstance>();
return (
<>
<ModalForm
title="投资人-等级"
formRef={restFormRef}
submitter={{
searchConfig: {
resetText: '重置',
},
resetButtonProps: {
onClick: () => {
restFormRef.current?.resetFields();
},
},
}}
onFinish={async (e) => {
await onSubmit(e)
restFormRef.current?.resetFields();
}}
open={modalOpenState}
onOpenChange={onModalOpenState}
>
<ProFormText hidden={true} width="md" name="id" />
<ProForm.Group>
<ProFormSelect
rules={[{ required: true, message: '请输入' }]}
name="level"
label="等级"
fieldProps={{
fieldNames: {
label: "level",
value: "level"
},
}}
request={async (param) => {
const { success, data } = await userlevelPage({});
return data.records;
}}
placeholder="请选择"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="userName"
label="用户名"
placeholder="请输入"
/>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="phone"
label="手机号"
placeholder="请输入"
/>
</ProForm.Group>
<ProFormTextArea
rules={[{ required: true, message: '请输入' }]}
width="md"
name="remark"
label="备注"
placeholder="请输入"
/>
</ModalForm>
</>
);
};

View File

@@ -0,0 +1,88 @@
/*
* @Note:
* @Author: 2058827620@qq.com
* @Date: 2022-04-03 17:02:15
*/
import { ProForm, ModalForm, ProFormSelect, ProFormText, ProFormTextArea, ProFormInstance } from '@ant-design/pro-components';
import { Divider } from 'antd';
import { useEffect, useRef } from 'react';
import FilesManager from '@/components/FilesManage/index';
export default ({ values, modalOpenState, onModalOpenState, onSubmit }) => {
const restFormRef = useRef<ProFormInstance>();
return (
<>
<ModalForm
title="投资人-转挂"
formRef={restFormRef}
submitter={{
searchConfig: {
resetText: '重置',
},
resetButtonProps: {
onClick: () => {
restFormRef.current?.resetFields();
},
},
}}
initialValues={values}
onFinish={async (e) => {
await onSubmit(e)
restFormRef.current?.resetFields();
}}
open={modalOpenState}
onOpenChange={onModalOpenState}
>
<div className='text-red-600 '>
<div>A</div>
<div>B</div>
<div>A转挂到B名下</div>
</div>
<Divider>A</Divider>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="userNameSource"
label="用户名(A)"
placeholder="请输入"
/>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="phoneSource"
label="手机号(A)"
placeholder="请输入"
/>
</ProForm.Group>
<Divider>BA转挂该投资人名下</Divider>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="userNameTarget"
label="用户名(B)"
placeholder="请输入"
/>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
width="md"
name="phoneTarget"
label="手机号(B)"
placeholder="请输入"
/>
</ProForm.Group>
<ProFormTextArea
rules={[{ required: true, message: '请输入' }]}
width="md"
name="remark"
label="备注"
placeholder="请输入"
/>
</ModalForm>
</>
);
};

View File

@@ -0,0 +1,144 @@
import React, { useEffect, useState } from 'react';
import {
ProForm,
ProFormDigit,
ProFormText,
ProFormSwitch,
ProFormSelect
} from '@ant-design/pro-components';
import { Form, Modal, InputNumber } from 'antd';
import { useIntl } from '@umijs/max';
import FilesManager from '@/components/FilesManage/index';
import { usersPage } from '@/services/user/user';
const RoleForm: React.FC = (props: any) => {
const [form] = Form.useForm();
const { values } = props;
useEffect(() => {
console.log(values, 'values');
form.resetFields();
form.setFieldsValue(values);
}, [form, props]);
const intl = useIntl();
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
props.onCancel();
};
const handleFinish = async (values: any) => {
props.onSubmit(values);
};
return (
<Modal
width={640}
title={'管理用户'}
open={props.open}
forceRender
destroyOnClose
onOk={handleOk}
onCancel={handleCancel}
>
<ProForm
form={form}
submitter={false}
layout="horizontal"
onFinish={handleFinish}>
<ProFormDigit
name="id"
label={'ID'}
disabled
hidden={true}
/>
<ProForm.Group>
<ProFormSelect
width="md"
colProps={{ xl: 8, md: 12 }}
name="formUserId"
label="推荐人"
fieldProps={{
fieldNames: {
label: "userName",
value: "id"
},
placeholder: "请输入用户名",
showSearch: true
}}
request={async (param) => {
const { success, data } = await usersPage({
userName: param.keyWords
});
data.records.map((i) => {
i.userName = i.userName + ' ' + i.phone
})
return data.records;
}}
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
name="userName"
label={'用户名'}
placeholder="请输入用户名"
/>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
name="nickName"
label={'昵称'}
placeholder="请输入昵称"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: '请输入' }]}
name="phone"
label={'手机号'}
placeholder="请输入手机号"
/>
<ProFormText
rules={!values.id ? [{ required: true, message: '请输入' }] : []}
name="passwd"
label={'密码'}
placeholder="请输入密码"
/>
<ProFormSwitch
checkedChildren="正常"
unCheckedChildren="禁用"
name="status"
label="是否禁用"
fieldProps={
{
defaultChecked: true
}
}
/>
</ProForm.Group>
<ProForm.Group>
<ProForm.Item label="头像" name="avatar">
<FilesManager
fileType="image"
defaultValue={values?.avatar}
count={1}
mode=""
/>
</ProForm.Item>
</ProForm.Group>
</ProForm>
</Modal>
);
};
export default RoleForm;

View File

@@ -0,0 +1,366 @@
import { usersPage, usersAdd, usersDelete, usersUpdate, updateUserLevel, userTransferHanging, exportUser } from '@/services/user/user';
import React, { useRef, useEffect, useState } from 'react';
import { useIntl, useAccess } from '@umijs/max';
import { message, Tag, Image, Button, Select, Modal } from 'antd';
import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
import UpdateForm from './edit';
import { PlusOutlined, LineChartOutlined } from '@ant-design/icons';
import { exportData } from '@/utils/func';
import LevelFormModal from './components/LevelFormModal';
import TempFormModal from './components/TempFormModal';
/**
* 添加节点
*
* @param fields
*/
const handleAdd = async (fields) => {
const hide = message.loading('正在添加');
try {
fields.status = fields.status ? 0 : 1
await usersAdd({ ...fields });
hide();
message.success('添加成功');
return true;
} catch (error) {
hide();
message.error('添加失败请重试!');
return false;
}
};
/**
* 更新节点
*
* @param fields
*/
const handleUpdate = async (fields: API.System.Menu) => {
const hide = message.loading('正在修改');
try {
await usersUpdate(fields);
hide();
message.success('修改成功');
return true;
} catch (error) {
hide();
message.error('修改失败请重试!');
return false;
}
};
const handleRemoveOne = async (selectedRow) => {
const hide = message.loading('正在删除');
if (!selectedRow) return true;
try {
const params = selectedRow;
await usersDelete(params);
hide();
message.success('删除成功');
return true;
} catch (error) {
hide();
message.error('删除失败,请重试');
return false;
}
};
const LogTableList: React.FC = () => {
const actionRef = useRef<ActionType>();
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState();
const [selectedRowsState, setSelectedRows] = useState([]);
const [levelFormModal, setLevelFormModal] = useState(false);
const [tempFormModal, setTempFormModal] = useState(false);
const [searchParams, setSearchParams] = useState(null);
// 更改等级
const handleLevel = async (fields) => {
const { success } = await updateUserLevel({ ...fields });
if (success) {
message.success('等级变更成功');
actionRef.current.reload();
setLevelFormModal(false);
}
};
const handleTemp = async (fields) => {
const { success } = await userTransferHanging(fields);
if (success) {
message.success('转挂成功');
actionRef.current?.reload();
setTempFormModal(false);
}
};
const columns = [
{
title: 'ID',
dataIndex: 'id',
valueType: 'text',
search: false,
},
{
title: '头像',
dataIndex: 'avatar',
valueType: 'image',
search: false,
},
{
title: '用户名',
dataIndex: 'userName',
valueType: 'text',
search: false,
},
{
title: '用户昵称',
dataIndex: 'nickName',
valueType: 'text',
search: false,
},
{
title: '手机号',
dataIndex: 'phone',
valueType: 'text',
search: false,
},
{
title: '钱包',
dataIndex: 'points2',
valueType: 'text',
search: false,
},
{
title: '收益',
dataIndex: 'points2',
valueType: 'text',
search: false,
},
{
title: '等级',
dataIndex: 'level',
valueType: 'text',
search: false,
},
{
title: '邀请码',
dataIndex: 'shareCode',
valueType: 'text',
search: false,
},
{
title: '状态',
dataIndex: 'status',
valueType: 'text',
search: false,
render: (_, record) => {
return <Tag color={record.status == 1 ? 'green' : 'red'}>{record.status == 1 ? '正常' : '禁用'}</Tag>
}
},
{
title: '推广码',
dataIndex: 'qrCode',
valueType: 'text',
search: false,
render: (_, record) => {
return record.qrCode ? <Image src={record.qrCode} width={60}></Image> : '-'
}
},
{
title: '创建时间',
dataIndex: 'createTime',
valueType: 'text',
search: false,
},
// {
// title: '操作',
// dataIndex: 'option',
// width: '220px',
// valueType: 'option',
// render: (_, record) => [
// // <Button
// // type="link"
// // size="small"
// // // hidden={!access.hasPerms('admin/banner/update')}
// // onClick={() => {
// // setModalVisible(true);
// // setCurrentRow(record);
// // }}
// // >
// // 编辑
// // </Button>,
// // <Button
// // type="link"
// // size="small"
// // danger
// // // hidden={!access.hasPerms('admin:banner:update')}
// // onClick={async () => {
// // Modal.confirm({
// // title: '删除',
// // content: '确定删除该项吗?',
// // okText: '确认',
// // cancelText: '取消',
// // onOk: async () => {
// // const success = await handleRemoveOne(record);
// // if (success) {
// // if (actionRef.current) {
// // actionRef.current.reload();
// // }
// // }
// // },
// // });
// // }}
// // >
// // 删除
// // </Button>,
// ],
// },
];
return (
<PageContainer>
<div style={{ width: '100%', float: 'right' }}>
<LevelFormModal
modalOpenState={levelFormModal}
onModalOpenState={setLevelFormModal}
onSubmit={handleLevel}
/>
<TempFormModal
modalOpenState={tempFormModal}
onModalOpenState={setTempFormModal}
onSubmit={handleTemp}
/>
<ProTable
actionRef={actionRef}
rowKey="id"
key="logList"
search={{
labelWidth: 120,
}}
toolBarRender={() => [
<Button
danger
onClick={() => {
setTradeFormModal(true)
}}
>
</Button>,
<Button
danger
onClick={() => {
setTradeFormModal(true)
}}
>
</Button>,
<Button
danger
onClick={() => {
setTempFormModal(true)
}}
>
</Button>,
<Button
danger
onClick={() => {
setLevelFormModal(true)
}}
>
</Button>,
<Button
danger
onClick={() => {
Modal.confirm({
title: '删除',
content: '确定删除该项吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
let data = selectedRowsState.map((item, index) => {
return item.id
})
const success = await handleRemoveOne(data);
if (success) {
if (actionRef.current) {
actionRef.current.reload();
}
}
},
});
}}
>
</Button>,
<Button
type="primary"
key="add"
onClick={async () => {
let _res: any = await exportUser(searchParams)
exportData(_res, '用户信息')
}}
>
<LineChartOutlined />
</Button>,
<Button
type="primary"
key="add"
onClick={async () => {
setCurrentRow(undefined);
setModalVisible(true);
}}
>
<PlusOutlined />
</Button>,
]}
request={async (params, sorter, filter) => {
setSearchParams({ token: '', ...params });
let { data } = await usersPage(params)
return {
data: data?.records || [],
total: data?.total,
};
}}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
</div>
<UpdateForm
onSubmit={async (values) => {
let success = false;
if (values.id) {
success = await handleUpdate({ ...values });
} else {
success = await handleAdd({ ...values });
}
if (success) {
setModalVisible(false);
setCurrentRow(undefined);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
setModalVisible(false);
setCurrentRow(undefined);
}}
open={modalVisible}
values={currentRow || {}}
/>
</PageContainer>
);
};
export default LogTableList;