Browse Source

[Feature][UI Next] Add security environment manage. (#8057)

songjianet 3 years ago
parent
commit
f8a719c6ea

+ 2 - 2
dolphinscheduler-ui-next/src/layouts/content/use-dataList.ts

@@ -230,8 +230,8 @@ export function useDataList() {
             icon: renderIcon(SlackOutlined)
           },
           {
-            label: t('menu.environmental_manage'),
-            key: 'environmental-manage',
+            label: t('menu.environment_manage'),
+            key: 'environment-manage',
             icon: renderIcon(EnvironmentOutlined)
           },
           {

+ 20 - 1
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@@ -70,7 +70,7 @@ const menu = {
   alarm_instance_manage: 'Alarm Instance Manage',
   worker_group_manage: 'Worker Group Manage',
   yarn_queue_manage: 'Yarn Queue Manage',
-  environmental_manage: 'Environmental Manage',
+  environment_manage: 'Environment Manage',
   token_manage: 'Token Manage'
 }
 
@@ -246,6 +246,25 @@ const security = {
     edit: 'Edit',
     queue_name_tips: 'Please enter your queue name',
     queue_value_tips: 'Please enter your queue value'
+  },
+  environment: {
+    create_environment: 'Create Environment',
+    edit_environment: 'Edit Environment',
+    search_tips: 'Please enter keywords',
+    edit: 'Edit',
+    delete: 'Delete',
+    environment_name: 'Environment Name',
+    environment_config: 'Environment Config',
+    environment_desc: 'Environment Desc',
+    worker_groups: 'Worker Groups',
+    create_time: 'Create Time',
+    update_time: 'Update Time',
+    operation: 'Operation',
+    delete_confirm: 'Delete?',
+    environment_name_tips: 'Please enter your environment name',
+    environment_config_tips: 'Please enter your environment config',
+    environment_description_tips: 'Please enter your environment description',
+    worker_group_tips: 'Please select worker group'
   }
 }
 

+ 21 - 2
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@@ -70,7 +70,7 @@ const menu = {
   alarm_instance_manage: '告警实例管理',
   worker_group_manage: 'Worker分组管理',
   yarn_queue_manage: 'Yarn队列管理',
-  environmental_manage: '环境管理',
+  environment_manage: '环境管理',
   token_manage: '令牌管理'
 }
 
@@ -243,8 +243,27 @@ const security = {
     update_time: '更新时间',
     operation: '操作',
     edit: '编辑',
-    queue_name_tips: '请输入队列名',
+    queue_name_tips: '请输入队列名',
     queue_value_tips: '请输入队列值'
+  },
+  environment: {
+    create_environment: '创建环境',
+    edit_environment: '编辑环境',
+    search_tips: '请输入关键词',
+    edit: '编辑',
+    delete: '删除',
+    environment_name: '环境名称',
+    environment_config: '环境配置',
+    environment_desc: '环境描述',
+    worker_groups: 'Worker分组',
+    create_time: '创建时间',
+    update_time: '更新时间',
+    operation: '操作',
+    delete_confirm: '确定删除吗?',
+    environment_name_tips: '请输入环境名',
+    environment_config_tips: '请输入环境配置',
+    environment_description_tips: '请输入环境描述',
+    worker_group_tips: '请选择Worker分组'
   }
 }
 

+ 8 - 0
dolphinscheduler-ui-next/src/router/modules/security.ts

@@ -52,6 +52,14 @@ export default {
       meta: {
         title: 'Yarn队列管理'
       }
+    },
+    {
+      path: '/security/environment-manage',
+      name: 'environment-manage',
+      component: components['environment-manage'],
+      meta: {
+        title: '环境管理'
+      }
     }
   ]
 }

+ 24 - 1
dolphinscheduler-ui-next/src/service/modules/environment/types.ts

@@ -40,10 +40,33 @@ interface CodeReq {
   code: number
 }
 
+interface EnvironmentItem {
+  id: number
+  code: any
+  name: string
+  config: string
+  description: string
+  workerGroups: string[]
+  operator: number
+  createTime: string
+  updateTime: string
+}
+
+interface EnvironmentRes {
+  totalList: EnvironmentItem[]
+  total: number
+  totalPage: number
+  pageSize: number
+  currentPage: number
+  start: number
+}
+
 export {
   EnvironmentReq,
   EnvironmentCodeReq,
   EnvironmentNameReq,
   ListReq,
-  CodeReq
+  CodeReq,
+  EnvironmentItem,
+  EnvironmentRes
 }

