Browse Source

feat: dict data management

hi-cactus! 3 years ago
parent
commit
192d3329e1

+ 27 - 26
app/containers/App/index.tsx

@@ -53,7 +53,6 @@ export class App extends React.PureComponent<AppProps> {
   constructor(props: AppProps) {
     super(props)
     this.checkTokenLink()
-    props.onGetServerConfigurations()
   }
 
   private getQs = () => {
@@ -72,7 +71,7 @@ export class App extends React.PureComponent<AppProps> {
     }
   }
 
-  private checkTokenLink = () => {
+  private checkTokenLink = async() => {
     const {
       history,
       onGetLoginUser
@@ -84,42 +83,44 @@ export class App extends React.PureComponent<AppProps> {
     // const dashboard = qs['dashboard']
 
     // @FIXME login with token from url query
-    // if (token) {
-    //   setToken(token)
-    //   // onGetLoginUser(() => {
-    //     history.replace('/projects')
-    //     // if (dashboard) {
-    //     //   router.replace(`/project/${this.props.params.projectId}/dashboard/${dashboard}`)
-    //     // } else {
-
-    //     // }
-    //   // })
-    // } else {
-    this.checkNormalLogin()
-    // }
+
+    const logged = await this.checkNormalLogin()
+    if (!logged) {
+      window.location.href = 'http://taihu.xt.wenhq.top:8083'
+      return
+    }
+    this.props.onGetServerConfigurations()
   }
 
-  private checkNormalLogin = () => {
+  private checkNormalLogin = async() => {
     if (checkLogin()) {
       const token = localStorage.getItem('TOKEN')
       const loginUser = localStorage.getItem('loginUser')
       setToken(token)
       this.props.onLogged(JSON.parse(loginUser))
       statistic.sendPrevDurationRecord()
+      return true
     } else {
       const qs = this.getQs()
       const ticket = qs['ticket']
       if (ticket) {
-        request(api.getUserInfo + `?ticket=${ticket}`, { method: 'get' })
-          .then((data) => {
-            if (data?.code === 200 && data?.data) {
-              setToken(data.data?.token)
-              const loginUser = data.data.userInfo
-              this.props.onLogged(JSON.parse(loginUser))
-              localStorage.setItem('loginUser', loginUser)
-              statistic.sendPrevDurationRecord()
-            }
-          })
+        try {
+          await request(api.getUserInfo + `?ticket=${ticket}`, { method: 'get' })
+            .then((data) => {
+              if (data?.code === 200 && data?.data) {
+                setToken(data.data?.token)
+                const loginUser = data.data.userInfo
+                this.props.onLogged(JSON.parse(loginUser))
+                localStorage.setItem('loginUser', loginUser)
+                statistic.sendPrevDurationRecord()
+              }
+            })
+          return true
+        } catch (e) {
+          console.log(e)
+          return false
+        }
+
       } else {
         console.log('onLogout')
         this.props.onLogout()

+ 2 - 1
app/containers/App/sagas.ts

@@ -108,10 +108,11 @@ export function* getServerConfigurations(action) {
       }
     )
     const configurations = result.payload
-    setTokenExpired(configurations.jwtToken.timeout)
+    // setTokenExpired(configurations.jwtToken.timeout)
     yield put(serverConfigurationsGetted(configurations))
   } catch (err) {
     console.log(err)
+    window.location.href = 'http://taihu.xt.wenhq.top:8083'
     yield put(getServerConfigurationsFail(err))
     errorHandler(err)
   }

+ 136 - 0
app/containers/DataManagerDictionary/components/DictDatasModal.tsx

@@ -0,0 +1,136 @@
+/*
+ * <<
+ * Davinci
+ * ==
+ * Copyright (C) 2016 - 2017 EDP
+ * ==
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * >>
+ */
+
+import React, { useEffect, useState } from 'react'
+import { Modal, Form, Button, Input, Col, Table, Row, Divider } from 'antd'
+import { FormComponentProps } from 'antd/lib/form'
+import { ICatalogue, IDictData, IViewBase } from '../types'
+import { ColumnProps } from 'antd/lib/table'
+import ButtonGroup from 'antd/es/button/button-group'
+import api from 'utils/api'
+import request from 'utils/request'
+
+interface IDictDatasModalProps {
+  visible: boolean
+  dictType: string
+  onCancel: () => void
+}
+
+const DictDatasModal = (props: IDictDatasModalProps) => {
+
+  const { visible, onCancel, dictType } = props
+
+  const [tableLoading, setTableLoading] = useState(false)
+  const [dictDatas, setDictDatas] = useState<IDictData[]>([])
+
+  const queryDictDatas = async() => {
+    if (!dictType) {
+      return
+    }
+    try {
+      setTableLoading(true)
+      const data = await request(api.dictDatas + `?dictType=${dictType}`, { method: 'GET' })
+      setDictDatas(data?.payload ?? [])
+    } finally {
+      setTableLoading(false)
+    }
+  }
+
+
+  const tableColumns: Array<ColumnProps<IDictData>> = [
+    {
+      title: '字典编码',
+      dataIndex: 'dictCode'
+    },
+    {
+      title: '字典标签',
+      dataIndex: 'dictLabel'
+    },
+    {
+      title: '字典键值',
+      dataIndex: 'dictValue'
+    },
+    {
+      title: '字典排序',
+      dataIndex: 'dictSort'
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (text) => (
+        <>
+          {text === 0 && <span style={{ padding: '2px 4px', background: 'green', color: '#fff' }}>正常</span>}
+          {text === 1 && <span style={{ padding: '2px 4px', background: 'red', color: '#fff' }}>停用</span>}
+        </>
+      )
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark'
+    },
+    {
+      title: '创建时间'
+      // dataIndex: 'isDefault'
+    },
+    {
+      title: '操作',
+      render: () => {
+        return (
+          <>
+            <a>编辑</a>
+            <Divider type='vertical' />
+            <a>删除</a>
+          </>
+        )
+      }
+    }
+  ]
+  useEffect(() => {
+    queryDictDatas()
+  }, [dictType])
+
+  return (
+    <Modal
+      title='数据字典数据'
+      // wrapClassName='ant-modal-small'
+      visible={visible}
+      footer={null}
+      onCancel={onCancel}
+      destroyOnClose
+      width={'80%'}
+    >
+      <Row>
+        <Col span={24}>
+          <Table
+            bordered
+            rowKey='dictCode'
+            loading={tableLoading}
+            dataSource={dictDatas}
+            columns={tableColumns}
+            pagination={false}
+            // onChange={this.tableChange}
+          />
+        </Col>
+      </Row>
+    </Modal>
+  )
+}
+
+export default (DictDatasModal)

+ 123 - 0
app/containers/DataManagerDictionary/components/DictTypeFormModal.tsx

@@ -0,0 +1,123 @@
+/*
+ * <<
+ * Davinci
+ * ==
+ * Copyright (C) 2016 - 2017 EDP
+ * ==
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * >>
+ */
+
+import React from 'react'
+import { Modal, Form, Button, Input, Switch } from 'antd'
+import { FormComponentProps } from 'antd/lib/form'
+import { IDictType } from '../types'
+
+const FormItem = Form.Item
+
+interface IDictTypeFormModalProps extends FormComponentProps<IDictType> {
+  visible: boolean,
+  loading: boolean,
+  fromView: IDictType,
+  onSave: (view: IDictType) => void,
+  onCancel: () => void
+}
+
+export class DictTypeFormModal extends React.PureComponent<IDictTypeFormModalProps> {
+  private formItemStyle = {
+    labelCol: { span: 6 },
+    wrapperCol: { span: 18 }
+  }
+
+  private save = () => {
+    const { form, fromView, onSave } = this.props
+    form.validateFieldsAndScroll((err, fieldsValue) => {
+      if (err) {
+        return
+      }
+      const copyView: IDictType = { ...fieldsValue }
+      onSave(copyView)
+    })
+  }
+
+  private clearFieldsValue = () => {
+    this.props.form.resetFields()
+  }
+
+  public render() {
+    const { form, visible, loading, fromView, onCancel } = this.props
+    const { getFieldDecorator } = form
+
+    const modalButtons = [(
+      <Button
+        key='back'
+        size='large'
+        onClick={onCancel}
+      >
+        取 消
+      </Button>
+    ), (
+      <Button
+        disabled={loading}
+        key='submit'
+        size='large'
+        type='primary'
+        onClick={this.save}
+      >
+        保 存
+      </Button>
+    )]
+
+    return (
+      <Modal
+        title={fromView ? '编辑' : '新增'}
+        visible={visible}
+        footer={modalButtons}
+        onCancel={onCancel}
+        afterClose={this.clearFieldsValue}
+        destroyOnClose
+      >
+        <Form>
+          <FormItem label='字典名称' {...this.formItemStyle}>
+            {getFieldDecorator<IDictType>('dictName', {
+              validateFirst: true,
+              rules: [
+                { required: true, message: '不能为空' }
+              ],
+              initialValue: fromView?.dictName
+            })(<Input />)}
+          </FormItem>
+          <FormItem label='字典类型' {...this.formItemStyle}>
+            {getFieldDecorator<IDictType>('dictType', {
+              initialValue: fromView?.dictType
+            })(<Input />)}
+          </FormItem>
+          <FormItem label='状态' {...this.formItemStyle}>
+            {getFieldDecorator<IDictType>('status', {
+              valuePropName: 'checked',
+              initialValue: !fromView ? true : fromView?.status === 0
+            })(<Switch checkedChildren='正常' unCheckedChildren='停用' />
+            )}
+          </FormItem>
+          <FormItem label='备注' {...this.formItemStyle}>
+            {getFieldDecorator<IDictType>('remark', {
+              initialValue: fromView?.remark
+            })(<Input />)}
+          </FormItem>
+        </Form>
+      </Modal>
+    )
+  }
+}
+
+export default Form.create<IDictTypeFormModalProps>()(DictTypeFormModal)

+ 0 - 0
app/containers/DataManagerDictionary/index.less


+ 157 - 29
app/containers/DataManagerDictionary/index.tsx

@@ -2,49 +2,164 @@ import React, { useEffect, useState } from 'react'
 import Helmet from 'react-helmet'
 import Container, { ContainerBody } from 'components/Container'
 import Box from 'components/Box'
-import { Col, Icon, Row, Table, Tooltip } from 'antd'
-import { PaginationConfig } from 'antd/lib/table'
+import { Button, Col, Divider, Icon, Popconfirm, Row, Table, Tooltip } from 'antd'
+import { ColumnProps } from 'antd/lib/table'
 import { compose } from 'redux'
+import request from 'utils/request'
+import api from 'utils/api'
+import DictDatasModal from 'containers/DataManagerDictionary/components/DictDatasModal'
+import { IDictType } from 'containers/DataManagerDictionary/types'
+import DictTypeFormModal from 'containers/DataManagerDictionary/components/DictTypeFormModal'
 
 function DataDictionary() {
-  const [screenWidth, setScreenWidth] = useState(
-    document.documentElement.clientWidth
-  )
+  const [tableLoading, setTableLoading] = useState(false)
+  const [dictTypes, setDictTypes] = useState<IDictType[]>([])
+
+  const [visible, setVisible] = useState(false)
+  const [dictType, setDictType] = useState<string>(null)
+
+  const [dictTypeFormVisible, setDictTypeFormVisible] = useState(false)
+  const [dictTypeFormLoading, setDictTypeFormLoading] = useState(false)
+  const [dictTypeForm, setDictTypeForm] = useState<IDictType>(null)
+
+  const tableColumns: Array<ColumnProps<IDictType>> = [
+    {
+      title: '字典主键',
+      dataIndex: 'dictId'
+    },
+    {
+      title: '字典名称',
+      dataIndex: 'dictName'
+    },
+    {
+      title: '字典类型',
+      dataIndex: 'dictType'
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (text) => (
+        <>
+          {text === 0 && (
+            <span
+              style={{ padding: '2px 4px', background: 'green', color: '#fff' }}
+            >
+              正常
+            </span>
+          )}
+          {text === 1 && (
+            <span
+              style={{ padding: '2px 4px', background: 'red', color: '#fff' }}
+            >
+              停用
+            </span>
+          )}
+        </>
+      )
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark'
+    },
+    {
+      title: '创建时间'
+      // dataIndex: 'isDefault'
+    },
+    {
+      title: '操作',
+      render: (_, data) => (
+        <>
+          <a
+            onClick={() => {
+              setDictTypeFormVisible(true)
+              setDictTypeForm(data)
+            }}
+          >
+            编辑
+          </a>
+          <Divider type='vertical' />
+          <a
+            onClick={() => {
+              setDictType(data.dictType)
+              setVisible(true)
+            }}
+          >
+            列表
+          </a>
 
-  const handleSetScreenWidth = () => {
-    setScreenWidth(document.documentElement.clientWidth)
+          <Divider type='vertical' />
+          <Popconfirm
+            title='确定删除?'
+            placement='bottom'
+            onConfirm={() => handleDeleteDictType(data.dictId)}
+          >
+            <a>删除</a>
+          </Popconfirm>
+        </>
+      )
+    }
+  ]
+
+  const queryDictTypes = async() => {
+    try {
+      setTableLoading(true)
+      const data = await request(api.dictTypes, { method: 'GET' })
+      setDictTypes(data?.payload ?? [])
+    } finally {
+      setTableLoading(false)
+    }
   }
-  const basePagination: PaginationConfig = {
-    defaultPageSize: 20,
-    showSizeChanger: true
+  const handleDeleteDictType = async(dictId) => {
+    try {
+      setTableLoading(true)
+      await request(`${api.deleteDictType}/${dictId}`, { method: 'delete' })
+      queryDictTypes()
+    } finally {
+      setTableLoading(false)
+    }
   }
-  const tablePagination: PaginationConfig = {
-    ...basePagination,
-    simple: screenWidth <= 768
+  const handleSaveDictType = async(dt: IDictType) => {
+    // // 0=正常,1=停用
+    try {
+      setDictTypeFormLoading(true)
+      const url = dictTypeForm?.dictId
+        ? `${api.updateDictType}/${dictTypeForm.dictId}`
+        : api.createDictType
+      await request(url, {
+        method: dictTypeForm?.dictId ? 'PUT' : 'POST',
+        data: { ...(dictTypeForm ?? {}), ...dt, status: dt.status ? 0 : 1 }
+      })
+      setDictTypeFormVisible(false)
+      queryDictTypes()
+    } finally {
+      setDictTypeFormLoading(false)
+    }
   }
+
   useEffect(() => {
-    window.addEventListener('resize', handleSetScreenWidth, false)
-    return () =>
-      window.removeEventListener('resize', handleSetScreenWidth, false)
+    queryDictTypes()
   }, [])
   return (
     <Container>
-      <Helmet title="数据字典" />
+      <Helmet title='数据字典' />
 
       <ContainerBody>
         <Box>
           <Box.Header>
             <Box.Title>
-              <Icon type="bars" />
+              <Icon type='bars' />
               数据字典列表
             </Box.Title>
             <Box.Tools>
-              <Tooltip placement="bottom" title="新增">
-                {/* <AdminButton */}
-                {/*  type="primary" */}
-                {/*  icon="plus" */}
-                {/*  onClick={this.addView} */}
-                {/* /> */}
+              <Tooltip placement='bottom' title='新增'>
+                <Button
+                  type='primary'
+                  icon='plus'
+                  onClick={() => {
+                    setDictTypeFormVisible(true)
+                    setDictTypeForm(null)
+                  }}
+                />
               </Tooltip>
             </Box.Tools>
           </Box.Header>
@@ -53,18 +168,31 @@ function DataDictionary() {
               <Col span={24}>
                 <Table
                   bordered
-                  rowKey="id"
-                  // loading={loading.view}
-                  // dataSource={filterViews}
-                  // columns={tableColumns}
-                  pagination={tablePagination}
+                  rowKey='dictId'
+                  loading={tableLoading}
+                  dataSource={dictTypes}
+                  columns={tableColumns}
+                  pagination={false}
                   // onChange={this.tableChange}
                 />
+                <div style={{ height: 20 }} />
               </Col>
             </Row>
           </Box.Body>
         </Box>
       </ContainerBody>
+      <DictDatasModal
+        visible={visible}
+        dictType={dictType}
+        onCancel={() => setVisible(false)}
+      />
+      <DictTypeFormModal
+        visible={dictTypeFormVisible}
+        loading={dictTypeFormLoading}
+        fromView={dictTypeForm}
+        onSave={handleSaveDictType}
+        onCancel={() => setDictTypeFormVisible(false)}
+      />
     </Container>
   )
 }

+ 22 - 0
app/containers/DataManagerDictionary/types.ts

@@ -0,0 +1,22 @@
+
+
+export interface IDictType {
+  dictId?: number
+  dictName?: string
+  dictType?: string
+  remark?: string
+  status?: number
+}
+
+export interface IDictData {
+  cssClass?: string
+  dictCode?: number
+  dictLabel?: string
+  dictSort?: number
+  dictType?: string
+  dictValue?: string
+  isDefault?: string
+  listClass?: string
+  remark?: string
+  status?: string
+}

+ 12 - 0
app/utils/api.ts

@@ -62,5 +62,17 @@ export default {
   deleteCatalogue: `/api/v3/catalogue/deleteCatalogue/`,
   // 根据目录id获取view /api/v3/views/getViewsByParentId params:  projectId parentId
   getViewsByParentId: `/api/v3/views/getViewsByParentId`,
+  // 查询数据字典数据 /api/v3/dict/dictDatas?dictType=xx
+  dictDatas: `/api/v3/dict/dictDatas`,
+
+  // 获取数据字典类型 /api/v3/dict/dictTypes
+  dictTypes: `/api/v3/dict/dictTypes`,
+  // 新增数据字典类型 /api/v3/dict/createDictType POST
+  createDictType: `/api/v3/dict/createDictType`,
+  // 修改数据字典类型 /api/v3/dict/updateDictType/{id} PUT
+  updateDictType: `/api/v3/dict/updateDictType/`,
+  // 删除数据字典类型 /api/v3/dict/deleteDictType/{id} DELETE
+  deleteDictType: `/api/v3/dict/deleteDictType/`,
   // getCatalogues: ``,
+
 }

+ 3 - 16
app/utils/checkLogin.ts

@@ -18,22 +18,9 @@
  * >>
  */
 
-import { removeToken } from 'utils/request'
+// import { removeToken } from 'utils/request'
 
-export default function () {
+export default function() {
   const token = localStorage.getItem('TOKEN')
-  if (token) {
-    const expire = localStorage.getItem('TOKEN_EXPIRE')
-    const timestamp = new Date().getTime()
-
-    if (Number(expire) > timestamp) {
-      return true
-    } else {
-      removeToken()
-      return false
-    }
-  } else {
-    return false
-  }
+  return !!token
 }
-

+ 19 - 13
app/utils/request.ts

@@ -23,25 +23,29 @@ import { DEFAULT_JWT_TOKEN_EXPIRED } from 'app/globalConstants'
 
 let tokenExpired = DEFAULT_JWT_TOKEN_EXPIRED
 
-axios.defaults.validateStatus = function (status) {
+axios.defaults.validateStatus = function(status) {
   return status < 400
 }
 
-function parseJSON (response: AxiosResponse) {
+function parseJSON(response: AxiosResponse) {
   return response.data
 }
 
-function refreshToken (response: AxiosResponse) {
+function refreshToken(response: AxiosResponse) {
   const token = response.data.header && response.data.header.token
+  if (response.data === 'token认证错误') {
+    removeToken()
+  }
   if (token) {
     setToken(token)
   }
   return response
 }
 
-export function request (url: string, options?: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>>
-export function request (config: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>>
-export default function request (url: string | AxiosRequestConfig, options?: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>> {
+export function request(url: string, options?: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>>
+export function request(config: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>>
+
+export default function request(url: string | AxiosRequestConfig, options?: AxiosRequestConfig): AxiosPromise<IDavinciResponse<object>> {
   const axiosPromise =
     typeof url === 'string' ? axios(url, options) : axios(url)
   return axiosPromise
@@ -49,15 +53,17 @@ export default function request (url: string | AxiosRequestConfig, options?: Axi
     .then(parseJSON)
 }
 
-export function setToken (token: string) {
+export function setToken(token: string) {
   localStorage.setItem('TOKEN', token)
-  localStorage.setItem('TOKEN_EXPIRE', `${new Date().getTime() + tokenExpired}`)
+  // localStorage.setItem('TOKEN_EXPIRE', `${new Date().getTime() + tokenExpired}`)
   axios.defaults.headers.common['Authorization'] = `${token}`
 }
 
-function syncToken (e: StorageEvent) {
+function syncToken(e: StorageEvent) {
   const { key, newValue } = e
-  if (key !== 'TOKEN') { return }
+  if (key !== 'TOKEN') {
+    return
+  }
   if (!newValue) {
     delete axios.defaults.headers.common['Authorization']
   } else {
@@ -65,14 +71,14 @@ function syncToken (e: StorageEvent) {
   }
 }
 
-export function removeToken () {
+export function removeToken() {
   localStorage.removeItem('TOKEN')
-  localStorage.removeItem('TOKEN_EXPIRE')
+  // localStorage.removeItem('TOKEN_EXPIRE')
   delete axios.defaults.headers.common['Authorization']
 
 }
 
-export function getToken () {
+export function getToken() {
   return axios.defaults.headers.common['Authorization']
 }