Browse Source

添加字典管理页面

王飞 1 year ago
parent
commit
a1b56fe5a3

+ 22 - 0
src/api/manage/dict.ts

@@ -0,0 +1,22 @@
+import request from '@/config/axios'
+// import type { FileData } from './types'
+
+export const getTree = (data?: any) => {
+  return request.post({ url: '/api/sysDictionary/showTree', data })
+}
+
+export const getDict = (data: any) => {
+  return request.post({ url: '/api/sysDictionary/page', data })
+}
+
+export const delTableListApi = (id: string | number): Promise<IResponse> => {
+  return request.delete({ url: `/api/sysDictionary/${id}` })
+}
+
+export const saveTableApi = (data: any) => {
+  return request.post({ url: '/api/sysDictionary', data })
+}
+
+export const updateTableApi = (data: any) => {
+  return request.put({ url: '/api/sysDictionary', data })
+}

+ 21 - 2
src/api/manage/types.ts

@@ -33,7 +33,6 @@ export type NewsTableData = {
   weight: number
 }
 
-
 export type CompanyData = {
   id?: string
   companyAddress?: string
@@ -54,4 +53,24 @@ export type ImgData = {
   updateTime?: string
   bannerType?: string
   bannerTarget?: string
-}
+}
+
+export type DictData = {
+  id?: string
+  code?: string
+  name?: string
+  pid?: string
+  showText?: string
+  description?: number
+  isActive?: string
+}
+
+export type DictRowData = {
+  id: string
+  code: string
+  name: string
+  pid?: string
+  showText?: string
+  description?: number
+  isActive: string
+}

+ 15 - 10
src/config/axios/config.ts

@@ -27,7 +27,7 @@ const config: AxiosConfig = {
     dev: '',
 
     // 打包生产环境接口前缀
-    pro: '',
+    pro: 'http://api.dacundianzi.com',
 
     // 打包测试环境接口前缀
     test: ''
@@ -89,6 +89,7 @@ const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
 ;(error: AxiosError) => {
   Promise.reject(error)
 }
+let isReLogin = false
 
 const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
   if (response?.config?.responseType === 'blob') {
@@ -97,15 +98,19 @@ const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
   } else if (response.data.code === config.code) {
     return response.data
   } else if (response.data.code === 'A0000A') {
-    ElMessageBox.alert(response.data.message, '提示', {
-      showClose: false,
-      callback: () => {
-        clear()
-        tagsViewStore.delAllViews()
-        resetRouter() // 重置静态路由表
-        router.replace('/login')
-      }
-    })
+    if (!isReLogin) {
+      isReLogin = true
+      ElMessageBox.alert(response.data.message, '提示', {
+        showClose: false,
+        callback: () => {
+          clear()
+          tagsViewStore.delAllViews()
+          resetRouter() // 重置静态路由表
+          router.replace('/login')
+          isReLogin = false
+        }
+      })
+    }
   } else {
     ElMessage.error(response.data.message)
   }

+ 9 - 1
src/router/index.ts

@@ -216,7 +216,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
     redirect: '/authorization/user',
     name: 'Authorization',
     meta: {
-      title: t('router.authorization'),
+      title: '系统管理',
       icon: 'eos-icons:role-binding',
       alwaysShow: true
     },
@@ -236,6 +236,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: t('router.user')
         }
+      },
+      {
+        path: 'dict',
+        component: () => import('@/views/Authorization/Dict/Dict.vue'),
+        name: 'Dict',
+        meta: {
+          title: '字典管理'
+        }
       }
       // {
       //   path: 'menu',

+ 314 - 0
src/views/Authorization/Dict/Dict.vue

