Yansen 2 年 前
コミット
c3edcafe29
共有4 個のファイルを変更した255 個の追加18 個の削除を含む
  1. 103
    0
      src/components/Page/Edit.jsx
  2. 90
    0
      src/components/Page/List.jsx
  3. 23
    0
      src/components/Page/index.jsx
  4. 39
    18
      src/utils/request.js

+ 103
- 0
src/components/Page/Edit.jsx ファイルの表示

@@ -0,0 +1,103 @@
1
+import React from 'react';
2
+import { Button, Card, Form } from 'antd';
3
+import { useSearchParams, useNavigate } from "react-router-dom";
4
+import useBool from '@/utils/hooks/useBool';
5
+import { restful } from '@/utils/request';
6
+import Page from './index';
7
+
8
+const FormItem = Form.Item;
9
+const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 14 } };
10
+
11
+export default (props) => {
12
+  /**
13
+   * resource 用来确定应该调用哪个接口
14
+   * dataFunc 将 form 表单数据转换为接口需要格式, 一般情况下不需要这个操作
15
+   * formDataFunc 将接口返回的值转换为 form 表单数据格式, 一般情况下不需要这个操作
16
+   * width form 表单宽度, 默认 800px
17
+   */
18
+  const { resource, dataFunc, formDataFunc, width = '800px' } = props;
19
+  const [loading, startLoading, stopLoading] = useBool();
20
+  const [form] = Form.useForm();
21
+  const navigate = useNavigate();
22
+
23
+  // 获取 id query 参数, 如果存在说明是编辑,而不是新增
24
+  const [params] = useSearchParams();
25
+  const id = params.get("id");
26
+
27
+  // 接口汇总
28
+  const api = React.useMemo(() => {
29
+    // 通过 resource 知道是哪个接口
30
+    // 通过 restfule 生成 增删改查的接口
31
+    const apis = restful(resource);
32
+
33
+    // 返回三个接口
34
+    // 1. 获取详情
35
+    // 2. 新增
36
+    // 3. 更新
37
+    return {
38
+      get: apis[1],
39
+      save: apis[2],
40
+      update: apis[3],
41
+    }
42
+  }, [resource]);
43
+  
44
+  const navBack = () => {
45
+    const t = setTimeout(() => {
46
+      navigate(-1);
47
+      clearTimeout(t);
48
+    }, 600);
49
+  }
50
+
51
+  // 表单提交
52
+  const onFinish = (values) => {    
53
+    startLoading();
54
+    // 可能需要转换下格式
55
+    const data = dataFunc ? dataFunc(id, values) : values;
56
+    if (id) {
57
+      api.update(id, data).then(() => {
58
+        stopLoading();
59
+        navBack();
60
+      }).catch(stopLoading);
61
+    } else {
62
+      api.save(data)
63
+        .then(() => {
64
+          stopLoading();
65
+          navBack();
66
+        }).catch(stopLoading);
67
+    }
68
+  }
69
+
70
+  // 查询详情
71
+  React.useEffect(() => {
72
+    if (id) {
73
+      api.get(id).then(res => {
74
+        // 此处可能需要转换下数据格式
75
+        const formData = formDataFunc ? formDataFunc(res) : res;
76
+        form.setFieldsValue(formData);
77
+      })
78
+    }
79
+  }, [id]);
80
+
81
+  return (
82
+    <Page>
83
+      <Card>
84
+        <Form {...formItemLayout} form={form} onFinish={onFinish} style={{width}}>
85
+          {props.children}
86
+          <FormItem label=" " colon={false}>
87
+            <Button type="default" onClick={() => navigate(-1)}>
88
+              取消
89
+            </Button>
90
+            <Button
91
+              type="primary"
92
+              htmlType="submit"
93
+              style={{ marginLeft: '4em' }}
94
+              loading={loading}
95
+            >
96
+              确认
97
+            </Button>
98
+          </FormItem>
99
+        </Form>
100
+      </Card>
101
+    </Page>
102
+  )
103
+}

+ 90
- 0
src/components/Page/List.jsx ファイルの表示