+ 187 - 0
dolphinscheduler-ui-next/src/views/security/environment-manage/components/environment-modal.tsx

@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { defineComponent, PropType, toRefs, watch } from 'vue'
+import Modal from '@/components/modal'
+import { NForm, NFormItem, NInput, NSelect } from 'naive-ui'
+import { useModal } from './use-modal'
+import { useI18n } from 'vue-i18n'
+
+const envConfigPlaceholder =
+  'export HADOOP_HOME=/opt/hadoop-2.6.5\n' +
+  'export HADOOP_CONF_DIR=/etc/hadoop/conf\n' +
+  'export SPARK_HOME=/opt/soft/spark\n' +
+  'export PYTHON_HOME=/opt/soft/python\n' +
+  'export JAVA_HOME=/opt/java/jdk1.8.0_181-amd64\n' +
+  'export HIVE_HOME=/opt/soft/hive\n' +
+  'export FLINK_HOME=/opt/soft/flink\n' +
+  'export DATAX_HOME=/opt/soft/datax\n' +
+  'export YARN_CONF_DIR=/etc/hadoop/conf\n' +
+  'export PATH=$HADOOP_HOME/bin:$SPARK_HOME/bin:$PYTHON_HOME/bin:$JAVA_HOME/bin:$HIVE_HOME/bin:$FLINK_HOME/bin:$DATAX_HOME/bin:$PATH\n' +
+  'export HADOOP_CLASSPATH=`hadoop classpath`\n'
+
+const EnvironmentModal = defineComponent({
+  name: 'YarnQueueModal',
+  props: {
+    showModalRef: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    statusRef: {
+      type: Number as PropType<number>,
+      default: 0
+    },
+    row: {
+      type: Object as PropType<any>,
+      default: {}
+    }
+  },
+  emits: ['cancelModal', 'confirmModal'],
+  setup(props, ctx) {
+    const { variables, handleValidate, getListData } = useModal(props, ctx)
+    const { t } = useI18n()
+
+    const cancelModal = () => {
+      if (props.statusRef === 0) {
+        variables.model.name = ''
+        variables.model.config = ''
+        variables.model.description = ''
+        variables.model.workerGroups = []
+      }
+      ctx.emit('cancelModal', props.showModalRef)
+    }
+
+    const confirmModal = () => {
+      handleValidate(props.statusRef)
+    }
+
+    watch(
+      () => props.showModalRef,
+      () => {
+        props.showModalRef && getListData()
+      }
+    )
+
+    watch(
+      () => props.statusRef,
+      () => {
+        if (props.statusRef === 0) {
+          variables.model.name = ''
+          variables.model.config = ''
+          variables.model.description = ''
+          variables.model.workerGroups = []
+        } else {
+          variables.model.code = props.row.code
+          variables.model.name = props.row.name
+          variables.model.config = props.row.config
+          variables.model.description = props.row.description
+          variables.model.workerGroups = props.row.workerGroups
+        }
+      }
+    )
+
+    watch(
+      () => props.row,
+      () => {
+        variables.model.code = props.row.code
+        variables.model.name = props.row.name
+        variables.model.config = props.row.config
+        variables.model.description = props.row.description
+        variables.model.workerGroups = props.row.workerGroups
+      }
+    )
+
+    return { t, ...toRefs(variables), cancelModal, confirmModal }
+  },
+  render() {
+    const { t } = this
+    return (
+      <div>
+        <Modal
+          title={
+            this.statusRef === 0
+              ? t('security.environment.create_environment')
+              : t('security.environment.edit_environment')
+          }
+          show={this.showModalRef}
+          onCancel={this.cancelModal}
+          onConfirm={this.confirmModal}
+          confirmDisabled={
+            !this.model.name || !this.model.config || !this.model.description
+          }
+        >
+          {{
+            default: () => (
+              <NForm
+                model={this.model}
+                rules={this.rules}
+                ref='environmentFormRef'
+              >
+                <NFormItem
+                  label={t('security.environment.environment_name')}
+                  path='name'
+                >
+                  <NInput
+                    placeholder={t(
+                      'security.environment.environment_name_tips'
+                    )}
+                    v-model={[this.model.name, 'value']}
+                  />
+                </NFormItem>
+                <NFormItem
+                  label={t('security.environment.environment_config')}
+                  path='config'
+                >
+                  <NInput
+                    placeholder={envConfigPlaceholder}
+                    type='textarea'
+                    autosize={{ minRows: 16 }}
+                    v-model={[this.model.config, 'value']}
+                  />
+                </NFormItem>
+                <NFormItem
+                  label={t('security.environment.environment_desc')}
+                  path='description'
+                >
+                  <NInput
+                    placeholder={t(
+                      'security.environment.environment_description_tips'
+                    )}
+                    v-model={[this.model.description, 'value']}
+                  />
+                </NFormItem>
+                <NFormItem
+                  label={t('security.environment.worker_groups')}
+                  path='workerGroups'
+                >
+                  <NSelect
+                    multiple
+                    placeholder={t('security.environment.worker_group_tips')}
+                    options={this.model.generalOptions}
+                    v-model={[this.model.workerGroups, 'value']}
+                  />
+                </NFormItem>
+              </NForm>
+            )
+          }}
+        </Modal>
+      </div>
+    )
+  }
+})
+
+export default EnvironmentModal

