fangmingyue 1 년 전
부모
커밋
06aa370dd8

+ 6
- 2
public/config.js 파일 보기

@@ -1,7 +1,11 @@
1
+var id = "cms";
2
+
3
+var uploadAPI = `http://192.168.89.13:7201/api/upload/${id}`
1 4
 
2 5
 var SERVER_BASE = '';
3 6
 
7
+var prefix = 'http://192.168.89.13:7777/'
4 8
 // 获取真实的上传文件地址
5
-function getRealPath(path) {
6
-  return path;
9
+function getRealPath (path) {
10
+  return `${prefix}` + path;
7 11
 }

+ 116
- 0
src/components/EditForm/index.jsx 파일 보기

@@ -0,0 +1,116 @@
1
+import React from 'react'
2
+import { Button, Card, Row, Col, Form } from 'antd'
3
+import { ProForm } from '@ant-design/pro-components'
4
+import { useSearchParams, useNavigate } from 'react-router-dom'
5
+
6
+const noop = (x) => x
7
+
8
+export default React.forwardRef(function EditForm (props, ref) {
9
+  const navigate = useNavigate()
10
+
11
+  const onBackDefault = () => navigate(-1);
12
+
13
+  const {
14
+    rowId,
15
+    rowKey,
16
+    request,
17
+    transform, // 提交时转换数据
18
+    convertValue, // 初始化表单时数值转换
19
+    maxWidth = '800px',
20
+    renderItems,
21
+    formRef,
22
+    initialValues, // 初始化 form 只生效一次
23
+    initialData, // 初始化 form 每次都生效
24
+    onFinish,
25
+    onDataReady,
26
+    onBack = onBackDefault,
27
+    ...leftProps
28
+  } = props
29
+
30
+  const [form] = Form.useForm()
31
+
32
+  if (formRef) {
33
+    formRef.current = form
34
+  }
35
+
36
+  // 表单提交
37
+  const onSubmit = async (values) => {
38
+    // 可能需要转换下格式
39
+    if (rowId) {
40
+      values[rowKey] = rowId
41
+    }
42
+    const data = transform ? transform(rowId, values) : values
43
+
44
+    if (rowId && request?.update) {
45
+      const res = await request.update(rowId, data)
46
+      if (onFinish) {
47
+        onFinish(res)
48
+      }
49
+    } else if (request?.save) {
50
+      const res = await request.save(data)
51
+      if (onFinish) {
52
+        onFinish(res)
53
+      }
54
+    }
55
+
56
+    return true
57
+  }
58
+
59
+  // 查询详情
60
+  React.useEffect(() => {
61
+    form.resetFields()
62
+
63
+    if (rowId && request?.get) {
64
+      request.get(rowId).then((res) => {
65
+        if (onDataReady) {
66
+          onDataReady(res)
67
+        }
68
+
69
+        // 此处可能需要转换下数据格式
70
+        const formData = convertValue ? convertValue(res) : res
71
+        form.setFieldsValue(formData)
72
+      })
73
+    } else {
74
+      form.resetFields()
75
+      const formData = convertValue ? convertValue(initialData) : initialData
76
+      form.setFieldsValue(formData)
77
+    }
78
+  }, [request?.get, rowId, initialData])
79
+
80
+  // React.useEffect(() => {
81
+  //   form.resetFields()
82
+  //   const formData = convertValue ? convertValue(initialData) : initialData
83
+  //   form.setFieldsValue(formData)
84
+  // }, [initialData])
85
+
86
+  React.useImperativeHandle(ref, () => ({
87
+    form: form,
88
+  }))
89
+
90
+  return (
91
+    <ProForm
92
+      form={form}
93
+      onFinish={onSubmit}
94
+      style={{ maxWidth }}
95
+      layout="horizontal"
96
+      labelCol={{ flex: '120px' }}
97
+      initialValues={initialValues}
98
+      submitter={{
99
+        render: (_, doms) => [
100
+          <Row gutter={48}>
101
+            <Col flex="200px"></Col>
102
+            <Col>
103
+              {doms[1]}
104
+            </Col>
105
+            <Col>
106
+              <Button onClick={onBack}>返回</Button>
107
+            </Col>
108
+          </Row>,
109
+        ],
110
+      }}
111
+      {...leftProps}
112
+    >
113
+      {renderItems()}
114
+    </ProForm>
115
+  )
116
+})

+ 21
- 65
src/components/Page/Edit.jsx 파일 보기

@@ -1,22 +1,19 @@
1 1
 import React from 'react';
2
-import { Button, Card, Form } from 'antd';
2
+import { Button, Card, Row, Col, Form } from 'antd';
3
+import { ProForm } from '@ant-design/pro-components';
4
+import EditForm from '@/components/EditForm';
3 5
 import { useSearchParams, useNavigate } from "react-router-dom";
4 6
 import useBool from '@/utils/hooks/useBool';
5 7
 import Page from './index';
6 8
 
7
-const FormItem = Form.Item;
8
-const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 14 } };
9
+export default function EditPage(props) {
10
+
11
+  const {
12
+    title,
13
+    onFinish,
14
+    ...leftProps
15
+  } = props;
9 16
 
10
-export default (props) => {
11
-  /**
12
-   * request 编辑或者新增接口
13
-   * dataFunc 将 form 表单数据转换为接口需要格式, 一般情况下不需要这个操作
14
-   * formDataFunc 将接口返回的值转换为 form 表单数据格式, 一般情况下不需要这个操作
15
-   * width form 表单宽度, 默认 800px
16
-   */
17
-  const { request, dataFunc, formDataFunc, width = '800px', onFinish, ...leftProps } = props;
18
-  const [loading, startLoading, stopLoading] = useBool();
19
-  const [form] = Form.useForm();
20 17
   const navigate = useNavigate();
21 18
 
22 19
   // 获取 id query 参数, 如果存在说明是编辑,而不是新增
@@ -30,63 +27,22 @@ export default (props) => {
30 27
     }, 600);
31 28
   }, [navigate]);
32 29
 