@@ -0,0 +1,90 @@
1
+import React, { useRef, useMemo } from 'react';
2
+import { Button, Popconfirm, message } from 'antd';
3
+import { ProTable } from '@ant-design/pro-components';
4
+import { Link, useNavigate } from "react-router-dom";
5
+import { queryTable, restful } from '@/utils/request';
6
+import Page from './index';
7
+
8
+export default (props) => {
9
+  /**
10
+   * resource 用来确定应该调用哪个接口
11
+   * rowKey 标识数据每一行的唯一键, 只能是字符串
12
+   * columns 定义表格每一列, 符合 ProTable 定义
13
+   * editURL 编辑页路由
14
+   */
15
+  const { resource, rowKey, columns, editURL } = props;
16
+  const actionRef = useRef();
17
+  const navigate = useNavigate();
18
+
19
+  // 接口汇总
20
+  const api = useMemo(() => {
21
+    // 通过 resource 知道是哪个接口
22
+    // 通过 restfule 生成 增删改查的接口
23
+    const apis = restful(resource);
24
+
25
+    // 返回两个,一个是列表查询, 一个是删除
26
+    return {
27
+      list: apis[0],
28
+      delete: apis[4],
29
+    }
30
+  }, [resource]);
31
+
32
+  // 响应 删除 操作
33
+  const onDelete = (row) => {
34
+    const hide = message.loading('操作中, 请稍候...');
35
+    api.delete(row[rowKey]).then(() => {
36
+      hide();
37
+      actionRef.current.reload();
38
+    }).catch(hide);
39
+  }
40
+
41
+  // 统一实现操作列
42
+  const cols = [
43
+    ...columns,
44
+    {
45
+      title: '操作',
46
+      valueType: 'option',
47
+      key: 'option',
48
+      ellipsis: true,
49
+      render: (_, record) => [
50
+        <Button style={{ padding: 0 }} type="link" key={1} onClick={() => { navigate(`${editURL}?id=${record[rowKey]}`) }}>
51
+          编辑
52
+        </Button>,
53
+        <Popconfirm
54
+          key={3}
55
+          title="您是否确认删除 ?"
56
+          onConfirm={() => onDelete(record)}
57
+          okText="确定"
58
+          cancelText="取消"
59
+        >
60
+          <Button style={{ padding: 0 }} type="link">
61
+            删除
62
+          </Button>
63
+        </Popconfirm>,
64
+      ],
65
+    },
66
+  ]
67
+
68
+  return (
69
+    <Page>
70
+      <ProTable
71
+        rowKey={rowKey}
72
+        columns={cols}
73
+        request={queryTable(api.list)}
74
+        cardBordered
75
+        actionRef={actionRef}
76
+        toolBarRender={() => [
77
+          <Button
78
+            key="1"
79
+            type="primary"
80
+            onClick={() => {
81
+              navigate(editURL);
82
+            }}
83
+          >
84
+            新增
85
+          </Button>,
86
+        ]}
87
+      />
88
+    </Page>
89
+  )
90
+}

+ 23
- 0
src/components/Page/index.jsx ファイルの表示

@@ -0,0 +1,23 @@
1
+import React from 'react';
2
+import { Typography } from 'antd';
3
+import useRoute from '@/utils/hooks/useRoute';
4
+
5
+const pageStyle = {
6
+  // margin: '24px 24px 0 24px',
7
+  margin: '24px',
8
+  minHeight: 'calc(100% - 48px)',
9
+}
10
+const { Title } = Typography;
11
+
12
+export default (props) => {
13
+  const { meta = {} } = useRoute() || {};
14
+  const style = meta.noLayout ? { height: '100%' } : pageStyle;
15
+  const title = props.title || meta.title;
16
+
17
+  return (
18
+    <div style={style}>
19
+      { title && !meta.noLayout && <Title level={4} style={{ paddingBottom: '12px' }}>{ title }</Title> }
20
+      {props.children}
21
+    </div>
22
+  )
23
+}

+ 39
- 18
src/utils/request.js ファイルの表示

@@ -1,30 +1,32 @@
1 1
 import axios from "axios";
2
+import React from 'react';
2 3
 import { message } from 'antd';
3 4
 
4 5
 const instance = axios.create({
5
-  baseURL: import.meta.env.PROD ? SERVER_BASE : import.meta.env.VITE_SERVER_BASE,
6
+  baseURL: import.meta.env.VITE_SERVER_BASE,
6 7
   timeout: 10000,
8
+  withCredentials: true, // 跨域
7 9
 });
8 10
 
9 11
 
10 12
 // 添加请求拦截器