+ 141 - 0
dolphinscheduler-ui-next/src/views/security/environment-manage/components/use-modal.ts

@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { reactive, ref, SetupContext } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useAsyncState } from '@vueuse/core'
+import { queryAllWorkerGroups } from '@/service/modules/worker-groups'
+import {
+  verifyEnvironment,
+  createEnvironment,
+  updateEnvironment
+} from '@/service/modules/environment'
+
+export function useModal(
+  props: any,
+  ctx: SetupContext<('cancelModal' | 'confirmModal')[]>
+) {
+  const { t } = useI18n()
+
+  const variables = reactive({
+    environmentFormRef: ref(),
+    model: {
+      code: ref<number>(-1),
+      name: ref(''),
+      config: ref(''),
+      description: ref(''),
+      workerGroups: ref<Array<string>>([]),
+      generalOptions: []
+    },
+    rules: {
+      name: {
+        required: true,
+        trigger: ['input', 'blur'],
+        validator() {
+          if (variables.model.name === '') {
+            return new Error(t('security.environment.environment_name_tips'))
+          }
+        }
+      },
+      config: {
+        required: true,
+        trigger: ['input', 'blur'],
+        validator() {
+          if (variables.model.config === '') {
+            return new Error(t('security.environment.environment_config_tips'))
+          }
+        }
+      },
+      description: {
+        required: true,
+        trigger: ['input', 'blur'],
+        validator() {
+          if (variables.model.description === '') {
+            return new Error(
+              t('security.environment.environment_description_tips')
+            )
+          }
+        }
+      }
+    }
+  })
+
+  const getListData = () => {
+    const { state } = useAsyncState(
+      queryAllWorkerGroups().then((res: any) => {
+        variables.model.generalOptions = res.map((item: any) => {
+          return {
+            label: item,
+            value: item
+          }
+        })
+      }),
+      {}
+    )
+
+    return state
+  }
+
+  const handleValidate = (statusRef: number) => {
+    variables.environmentFormRef.validate((errors: any) => {
+      if (!errors) {
+        statusRef === 0 ? submitEnvironmentModal() : updateEnvironmentModal()
+      } else {
+        return
+      }
+    })
+  }
+
+  const submitEnvironmentModal = () => {
+    verifyEnvironment({ environmentName: variables.model.name }).then(() => {
+      const data = {
+        name: variables.model.name,
+        config: variables.model.config,
+        description: variables.model.description,
+        workerGroups: JSON.stringify(variables.model.workerGroups)
+      }
+
+      createEnvironment(data).then(() => {
+        variables.model.name = ''
+        variables.model.config = ''
+        variables.model.description = ''
+        variables.model.workerGroups = []
+        ctx.emit('confirmModal', props.showModalRef)
+      })
+    })
+  }
+
+  const updateEnvironmentModal = () => {
+    const data = {
+      code: variables.model.code,
+      name: variables.model.name,
+      config: variables.model.config,
+      description: variables.model.description,
+      workerGroups: JSON.stringify(variables.model.workerGroups)
+    }
+
+    updateEnvironment(data).then(() => {
+      ctx.emit('confirmModal', props.showModalRef)
+    })
+  }
+
+  return {
+    variables,
+    handleValidate,
+    getListData
+  }
+}

