ソースを参照

!1 合并
Merge pull request !1 from GaoKunW/devlop

GaoKunW 10 ヶ月 前
コミット
eead0dc039

+ 8 - 0
README.md

@@ -1,5 +1,13 @@
 # eco-web
 
+<h4 align="center">基于Vue3+vite+ts+pinia+Element-Plus后台管理</h4>
+
+# 项目功能
+
+- TablePro组件
+- FormPro组件
+- 代码自动生成
+
 ```
 yarn install
 

+ 5 - 4
src/api/index.ts

@@ -130,7 +130,9 @@ class RequestHttp {
      *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
      */
     this.service.interceptors.response.use(
-      (response: AxiosResponse) => {
+      (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
+        const { config } = response
+        axiosCanceler.removePending(config)
         // 加密后的 AES 秘钥
         const keyStr = response.headers[encryptHeader]
         // 加密
@@ -145,8 +147,7 @@ class RequestHttp {
           // 将结果 (得到的是 JSON 字符串) 转为 JSON
           response.data = JSON.parse(decryptData)
         }
-        const { data, config, request } = response
-        axiosCanceler.removePending(config)
+        const { data, request } = response
         tryHideFullScreenLoading()
         if (request.responseType === 'blob' || request.responseType === 'arraybuffer') {
           return data
@@ -176,7 +177,7 @@ class RequestHttp {
         // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
         if (data.code && data.code == ResultEnum.ERROR) {
           ElMessage.error(data.msg)
-          return Promise.reject(data)
+          return Promise.reject(new Error(data.msg))
         }
         // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
         return data

+ 4 - 3
src/components/ProForm/components/Item.vue

@@ -2,7 +2,7 @@
   <component
     :is="elTagNameValue"
     v-bind="item.compOptions"
-    v-model.trim="_formModel[handleProp(item.prop)]"
+    v-model.trim="_model[handleProp(item.prop)]"
     :data="['tree-select'].includes(item.compOptions.elTagName!) ? itemEnum : []"
     :options="['cascader', 'select-v2'].includes(item.compOptions.elTagName!) ? itemEnum : []"
   >
@@ -45,10 +45,11 @@
 import { handleProp } from '@/utils'
 interface FormItem {
   item: ProForm.ItemsOptions
-  formModel: Record<string, any>
+  model: Record<string, any>
 }
 const props = defineProps<FormItem>()
-const _formModel = computed(() => props.formModel)
+const _model = computed(() => props.model)
+console.log('_model', _model)
 const elTagNameValue = computed(() => {
   const val = props.item.compOptions.elTagName
   if ('radio-button' == val) return `el-radio-group`

+ 12 - 9
src/components/ProForm/index.vue

@@ -22,21 +22,21 @@
             <slot name="default" :form-model="formModel"></slot>
             <!-- </template> -->
             <template v-if="item.compOptions.elTagName === 'icon'">
-              <SelectIcon v-model:icon-value="formModel[item.prop]" />
+              <SelectIcon v-model:icon-value="formModel[handleProp(item.prop)]" />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'file-upload'">
-              <FileUpload v-model:model-value="formModel[item.prop]" />
+              <FileUpload v-model:model-value="formModel[handleProp(item.prop)]" v-bind="$attrs" />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'img-upload'">
-              <Imgs v-model="formModel[item.prop]" v-bind="$attrs" />
+              <Imgs v-model="formModel[handleProp(item.prop)]" v-bind="$attrs" />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'file-upload-s3'">
-              <FileUploadS3 v-model:model-value="formModel[item.prop]" />
+              <FileUploadS3 v-model:model-value="formModel[handleProp(item.prop)]" v-bind="$attrs" />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'img-upload-s3'">
-              <ImgsS3 v-model="formModel[item.prop]" v-bind="$attrs" />
+              <ImgsS3 v-model="formModel[handleProp(item.prop)]" v-bind="$attrs" />
             </template>
-            <Item v-else :item="item" :form-model="formModel" />
+            <Item v-else :item="item" :model="formModel" />
           </component>
         </el-col>
       </template>
@@ -63,7 +63,7 @@ import FileUpload from '@/components/Upload/File.vue'
 import Imgs from '@/components/Upload/Imgs.vue'
 import FileUploadS3 from '@/components/Upload/FileS3.vue'
 import ImgsS3 from '@/components/Upload/ImgsS3.vue'
-// import { handleProp } from '@/utils'
+import { handleProp } from '@/utils'
 // 表单整体配置项
 export interface ProFormProps {
   formOptions?: ProForm.FormOptions
@@ -151,11 +151,14 @@ watch(props, () => {
 watch(
   () => props.model,
   () => {
-    props.itemsOptions.map((item: ProForm.ItemsOptions) => {
+    props.itemsOptions.forEach((item: ProForm.ItemsOptions) => {
       // 如果类型为checkbox,默认值需要设置一个空数组
       let value = ['checkbox', 'transfer'].includes(item.compOptions.elTagName!) ? [] : props.model[item.prop]
-      props.model[item.prop] ? (formModel.value = props.model) : (formModel.value[item.prop] = item.compOptions.value || value)
+      props.model[item.prop]
+        ? (formModel.value = props.model)
+        : (formModel.value[item.prop] = item.compOptions.value === 0 ? 0 : item.compOptions.value || value)
     })
+    console.log('formModel.value', formModel.value)
   },
   { immediate: true }
 )

+ 2 - 2
src/layouts/components/Menu/SubMenu.vue

@@ -1,6 +1,6 @@
 <template>
-  <template v-for="subItem in menuList" :key="subItem.path">
-    <el-sub-menu v-if="subItem.children?.length" :index="subItem.path">
+  <template v-for="(subItem, index) in menuList" :key="subItem.path + index">
+    <el-sub-menu v-if="subItem.children?.length" :index="index + ''">
       <template #title>
         <svg-icon v-if="subItem.meta.icon" :name="subItem.meta.icon" class="menu-svg-icon" />
         <span class="sle" :title="subItem.meta.title">{{ subItem.meta.title }}</span>

+ 3 - 2
src/stores/modules/auth.ts

@@ -1,6 +1,6 @@
 import { AuthState } from '@/stores/interface'
 import { getRoutersApi } from '@/api/modules/system/menu'
-import { getFlatMenuList, getShowMenuList, getAllBreadcrumbList } from '@/utils'
+import { getFlatMenuList, getShowMenuList, getAllBreadcrumbList, getMenuPath } from '@/utils'
 // 动态路由
 import dynamicRouterList from '@/routers/modules/dynamicRouter.json'
 
@@ -29,7 +29,8 @@ export const useAuthStore = defineStore('admin-auth', {
     // Get AuthMenuList
     async getAuthMenuList() {
       const { data } = await getRoutersApi()
-      this.authMenuList = [...dynamicRouterList.data, ...data] as any[]
+      let menuList = [...dynamicRouterList.data, ...data] as any[]
+      this.authMenuList = getMenuPath(menuList)
     },
     // Set RouteName
     async setRouteName(name: string) {

+ 3 - 3
src/stores/modules/global.ts

@@ -6,7 +6,7 @@ export const useGlobalStore = defineStore('admin-global', {
   // 修改默认值之后,需清除 localStorage 数据
   state: (): GlobalState => ({
     // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns)
-    layout: 'vertical',
+    layout: 'columns',
     // element 组件大小
     assemblySize: 'default',
     // 当前系统语言
@@ -26,7 +26,7 @@ export const useGlobalStore = defineStore('admin-global', {
     // 头部反转
     headerInverted: false,
     // 折叠菜单
-    isCollapse: false,
+    isCollapse: true,
     // 菜单手风琴
     accordion: true,
     // 面包屑导航
@@ -38,7 +38,7 @@ export const useGlobalStore = defineStore('admin-global', {
     // 标签页图标
     tabsIcon: true,
     // 页脚
-    footer: true
+    footer: false
   }),
   getters: {},
   actions: {

+ 17 - 0
src/utils/index.ts

@@ -146,6 +146,23 @@ export function getUrlWithParams() {
   return url[mode]
 }
 
+/**
+ * @description 使用递归过滤出需要渲染在左侧菜单的列表 (需剔除 hidden == true 的菜单)
+ * @param {Array} menuList 菜单列表
+ * @returns {Array}
+ * */
+export function getMenuPath(menuList: Menu.MenuOptions[], path?: string) {
+  let newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList))
+  return newMenuList.map(item => {
+    if (path) {
+      const newPath = path.replace(/none|\/none/g, '')
+      item.path = `${newPath}/${item.path}`
+    }
+    item.children?.length && (item.children = getMenuPath(item.children, item.path))
+    return item
+  })
+}
+
 /**
  * @description 使用递归扁平化菜单,方便添加动态路由
  * @param {Array} menuList 菜单列表

+ 2 - 2
src/views/system/dict/index.vue

@@ -98,8 +98,8 @@ const handleRefreshCache = async () => {
 
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
-  { type: 'selection', fixed: 'left', width: 70 },
-  { prop: 'dictId', label: '字典编号', width: 70 },
+  { type: 'selection', fixed: 'left', width: 60 },
+  { prop: 'dictId', label: '字典编号', width: 100 },
   {
     prop: 'dictName',
     label: '字典名称',

+ 1 - 1
src/views/system/menu/index.vue

@@ -251,7 +251,7 @@ const setItemsOptions = () => {
       show: val => {
         return val?.menuType !== 'F'
       },
-      tooltip: '访问的路由地址,如:`/system/user`,如外网地址需内链访问则以`http(s)://`开头',
+      tooltip: '访问的路由地址,如:`user`(none 为空),如外网地址需内链访问则以`http(s)://`开头',
       compOptions: {
         elTagName: 'input',
         placeholder: '请输入路由地址'

+ 2 - 2
src/views/system/user/index.vue

@@ -209,8 +209,8 @@ const openDialog = async (type: number, title: string, row?: any) => {
 }
 // 表格配置项
 const columns = reactive<ColumnProps<User.ResUserList>[]>([
-  { type: 'selection', fixed: 'left', width: 70 },
-  { prop: 'userId', label: '用户编号' },
+  { type: 'selection', fixed: 'left', width: 60 },
+  { prop: 'userId', label: '用户编号', width: 100 },
   { prop: 'userName', label: '用户名称', search: { el: 'input' }, width: 120 },
   { prop: 'nickName', label: '用户昵称', search: { el: 'input' }, width: 120 },
   { prop: 'dept.deptName', label: '部门', width: 120 },

+ 1 - 0
src/views/tool/gen/components/columnInfo.vue

@@ -156,6 +156,7 @@ const columns = reactive<ColumnProps<any>[]>([
         <div>
           <el-select vModel_trim={scope.row.htmlType}>
             <el-option label="文本框" value="input" />
+            <el-option label="数字输入框" value="input-number" />
             <el-option label="文本域" value="textarea" />
             <el-option label="下拉框" value="select" />
             <el-option label="单选框" value="radio" />