@@ -0,0 +1,314 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table } from '@/components/Table'
+import { ref, unref, reactive } from 'vue'
+import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
+import { getTree, saveTableApi, delTableListApi, updateTableApi } from '@/api/manage/dict'
+import type { DictData, DictRowData } from '@/api/manage/types'
+import { useTable } from '@/hooks/web/useTable'
+import { Search } from '@/components/Search'
+import Write from './components/Write.vue'
+import Detail from './components/Detail.vue'
+import { Dialog } from '@/components/Dialog'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { useIcon } from '@/hooks/web/useIcon'
+const EditIcon = useIcon({ icon: 'ep:edit' })
+const DetailIcon = useIcon({ icon: 'ep:document' })
+const DeleteIcon = useIcon({ icon: 'ep:delete' })
+
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getTree({
+      pid: '',
+      ...unref(searchParams)
+    })
+    return {
+      list:
+        res.data.map((e: DictRowData) => {
+          return {
+            ...e,
+            hasChildren: true
+          }
+        }) || []
+    }
+  },
+  fetchDelApi: async () => {
+    const res = await delTableListApi(unref(id))
+    return !!res
+  }
+})
+const { loading, dataList, pageSize, currentPage } = tableState
+const { getList, delList } = tableMethods
+
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'name',
+    label: '名称'
+  },
+  {
+    field: 'code',
+    label: '编码',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'showText',
+    label: '显示文字',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'value',
+    label: '值',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'isActive',
+    label: '是否启用',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      component: 'Select',
+      componentProps: {
+        options: [
+          {
+            label: '启用',
+            value: '1'
+          },
+          {
+            label: '禁用',
+            value: '0'
+          }
+        ]
+      }
+    },
+    form: {
+      component: 'Switch',
+      value: '1',
+      componentProps: {
+        activeText: '启用',
+        inactiveText: '禁用',
+        activeValue: '1',
+        inactiveValue: '0'
+      }
+    }
+  },
+  {
+    field: 'description',
+    label: '备注',
+    form: {
+      componentProps: {
+        type: 'textarea'
+      },
+      colProps: {
+        span: 24
+      }
+    },
+    search: {
+      hidden: true
+    }
+  },
+
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    search: {
+      hidden: true
+    },
+    table: {
+      width: 160,
+      fixed: 'right',
+      slots: {
+        default: (data: any) => {
+          const row = data.row as DictRowData
+          return (
+            <>
+              <ElButtonGroup>
+                <ElTooltip content="编辑">
+                  <ElButton text icon={EditIcon} onClick={() => action(row, 'edit')} />
+                </ElTooltip>
+                <ElTooltip content="管理子项">
+                  <ElButton text icon={DetailIcon} onClick={() => action(row, 'detail')} />
+                </ElTooltip>
+                <ElTooltip content="删除">
+                  <ElButton
+                    text
+                    icon={DeleteIcon}
+                    type="danger"
+                    onClick={() => delData(data.row)}
+                  />
+                </ElTooltip>
+              </ElButtonGroup>
+            </>
+          )
+        }
+      }
+    }
+  }
+])
+
+const { allSchemas } = useCrudSchemas(crudSchemas)
+
+const searchParams = ref({})
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+
+const currentRow = ref<DictData>()
+const actionType = ref('')
+
+const load = async (
+  row: DictRowData,
+  _treeNode: unknown,
+  resolve: (date: DictRowData[]) => void
+) => {
+  const res = await getTree({
+    pid: row.id,
+    ...unref(searchParams)
+  })
+  resolve(
+    res.data.map((e: DictRowData) => {
+      return {
+        ...e,
+        hasChildren: true
+      }
+    })
+  )
+}
+
+const AddAction = (key: string) => {
+  dialogTitle.value = '新增'
+  currentRow.value = undefined
+  if (key) {
+    currentRow.value = {
+      pid: key
+    }
+  }
+  dialogVisible.value = true
+  actionType.value = 'add'
+}
+
+const delLoading = ref(false)
+const id = ref<string>('')
+
+const delData = async (row: DictRowData) => {
+  id.value = row.id
+  delLoading.value = true
+
+  await delList(unref(id).length).finally(() => {
+    delLoading.value = false
+  })
+}
+
+const action = (row: DictData, type: string) => {
+  dialogTitle.value = type === 'edit' ? '编辑' : '子项列表'
+  actionType.value = type
+  currentRow.value = row
+  dialogVisible.value = true
+}
+
+const writeRef = ref<ComponentRef<typeof Write>>()
+const tableRef = ref<ComponentRef<typeof Table>>()
+const saveLoading = ref(false)
+const save = async (action) => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    saveLoading.value = true
+    try {
+      if (action == 'add') {
+        const res = await saveTableApi(formData)
+        if (res) {
+          getList()
+        }
+      } else {
+        const res = await updateTableApi(formData)
+        if (res) {
+          getList()
+        }
+      }
+    } catch (error) {
+      console.log(error)
+    } finally {
+      saveLoading.value = false
+      dialogVisible.value = false
+    }
+  }
+}
+</script>
+
+<template>
+  <div>
+    <ContentWrap>
+      <Search
+        :schema="allSchemas.searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+      />
+
+      <div class="mb-10px">
+        <ElButton type="primary" @click="AddAction('')">{{ t('exampleDemo.add') }}</ElButton>
+      </div>
+      <Table
+        ref="tableRef"
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :columns="allSchemas.tableColumns"
+        :data="dataList"
+        :loading="loading"
+        row-key="id"
+        :load="load"
+        @register="tableRegister"
+      />
+    </ContentWrap>
+
+    <Dialog v-model="dialogVisible" :title="dialogTitle">
+      <Write
+        v-if="actionType !== 'detail'"
+        ref="writeRef"
+        :form-schema="allSchemas.formSchema"
+        :current-row="currentRow"
+      />
+
+      <Detail
+        v-if="actionType === 'detail'"
+        ref="detailRef"
+        :columns="allSchemas"
+        :row-data="currentRow"
+      />
+
+      <template #footer>
+        <ElButton
+          v-if="actionType !== 'detail'"
+          type="primary"
+          :loading="saveLoading"
+          @click="save(actionType)"
+        >
+          {{ t('exampleDemo.save') }}
+        </ElButton>
+
+        <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+      </template>
+    </Dialog>
+  </div>
+</template>