11 13
 instance.interceptors.request.use(function (config) {
12
-  const { headers = {}, method = 'get', responseType = 'json', download = false, successTip } = config;
14
+  const { headers = {}, method = 'get', responseType = 'json', download = false, silent } = config;
13 15
   const token = localStorage.getItem('token') || '';
14 16
 
15
-  let tip = successTip;
16
-  if (tip === undefined) {
17
-    tip = method === 'get' ? false : true;
17
+  let noTip = silent;
18
+  if (noTip === undefined) {
19
+    noTip = method.toLocaleLowerCase() === 'get' ? true : false;
18 20
   }
19 21
 
20 22
   // 在发送请求之前做些什么
21 23
   return {
22 24
     ...config,
23
-    successTip : tip,
25
+    silent: noTip,
24 26
     headers: {
25 27
       ...headers,
26 28
       Authorization: token,
27
-    },    
29
+    },
28 30
     responseType: download ? 'blob' : responseType,
29 31
   };
30 32
 }, function (error) {
@@ -38,17 +40,18 @@ instance.interceptors.response.use(function (response) {
38 40
   // 对响应数据做点什么
39 41
 
40 42
   const { data, config } = response;
43
+
41 44
   if (config.download && !data.code) {
42 45
     return downloadBlob(response, '下载文件');
43 46
   }
44
-  
47
+
45 48
   if (data.code === 1000) {
46 49
     if (data.data.token) {
47 50
       localStorage.setItem('token', data.data.token);
48 51
     }
49 52
 
50
-    if (config.successTip && !config.silent) {
51
-      message.success(typeof success === 'string' ? successTip : '操作成功');
53
+    if (!config.silent) {
54
+      message.success('操作成功');
52 55
     }
53 56
 
54 57
     return data.data;
@@ -57,6 +60,7 @@ instance.interceptors.response.use(function (response) {
57 60
       message.error('未登录或者超时, 请重新登录');
58 61
     }
59 62
   } else {
63
+    console.log(config)
60 64
     if (!config.silent) {
61 65
       const errMsg = data.message || '系统错误';
62 66
       message.error(errMsg.indexOf('exception') > -1 ? '服务异常' : errMsg);
@@ -75,13 +79,13 @@ instance.interceptors.response.use(function (response) {
75 79
 
76 80
 export default instance;
77 81
 
78
-export function queryTable(apiRequest) {
82
+export function queryTable (apiRequest) {
79 83
   return function (params) {
80
-    const { current, pageSize, ...leftParams } = params;
84
+    const { pageSize } = params;
81 85
     return apiRequest({
86
+      ...params,
82 87
       pageSize,
83 88
       pageNum: params.current,
84
-      ...(leftParams || {}),
85 89
     })
86 90
       .then((res) => {
87 91
         return {
@@ -98,7 +102,7 @@ export function queryTable(apiRequest) {
98 102
   };
99 103
 }
100 104
 
101
-export function queryDict(apiRequest) {
105
+export function queryDict (apiRequest) {
102 106
   return function (params, labelKey = 'name', valueKey = 'id') {
103 107
     const { current, pageSize, ...leftParams } = params || {};
104 108
     return apiRequest({
@@ -121,9 +125,9 @@ export function queryDict(apiRequest) {
121 125
   };
122 126
 }
123 127
 
124
-export function restful(url) {
125
-  const list = params => instance.get(url, { params, successTip: false });
126
-  const get = id => instance.get(`${url}/${id}`,{ successTip: false });
128
+export function restful (url) {
129
+  const list = params => instance.get(url, { params });
130
+  const get = id => instance.get(`${url}/${id}`);
127 131
   const add = data => instance.post(url, data);
128 132
   const update = (id, data) => instance.put(`${url}/${id}`, data);
129 133
   const del = id => instance.delete(`${url}/${id}`);
@@ -131,7 +135,24 @@ export function restful(url) {
131 135
   return [list, get, add, update, del];
132 136
 }
133 137
 
134
-function downloadBlob(response) {
138
+export function useRequest (fn) {
139
+  const [loading, setLoading] = React.useState(false);
140
+
141
+  const p = (...args) => new Promise((resolve, reject) => {
142
+    setLoading(true);
143
+    fn(...args).then(res => {
144
+      setLoading(false);
145
+      resolve(res);
146
+    }).catch(e => {
147
+      setLoading(false);
148
+      reject(e);
149
+    });
150
+  });
151
+
152
+  return [loading, p]
153
+}
154
+
155
+function downloadBlob (response) {
135 156
   let fileName = '未知文件';
136 157
   const contentType = response.headers['content-type'];
137 158
   const contentDisposition = response.headers['content-disposition'];