瀏覽代碼

Merge branch 'master' of lizhiqi/yongxu-web into master

lizhiqi 2 年之前
父節點
當前提交
48ebd76bb1

+ 2 - 2
config/defaultSettings.ts

@@ -12,9 +12,9 @@ const Settings: LayoutSettings & {
   fixedHeader: false,
   fixSiderbar: true,
   colorWeak: false,
-  title: '永续绿建web管理平台',
+  title: '永续绿建管理平台',
   pwa: false,
-  logo: '/logo.png',
+  logo: '/assets/logo.png',
   iconfontUrl: '',
   menu: {
     locale: false, // 关闭菜单国际化

+ 7 - 0
config/proxy.ts

@@ -18,9 +18,16 @@ export default {
     },
     '/web/': {
       // 要代理的地址
+      // target: 'http://yongxu.yehaoji.cn:8198',
       target: 'http://yongxu.yehaoji.cn:8198',
       changeOrigin: true,
     },
+    '/s/': {
+      // 要代理的地址
+      // target: 'http://yongxu.yehaoji.cn:8198',
+      target: 'http://192.168.0.224:18199/',
+      changeOrigin: true,
+    },
   },
   test: {
     '/api/': {

+ 6 - 1
config/routes.ts

@@ -19,6 +19,12 @@
     icon: 'smile',
     component: './Welcome',
   },
+  {
+    path: '/userManagement',
+    name: '用户管理',
+    icon: 'file',
+    component: './Setting/UserManagement',
+  },
   {
     name: 'list.table-list',
     icon: 'table',
@@ -31,7 +37,6 @@
     icon: 'smile',
     component: './Demo',
   },
-
   {
     path: '/',
     redirect: '/welcome',

+ 1 - 0
package.json

@@ -58,6 +58,7 @@
     "lodash": "^4.17.0",
     "moment": "^2.29.0",
     "omit.js": "^2.0.2",
+    "qs": "^6.11.0",
     "rc-menu": "^9.1.0",
     "rc-util": "^5.16.0",
     "react": "^17.0.0",

+ 0 - 0
public/assets/login-left.png → public/assets/login_left.png


二進制
public/assets/welcome.png


二進制
public/favicon.ico


二進制
public/icons/icon-128x128.png


二進制
public/icons/icon-192x192.png


二進制
public/icons/icon-512x512.png


文件差異過大導致無法顯示
+ 0 - 0
public/logo.svg


+ 36 - 53
src/app.tsx

@@ -1,15 +1,13 @@
 import Footer from '@/components/Footer';
 import RightContent from '@/components/RightContent';
-import { BookOutlined, LinkOutlined } from '@ant-design/icons';
 import type { Settings as LayoutSettings } from '@ant-design/pro-components';
 import { PageLoading, SettingDrawer } from '@ant-design/pro-components';
-// import { message, notification } from 'antd';
 import type { RunTimeLayoutConfig } from 'umi';
-import { history, Link } from 'umi';
+import { history } from 'umi';
 import defaultSettings from '../config/defaultSettings';
 import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
+import { message } from 'antd';
 
-const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';
 
 /** 获取用户信息比较慢的时候会展示一个 loading */
@@ -68,18 +66,6 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
         history.push(loginPath);
       }
     },
-    links: isDev
-      ? [
-          <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
-            <LinkOutlined />
-            <span>OpenAPI 文档</span>
-          </Link>,
-          <Link to="/~docs" key="docs">
-            <BookOutlined />
-            <span>业务组件文档</span>
-          </Link>,
-        ]
-      : [],
     menuHeaderRender: undefined,
     // 自定义 403 页面
     // unAccessible: <div>unAccessible</div>,
@@ -95,7 +81,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
               enableDarkTheme
               settings={initialState?.settings}
               onSettingChange={(settings) => {
-                setInitialState((preInitialState) => ({
+                setInitialState((preInitialState: any) => ({
                   ...preInitialState,
                   settings,
                 }));
@@ -108,39 +94,36 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
     ...initialState?.settings,
   };
 };
-// const addToken = async (url: any, options: any) => {
-//   // 此处为拦截器,每次发送请求之前判断能否取到token
-//   if (localStorage.getItem('token')) {
-//     const headers = {
-//       Authorization: `${localStorage.getItem('token')}`,
-//     };
-//     return {
-//       url,
-//       options: { ...options, headers },
-//     };
-//   }
-// };
-//
-// const MainResponseInterceptors = (response: { status: number }, opts: any) => {
-//   if (response.status === 401) {
-//     message.warn('帐号登录过期,请重新登录');
-//     history.push(loginPath);
-//   }
-//   return response;
-// };
-// export const request = {
-//   errorHandler: (error: { response: any }) => {
-//     const { response } = error;
-//
-//     if (!response) {
-//       notification.error({
-//         description: '您的网络发生异常,无法连接服务器',
-//         message: '网络异常',
-//       });
-//     }
-//
-//     throw error;
-//   },
-//   requestInterceptors: [addToken],
-//   responseInterceptors: [MainResponseInterceptors],
-// };
+const addToken = async (url: any, options: any) => {
+  // 此处为拦截器,每次发送请求之前判断能否取到token
+  if (localStorage.getItem('token')) {
+    const headers = {
+      Authorization: `${localStorage.getItem('token')}`,
+    };
+    return {
+      url,
+      options: { ...options, headers },
+    };
+  }
+  return false;
+};
+
+const MainResponseInterceptors = (response: { status: number }) => {
+  if (response.status === 401) {
+    message.warn('帐号登录过期,请重新登录');
+    history.push(loginPath);
+  }
+  return response;
+};
+export const request = {
+  errorConfig: {
+    adaptor: (resData: any) => {
+      return {
+        showType: resData.code ? 2 : 0,
+        errorMessage: resData.message,
+      };
+    },
+  },
+  requestInterceptors: [addToken],
+  responseInterceptors: [MainResponseInterceptors],
+};

+ 1 - 1
src/components/Footer/index.tsx

@@ -2,7 +2,7 @@ import { DefaultFooter } from '@ant-design/pro-components';
 
 const Footer: React.FC = () => {
   const currentYear = new Date().getFullYear();
-  const defaultMessage = '永续绿建web管理平台';
+  const defaultMessage = '永续绿建管理平台';
 
   return <DefaultFooter copyright={`${currentYear} ${defaultMessage}`} />;
 };

+ 0 - 36
src/components/RightContent/index.tsx

@@ -1,13 +1,9 @@
-// import { QuestionCircleOutlined } from '@ant-design/icons';
 import { Space } from 'antd';
 import React from 'react';
 import { useModel } from 'umi';
-// import HeaderSearch from '../HeaderSearch';
 import Avatar from './AvatarDropdown';
 import styles from './index.less';
 
-export type SiderTheme = 'light' | 'dark';
-
 const GlobalHeaderRight: React.FC = () => {
   const { initialState } = useModel('@@initialState');
 
@@ -23,39 +19,7 @@ const GlobalHeaderRight: React.FC = () => {
   }
   return (
     <Space className={className}>
-      {/* <HeaderSearch
-        className={`${styles.action} ${styles.search}`}
-        placeholder="站内搜索"
-        defaultValue="umi ui"
-        options={[
-          { label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
-          {
-            label: <a href="next.ant.design">Ant Design</a>,
-            value: 'Ant Design',
-          },
-          {
-            label: <a href="https://protable.ant.design/">Pro Table</a>,
-            value: 'Pro Table',
-          },
-          {
-            label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
-            value: 'Pro Layout',
-          },
-        ]}
-        // onSearch={value => {
-        //   console.log('input', value);
-        // }}
-      /> */}
-      {/* <span
-        className={styles.action}
-        onClick={() => {
-          window.open('https://pro.ant.design/docs/getting-started');
-        }}
-      >
-        <QuestionCircleOutlined />
-      </span> */}
       <Avatar />
-      {/* <SelectLang className={styles.action} /> */}
     </Space>
   );
 };

+ 10 - 5
src/pages/Welcome.less

@@ -1,8 +1,13 @@
 @import (reference) '~antd/es/style/themes/index';
 
-.pre {
-  margin: 12px 0;
-  padding: 12px 20px;
-  background: @input-bg;
-  box-shadow: @card-shadow;
+.welcome {
+  margin: 100px 0;
+  text-align: center;
+  .img_style {
+    width: 500px;
+  }
+  .welcome_text {
+    color: #999;
+    font-size: 16px;
+  }
 }

+ 12 - 35
src/pages/Welcome.tsx

@@ -1,46 +1,23 @@
 import { PageContainer } from '@ant-design/pro-components';
-import { Alert, Card, Typography } from 'antd';
+import { Card } from 'antd';
 import React from 'react';
-import { FormattedMessage, useIntl } from 'umi';
 import styles from './Welcome.less';
 
-const CodePreview: React.FC = ({ children }) => (
-  <pre className={styles.pre}>
-    <code>
-      <Typography.Text copyable>{children}</Typography.Text>
-    </code>
-  </pre>
-);
-
+/**
+ * 欢迎首页
+ * @constructor
+ */
 const Welcome: React.FC = () => {
-  const intl = useIntl();
-
   return (
     <PageContainer>
       <Card>
-        <Alert
-          message={intl.formatMessage({
-            id: 'pages.welcome.alertMessage',
-            defaultMessage: 'Faster and stronger heavy-duty components have been released.',
-          })}
-          type="success"
-          showIcon
-          banner
-          style={{
-            margin: -12,
-            marginBottom: 24,
-          }}
-        />
-        <Typography.Text strong>
-          <a
-            href="https://procomponents.ant.design/components/table"
-            rel="noopener noreferrer"
-            target="__blank"
-          >
-            <FormattedMessage id="pages.welcome.link" defaultMessage="Welcome" />
-          </a>
-        </Typography.Text>
-        <CodePreview>yarn add @ant-design/pro-components</CodePreview>
+        <div style={{ width: '100%' }} className={styles.welcome}>
+          <img src="/assets/welcome.png" className={styles.img_style} alt="" />
+          <div className={styles.welcome_text} style={{ marginTop: '10px' }}>
+            WELCOME
+          </div>
+          <div className={styles.welcome_text}>欢迎进入永续绿建管理平台</div>
+        </div>
       </Card>
     </PageContainer>
   );

+ 1 - 13
src/pages/document.ejs

@@ -22,7 +22,7 @@
       name="viewport"
       content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
     />
-    <title>永续绿建web管理平台</title>
+    <title>永续绿建管理平台</title>
     <link rel="icon" href="<%= context.config.publicPath +'favicon.ico'%>" type="image/x-icon" />
   </head>
   <body>
@@ -86,9 +86,6 @@
           opacity: 0;
           -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
           transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-            -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
           -webkit-font-feature-settings: 'tnum';
           font-feature-settings: 'tnum';
         }
@@ -213,7 +210,6 @@
           min-height: 420px;
         "
       >
-        <img src="<%= context.config.publicPath +'pro_icon.svg'%>" alt="logo" width="256" />
         <div class="page-loading-warp">
           <div class="ant-spin ant-spin-lg ant-spin-spinning">
             <span class="ant-spin-dot ant-spin-dot-spin"
@@ -222,14 +218,6 @@
             ></span>
           </div>
         </div>
-        <div style="display: flex; align-items: center; justify-content: center">
-          <img
-            src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
-            width="32"
-            style="margin-right: 8px"
-          />
-          Ant Design
-        </div>
       </div>
     </div>
   </body>

+ 206 - 0
src/pages/setting/UserManagement/edit.tsx

@@ -0,0 +1,206 @@
+import React, { useState } from 'react';
+import { Col, Form, Input, message, Modal, Row, Upload } from 'antd';
+import { createUser, editUser } from '@/services/setting';
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
+import type { UploadProps } from 'antd';
+import md5 from 'js-md5';
+
+interface userEditPros {
+  visible: boolean;
+  editCallback: () => void;
+  params: any;
+}
+
+/**
+ * 用户管理编辑
+ * @param props
+ * @constructor
+ */
+const Edit: React.FC<userEditPros> = (props) => {
+  const { visible, editCallback, params } = props;
+  const [form] = Form.useForm();
+  const uploadHeaders = { Authorization: `${localStorage.getItem('token')}` };
+  const [fileUrl, setFileUrl] = useState(params && params.photo ? params.photo : '');
+  const [loading, setLoading] = useState(false);
+
+  const getBase64 = (img: any) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(img);
+    reader.onloadend = () => {
+      const url = reader.result;
+      setFileUrl(url);
+    };
+  };
+
+  const filesProps: UploadProps = {
+    maxCount: 1,
+    action: '/web/v1/files',
+    headers: uploadHeaders,
+    listType: 'picture-card',
+    showUploadList: false,
+    onChange(info) {
+      if (info.file.status === 'uploading') {
+        setLoading(true);
+        return;
+      }
+      if (info.file.status === 'done') {
+        setLoading(false);
+        getBase64(info.file.originFileObj);
+      } else if (info.file.status === 'error') {
+        setLoading(false);
+        message.error('文件上传失败');
+      }
+    },
+  };
+
+  /**
+   * 确定
+   */
+  const onOk = () => {
+    form.validateFields().then((values) => {
+      if (values) {
+        const data = { ...values };
+        if (values?.photo) {
+          data.photo = values?.photo?.file?.response?.url;
+        }
+        if (values?.password) {
+          data.password = md5(values.password);
+        }
+        if (params) {
+          data.record_id = params.record_id;
+          //  编辑
+          editUser(data)
+            .then((res) => {
+              if (res && !res.error) {
+                message.success('编辑成功');
+                editCallback();
+              } else {
+                message.error(res?.error?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.error?.message);
+              editCallback();
+            });
+        } else {
+          // 新增
+          createUser(data)
+            .then((res) => {
+              if (res && !res.error) {
+                message.success('保存成功');
+                editCallback();
+              } else {
+                message.error(res?.error?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.error?.message);
+              editCallback();
+            });
+        }
+      }
+    });
+  };
+
+  /**
+   * 取消
+   */
+  const onCancel = () => {
+    editCallback();
+  };
+
+  const formItemLayout = {
+    labelCol: {
+      span: 6,
+    },
+    wrapperCol: {
+      span: 16,
+    },
+  };
+
+  const uploadButton = (
+    <div>
+      {loading ? <LoadingOutlined /> : <PlusOutlined />}
+      <div className="ant-upload-text">上传</div>
+    </div>
+  );
+
+  return (
+    <Modal
+      title={`${params ? '编辑' : '新增'}`}
+      open={visible}
+      onOk={onOk}
+      onCancel={onCancel}
+      width={800}
+    >
+      <Form form={form}>
+        <Row>
+          <Col span={12}>
+            <Form.Item
+              {...formItemLayout}
+              name="user_name"
+              label="用户姓名"
+              rules={[{ required: true, message: '请输入用户姓名' }]}
+              initialValue={params?.user_name}
+            >
+              <Input placeholder="请输入用户姓名" />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item
+              {...formItemLayout}
+              name="password"
+              label="密码"
+              // initialValue={params?.password}
+            >
+              <Input type="password" placeholder="请输入密码" />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item
+              {...formItemLayout}
+              name="real_name"
+              label="真实姓名"
+              rules={[{ required: true, message: '请输入真实姓名' }]}
+              initialValue={params?.real_name}
+            >
+              <Input placeholder="请输入真实姓名" />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item
+              {...formItemLayout}
+              name="phone"
+              label="手机号"
+              rules={[
+                { required: true, message: '请输入手机号' },
+                {
+                  pattern:
+                    /^1((34[0-8])|(8\d{2})|(([35][0-35-9]|4[579]|66|7[35678]|9[1389])\d{1}))\d{7}$/,
+                  message: '请输入正确的手机号',
+                },
+              ]}
+              initialValue={params?.phone}
+            >
+              <Input placeholder="请输入手机号" />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item {...formItemLayout} name="photo" label="头像" initialValue={[]}>
+              <Upload {...filesProps}>
+                {fileUrl === '' ? (
+                  uploadButton
+                ) : (
+                  <img src={fileUrl} alt="data" style={{ width: '100%' }} />
+                )}
+              </Upload>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default Edit;

+ 290 - 0
src/pages/setting/UserManagement/index.tsx

@@ -0,0 +1,290 @@
+import { Button, Card, Form, Input, Space, Table, Image, Select, message } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { PlusCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import moment from 'moment';
+import Edit from '@/pages/setting/UserManagement/edit';
+import { disableUser, enableUser, queryUserList } from '@/services/setting';
+import { PageContainer } from '@ant-design/pro-components';
+
+interface DataType {
+  key: string;
+  user_name: string;
+  real_name: string;
+  photo: string;
+  phone: string;
+  status: number;
+  record_id: string;
+  updated_at: string;
+}
+
+/**
+ * 用户管理页面
+ * @constructor
+ */
+const UserManagement: React.FC = () => {
+  const [form] = Form.useForm();
+  const [visible, setVisible] = useState(false);
+  const [editData, setEditData] = useState<object | null>({});
+  const [searchData, setSearchData] = useState<object | null>({});
+  const [dataList, setDataList] = useState([]);
+  const [pagination, setPagination] = useState({ total: 0, current: 1, pageSize: 10 });
+  const [loading, setLoading] = useState(false);
+
+  // 获取列表数据
+  const getListData = () => {
+    const params = {
+      q: 'page',
+      current: pagination.current,
+      pageSize: pagination.pageSize,
+      ...searchData,
+    };
+    queryUserList(params).then((res) => {
+      if (res && !res.error) {
+        setDataList(res.list);
+        setPagination(res.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  useEffect(() => {
+    setLoading(true);
+    getListData();
+  }, []);
+
+  // 新增弹框
+  const onAdd = () => {
+    setVisible(true);
+    setEditData(null);
+  };
+
+  // 编辑弹框
+  const toEdit = (data: object) => {
+    setVisible(true);
+    setEditData(data);
+  };
+
+  // 新增编辑弹框回调
+  const editCallback = () => {
+    setVisible(false);
+    setLoading(true);
+    getListData();
+  };
+
+  // 搜索
+  const onFinish = () => {
+    form.validateFields().then((data) => {
+      setLoading(true);
+      setSearchData(data);
+    });
+  };
+
+  // 重置
+  const onReset = () => {
+    form.resetFields();
+    setLoading(true);
+    setSearchData(null);
+  };
+
+  // 启用
+  const toEnable = (record: any) => {
+    enableUser(record.record_id)
+      .then((res) => {
+        if (res && !res.error) {
+          message.success('启用成功');
+          setLoading(true);
+          getListData();
+        } else {
+          message.error('启用失败');
+        }
+      })
+      .catch((e) => {
+        message.error(e?.error?.message);
+      });
+  };
+
+  //停用
+  const toDisable = (record: any) => {
+    disableUser(record.record_id)
+      .then((res) => {
+        if (res && !res.error) {
+          message.success('停用成功');
+          setLoading(true);
+          getListData();
+        } else {
+          message.error('停用失败');
+        }
+      })
+      .catch((e) => {
+        message.error(e?.error?.message);
+      });
+  };
+
+  useEffect(() => {
+    getListData();
+  }, [searchData]);
+
+  // 分页切换
+  const tableChange = (page: any) => {
+    setLoading(true);
+    const param = {
+      q: 'page',
+      current: page.current,
+      pageSize: page.pageSize,
+      ...searchData,
+    };
+    queryUserList(param).then((res) => {
+      if (res && !res.error) {
+        setDataList(res.list);
+        setPagination(res.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  const columns: ColumnsType<DataType> = [
+    {
+      title: '序号',
+      align: 'center',
+      key: 'index',
+      render: (_: any, row: any, index: number) => index + 1,
+    },
+    {
+      title: '头像',
+      dataIndex: 'photo',
+      key: 'photo',
+      render: (v) => v && <Image width={50} src={`http://192.168.0.224:18199${v}`} alt="头像" />,
+    },
+    {
+      title: '用户姓名',
+      dataIndex: 'user_name',
+      key: 'user_name',
+    },
+    {
+      title: '真实姓名',
+      dataIndex: 'real_name',
+      key: 'real_name',
+    },
+    {
+      title: '手机号',
+      dataIndex: 'phone',
+      key: 'phone',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      key: 'status',
+      render: (v) =>
+        v && (
+          <span style={{ color: `${{ 1: '#00a650', 2: 'red' }[v]}` }}>
+            {{ 1: '启用', 2: '停用' }[v]}
+          </span>
+        ),
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updated_at',
+      key: 'updated_at',
+      render: (v) => v && moment(v).format('YYYY-MM-DD HH:ss'),
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_, record) => (
+        <Space size="middle">
+          <a
+            onClick={() => {
+              toEdit(record);
+            }}
+          >
+            编辑
+          </a>
+          {record?.status === 2 && (
+            <a
+              style={{ color: 'green' }}
+              onClick={() => {
+                toEnable(record);
+              }}
+            >
+              启用
+            </a>
+          )}
+          {record?.status === 1 && (
+            <a
+              style={{ color: 'red' }}
+              onClick={() => {
+                toDisable(record);
+              }}
+            >
+              停用
+            </a>
+          )}
+        </Space>
+      ),
+    },
+  ];
+
+  const paginationProps = {
+    showSizeChanger: true,
+    showQuickJumper: true,
+    showTotal: (total: number) => {
+      return <span> 共 {total}条 </span>;
+    },
+    ...pagination,
+  };
+
+  return (
+    <PageContainer>
+      <Card>
+        <Form form={form} layout="inline" onFinish={onFinish}>
+          <Form.Item name="user_name" label="用户姓名">
+            <Input placeholder="请输入用户姓名" />
+          </Form.Item>
+          <Form.Item name="real_name" label="真实姓名">
+            <Input placeholder="请输入真实姓名" />
+          </Form.Item>
+          <Form.Item name="phone" label="手机号">
+            <Input placeholder="请输入手机号" />
+          </Form.Item>
+          <Form.Item name="status" label="状态">
+            <Select style={{ width: '175px' }} placeholder="请选择状态">
+              <Select.Option key={1} value={1}>
+                启用
+              </Select.Option>
+              <Select.Option key={2} value={2}>
+                停用
+              </Select.Option>
+            </Select>
+          </Form.Item>
+          <Form.Item style={{ marginBottom: '10px' }}>
+            <Space>
+              <Button type="primary" htmlType="submit">
+                <SearchOutlined />
+                查询
+              </Button>
+              <Button htmlType="button" onClick={onReset}>
+                <ReloadOutlined />
+                重置
+              </Button>
+            </Space>
+          </Form.Item>
+        </Form>
+        <Button htmlType="button" type="primary" style={{ margin: '20px 0' }} onClick={onAdd}>
+          <PlusCircleOutlined />
+          新增用户
+        </Button>
+        <Table
+          columns={columns}
+          dataSource={dataList}
+          rowKey={(record) => record.record_id}
+          pagination={paginationProps}
+          loading={loading}
+          onChange={tableChange}
+        />
+        {visible && <Edit visible={visible} editCallback={editCallback} params={editData} />}
+      </Card>
+    </PageContainer>
+  );
+};
+export default UserManagement;

+ 11 - 21
src/pages/user/Login/index.less

@@ -2,7 +2,13 @@
 
 .container {
   height: 100%;
+  background: url('../../../../public/assets/login_left.png') top right no-repeat;
   .loginBox {
+    .logo_style {
+      width: 150px;
+      margin-top: 30px;
+      margin-left: 50px;
+    }
     .login_bg {
       position: absolute;
       top: 0;
@@ -10,30 +16,23 @@
     }
     .main {
       position: absolute;
-      top: 20%;
+      top: 25%;
       left: 12%;
-      width: 500px;
-      padding: 60px 50px;
+      width: 350px;
       overflow: hidden;
-      background: #f3f7ff;
       border-radius: 10px;
       .title {
         margin-bottom: 20px;
-        text-align: center;
         .logoStyle {
           width: 170px;
         }
       }
-
       .formItem {
         display: block;
-        height: 100px;
-        margin-bottom: 0;
       }
-
       .formItemInput {
         height: 46px;
-        border-radius: 5px;
+        border-radius: 25px;
       }
 
       .captcha {
@@ -45,27 +44,18 @@
       .submit {
         display: block;
         width: 100%;
-        height: 50px;
-        margin-top: 10px;
+        height: 45px;
+        margin-top: 40px;
         color: #fff;
         font-size: 20px;
         background: #4e94fe;
         border: 0 none;
         border-radius: 25px;
-        box-shadow: 0 4px 11px 0 rgba(204, 204, 204, 0.46);
         cursor: pointer;
       }
     }
   }
 }
-@media (min-width: @screen-md-min) {
-  .container {
-    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
-    background-repeat: no-repeat;
-    background-position: center 110px;
-    background-size: 100%;
-  }
-}
 .footer {
   position: absolute;
   bottom: 0;

+ 25 - 7
src/pages/user/Login/index.tsx

@@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react';
 import { history, useModel } from 'umi';
 import styles from './index.less';
 import md5 from 'js-md5';
+import { SafetyCertificateOutlined, UnlockOutlined, UserOutlined } from '@ant-design/icons';
 
 const FormItem = Form.Item;
 
@@ -15,7 +16,7 @@ const FormItem = Form.Item;
  */
 const Footer: React.FC = () => {
   const currentYear = new Date().getFullYear();
-  const defaultMessage = '永续绿建web管理平台';
+  const defaultMessage = '永续绿建管理平台';
   return <DefaultFooter className={styles.footer} copyright={`${currentYear} ${defaultMessage}`} />;
 };
 
@@ -76,39 +77,55 @@ const Login: React.FC = () => {
         history.push(redirect || '/');
       } else {
         message.error(msg?.error?.message || '登录失败');
+        fetchCaptcha();
       }
     } catch (error) {
-      // const _ = fetchCaptcha();
       message.success(`登录失败`);
+      fetchCaptcha();
     }
   };
 
   return (
     <div className={styles.container}>
       <div className={styles.loginBox}>
-        <img alt="请登录" src="/assets/login-left.png" className={styles.login_bg} />
+        <img src="/assets/logo.png" className={styles.logo_style} alt="" />
+        {/*<img alt="请登录" src="/assets/login_left.png" className={styles.login_bg} />*/}
         <div className={styles.main}>
           <div className={styles.title}>
-            <img src="/assets/logo.png" alt="logo" className={styles.logoStyle} />
+            <div
+              style={{
+                fontSize: '28px',
+                fontWeight: 'bold',
+                marginLeft: '10px',
+                marginBottom: '5px',
+              }}
+            >
+              永续绿建管理平台
+            </div>
+            <div
+              style={{ marginLeft: '10px', fontSize: '16px', color: '#999', marginBottom: '60px' }}
+            >
+              欢迎登录
+            </div>
+            {/*<img src="/assets/logo.png" alt="logo" className={styles.logoStyle} />*/}
           </div>
           <Form layout="vertical" onFinish={handleSubmit}>
             <FormItem
               rules={[{ required: true, message: '请输入用户名!' }]}
               className={styles.formItem}
               name="user_name"
-              label="用户名"
             >
               <Input
                 className={styles.formItemInput}
                 size="large"
                 placeholder="请输入用户名"
+                prefix={<UserOutlined />}
                 allowClear
               />
             </FormItem>
             <FormItem
               className={styles.formItem}
               name="password"
-              label="密码"
               rules={[{ required: true, message: '请输入密码!' }]}
             >
               <Input
@@ -116,18 +133,19 @@ const Login: React.FC = () => {
                 size="large"
                 type="password"
                 placeholder="请输入密码"
+                prefix={<UnlockOutlined />}
                 allowClear
               />
             </FormItem>
             <FormItem
               className={styles.formItem}
               name="captcha_code"
-              label="验证码"
               rules={[{ required: true, message: '请输入验证码!' }]}
             >
               <Input
                 className={styles.formItemInput}
                 size="large"
+                prefix={<SafetyCertificateOutlined />}
                 suffix={
                   <img
                     className={styles.captcha}

+ 1 - 1
src/services/ant-design-pro/api.ts

@@ -15,7 +15,7 @@ export async function currentUser(param: any) {
 
 /** 退出登录接口 POST */
 export async function outLogin(options?: { [key: string]: any }) {
-  return request<Record<string, any>>('/api/login/outLogin', {
+  return request<Record<string, any>>('/web/v1/login/exit', {
     method: 'POST',
     ...(options || {}),
   });

+ 52 - 0
src/services/setting/index.ts

@@ -0,0 +1,52 @@
+import { stringify } from 'qs';
+import { request } from 'umi';
+
+/**
+ * 用户列表
+ * @param param
+ */
+export async function queryUserList(param: object) {
+  return request(`/web/v1/users?${stringify(param)}`);
+}
+
+/**
+ * 新增用户信息
+ * @param params
+ */
+export async function createUser(params: object) {
+  return request(`/web/v1/users`, {
+    method: 'POST',
+    data: params,
+  });
+}
+
+/**
+ * 编辑用户信息
+ * @param params
+ */
+export async function editUser(params: any) {
+  return request(`/web/v1/users/${params.record_id}`, {
+    method: 'PUT',
+    data: params,
+  });
+}
+
+/**
+ * 启用
+ * @param id
+ */
+export async function enableUser(id: string) {
+  return request(`/web/v1/users/${id}/enable`, {
+    method: 'PATCH',
+  });
+}
+
+/**
+ * 停用
+ * @param id
+ */
+export async function disableUser(id: string) {
+  return request(`/web/v1/users/${id}/disable`, {
+    method: 'PATCH',
+  });
+}

部分文件因文件數量過多而無法顯示