+ 284 - 0
src/views/Authorization/Dict/components/Detail.vue

@@ -0,0 +1,284 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table } from '@/components/Table'
+import { ref, unref, PropType, reactive } from 'vue'
+import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
+import { getTree, saveTableApi, delTableListApi, updateTableApi } from '@/api/manage/dict'
+import type { DictData, DictRowData } from '@/api/manage/types'
+import { useTable } from '@/hooks/web/useTable'
+import { Search } from '@/components/Search'
+import Write from './Write.vue'
+// import Detail from './components/Detail.vue'
+import { Dialog } from '@/components/Dialog'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { useIcon } from '@/hooks/web/useIcon'
+const EditIcon = useIcon({ icon: 'ep:edit' })
+// const DetailIcon = useIcon({ icon: 'ep:document' })
+const DeleteIcon = useIcon({ icon: 'ep:delete' })
+
+const props = defineProps({
+  rowData: {
+    type: Object as PropType<DictData>
+  }
+})
+
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getTree({
+      pid: props.rowData?.id,
+      ...unref(searchParams)
+    })
+    return {
+      list:
+        res.data.map((e: DictRowData) => {
+          return {
+            ...e,
+            hasChildren: true
+          }
+        }) || []
+    }
+  },
+  fetchDelApi: async () => {
+    const res = await delTableListApi(unref(id))
+    return !!res
+  }
+})
+const { loading, dataList, pageSize, currentPage } = tableState
+const { getList, delList } = tableMethods
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'name',
+    label: '名称'
+  },
+  {
+    field: 'code',
+    label: '编码',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'showText',
+    label: '显示文字',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'value',
+    label: '值',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'isActive',
+    label: '是否启用',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      component: 'Switch',
+      value: '1',
+      componentProps: {
+        activeText: '启用',
+        inactiveText: '禁用',
+        activeValue: '1',
+        inactiveValue: '0'
+      }
+    }
+  },
+  {
+    field: 'description',
+    label: '备注',
+    form: {
+      componentProps: {
+        type: 'textarea'
+      },
+      colProps: {
+        span: 24
+      }
+    },
+    search: {
+      hidden: true
+    }
+  },
+
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    search: {
+      hidden: true
+    },
+    table: {
+      width: 160,
+      fixed: 'right',
+      slots: {
+        default: (data: any) => {
+          const row = data.row as DictRowData
+          return (
+            <>
+              <ElButtonGroup>
+                <ElTooltip content="编辑">
+                  <ElButton text icon={EditIcon} onClick={() => action(row, 'edit')} />
+                </ElTooltip>
+                <ElTooltip content="删除">
+                  <ElButton
+                    text
+                    icon={DeleteIcon}
+                    type="danger"
+                    onClick={() => delData(data.row)}
+                  />
+                </ElTooltip>
+              </ElButtonGroup>
+            </>
+          )
+        }
+      }
+    }
+  }
+])
+
+const { allSchemas } = useCrudSchemas(crudSchemas)
+const searchParams = ref({})
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+
+const currentRow = ref<DictData>()
+const actionType = ref('')
+
+const AddAction = (key: string) => {
+  dialogTitle.value = '新增'
+  currentRow.value = undefined
+  if (key) {
+    currentRow.value = {
+      pid: key
+    }
+  }
+  dialogVisible.value = true
+  actionType.value = 'add'
+}
+const delLoading = ref(false)
+const id = ref<string>('')
+const delData = async (row: DictRowData) => {
+  id.value = row.id
+  delLoading.value = true
+
+  await delList(unref(id).length).finally(() => {
+    delLoading.value = false
+  })
+}
+const action = (row: DictData, type: string) => {
+  dialogTitle.value = type === 'edit' ? '编辑' : '子项列表'
+  actionType.value = type
+  currentRow.value = row
+  dialogVisible.value = true
+}
+const writeRef = ref<ComponentRef<typeof Write>>()
+const tableRef = ref<ComponentRef<typeof Table>>()
+const saveLoading = ref(false)
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    saveLoading.value = true
+    try {
+      if (actionType.value == 'add') {
+        const res = await saveTableApi(formData)
+        if (res) {
+          getList()
+        }
+      } else {
+        const res = await updateTableApi(formData)
+        if (res) {
+          getList()
+        }
+      }
+    } catch (error) {
+      console.log(error)
+    } finally {
+      saveLoading.value = false
+      dialogVisible.value = false
+    }
+  }
+}
+
+defineExpose({
+  getList
+})
+</script>
+
+<template>
+  <div>
+    <ContentWrap>
+      <Search
+        :schema="allSchemas.searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+      />
+
+      <div class="mb-10px">
+        <ElButton type="primary" @click="AddAction(props.rowData?.id || '')">{{
+          t('exampleDemo.add')
+        }}</ElButton>
+      </div>
+      <Table
+        ref="tableRef"
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :columns="allSchemas.tableColumns"
+        :data="dataList"
+        :loading="loading"
+        row-key="id"
+        @register="tableRegister"
+      />
+    </ContentWrap>
+
+    <Dialog v-model="dialogVisible" :title="dialogTitle">
+      <Write
+        v-if="actionType !== 'detail'"
+        ref="writeRef"
+        :form-schema="allSchemas.formSchema"
+        :current-row="currentRow"
+      />
+
+      <!-- <Detail
+        v-if="actionType === 'detail'"
+        :columns="props.allSchemas.tableColumns"
+        :current-row="currentRow"
+      /> -->
+
+      <template #footer>
+        <ElButton
+          v-if="actionType !== 'detail'"
+          type="primary"
+          :loading="saveLoading"
+          @click="save"
+        >
+          {{ t('exampleDemo.save') }}
+        </ElButton>
+
+        <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+      </template>
+    </Dialog>
+  </div>
+</template>