+ 43 - 0
dolphinscheduler-ui-next/src/views/security/environment-manage/index.module.scss

@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+.search-card {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .box {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    width: 300px;
+
+    button {
+      margin-left: 10px;
+    }
+  }
+}
+
+.table-card {
+  margin-top: 8px;
+
+  .pagination {
+    margin-top: 20px;
+    display: flex;
+    justify-content: center;
+  }
+}

+ 158 - 0
dolphinscheduler-ui-next/src/views/security/environment-manage/index.tsx

@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { defineComponent, onMounted, toRefs, watch } from 'vue'
+import {
+  NButton,
+  NCard,
+  NDataTable,
+  NIcon,
+  NInput,
+  NPagination
+} from 'naive-ui'
+import { SearchOutlined } from '@vicons/antd'
+import { useI18n } from 'vue-i18n'
+import { useTable } from './use-table'
+import Card from '@/components/card'
+import EnvironmentModal from './components/environment-modal'
+import styles from './index.module.scss'
+
+const environmentManage = defineComponent({
+  name: 'environment-manage',
+  setup() {
+    const { t } = useI18n()
+    const { variables, getTableData, createColumns } = useTable()
+
+    const requestData = () => {
+      getTableData({
+        pageSize: variables.pageSize,
+        pageNo: variables.page,
+        searchVal: variables.searchVal
+      })
+    }
+
+    const onUpdatePageSize = () => {
+      variables.page = 1
+      requestData()
+    }
+
+    const onSearch = () => {
+      variables.page = 1
+      requestData()
+    }
+
+    const handleModalChange = () => {
+      variables.showModalRef = true
+      variables.statusRef = 0
+    }
+
+    const onCancelModal = () => {
+      variables.showModalRef = false
+    }
+
+    const onConfirmModal = () => {
+      variables.showModalRef = false
+      requestData()
+    }
+
+    onMounted(() => {
+      createColumns(variables)
+      requestData()
+    })
+
+    watch(useI18n().locale, () => {
+      createColumns(variables)
+    })
+
+    return {
+      t,
+      ...toRefs(variables),
+      requestData,
+      onCancelModal,
+      onConfirmModal,
+      onUpdatePageSize,
+      handleModalChange,
+      onSearch
+    }
+  },
+  render() {
+    const {
+      t,
+      requestData,
+      onUpdatePageSize,
+      onCancelModal,
+      onConfirmModal,
+      handleModalChange,
+      onSearch
+    } = this
+
+    return (
+      <div>
+        <NCard>
+          <div class={styles['search-card']}>
+            <div>
+              <NButton size='small' type='primary' onClick={handleModalChange}>
+                {t('security.environment.create_environment')}
+              </NButton>
+            </div>
+            <div class={styles.box}>
+              <NInput
+                size='small'
+                clearable
+                v-model={[this.searchVal, 'value']}
+                placeholder={t('security.environment.search_tips')}
+              />
+              <NButton size='small' type='primary' onClick={onSearch}>
+                {{
+                  icon: () => (
+                    <NIcon>
+                      <SearchOutlined />
+                    </NIcon>
+                  )
+                }}
+              </NButton>
+            </div>
+          </div>
+        </NCard>
+        <Card class={styles['table-card']}>
+          <NDataTable columns={this.columns} data={this.tableData} />
+          <div class={styles.pagination}>
+            <NPagination
+              v-model:page={this.page}
+              v-model:page-size={this.pageSize}
+              page-count={this.totalPage}
+              show-size-picker
+              page-sizes={[10, 30, 50]}
+              show-quick-jumper
+              onUpdatePage={requestData}
+              onUpdatePageSize={onUpdatePageSize}
+            />
+          </div>
+        </Card>
+        <EnvironmentModal
+          showModalRef={this.showModalRef}
+          statusRef={this.statusRef}
+          row={this.row}
+          onCancelModal={onCancelModal}
+          onConfirmModal={onConfirmModal}
+        />
+      </div>
+    )
+  }
+})
+
+export default environmentManage

+ 204 - 0
dolphinscheduler-ui-next/src/views/security/environment-manage/use-table.ts