33
-  // 表单提交
34
-  const onSubmit = (values) => {    
35
-    startLoading();
36
-    // 可能需要转换下格式
37
-    const data = dataFunc ? dataFunc(id, values) : values;
38
-    if (id) {
39
-      request.update(id, data).then((res) => {
40
-        stopLoading();
41
-        if (onFinish) {
42
-          onFinish(res);
43
-        } else {
44
-          navBack();
45
-        }
46
-      }).catch(stopLoading);
30
+  const handleFinish = (res) => {
31
+    if (onFinish) {
32
+      onFinish(res);
47 33
     } else {
48
-      request.save(data)
49
-        .then((res) => {
50
-          stopLoading();
51
-          if (onFinish) {
52
-            onFinish(res);
53
-          } else {
54
-            navBack();
55
-          }
56
-        }).catch(stopLoading);
34
+      navBack();
57 35
     }
58 36
   }
59 37
 
60
-  // 查询详情
61
-  React.useEffect(() => {
62
-    if (id && request) {
63
-      request.get(id).then(res => {
64
-        // 此处可能需要转换下数据格式
65
-        const formData = formDataFunc ? formDataFunc(res) : res;
66
-        form.setFieldsValue(formData);
67
-      })
68
-    }
69
-  }, [request, id]);
70
-
71 38
   return (
72
-    <Page>
73
-      <Card bodyStyle={{padding: '48px 24px'}}>
74
-        <Form {...formItemLayout} form={form} onFinish={onSubmit} style={{width}} {...leftProps} >
75
-          {props.children}
76
-          <FormItem label=" " colon={false} style={{marginBottom: 0}}>
77
-            <Button type="default" onClick={() => navigate(-1)}>
78
-              取消
79
-            </Button>
80
-            <Button
81
-              type="primary"
82
-              htmlType="submit"
83
-              style={{ marginLeft: '4em' }}
84
-              loading={loading}
85
-            >
86
-              确认
87
-            </Button>
88
-          </FormItem>
89
-        </Form>
39
+    <Page title={title}>
40
+      <Card title={title}>
41
+        <EditForm
42
+          rowId={id}
43
+          onFinish={handleFinish}
44
+          {...leftProps}
45
+        />
90 46
       </Card>
91 47
     </Page>
92 48
   )

+ 34
- 75
src/components/Page/List.jsx 파일 보기

@@ -1,89 +1,48 @@
1
-import React from 'react';
2
-import { Button, Popconfirm, message } from 'antd';
3
-import { ProTable } from '@ant-design/pro-components';
4
-import { queryTable } from '@/utils/request';
5
-import Page from './index';
1
+import React from 'react'
2
+import { message } from 'antd'
3
+import Page from './index'
4
+import XTable from '../XTable'
6 5
 
7 6
 export default React.forwardRef((props, ref) => {
8 7
   /**
9 8
    * request 与 ProTable 定义不同,这个只是普通的接口即可
10 9
    */
11
-  const { request, rowKey, columns, onAdd, onDelete, onEdit, toolBarRender, columnOptionRender, ...leftProps } = props;
12
-  const actionRef = React.useRef();
13
-  const hideRef = React.useRef();
10
+  const {
11
+    attach,
12
+    title,
13
+    extra,
14
+    ...leftProps
15
+  } = props
16
+  const [loading, setLoading] = React.useState(false);
17
+  const actionRef = React.useRef()
18
+  const hideRef = React.useRef()
14 19
 
15
-  const api = React.useMemo(() => queryTable(request), [request]);
20
+  React.useImperativeHandle(
21
+    ref,
22
+    () => {
23
+      const showLoading = (msg) => (hideRef.current = message.loading(msg || '操作中, 请稍候...'))
24
+      const hideLoading = () => hideRef.current && hideRef.current()
25
+      const reload = () => actionRef.current?.reload && actionRef.current.reload()
16 26
 
17
-  // 统一实现操作列
18
-  const cols = React.useMemo(() => [
19
-    ...columns,
20
-    {
21
-      title: '操作',
22
-      valueType: 'option',
23
-      key: 'option',
24
-      ellipsis: true,
25
-      render: (_, record) => [
26
-        (
27
-          onEdit && 
28
-          <Button style={{ padding: 0 }} type="link" key={1} onClick={() => onEdit(record) }>
29
-            编辑
30
-          </Button>
31
-        ),
32
-        (
33
-          onDelete &&
34
-          <Popconfirm
35
-            key={3}
36
-            title="您是否确认删除 ?"
37
-            onConfirm={() => onDelete(record)}
38
-            okText="确定"
39
-            cancelText="取消"
40
-          >
41
-            <Button style={{ padding: 0 }} type="link">
42
-              删除
43
-            </Button>
44
-          </Popconfirm>
45
-        ),
46
-        ...(columnOptionRender ? columnOptionRender(_, record) : []),
47
-      ].filter(Boolean),
27
+      return {
28
+        showLoading,
29
+        hideLoading,
30
+        reload,
31
+        setLoading,
32
+        actionRef: actionRef,
33
+      }
48 34
     },
49
-  ], [columns, onEdit, onDelete]);
50
-
51
-  React.useImperativeHandle(ref, () => {
52
-    const showLoading = msg => (hideRef.current = message.loading(msg || '操作中, 请稍候...'));
53
-    const hideLoading = () => hideRef.current && hideRef.current();
54
-    const reload = () => actionRef.current?.reload && actionRef.current.reload();
55
-
56
-    return {
57
-      showLoading,
58
-      hideLoading,
59
-      reload,
60
-      actionRef: actionRef,
61
-    }
62
-  }, []);
35
+    []
36
+  )
63 37
 
64 38
   return (
65
-    <Page>
66
-      <ProTable
67
-        rowKey={rowKey}
68
-        columns={cols}
69
-        request={api}
70
-        cardBordered
71
-        actionRef={actionRef}
72
-        toolBarRender={() => [
73
-          (
74
-            onAdd && 
75
-            <Button
76
-              key="1"
77
-              type="primary"
78
-              onClick={onAdd}
79
-            >
80
-              新增
81
-            </Button>
82
-          ),
83
-          ...(toolBarRender ? toolBarRender() : []),
84
-        ].filter(Boolean)}
39
+    <Page title={title} extra={extra}>
40
+      {attach}
41
+      <XTable
42
+        ref={actionRef}
43
+        loading={loading}
85 44
         {...leftProps}
86 45
       />
87 46
     </Page>
88 47
   )
89
-});
48
+})

+ 0
- 61
src/components/Upload/UploadFile.jsx 파일 보기

@@ -1,61 +0,0 @@
1
-import React from "react";
2
-import { UploadOutlined } from "@ant-design/icons";
3
-import { Button } from "antd";
4
-import Upload from "@/components/Upload/Upload";
5
-
6
-const getFileInfo = (x) => ({
7
-  uid: x,
8
-  name: x?.split("/")?.at(-1)?.replace(/^\d+-/, ""),
9
-  status: "done",
10
-  thumbUrl: x,
11
-  url: x,
12
-});
13
-
14
-export default (props) => {
15
-  const { value, onChange, accept = ".ppt,.pptx,.pdf" } = props;
16
-
17
-  const [loading, setLoading] = React.useState(false);
18
-
19
-  const [fileList, setFileList] = React.useState([]);
20
-
21
-
22
-  const handleChange = (info) => {
23
-    const newFileList = [...info.fileList];
24
-
25
-    if (info.file.status === "uploading") {
26
-      setLoading(true);
27
-    } else if (info.file.status === "error") {
28
-      setLoading(false);
29
-    } else if (info.file.status === "done") {
30
-      setLoading(false);
31
-      const { url, fileType } = info.file.response;
32
-      if (onChange) {
33
-        onChange(url, fileType);
34
-      }
35
-    }
36
-
37
-    setFileList(newFileList);
38
-  };
39
-
40
-  React.useEffect(() => {
41
-    if (!value) {
42
-      setFileList([]);
43
-    } else {
44
-      setFileList([getFileInfo(value)]);
45
-    }
46
-  }, [value]);
47
-
48
-  return (
49
-    <>
50
-      <Upload
51
-        accept={accept}
52
-        fileList={fileList}
53
-        onChange={handleChange}
54
-      >
55
-        <Button loading={loading} icon={<UploadOutlined />}>
56
-          点击上传文件
57
-        </Button>
58
-      </Upload>
59
-    </>
60
-  );
61
-};

+ 145
- 0
src/components/Upload/UploadFiles.jsx 파일 보기

@@ -0,0 +1,145 @@
1
+import React from 'react';
2
+import { UploadOutlined } from '@ant-design/icons'
3
+import { Button, Space, Row, Col, Popconfirm } from 'antd';
4
+import Upload from './Upload'
5
+
6
+const noop = x => x;
7
+
8
+export default function UploadFiles (props) {
9
+
10
+  const {
11
+    rowKey = "id",
12
+    rowLabel = "attachName",
13
+    readonly = false,
14
+    multiple = true,
15
+    value,
16
+    onChange,
17
+    onPreview,
18
+    onDownload,
19
+    transform = noop,
20
+    renderItem = defRenderItem,
21
+    ...leftProps
22
+  } = props;
23
+
24
+  const [loading, setLoading] = React.useState(false)
25
+
26
+  const list = React.useMemo(() => {
27
+    return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean);
28
+  }, [value]);
29
+
30
+  const getId = (item) => {
31
+    return typeof rowKey == 'function' ? rowKey(item) : item[rowKey];
32
+  };
33
+
34
+  const getLabel = (item) => {
35
+    return typeof rowLabel == 'function' ? rowLabel(item) : item[rowLabel];
36
+  }
37
+
38
+  const handleChange = (info) => {
39
+    // console.log('info-------->', info)
40
+    if (info.file.status === 'uploading') {
41
+      setLoading(true)
42
+      return
43
+    }
44
+    if (info.file.status === 'error') {
45
+      setLoading(false)
46
+      return
47
+    }
48
+
49
+    if (info.file.status === 'done') {
50
+      setLoading(false)
51
+      const resp = info.file.response;
52
+      const data = typeof transform == 'function' ? transform(resp) : resp;
53
+      // let da = data?.url
54
+      if (multiple) {
55
+        // console.log('-------2---->>');
56
+        const idList = list.map(getId);
57
+        const targId = getId(data);
58
+        // if (idList.findIndex(x => x == targId) == -1) {
59
+        let da = { ...data, status: 1 }
60
+        const destList = list.map(item => {
61
+          const { createdBy, createdTime, updatedBy, updatedTime, ...rest } = item;
62
+          return rest;
63
+        });
64
+        onChange(destList.concat(da))
65
+        // }
66
+      } else {
67
+        onChange(data)
68
+      }
69
+    }
70
+  }
71
+
72
+  const onDelete = (item) => {
73
+    if (multiple) {
74
+      const id = getId(item);
75
+      const vals = list.filter(x => getId(x) != id);
76
+      onChange(vals);
77
+    } else {
78
+      onChange();
79
+    }
80
+  }
81
+
82
+  // console.log('-----valuefang--->', value);
83
+
84
+  return (
85
+    <Upload
86
+      multiple={multiple}
87
+      showUploadList={false}
88
+      onChange={handleChange}
89
+      style={{ display: 'inline-block', width: '100%' }}
90
+      {...leftProps}
91
+    >
92
+      {!readonly && <Button icon={<UploadOutlined />}>上传附件</Button>}
93
+      <div onClick={e => e.stopPropagation()} style={{ marginTop: 10 }}>
94
+        {
95
+          list.map(item => (
96
+            <div key={getId(item)}>{renderItem(item, { readonly, loading, getLabel, getId, onDelete, onPreview, onDownload })}</div>
97
+          ))
98
+        }
99
+      </div>
100
+    </Upload>
101
+  );
102
+}
103
+
104
+const btnStyle = { padding: 0, height: '24px' };
105
+
106
+function defRenderItem (item, { readonly, loading, getLabel, getId, onDelete, onPreview, onDownload }) {
107
+
108
+  const handlePreview = () => {
109
+    if (typeof onPreview == 'function') {
110
+      onPreview(item);
111
+    }
112
+  }
113
+
114
+  const handleDownload = () => {
115
+    if (typeof onDownload == 'function') {
116
+      onDownload(item);
117
+    }
118
+  }
119
+
120
+  return (
121
+    <Row gutter={16}>
122
+      <Col flex={1} style={{ display: 'flex', alignItems: 'center' }}>
123
+        <div style={{ fontSize: '12px' }}>{getLabel(item)}</div>
124
+      </Col>
125
+      <Col flex="none">
126
+        <Button type='link' style={btnStyle} onClick={handlePreview}>预览</Button>
127
+      </Col>
128
+      <Col flex="none">
129
+        <Button type='link' style={btnStyle} onClick={handleDownload}>下载</Button>
130
+      </Col>
131
+      {
132
+        !readonly ? (
133
+          <Col flex="none">
134
+            <Popconfirm
135
+              title="确定进行删除操作 ?"
136
+              onConfirm={() => onDelete(item)}
137
+            >
138
+              <Button type='link' style={btnStyle} danger>删除</Button>
139
+            </Popconfirm>
140
+          </Col>
141
+        ) : null
142
+      }
143
+    </Row>
144
+  );
145
+}