+ 58 - 0
src/views/Authorization/Dict/components/Write.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch } from 'vue'
+import { DictData } from '@/api/manage/types'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<DictData>,
+    default: () => undefined
+  },
+  formSchema: {
+    type: Array as PropType<FormSchema[]>,
+    default: () => []
+  }
+})
+
+const rules = reactive({
+  code: [required()]
+})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    return formData
+  }
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) return
+    setValues(currentRow)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 6 - 6
src/views/Manage/Img/ImgPage.vue

@@ -86,11 +86,11 @@ const crudSchemas: CrudSchema[] = [
       formatter: (_: Recordable, _colomun, cellValue: string) => {
         return cellValue
           ? cellValue === '0'
-            ? '首页'
+            ? '首页Banner'
             : cellValue === '1'
-            ? '中间'
+            ? '公司图片'
             : cellValue === '2'
-            ? '底部'
+            ? '合作logo'
             : ''
           : ''
       }
@@ -100,15 +100,15 @@ const crudSchemas: CrudSchema[] = [
       componentProps: {
         options: [
           {
-            label: '首页',
+            label: '首页Banner',
             value: '0'
           },
           {
-            label: '中间',
+            label: '公司图片',
             value: '1'
           },
           {
-            label: '底部',
+            label: '合作logo',
             value: '2'
           }
         ]

+ 57 - 0
src/views/Manage/News/NewsPage.vue

@@ -88,6 +88,63 @@ const crudSchemas: CrudSchema[] = [
       span: 24
     }
   },
+  {
+    field: 'categoryId',
+    label: '文章类目',
+    width: 120,
+    table: {
+      hidden: false,
+      slots: {
+        default: (data: any) => {
+          let arr = [
+            {
+              label: '新闻资讯',
+              value: '0'
+            },
+            {
+              label: '经典案例',
+              value: '1'
+            },
+            {
+              label: '解决方案',
+              value: '2'
+            },
+            {
+              label: '技术支持',
+              value: '3'
+            }
+          ]
+          let item = arr.find((e) => {
+            return e.value === data.row.categoryId
+          })
+          return item ? item.label : ''
+        }
+      }
+    },
+    search: {
+      component: 'Select',
+      componentProps: {
+        options: [
+          {
+            label: '新闻资讯',
+            value: '0'
+          },
+          {
+            label: '经典案例',
+            value: '1'
+          },
+          {
+            label: '解决方案',
+            value: '2'
+          },
+          {
+            label: '技术支持',
+            value: '3'
+          }
+        ]
+      }
+    }
+  },
   {
     field: 'introduce',
     label: '文章简介',

+ 34 - 3
src/views/Manage/News/components/Write.vue

@@ -48,11 +48,42 @@ const schema = reactive<FormSchema[]>([
       placeholder: '请输入'
     }
   },
+  {
+    field: 'categoryId',
+    label: '文章类目',
+    component: 'Select',
+    colProps: {
+      span: 8
+    },
+    componentProps: {
+      options: [
+        {
+          label: '新闻资讯',
+          value: '0'
+        },
+        {
+          label: '经典案例',
+          value: '1'
+        },
+        {
+          label: '解决方案',
+          value: '2'
+        },
+        {
+          label: '技术支持',
+          value: '3'
+        }
+      ]
+    }
+  },
   {
     field: 'weight',
     label: '权重',
     component: 'InputNumber',
     value: 0,
+    colProps: {
+      span: 8
+    },
     formItemProps: {
       rules: [required()]
     }
@@ -62,6 +93,9 @@ const schema = reactive<FormSchema[]>([
     label: '当前浏览量',
     component: 'InputNumber',
     value: 0,
+    colProps: {
+      span: 8
+    },
     formItemProps: {
       rules: [required()]
     }
@@ -99,9 +133,6 @@ const schema = reactive<FormSchema[]>([
     field: 'remark',
     label: '备注',
     component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
     colProps: {
       span: 24
     },

+ 0 - 3
src/views/Manage/Product/components/Write.vue

@@ -110,9 +110,6 @@ const schema = reactive<FormSchema[]>([
     field: 'remark',
     label: '备注',
     component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
     colProps: {
       span: 24
     },

+ 1 - 1
vite.config.ts

@@ -121,7 +121,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       proxy: {
         // 选项写法
         '^/api': {
-          target: 'http://192.168.0.131:8081',
+          target: 'http://api.dacundianzi.com/',
           changeOrigin: true,
           rewrite: path => path
         }