@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { useAsyncState } from '@vueuse/core'
+import { reactive, h, ref } from 'vue'
+import { format } from 'date-fns'
+import { NButton, NPopconfirm, NSpace, NTooltip, NTag } from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+import {
+  queryEnvironmentListPaging,
+  deleteEnvironmentByCode
+} from '@/service/modules/environment'
+import { DeleteOutlined, EditOutlined } from '@vicons/antd'
+import type {
+  EnvironmentRes,
+  EnvironmentItem
+} from '@/service/modules/environment/types'
+
+export function useTable() {
+  const { t } = useI18n()
+
+  const handleEdit = (row: any) => {
+    variables.showModalRef = true
+    variables.statusRef = 1
+    variables.row = row
+  }
+
+  const createColumns = (variables: any) => {
+    variables.columns = [
+      {
+        title: '#',
+        key: 'index'
+      },
+      {
+        title: t('security.environment.environment_name'),
+        key: 'name'
+      },
+      {
+        title: t('security.environment.environment_config'),
+        key: 'config'
+      },
+      {
+        title: t('security.environment.environment_desc'),
+        key: 'description'
+      },
+      {
+        title: t('security.environment.worker_groups'),
+        key: 'workerGroups',
+        render: (row: EnvironmentItem) =>
+          h(NSpace, null, {
+            default: () =>
+              row.workerGroups.map((item: any) =>
+                h(
+                  NTag,
+                  { type: 'success', size: 'small' },
+                  { default: () => item }
+                )
+              )
+          })
+      },
+      {
+        title: t('security.environment.create_time'),
+        key: 'createTime'
+      },
+      {
+        title: t('security.environment.update_time'),
+        key: 'updateTime'
+      },
+      {
+        title: t('security.environment.operation'),
+        key: 'operation',
+        render(row: any) {
+          return h(NSpace, null, {
+            default: () => [
+              h(
+                NTooltip,
+                {},
+                {
+                  trigger: () =>
+                    h(
+                      NButton,
+                      {
+                        circle: true,
+                        type: 'info',
+                        size: 'small',
+                        onClick: () => {
+                          handleEdit(row)
+                        }
+                      },
+                      {
+                        icon: () => h(EditOutlined)
+                      }
+                    ),
+                  default: () => t('security.environment.edit')
+                }
+              ),
+              h(
+                NPopconfirm,
+                {
+                  onPositiveClick: () => {
+                    handleDelete(row)
+                  }
+                },
+                {
+                  trigger: () =>
+                    h(
+                      NTooltip,
+                      {},
+                      {
+                        trigger: () =>
+                          h(
+                            NButton,
+                            {
+                              circle: true,
+                              type: 'error',
+                              size: 'small'
+                            },
+                            {
+                              icon: () => h(DeleteOutlined)
+                            }
+                          ),
+                        default: () => t('security.environment.delete')
+                      }
+                    ),
+                  default: () => t('security.environment.delete_confirm')
+                }
+              )
+            ]
+          })
+        }
+      }
+    ]
+  }
+
+  const variables = reactive({
+    columns: [],
+    tableData: [],
+    page: ref(1),
+    pageSize: ref(10),
+    searchVal: ref(null),
+    totalPage: ref(1),
+    showModalRef: ref(false),
+    statusRef: ref(0),
+    row: {}
+  })
+
+  const handleDelete = (row: any) => {
+    deleteEnvironmentByCode({ environmentCode: row.code }).then(() => {
+      getTableData({
+        pageSize: variables.pageSize,
+        pageNo:
+          variables.tableData.length === 1 && variables.page > 1
+            ? variables.page - 1
+            : variables.page,
+        searchVal: variables.searchVal
+      })
+    })
+  }
+
+  const getTableData = (params: any) => {
+    const { state } = useAsyncState(
+      queryEnvironmentListPaging({ ...params }).then((res: EnvironmentRes) => {
+        variables.tableData = res.totalList.map((item, index) => {
+          item.createTime = format(
+            new Date(item.createTime),
+            'yyyy-MM-dd HH:mm:ss'
+          )
+          item.updateTime = format(
+            new Date(item.updateTime),
+            'yyyy-MM-dd HH:mm:ss'
+          )
+          return {
+            index: index + 1,
+            ...item
+          }
+        }) as any
+        variables.totalPage = res.totalPage
+      }),
+      {}
+    )
+
+    return state
+  }
+
+  return {
+    variables,
+    getTableData,
+    createColumns
+  }
+}