+ 6
- 2
src/components/Upload/UploadImage.jsx 파일 보기

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
2 2
 import { setProperty } from '@/utils/css';
3 3
 import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
4 4
 import Upload from './Upload';
5
+import styles from './style.less';
5 6
 
6 7
 function beforeUpload (file) {
7 8
   const isImage = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
@@ -41,12 +42,13 @@ export default (props) => {
41 42
     if (info.file.status === 'done') {
42 43
       setLoading(false);
43 44
       const { url, fileType } = info.file.response;
45
+      // console.log('--------方--url-.>', url);
44 46
       onChange(url);
45 47
     }
46 48
   };
47 49
 
48 50
   const previewUrl = getRealPath ? getRealPath(value) : value;
49
-
51
+  // console.log('--------previewUrl----->', previewUrl);
50 52
   React.useEffect(() => {
51 53
     setProperty('--cust-upload-ratio', ratio);
52 54
   }, [ratio]);
@@ -55,10 +57,12 @@ export default (props) => {
55 57
     setProperty('--cust-upload-width', `${width}px`);
56 58
   }, [width]);
57 59
 
60
+  // console.log('-----里层----->value,', value);
58 61
   return (
59 62
     <Upload
60 63
       listType="picture-card"
61
-      className="cust-img-upload"
64
+      className={styles['ant-upload']}
65
+      style={{ width: '200px' }}
62 66
       showUploadList={false}
63 67
       beforeUpload={beforeUpload}
64 68
       onChange={handleChange}

+ 22
- 26
src/components/Upload/UploadImageList.jsx 파일 보기

@@ -4,8 +4,8 @@ import { Modal } from 'antd'
4 4
 import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
5 5
 import Upload from './Upload';
6 6
 
7
-function beforeUpload(file) {
8
-  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'|| file.type === 'image/gif';
7
+function beforeUpload (file) {
8
+  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
9 9
   if (!isJpgOrPng) {
10 10
     message.error('请上传 JPG,PNG 或 GIF 图片!');
11 11
   }
@@ -24,8 +24,11 @@ const UploadButton = (props) => (
24 24
   </div>
25 25
 );
26 26
 
27
+const defaultInput = x => ({ uid: x, url: x });
28
+const defaultOutput = x => x.url;
29
+
27 30
 export default (props) => {
28
-  const { value, onChange, input, output } = props;
31
+  const { value, onChange, input = defaultInput, output = defaultOutput } = props;
29 32
 
30 33
   const [loading, setLoading] = useState(false);
31 34
   const [previewVisible, setPreviewVisible] = useState(false);
@@ -47,10 +50,10 @@ export default (props) => {
47 50
     }
48 51
   };
49 52
 
50
-  const handleSuccess = ({url}) => {
53
+  const handleSuccess = ({ url }) => {
51 54
     setLoading(false)
52 55
     const inx = fileList?.length ? fileList.length + 1 : 1
53
-    const list = fileList.concat({
56
+    const list = (fileList || []).concat({
54 57
       url,
55 58
       uid: `new-${inx}`,
56 59
       status: 'done'
@@ -59,35 +62,28 @@ export default (props) => {
59 62
   }
60 63
 
61 64
   const toggleChange = (list) => {
62
-    if (output) {
63
-      onChange(list.map(x => output(x)))
64
-    } else {
65
-      onChange(list)
66
-    }
65
+    onChange(list.map(x => output(x)))
67 66
   }
68 67
 
69 68
   const handlePreview = (file) => {
70
-    setPreviewImage(file.url)
69
+    setPreviewImage(`${prefix}` + file.url)
71 70
     setPreviewVisible(true)
72 71
   }
73 72
 
74 73
   useEffect(() => {
75
-    if (input) {
76
-      const lst = (value || []).map((it) => {
77
-        const { uid, url } = input(it)
78
-        return {
79
-          uid,
80
-          url,
81
-          status: 'done',
82
-          raw: it,
83
-        }
84
-      })
85
-      setFileList(lst)
86
-    } else {
87
-      setFileList(value || [])
88
-    }
74
+    const lst = (value || []).map((it) => {
75
+      const { uid, url } = input(it)
76
+      let ur = `${prefix}` + url
77
+      return {
78
+        uid,
79
+        ur,
80
+        status: 'done',
81
+        raw: it,
82
+      }
83
+    })
84
+    setFileList(lst)
89 85
   }, [value])
90
-
86
+  // console.log('------fileList---->>>>', fileList);
91 87
   return (
92 88
     <>
93 89
       <Upload

+ 6
- 6
src/components/Upload/UploadImages.jsx 파일 보기

@@ -3,19 +3,19 @@ import { Space } from 'antd';
3 3
 import UploadImage from "@/components/Upload/UploadImage";
4 4
 
5 5
 export default (props) => {
6
-  const { value = [], onChange, limit = 1, ratio, tips } = props;
6
+  const { value, onChange, limit = 1, ratio, tips } = props;
7 7
 
8
-  const handleChange = (inx = 0) => e => {
9
-    value[inx] = e;
10
-    onChange(value.slice());
8
+  const handleChange = () => e => {
9
+    let val = e
10
+    onChange(val);
11 11
   }
12 12
 
13 13
   return (
14 14
     <div className="flex">
15 15
       <div className="flex-0">
16 16
         <UploadImage
17
-          onChange={handleChange(0)}
18
-          value={value[0]}
17
+          onChange={handleChange()}
18
+          value={value}
19 19
           ratio={ratio}
20 20
           tips={tips}
21 21
         />

+ 6
- 21
src/components/Upload/request.js 파일 보기

@@ -1,42 +1,27 @@
1 1
 import request from "@/utils/request";
2 2
 
3
-import { useModel } from "@/store";
4
-
5
-const upload = (file, fileType = "image") => {
6
-  const { policyList } = useModel("policy");
3
+const upload = file => {
7 4
   const formData = new FormData();
8
-  formData.append("OSSAccessKeyId", policyList.OSSAccessKeyId);
9
-  formData.append("signature", policyList.signature);
10
-  formData.append("policy", policyList.policy);
11
-  formData.append("key", policyList.dir);
12
-  formData.append("success_action_status", 200);
13 5
   formData.append("file", file);
14
-
15
-  // return request(policyList.host, {
16
-  //   method: "post",
17
-  //   data: formData,
18
-  //   headers: {
19
-  //     "Content-Type": "multipart/form-data",
20
-  //     "Access-Control-Allow-Origin": "*",
21
-  //   },
22
-  // });
6
+  formData.append("fileType", file.type);
7
+  return request(uploadAPI, { method: "POST", data: formData });
23 8
 };
24 9
 
25 10
 /**
26 11
  * 上传文件
27 12
  * @returns
28 13
  */
29
-export function uploadFile(params) {
14
+export function uploadFile (params) {
30 15
   const { file, onSuccess, onError } = params;
31 16
   upload(file)
32 17
     .then((res) => {
33
-      onSuccess(res, file);
18
+      onSuccess({ url: res.attachUrl, fileType: file.type, name: file.name }, file);
34 19
     })
35 20
     .catch((e) => {
36 21
       onError(e);
37 22
     });
38 23
 
39 24
   return {
40
-    abort: () => {},
25
+    abort: () => { },
41 26
   };
42 27
 }

+ 1
- 1
src/components/Upload/style.less 파일 보기

@@ -6,7 +6,7 @@
6 6
   }
7 7
 }
8 8
 
9
-.cust-img-upload {
9
+.ant-upload {
10 10
   :global(.ant-upload-select) {
11 11
     width: var(--cust-upload-width) !important;
12 12
     height: auto !important;

+ 36
- 0
src/components/XTable/components/Actions.jsx 파일 보기

@@ -0,0 +1,36 @@
1
+import React from 'react';
2
+import { Dropdown, Space } from 'antd';
3
+import { HolderOutlined } from '@ant-design/icons';
4
+
5
+export default function renderActions(children = []) {
6
+
7
+  // children 小于,等于 2 个直接返回
8
+  if (children.length <= 2) {
9
+    return (
10
+      <Space size={8}>
11
+        {children}
12
+      </Space>
13
+    );
14
+  }
15
+
16
+  // 第一个拿出来
17
+  const first = children.shift();
18
+  const items = children.map((item, index) => {
19
+    return ({
20
+      key: index,
21
+      label: item,
22
+    });
23
+  });
24
+
25
+  return (
26
+    <Space size={8}>
27
+      {first}
28
+      <Dropdown menu={{ items }}>
29
+        <a onClick={(e) => e.preventDefault()}>
30
+          更多
31
+          <HolderOutlined />
32
+        </a>
33
+      </Dropdown>
34
+    </Space>
35
+  );
36
+}

+ 131
- 0
src/components/XTable/index.jsx 파일 보기

@@ -0,0 +1,131 @@
1
+import React from 'react';
2
+import { ProTable } from '@ant-design/pro-components'
3
+import { Button, Popconfirm, message } from 'antd'
4
+import { UploadOutlined, PlusOutlined } from '@ant-design/icons'
5
+import { queryTable } from '@/utils/request'
6
+import renderActions from './components/Actions'
7
+
8
+function XTable (props, ref) {
9
+  /**
10
+   * request 与 ProTable 定义不同,这个只是普通的接口即可
11
+   */
12
+  const {
13
+    request,
14
+    rowKey,
15
+    columns,
16
+    onAdd,
17
+    onDelete,
18
+    onEdit,
19
+    onDetail,
20
+    onExport,
21
+    toolBarRender,
22
+    columnOptionRender,
23
+    option = true,
24
+    ...leftProps
25
+  } = props;
26
+
27
+  const actionRef = React.useRef();
28
+  const paramsRef = React.useRef({});
29
+  const api = React.useMemo(() => {
30
+    if (typeof request == 'function') {
31
+      return queryTable(request, { paramsCallback: x => paramsRef.current = x });
32
+    }
33
+  }, [request]);
34
+
35
+  // 统一实现操作列
36
+  const newColumns = React.useMemo(
37
+    () => [
38
+      ...columns,
39
+      option ? {
40
+        title: '操作',
41
+        key: 'option',
42
+        fixed: 'right',
43
+        search: false,
44
+        width: 140,
45
+        render: (_, record) => renderActions([
46
+          onDetail && (
47
+            <Button
48
+              style={{ padding: 0 }}
49
+              type="link"
50
+              key="detail"
51
+              onClick={() => onDetail(record)}
52
+            >
53
+              详情
54
+            </Button>
55
+          ),
56
+          onEdit && (
57
+            <Button
58
+              style={{ padding: 0 }}
59
+              type="link"
60
+              key="edit"
61
+              onClick={() => onEdit(record)}
62
+            >
63
+              编辑
64
+            </Button>
65
+          ),
66
+          onDelete && (
67
+            <Popconfirm
68
+              key="delete"
69
+              title="您是否确认删除 ?"
70
+              onConfirm={() => onDelete(record, () => actionRef.current?.reload())}
71
+              okText="确定"
72
+              cancelText="取消"
73
+            >
74
+              <Button style={{ padding: 0 }} type="link" danger>
75
+                删除
76
+              </Button>
77
+            </Popconfirm>
78
+          ),
79
+          ...(columnOptionRender ? columnOptionRender(_, record) : []),
80
+        ].filter(Boolean)),
81
+      } : false,
82
+    ].filter(Boolean),
83
+    [columns, option, onEdit, onDelete]
84
+  );
85
+
86
+  React.useImperativeHandle(ref, () => actionRef.current);
87
+
88
+  return (
89
+    <ProTable
90
+      rowKey={rowKey}
91
+      columns={newColumns}
92
+      request={api}
93
+      cardBordered
94
+      actionRef={actionRef}
95
+      form={{
96
+        // syncToUrl: true,
97
+        // syncToInitialValues: false,
98
+      }}
99
+      scroll={{ x: 'max-content' }}
100
+      toolBarRender={() =>
101
+        [
102
+          onAdd && (
103
+            <Button
104
+              key="add"
105
+              type="primary"
106
+              icon={<PlusOutlined />}
107
+              onClick={onAdd}
108
+            >
109
+              新增
110
+            </Button>
111
+          ),
112
+          onExport && (
113
+            <Button
114
+              ghost
115
+              key="export"
116
+              type="primary"
117
+              onClick={() => onExport(paramsRef.current)}
118
+              icon={<UploadOutlined />}
119
+            >
120
+              导出
121
+            </Button>
122
+          ),
123
+          ...(toolBarRender ? toolBarRender() : []),
124
+        ].filter(Boolean)
125
+      }
126
+      {...leftProps}
127
+    />
128
+  );
129
+}
130
+
131
+export default React.forwardRef(XTable);

+ 19
- 0
src/pages/PreviewObj.jsx 파일 보기

@@ -0,0 +1,19 @@
1
+import React from 'react';
2
+import { useSearchParams } from 'react-router-dom';
3
+
4
+
5
+export default function PreviewObj(props) {
6
+
7
+  const [ searchParams ] = useSearchParams();
8
+  const url = decodeURIComponent(searchParams.get('url'));
9
+  const type = decodeURIComponent(searchParams.get('type'));
10
+
11
+  return (
12
+    <object
13
+      data={url}
14
+      type={type}
15
+      width="100%"
16
+      height="100%"
17
+    />
18
+  );
19
+}

+ 40
- 39
src/pages/article/Detail.jsx 파일 보기

@@ -1,54 +1,55 @@
1
-import React, { useRef, useState } from 'react'
1
+import React, { useRef, useState, useEffect } from 'react'
2 2
 import Page from '@/components/Page'
3 3
 import { Button, Input, Descriptions, Form, Image, Card, Row, Col, Space, Tag } from 'antd'
4
+import { useSearchParams } from "react-router-dom";
5
+import { getTaPostById } from "@/services/taPost";
4 6
 
5 7
 export default (props) => {
6
-
7
-  const { list } = props;
8
-
8
+  const [list, setList] = useState()
9
+  const [searchParams] = useSearchParams();
10
+  const id = searchParams.get("id");
11
+  useEffect(() => {
12
+    getTaPostById(id).then((res) => {
13
+      setList(res)
14
+    })
15
+  }, [id])
16
+  // console.log('-------list?.images------>>>>>', list?.images ? JSON.parse(list?.images) : null);
9 17
   return (
10 18
     <Page>
11 19
       <Card bordered={false}>
12 20
         <Descriptions layout="vertical" bordered>
13
-          <Descriptions.Item label="标题">AIgc</Descriptions.Item>
14
-          <Descriptions.Item label="描述" span={2}>我喜欢的智能</Descriptions.Item>
21
+          <Descriptions.Item label="标题">{list?.title}</Descriptions.Item>
22
+          <Descriptions.Item label="描述" span={2}>{list?.describe}</Descriptions.Item>
15 23
           <Descriptions.Item label="正文" span={3}>
16
-            AIGC,全称为AI-Generated Content,是指基于人工智能技术自动生成各种类型的内容,如文本、图像、声音、视频等。它利用已有数据寻找规律,并自
17
-            动生成内容,既是一种内容分类方式,也是一种内容生产方式,还是一种用于内容自动生成的一类技术集合。
18
-            AIGC涵盖了人工智能、计算机图形学和深度学习等领域技术的综合平台,其目的在于将这些技术结合起来,实现更加高效、智能化的图像识别和处理,提升
19
-            人机交互的用户体验。与百度AI文心一言和ChatGPT相比,AIGC主要侧重于图像识别与处理等技术,而百度AI文心一言则更侧重于自然语言处理。
20
-            AIGC在多个领域具有广泛的应用。在智能安防领域,它可以通过图像识别技术实现人脸识别、车辆识别等功能,提升安全监控的效率和准确性。在游戏和
21
-            虚拟现实领域,AIGC可以实现高度逼真的图像渲染和物理模拟,提升游戏体验。此外,AIGC还可以参与内容共创,推动媒体融合转型,参与制作全流程,推
22
-            进虚实交融,提供发展动能,助力产业加快升级等。
23
-            具体来说,AIGC可以生成高质量、独特的图像作品,包括绘画、插图、设计、艺术品等;可以创作音乐、歌曲、声音效果或其他音频内容,提供新颖和多样
24
-            化的音乐体验;可以生成影片、动画、短视频等,具备专业级的画面效果和剧情呈现;还可以生成3D模型、场景、动画等,为游戏开发、虚拟现实和影视制
25
-            作提供多样化的创意和设计。
26
-            然而,尽管AIGC具有强大的创造力,但仍然需要人类的指导和监督,以确保创作的质量和道德性。在创作过程中,AIGC会遵循人类设定的规则和算法,但其
27
-            结果仍然需要人类进行审查和评估,以确保内容符合社会价值观和法律法规。
28
-            总的来说,AIGC是一种强大的内容生成工具,其应用前景广阔,将在未来为人们的生活带来更多便利和乐趣。
29
-          </Descriptions.Item>
30
-          <Descriptions.Item label="标签">
31
-            <Tag color="red">ai</Tag>
32
-            <Tag color="red">智能</Tag>
33
-          </Descriptions.Item>
34
-          <Descriptions.Item label="横向封面图">
35
-            <Image src="https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.webp" width={80} />
36
-          </Descriptions.Item>
37
-          <Descriptions.Item label="纵向封面图">
38
-            <Image src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp" width={80} />
24
+            <div dangerouslySetInnerHTML={{ __html: list?.content }} />
39 25
           </Descriptions.Item>
40 26
           <Descriptions.Item label="图片">
41
-            <Image src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg" width={80} />
42
-          </Descriptions.Item>
43
-          <Descriptions.Item label="分类">
44
-            人工智能
45
-          </Descriptions.Item>
46
-          <Descriptions.Item label="链接地址">
47
-            https://www.baidu.com/
48
-          </Descriptions.Item>
49
-          <Descriptions.Item label="附件" span={3}>
50
-
27
+            {
28
+              list?.images ? JSON.parse(list?.images).map((item, index) => (
29
+                <Image key={index} src={`${prefix}` + item} width={80} />
30
+              )) : null
31
+            }
32
+          </Descriptions.Item>
33
+          <Descriptions.Item label="横向封面">
34
+            {
35
+              list?.coverHorizontal ? <Image src={`${prefix}` + list?.coverHorizontal} width={80} /> : null
36
+            }
37
+          </Descriptions.Item>
38
+          <Descriptions.Item label="纵向封面">
39
+            {
40
+              list?.coverVertical ? <Image src={`${prefix}` + list?.coverVertical} width={80} /> : null
41
+            }
51 42
           </Descriptions.Item>
43
+          <Descriptions.Item label="标签">
44
+            {
45
+              list?.tags ? JSON.parse(list?.tags).map((item, index) => (
46
+                <Tag color="red" key={index}>{item}</Tag>
47
+              )) : null
48
+            }
49
+          </Descriptions.Item>
50
+          <Descriptions.Item label="作者">{list?.author}</Descriptions.Item>
51
+          <Descriptions.Item label="链接地址">{list?.link}</Descriptions.Item>
52
+          <Descriptions.Item label="附件"></Descriptions.Item>
52 53
         </Descriptions>
53 54
       </Card>
54 55
     </Page>

+ 70
- 36
src/pages/article/Edit.jsx 파일 보기

@@ -1,49 +1,83 @@
1 1
 import React, { useRef, useState } from 'react'
2
-import { Button, Input, Form } from 'antd'
2
+import { Button, Input, Form, Select } from 'antd'
3 3
 import Edit from '@/components/Page/Edit'
4 4
 import Wangeditor from '@/components/Wangeditor'
5
-import UploadFile from '@/components/Upload/UploadFile'
5
+import UploadFiles from '@/components/Upload/UploadFiles'
6 6
 import UploadImages from '@/components/Upload/UploadImages'
7 7
 import UploadImageList from '@/components/Upload/UploadImageList'
8
+import { previewFile, downladFile } from '@/utils/attach';
9
+import { getTaPostById, postTaPost, putTaPost } from "@/services/taPost";
10
+
11
+const request = {
12
+  get: getTaPostById,
13
+  save: postTaPost,
14
+  update: putTaPost,
15
+}
8 16
 
9 17
 export default (props) => {
10 18
 
11
-  const onFinish = () => { }
19
+  const transform = (_, item) => {
20
+    let images = item?.images ? JSON.stringify(item?.images) : null
21
+    let taAttachList = item?.taAttachList ? item?.taAttachList : []
22
+    let tags = item?.tags ? JSON.stringify(item?.tags) : null
23
+    let data = { ...item, images: images, taAttachList: taAttachList, tags: tags }
24
+    return data
25
+  }
26
+
27
+  const convertValue = (formData) => {
28
+    let tags = formData?.tags ? JSON.parse(formData?.tags) : null
29
+    let images = formData?.images ? JSON.parse(formData?.images) : null
30
+    return { ...formData, tags: tags, images: images }
31
+  }
12 32
 
13 33
   return (
14 34
     <Edit
15
-      onFinish={onFinish}
16
-    >
17
-      <Form.Item label="文章标题" required>
18
-        <Input placeholder="请输入" />
19
-      </Form.Item>
20
-      <Form.Item label="文章描述">
21
-        <Input placeholder="请输入" />
22
-      </Form.Item>
23
-      <Form.Item label="文章正文">
24
-        <Wangeditor />
25
-      </Form.Item>
26
-      <Form.Item label="文章标签">
27
-        <Input placeholder="请输入" />
28
-      </Form.Item>
29
-      <Form.Item label="横向封面图">
30
-        <UploadImages ratio={0.5} />
31
-      </Form.Item>
32
-      <Form.Item label="纵向封面图">
33
-        <UploadImages ratio={'100:200'} />
34
-      </Form.Item>
35
-      <Form.Item label="文章图片">
36
-        <UploadImageList />
37
-      </Form.Item>
38
-      <Form.Item label="文章分类">
39
-        <Input placeholder="请输入" />
40
-      </Form.Item>
41
-      <Form.Item label="链接地址">
42
-        <Input placeholder="请输入" />
43
-      </Form.Item>
44
-      <Form.Item label="文章附件">
45
-        <UploadFile />
46
-      </Form.Item>
47
-    </Edit>
35
+      rowKey="postId"
36
+      request={request}
37
+      transform={transform}
38
+      convertValue={convertValue}
39
+      // onValuesChange={console.log}
40
+      renderItems={() => (
41
+        <>
42
+          <Form.Item label="文章标题" required name="title">
43
+            <Input placeholder="请输入" />
44
+          </Form.Item>
45
+          <Form.Item label="文章描述" name="describe">
46
+            <Input placeholder="请输入" />
47
+          </Form.Item>
48
+          <Form.Item label="文章正文" name="content">
49
+            <Wangeditor />
50
+          </Form.Item>
51
+          <Form.Item label="文章图片" name="images">
52
+            <UploadImageList />
53
+          </Form.Item>
54
+          <Form.Item label="横向封面" name="coverHorizontal">
55
+            <UploadImages ratio={0.5} />
56
+          </Form.Item>
57
+          <Form.Item label="纵向封面" name="coverVertical">
58
+            <UploadImages ratio={'100:200'} />
59
+          </Form.Item>
60
+
61
+          <Form.Item label="文章标签" name="tags">
62
+            <Select mode="tags" tokenSeparators={[',']} placeholder="请输入" />
63
+          </Form.Item>
64
+          <Form.Item label="文章作者" name="author">
65
+            <Input placeholder="请输入" />
66
+          </Form.Item>
67
+          <Form.Item label="链接地址" name="link">
68
+            <Input placeholder="请输入" />
69
+          </Form.Item>
70
+          <Form.Item label="上传附件" name="taAttachList">
71
+            <UploadFiles
72
+              rowKey={x => x.attachUrl}
73
+              rowLabel={x => x.attachName}
74
+              transform={x => ({ attachUrl: x.url, attachName: x.name, fileType: x.fileType })}
75
+              onDownload={x => downladFile(x)}
76
+              onPreview={x => previewFile(x)}
77
+            />
78
+          </Form.Item>
79
+        </>
80
+      )}
81
+    />
48 82
   )
49 83
 }

+ 52
- 58
src/pages/article/index.jsx 파일 보기

@@ -2,8 +2,10 @@ import React, { useRef, useState } from 'react'
2 2
 import List from '@/components/Page/List'
3 3
 import { Button, Tag, Image } from 'antd'
4 4
 import { useNavigate } from 'react-router-dom'
5
+import { getTaPost, deleteTaPost } from "@/services/taPost";
5 6
 
6 7
 export default (props) => {
8
+  const actionRef = useRef();
7 9
   const navigate = useNavigate();
8 10
 
9 11
   const columns = [
@@ -21,77 +23,80 @@ export default (props) => {
21 23
     {
22 24
       title: '正文',
23 25
       dataIndex: 'content',
24
-      ellipsis: { showTitle: false },
26
+      render: (_, it) =>
27
+        <div style={{ width: '9vw' }}>
28
+          <div dangerouslySetInnerHTML={{ __html: it?.content }} style={{ textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }} />
29
+        </div>,
25 30
       search: false,
26 31
     },
27 32
     {
28
-      title: '标签',
29
-      dataIndex: 'tag',
30
-      search: false,
31
-      render: (_, it) => <Tag color="red">{it?.tag}</Tag>
32
-    },
33
-    {
34
-      title: '横向封面图',
35
-      dataIndex: 'HImg',
36
-      render: (_, it) => <Image src={it?.HImg} width={80} />,
37
-      search: false,
38
-    },
39
-    {
40
-      title: '纵向封面图',
41
-      dataIndex: 'VImg',
42
-      render: (_, it) => <Image src={it?.VImg} width={80} />,
33
+      title: '图片',
34
+      dataIndex: 'images',
35
+      render: (_, it) => it?.images ? <Image src={`${prefix}` + JSON.parse(it?.images)[0]} width={80} /> : null,
43 36
       search: false,
44 37
     },
38
+    // {
39
+    //   title: '横向封面',
40
+    //   dataIndex: 'coverHorizontal',
41
+    //   render: (_, it) => it?.coverHorizontal ? <Image src={`${prefix}` + it?.coverHorizontal} width={80} /> : null,
42
+    //   search: false,
43
+    // },
44
+    // {
45
+    //   title: '纵向封面',
46
+    //   dataIndex: 'coverVertical',
47
+    //   render: (_, it) => it?.coverVertical ? <Image src={`${prefix}` + it?.coverVertical} width={80} /> : null,
48
+    //   search: false,
49
+    // },
45 50
     {
46
-      title: '图片',
47
-      dataIndex: 'img',
48
-      render: (_, it) => <Image src={it?.img} width={80} />,
51
+      title: '标签',
52
+      dataIndex: 'tags',
49 53
       search: false,
54
+      render: (_, it) => it?.tags ? JSON.parse(it?.tags).map(it => <Tag color="red">{it}</Tag>) : null
50 55
     },
51 56
     {
52
-      title: '分类',
53
-      dataIndex: 'classify'
57
+      title: '作者',
58
+      dataIndex: 'author'
54 59
     },
55 60
     {
56
-      title: '链接地址',
61
+      title: '链接',
57 62
       dataIndex: 'link',
58 63
       ellipsis: true,
59
-      render: (_, it) => <a href="#">{it?.link}</a>,
64
+      render: (_, it) =>
65
+        <div style={{ width: '8vw' }}>
66
+          <div style={{ textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}><a href="#">{it?.link}</a></div>
67
+        </div>,
60 68
       search: false,
61 69
     },
62
-    {
63
-      title: '附件',
64
-      dataIndex: 'attachment',
65
-      search: false,
66
-    }
70
+    // {
71
+    //   title: '附件',
72
+    //   dataIndex: 'attachment',
73
+    //   search: false,
74
+    // }
67 75
   ]
68 76
 
69
-  const dataSource = [
70
-    {
71
-      id: 1,
72
-      title: 'AIgc',
73
-      describe: '我喜欢的智能',
74
-      content: 'AIGC,全称为AI-Generated Content,是指基于人工智能技术自动生成各种类型的内容,如文本、图像、声音、视频等。它利用已有数据寻找规律,并自动生成内容,既是一种内容分类方式,也是一种内容生产方式,还是一种用于内容自动生成的一类技术集合。AIGC涵盖了人工智能、计算机图形学和深度学习等领域技术的综合平台,其目的在于将这些技术结合起来,实现更加高效、智能化的图像识别和处理,提升人机交互的用户体验。与百度AI文心一言和ChatGPT相比,AIGC主要侧重于图像识别与处理等技术,而百度AI文心一言则更侧重于自然语言处理。AIGC在多个领域具有广泛的应用。在智能安防领域,它可以通过图像识别技术实现人脸识别、车辆识别等功能,提升安全监控的效率和准确性。在游戏和虚拟现实领域,AIGC可以实现高度逼真的图像渲染和物理模拟,提升游戏体验。此外,AIGC还可以参与内容共创,推动媒体融合转型,参与制作全流程,推进虚实交融,提供发展动能,助力产业加快升级等。具体来说,AIGC可以生成高质量、独特的图像作品,包括绘画、插图、设计、艺术品等;可以创作音乐、歌曲、声音效果或其他音频内容,提供新颖和多样化的音乐体验;可以生成影片、动画、短视频等,具备专业级的画面效果和剧情呈现;还可以生成3D模型、场景、动画等,为游戏开发、虚拟现实和影视制作提供多样化的创意和设计。然而,尽管AIGC具有强大的创造力,但仍然需要人类的指导和监督,以确保创作的质量和道德性。在创作过程中,AIGC会遵循人类设定的规则和算法,但其结果仍然需要人类进行审查和评估,以确保内容符合社会价值观和法律法规。总的来说,AIGC是一种强大的内容生成工具,其应用前景广阔,将在未来为人们的生活带来更多便利和乐趣。',
75
-      tag: 'ai',
76
-      HImg: 'https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.webp',
77
-      VImg: 'https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp',
78
-      img: 'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
79
-      classify: '人工智能',
80
-      link: 'https://www.baidu.com/',
81
-    }
82
-  ]
77
+  const onEdit = (it) => {
78
+    navigate(`/article/edit?id=${it.postId}`)
79
+  }
80
+
81
+  const onDelete = (it) => {
82
+    deleteTaPost(it.postId).then(res => {
83
+      actionRef.current.reload();
84
+    })
85
+  }
83 86
 
84
-  const onEdit = (_, it) => {
85
-    navigate(`/article/edit?id=${'it.id'}`)
87
+  const onDetail = (it) => {
88
+    navigate(`/article/detail?id=${it.postId}`)
86 89
   }
87 90
 
88 91
   return (
89 92
     <List
90
-      rowKey="id"
93
+      rowKey="postId"
91 94
       columns={columns}
92
-      dataSource={dataSource}
95
+      actionRef={actionRef}
93 96
       onEdit={onEdit}
94
-      request={{}}
97
+      onDelete={onDelete}
98
+      // onDetail={onDetail}
99
+      request={getTaPost}
95 100
       toolBarRender={() => [
96 101
         <Button
97 102
           key="2"
@@ -103,17 +108,6 @@ export default (props) => {
103 108
           新增
104 109
         </Button>
105 110
       ]}
106
-      columnOptionRender={() => [
107
-        <Button
108
-          key="3"
109
-          type="link"
110
-          onClick={() => {
111
-            navigate('/article/detail')
112
-          }}
113
-        >
114
-          详情
115
-        </Button>,
116
-      ]}
117 111
     />
118 112
   )
119 113
 }

+ 91
- 0
src/pages/classify/components/Form.jsx 파일 보기

@@ -0,0 +1,91 @@
1
+import React from "react";
2
+import { Button, Card, Form, Input, InputNumber, Select } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import { postTdCategory, putTdCategory } from "@/services/tdCategory";
5
+import { formItemLayout, tailFormItemLayout } from "@/utils/form";
6
+
7
+export default (props) => {
8
+  const { category, list, parentId, onChange } = props;
9
+
10
+  const [submiting, startSubmit, cancelSubmit] = useBool();
11
+  const [form] = Form.useForm();
12
+
13
+  const onFinish = (values) => {
14
+    startSubmit();
15
+
16
+    if (category?.categoryId) {
17
+      // 修改
18
+      putTdCategory(category.categoryId, values)
19
+        .then((res) => {
20
+          cancelSubmit();
21
+          onChange(res);
22
+        })
23
+        .catch(() => {
24
+          cancelSubmit();
25
+        });
26
+    } else {
27
+      // 新增
28
+      postTdCategory(values)
29
+        .then((res) => {
30
+          cancelSubmit();
31
+          onChange(res);
32
+        })
33
+        .catch(() => {
34
+          cancelSubmit();
35
+        });
36
+    }
37
+  };
38
+
39
+  React.useEffect(() => {
40
+
41
+    form.resetFields();
42
+    if (category) {
43
+      form.setFieldsValue(category);
44
+    } else {
45
+      form.setFieldValue("parentId", parentId);
46
+    }
47
+
48
+  }, [category, parentId]);
49
+
50
+  return (
51
+    <Form
52
+      form={form}
53
+      {...formItemLayout}
54
+      style={{ maxWidth: "800px" }}
55
+      onFinish={onFinish}
56
+    >
57
+      <Form.Item
58
+        name="sortNo"
59
+        label="序号"
60
+      >
61
+        <Input />
62
+      </Form.Item>
63
+      <Form.Item
64
+        name="categoryName"
65
+        label="分类"
66
+        rules={[{ required: true, message: "请填写分类名称" }]}
67
+      >
68
+        <Input />
69
+      </Form.Item>
70
+      <Form.Item name="parentId" label="上级分类">
71
+        <Select disabled={true}>
72
+          {(list || []).map((x) => (
73
+            <Select.Option key={x.categoryId}>{x.categoryName}</Select.Option>
74
+          ))}
75
+        </Select>
76
+      </Form.Item>
77
+
78
+      <Form.Item name="status" label="状态">
79
+        <Select style={{ width: "100%" }} placeholder="请选择状态">
80
+          <Select.Option value={0}>不正常</Select.Option>
81
+          <Select.Option value={1}>正常</Select.Option>
82
+        </Select>
83
+      </Form.Item>
84
+      <Form.Item {...tailFormItemLayout}>
85
+        <Button loading={submiting} type="primary" htmlType="submit">
86
+          保存
87
+        </Button>
88
+      </Form.Item>
89
+    </Form>
90
+  );
91
+};

+ 159
- 0
src/pages/classify/index.jsx 파일 보기

@@ -0,0 +1,159 @@
1
+import React from "react";
2
+import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
3
+import { Button, Card, Row, Col, Tree, Tooltip, Popconfirm, Spin } from "antd";
4
+import Page from "@/components/Page";
5
+import { getTdCategory, deleteTdCategory } from "@/services/tdCategory";
6
+import { arr2Tree } from "@/utils/array";
7
+import useBool from "@/utils/hooks/useBool";
8
+import Form from "./components/Form";
9
+import { useModel } from "@/store";
10
+
11
+export default (props) => {
12
+  const { user } = useModel("user");
13
+
14
+  const [loading, startLoading, stopLoading] = useBool();
15
+  const [list, setList] = React.useState([]);
16
+  const [current, setCurrernt] = React.useState();
17
+  const [parentId, setParentId] = React.useState();
18
+
19
+  const [parentList, treeData] = React.useMemo(() => {
20
+    const plist = [{ categoryId: "-1", name: "根节点" }].concat(list);
21
+    const [tree] = arr2Tree(
22
+      (list || []).map((x) => ({
23
+        title: x.categoryName,
24
+        key: x.categoryId,
25
+        parentId: x.parentId,
26
+        raw: x,
27
+      }))
28
+    );
29
+
30
+    return [plist, tree];
31
+  }, [list]);
32
+
33
+  const changeCurrent = (category) => {
34
+    setCurrernt(category);
35
+    setParentId(category?.categoryPId || "-1");
36
+  };
37
+
38
+  const onSelect = (selectedKeys, e) => {
39
+    changeCurrent(e.node.raw);
40
+  };
41
+
42
+  const onClick = (category) => {
43
+    changeCurrent(category);
44
+  };
45
+
46
+  const onAdd = (parent = "-1") => {
47
+    setParentId(parent);
48
+    setCurrernt();
49
+  };
50
+
51
+  const onDelete = (category) => {
52
+    deleteTdCategory(category.categoryId).then(() => {
53
+      queryList();
54
+    });
55
+  };
56
+
57
+  const queryList = React.useCallback(() => {
58
+    startLoading();
59
+    getTdCategory({ pageSize: 500 }).then((res) => {
60
+      setList(res.records || []);
61
+      stopLoading();
62
+    });
63
+  }, []);
64
+
65
+  const onFormChange = () => {
66
+    // 重新查一次数据
67
+    queryList();
68
+  };
69
+
70
+  React.useEffect(() => {
71
+    queryList();
72
+  }, []);
73
+
74
+  return (
75
+    <Page>
76
+      <Row gutter={24}>
77
+        <Col span={8} style={{ overflow: "hidden" }}>
78
+          <Card
79
+            title="分类"
80
+            onSelect={onSelect}
81
+            extra={
82
+              // user.orgId === "1" && (
83
+              <Button type="link" onClick={() => onAdd()}>
84
+                新增
85
+              </Button>
86
+              // )
87
+            }
88
+          >
89
+            {/* <Spin spinning={loading}> */}
90
+            <Tree
91
+              blockNode
92
+              defaultExpandParent={true}
93
+              treeData={treeData}
94
+              titleRender={(node) => (
95
+                <div
96
+                  style={{ display: "flex" }}
97
+                  onClick={(e) => e.stopPropagation()}
98
+                >
99
+                  <div
100
+                    style={{
101
+                      lineHeight: "32px",
102
+                      flex: 1,
103
+                      width: 0,
104
+                    }}
105
+                    onClick={() => onClick(node.raw)}
106
+                  >
107
+                    <div
108
+                      style={{
109
+                        overflow: "hidden",
110
+                        whiteSpace: "nowrap",
111
+                        textOverflow: "ellipsis",
112
+                      }}
113
+                    >
114
+                      {node.title}
115
+                    </div>
116
+                  </div>
117
+                  <div style={{ width: "80px", flex: "none" }}>
118
+                    <Tooltip title="新增子节点">
119
+                      <Button
120
+                        type="link"
121
+                        icon={<PlusOutlined />}
122
+                        onClick={() => onAdd(node.raw.categoryId)}
123
+                      ></Button>
124
+                    </Tooltip>
125
+                    <Tooltip title="删除节点">
126
+                      <Popconfirm
127
+                        title="确认进行删除操作?"
128
+                        onConfirm={() => onDelete(node.raw)}
129
+                      >
130
+                        <Button
131
+                          type="link"
132
+                          danger
133
+                          icon={<DeleteOutlined />}
134
+                        ></Button>
135
+                      </Popconfirm>
136
+                    </Tooltip>
137
+                  </div>
138
+                </div>
139
+              )}
140
+            />
141
+            {/* </Spin> */}
142
+          </Card>
143
+        </Col>
144
+        <Col span={16}>
145
+          {parentId && (
146
+            <Card>
147
+              <Form
148
+                category={current}
149
+                parentId={parentId}
150
+                list={parentList}
151
+                onChange={onFormChange}
152
+              />
153
+            </Card>
154
+          )}
155
+        </Col>
156
+      </Row>
157
+    </Page>
158
+  );
159
+};

+ 0
- 1
src/pages/login/LoginForm.jsx 파일 보기

@@ -21,7 +21,6 @@ export default (props) => {
21 21
         setLoading(false);
22 22
       })
23 23
       .catch((err) => {
24
-        // console.log('----err--', err);
25 24
         setLoading(false);
26 25
       });
27 26
 

+ 21
- 7
src/routes/routes.jsx 파일 보기

@@ -2,8 +2,8 @@ import {
2 2
   UserOutlined,
3 3
   SolutionOutlined,
4 4
   FileTextOutlined,
5
-  HeartOutlined,
6
-  HourglassTwoTone,
5
+  ContainerOutlined,
6
+  EllipsisOutlined,
7 7
   StopFilled,
8 8
 } from '@ant-design/icons'
9 9
 import { Outlet, Navigate } from 'react-router-dom'
@@ -13,6 +13,8 @@ import Page404 from '@/pages/404'
13 13
 import Article from '@/pages/article'
14 14
 import ArticleEdit from '@/pages/article/Edit'
15 15
 import ArticleDetail from '@/pages/article/detail'
16
+import Classify from '@/pages/classify'
17
+import PreviewObj from "@/pages/PreviewObj";
16 18
 
17 19
 // import RoleEdit from "@/pages/role/Edit";
18 20
 /**
@@ -38,7 +40,7 @@ export const authRoutes = [
38 40
     element: <Article />,
39 41
     meta: {
40 42
       title: '文章管理',
41
-      icon: <UserOutlined />,
43
+      icon: <ContainerOutlined />,
42 44
     },
43 45
   },
44 46
   {
@@ -49,12 +51,20 @@ export const authRoutes = [
49 51
       hideInMenu: true
50 52
     },
51 53
   },
54
+  // {
55
+  //   path: 'article/detail',
56
+  //   element: <ArticleDetail />,
57
+  //   meta: {
58
+  //     title: '文章详情',
59
+  //     hideInMenu: true
60
+  //   },
61
+  // },
52 62
   {
53
-    path: 'article/detail',
54
-    element: <ArticleDetail />,
63
+    path: 'classify',
64
+    element: <Classify />,
55 65
     meta: {
56
-      title: '文章详情',
57
-      hideInMenu: true
66
+      title: '分类管理',
67
+      icon: <EllipsisOutlined />,
58 68
     },
59 69
   }
60 70
 ]
@@ -70,6 +80,10 @@ export const defaultRoutes = [
70 80
       },
71 81
     ],
72 82
   },
83
+  {
84
+    path: "/previewobj",
85
+    element: <PreviewObj />,
86
+  },
73 87
   {
74 88
     path: '/login',
75 89
     element: <Login />,

+ 1
- 1
src/services/login.js 파일 보기

@@ -33,4 +33,4 @@ export const postQrCode = (id, params) =>
33 33
 /**
34 34
 * 获取当前用户信息
35 35
 */
36
-export const currentUser = (params) => request("/api/admin/current", { params })
36
+export const currentUser = (params) => request("/api/sysUser/current", { params })

+ 26
- 0
src/services/taPost.js 파일 보기

@@ -0,0 +1,26 @@
1
+import request from "@/utils/request";
2
+
3
+/*
4
+ * 分页查询
5
+ */
6
+export const getTaPost = (params) => request('/api/taPost', { params });
7
+
8
+/*
9
+ * 新增数据
10
+ */
11
+export const postTaPost = (data) => request('/api/taPost', { data, method: 'post' });
12
+
13
+/*
14
+ * 通过ID查询单条数据
15
+ */
16
+export const getTaPostById = (id) => request(`/api/taPost/${id}`);
17
+
18
+/*
19
+ * 更新数据
20
+ */
21
+export const putTaPost = (id, data) => request(`/api/taPost/${id}`, { data, method: 'put' });
22
+
23
+/*
24
+ * 通过主键删除数据
25
+ */
26
+export const deleteTaPost = (id) => request(`/api/taPost/${id}`, { method: 'delete' });

+ 26
- 0
src/services/tdCategory.js 파일 보기

@@ -0,0 +1,26 @@
1
+import request from "@/utils/request";
2
+
3
+/*
4
+ * 分页查询
5
+ */
6
+export const getTdCategory = (params) => request('/api/tdCategory', { params });
7
+
8
+/*
9
+ * 新增数据
10
+ */
11
+export const postTdCategory = (data) => request('/api/tdCategory', { data, method: 'post' });
12
+
13
+/*
14
+ * 通过ID查询单条数据
15
+ */
16
+export const getTdCategoryById = (id) => request(`/api/tdCategory/${id}`);
17
+
18
+/*
19
+ * 更新数据
20
+ */
21
+export const putTdCategory = (id, data) => request(`/api/tdCategory/${id}`, { data, method: 'put' });
22
+
23
+/*
24
+ * 通过主键删除数据
25
+ */
26
+export const deleteTdCategory = (id) => request(`/api/tdCategory/${id}`, { method: 'delete' });

+ 1
- 1
src/store/index.js 파일 보기

@@ -6,7 +6,7 @@ import usePolicy from "./models/useOssPolicy";
6 6
 const store = createStore({
7 7
   system: useSystem,
8 8
   user: useUser,
9
-  policy: usePolicy,
9
+  // policy: usePolicy,
10 10
 });
11 11
 
12 12
 export default store

+ 20
- 24
src/store/models/user.js 파일 보기

@@ -5,38 +5,34 @@ import { currentUser } from "@/services/login";
5 5
 import { getAuthedRoutes } from "@/routes/permissions";
6 6
 
7 7
 export default function useUser () {
8
-  const [user, setUser] = useState({ userId: 1, userName: "admin" });
9
-  const [login, setLogin] = useState(false);
8
+  const [user, setUser] = useState({ name: 'admin', userId: 123 });
9
+  const [isLogin, setIsLogin] = useState(true);
10 10
   const menusRef = useRef();
11 11
   const routesRef = useRef();
12 12
   const dbMenusRef = useRef([]);
13 13
 
14 14
   const getCurrentUser = (params) =>
15 15
     new Promise((resolve, reject) => {
16
-      // currentUser(params)
17
-      //   .then((res) => {
18
-      // dbMenusRef.current = res?.menuList || [];
19
-      // const permissions = dbMenusRef.current.map((x) => x.menuCode);
16
+      // { name: 'admin', userId: 123 }
17
+      currentUser(params)
18
+        .then((res) => {
19
+          dbMenusRef.current = res?.menuList || [];
20
+          // const permissions = dbMenusRef.current.map((x) => x.menuCode);
20 21
 
21
-      // // authRoutes 是所有待验证授权的路由
22
-      // // authedRoutes 是已经被授权的路由
23
-      // const authedRoutes = getAuthedRoutes(authRoutes, permissions || []);
22
+          // authRoutes 是所有待验证授权的路由
23
+          // authedRoutes 是已经被授权的路由
24
+          const authedRoutes = getAuthedRoutes(authRoutes);
24 25
 
25
-      // menusRef.current = getMenuItems(authedRoutes);
26
-      // routesRef.current = mergeAuthRoutes(defaultRoutes, authedRoutes);
27
-
28
-      //   setUser(res);
29
-      //   resolve();
30
-      // })
31
-      // .catch(reject);
26
+          menusRef.current = getMenuItems(authedRoutes);
27
+          routesRef.current = mergeAuthRoutes(defaultRoutes, authedRoutes);
28
+          setUser(res);
29
+          setIsLogin(true)
30
+          resolve();
31
+        })
32
+        .catch(reject);
32 33
     });
33
-
34
-  // authRoutes 是所有待验证授权的路由
35
-  // authedRoutes 是已经被授权的路由
36
-  const authedRoutes = getAuthedRoutes(authRoutes);
37
-  menusRef.current = getMenuItems(authedRoutes);
38
-  routesRef.current = mergeAuthRoutes(defaultRoutes, authedRoutes);
39
-
34
+  menusRef.current = getMenuItems(authRoutes);
35
+  console.log('-------->isLogin', isLogin);
40 36
   return {
41 37
     user,
42 38
     setUser,
@@ -44,6 +40,6 @@ export default function useUser () {
44 40
     menus: menusRef.current || [],
45 41
     routes: routesRef.current || [],
46 42
     dbMenus: dbMenusRef.current,
47
-    login,
43
+    isLogin,
48 44
   };
49 45
 }

+ 17
- 0
src/utils/attach.js 파일 보기

@@ -0,0 +1,17 @@
1
+
2
+export const downladFile = (attach) => {
3
+  const ele = document.createElement('a');
4
+  ele.href = getRealPath(attach.attachUrl);
5
+  ele.download = attach.attachName;
6
+  ele.target = '_blank';
7
+  ele.click();
8
+}
9
+
10
+export const previewFile = (attach) => {
11
+  const { pathname, hash } = window.location;
12
+  const ele = document.createElement('a');
13
+  const searchParams = '?url=' + encodeURIComponent(getRealPath(attach.attachUrl)) + '&type=' + encodeURIComponent(attach.fileType);
14
+  ele.href = pathname + '#/previewobj' + searchParams;
15
+  ele.target = '_blank';
16
+  ele.click();
17
+}

+ 1
- 1
vite.config.js 파일 보기

@@ -11,7 +11,7 @@ export default defineConfig({
11 11
     host: "0.0.0.0",
12 12
     proxy: {
13 13
       "/api": {
14
-        target: "http://192.168.89.25:7009",
14
+        target: "http://192.168.89.13:8080",
15 15
         changeOrigin: true,
16 16
       },
17 17
     },