Merge pull request #301 from kailong321200875/master
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=base
|
||||
VITE_API_BASE_PATH=base
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
2
.env.dev
2
.env.dev
@@ -2,7 +2,7 @@
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=dev
|
||||
VITE_API_BASE_PATH=dev
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-dev/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=pro
|
||||
VITE_API_BASE_PATH=pro
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/vue-element-plus-admin/
|
||||
|
||||
2
.env.pro
2
.env.pro
@@ -2,7 +2,7 @@
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=pro
|
||||
VITE_API_BASE_PATH=pro
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=test
|
||||
VITE_API_BASE_PATH=test
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-test/
|
||||
|
||||
1
.github/workflows/auto-merge.yml
vendored
1
.github/workflows/auto-merge.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
||||
- name: Automerge
|
||||
uses: 'pascalgn/automerge-action@v0.14.3'
|
||||
env:
|
||||
BASE_BRANCHES: 'release'
|
||||
GITHUB_TOKEN: '${{ secrets.TOKEN }}'
|
||||
MERGE_LABELS: ''
|
||||
MERGE_FILTER_AUTHOR: 'kailong321200875'
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -1,7 +1,7 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
|
||||
name: Release
|
||||
|
||||
|
||||
0
.husky/commit-msg
Normal file → Executable file
0
.husky/commit-msg
Normal file → Executable file
0
.husky/pre-commit
Normal file → Executable file
0
.husky/pre-commit
Normal file → Executable file
219
mock/department/index.ts
Normal file
219
mock/department/index.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { toAnyString } from '@/utils'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const departmentList: any = []
|
||||
|
||||
const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
departmentList.push({
|
||||
// 部门名称
|
||||
departmentName: citys[i],
|
||||
id: toAnyString(),
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
// 备注
|
||||
remark: '@cword(10, 15)',
|
||||
children: [
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '研发部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '产品部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '运营部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '市场部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '销售部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '客服部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/department/list',
|
||||
method: 'get',
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: departmentList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/department/table/list',
|
||||
method: 'get',
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: departmentList,
|
||||
total: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/department/users',
|
||||
method: 'get',
|
||||
timeout: 1000,
|
||||
response: ({ query }) => {
|
||||
const { pageSize } = query
|
||||
// 根据pageSize来创建数据
|
||||
const mockList: any = []
|
||||
for (let i = 0; i < pageSize; i++) {
|
||||
mockList.push(
|
||||
Mock.mock({
|
||||
// 用户名
|
||||
username: '@cname',
|
||||
// 账号
|
||||
account: '@first',
|
||||
// 邮箱
|
||||
email: '@EMAIL',
|
||||
// 创建时间
|
||||
createTime: '@datetime',
|
||||
// 角色
|
||||
role: '@first',
|
||||
// 用户id
|
||||
id: toAnyString()
|
||||
})
|
||||
)
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: 100,
|
||||
list: mockList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 保存接口
|
||||
{
|
||||
url: '/department/user/save',
|
||||
method: 'post',
|
||||
timeout: 1000,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 删除接口
|
||||
{
|
||||
url: '/department/user/delete',
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 保存接口
|
||||
{
|
||||
url: '/department/save',
|
||||
method: 'post',
|
||||
timeout: 1000,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 删除接口
|
||||
{
|
||||
url: '/department/delete',
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
||||
250
mock/menu/index.ts
Normal file
250
mock/menu/index.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/menu/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 1,
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 2,
|
||||
meta: {
|
||||
title: '分析页',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 3,
|
||||
meta: {
|
||||
title: '工作台',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {
|
||||
title: '文档',
|
||||
icon: 'clarity:document-solid'
|
||||
},
|
||||
name: 'ExternalLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 4,
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 5,
|
||||
meta: {
|
||||
title: '文档'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 6,
|
||||
meta: {
|
||||
title: '菜单',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 7,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 8,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1-1',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 9,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-1-1'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 10,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-2'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 11,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单2'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 12,
|
||||
meta: {
|
||||
title: '综合示例',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 13,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-弹窗',
|
||||
permission: ['edit', 'add']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 14,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-页面',
|
||||
permission: ['edit', 'add']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 15,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-新增',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['delete', 'add']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 16,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-编辑',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['delete', 'add']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 17,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-详情',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['delete', 'edit']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
||||
@@ -1,5 +1,7 @@
|
||||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import Mock from 'mockjs'
|
||||
import { toAnyString } from '@/utils'
|
||||
|
||||
const { code } = config
|
||||
|
||||
@@ -105,14 +107,6 @@ const adminList = [
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-form',
|
||||
component: 'views/Components/Form/RefForm',
|
||||
name: 'RefForm',
|
||||
meta: {
|
||||
title: 'RefForm'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -143,13 +137,29 @@ const adminList = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-table',
|
||||
component: 'views/Components/Table/RefTable',
|
||||
name: 'RefTable',
|
||||
path: 'tree-table',
|
||||
component: 'views/Components/Table/TreeTable',
|
||||
name: 'TreeTable',
|
||||
meta: {
|
||||
title: 'RefTable'
|
||||
title: 'TreeTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'table-image-preview',
|
||||
component: 'views/Components/Table/TableImagePreview',
|
||||
name: 'TableImagePreview',
|
||||
meta: {
|
||||
title: 'router.PicturePreview'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'ref-table',
|
||||
// component: 'views/Components/Table/RefTable',
|
||||
// name: 'RefTable',
|
||||
// meta: {
|
||||
// title: 'RefTable'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -259,14 +269,6 @@ const adminList = [
|
||||
meta: {
|
||||
title: 'router.inputPassword'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'sticky',
|
||||
component: 'views/Components/Sticky',
|
||||
name: 'Sticky',
|
||||
meta: {
|
||||
title: 'router.sticky'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -290,13 +292,21 @@ const adminList = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useCrudSchemas',
|
||||
component: 'views/hooks/useCrudSchemas',
|
||||
name: 'UseCrudSchemas',
|
||||
path: 'useOpenTab',
|
||||
component: 'views/hooks/useOpenTab',
|
||||
name: 'UseOpenTab',
|
||||
meta: {
|
||||
title: 'useCrudSchemas'
|
||||
title: 'useOpenTab'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'useCrudSchemas',
|
||||
// component: 'views/hooks/useCrudSchemas',
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -462,6 +472,59 @@ const adminList = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/authorization',
|
||||
component: '#',
|
||||
redirect: '/authorization/user',
|
||||
name: 'Authorization',
|
||||
meta: {
|
||||
title: 'router.authorization',
|
||||
icon: 'eos-icons:role-binding',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'department',
|
||||
component: 'views/Authorization/Department/Department',
|
||||
name: 'Department',
|
||||
meta: {
|
||||
title: 'router.department'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
component: 'views/Authorization/User/User',
|
||||
name: 'User',
|
||||
meta: {
|
||||
title: 'router.user'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'menu',
|
||||
component: 'views/Authorization/Menu/Menu',
|
||||
name: 'Menu',
|
||||
meta: {
|
||||
title: 'router.menuManagement'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
component: 'views/Authorization/Role/Role',
|
||||
name: 'Role',
|
||||
meta: {
|
||||
title: 'router.role'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
component: 'views/Authorization/Test/Test',
|
||||
name: 'Test',
|
||||
meta: {
|
||||
title: 'router.permission'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -481,6 +544,8 @@ const testList: string[] = [
|
||||
'/components/table',
|
||||
'/components/table/default-table',
|
||||
'/components/table/use-table',
|
||||
'/components/table/tree-table',
|
||||
'/components/table/table-image-preview',
|
||||
'/components/table/ref-table',
|
||||
'/components/editor-demo',
|
||||
'/components/editor-demo/editor',
|
||||
@@ -498,7 +563,8 @@ const testList: string[] = [
|
||||
'/Components/Sticky',
|
||||
'/hooks',
|
||||
'/hooks/useWatermark',
|
||||
'/hooks/useCrudSchemas',
|
||||
'/hooks/useOpenTab',
|
||||
// '/hooks/useCrudSchemas',
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
@@ -511,12 +577,441 @@ const testList: string[] = [
|
||||
'/example/example-add',
|
||||
'/example/example-edit',
|
||||
'/example/example-detail',
|
||||
'/authorization',
|
||||
'/authorization/department',
|
||||
'/authorization/user',
|
||||
'/authorization/role',
|
||||
'/authorization/menu',
|
||||
'/authorization/test',
|
||||
'/error',
|
||||
'/error/404-demo',
|
||||
'/error/403-demo',
|
||||
'/error/500-demo'
|
||||
]
|
||||
|
||||
const List: any[] = []
|
||||
|
||||
const roleNames = ['超级管理员', '管理员', '普通用户', '游客']
|
||||
const menus = [
|
||||
[
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 1,
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 2,
|
||||
meta: {
|
||||
title: '分析页',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 3,
|
||||
meta: {
|
||||
title: '工作台',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {
|
||||
title: '文档',
|
||||
icon: 'clarity:document-solid'
|
||||
},
|
||||
name: 'ExternalLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 4,
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 5,
|
||||
meta: {
|
||||
title: '文档'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 6,
|
||||
meta: {
|
||||
title: '菜单',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 7,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 8,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1-1',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 9,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-1-1',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 10,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-2',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 11,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单2',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 12,
|
||||
meta: {
|
||||
title: '综合示例',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 13,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-弹窗',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 14,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-页面',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 15,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-新增',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 16,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-编辑',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 17,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-详情',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 1,
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 2,
|
||||
meta: {
|
||||
title: '分析页',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 3,
|
||||
meta: {
|
||||
title: '工作台',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {
|
||||
title: '文档',
|
||||
icon: 'clarity:document-solid'
|
||||
},
|
||||
name: 'ExternalLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 4,
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 5,
|
||||
meta: {
|
||||
title: '文档'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 6,
|
||||
meta: {
|
||||
title: '菜单',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 7,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 8,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1-1',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 9,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-1-1',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 10,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单1-2',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 11,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '菜单2',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 12,
|
||||
meta: {
|
||||
title: '综合示例',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: 17,
|
||||
permission: ['edit', 'add', 'delete'],
|
||||
meta: {
|
||||
title: '综合示例-详情',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
List.push(
|
||||
Mock.mock({
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
roleName: roleNames[i],
|
||||
role: '@first',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
createTime: '@datetime',
|
||||
remark: '@cword(10, 15)',
|
||||
menu: menus[i]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
@@ -532,5 +1027,21 @@ export default [
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/role/table',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: List,
|
||||
total: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
||||
|
||||
@@ -12,7 +12,7 @@ const count = 100
|
||||
const baseContent =
|
||||
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
|
||||
|
||||
let List: {
|
||||
interface ListProps {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
@@ -20,7 +20,21 @@ let List: {
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
}[] = []
|
||||
image_uri: string
|
||||
}
|
||||
|
||||
interface TreeListProps {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
content: string
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
children: TreeListProps[]
|
||||
}
|
||||
|
||||
let List: ListProps[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
List.push(
|
||||
@@ -32,13 +46,120 @@ for (let i = 0; i < count; i++) {
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
pageviews: '@integer(300, 5000)',
|
||||
image_uri: Mock.Random.image('@integer(300, 5000)x@integer(300, 5000)')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const treeList: TreeListProps[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
treeList.push(
|
||||
Mock.mock({
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)',
|
||||
children: [
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)',
|
||||
children: [
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}
|
||||
]
|
||||
// image_uri
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default [
|
||||
// 树形列表接口
|
||||
{
|
||||
url: '/example/treeList',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { title, pageIndex, pageSize } = query
|
||||
const mockList = treeList.filter((item) => {
|
||||
if (title && item.title.indexOf(title) < 0) return false
|
||||
return true
|
||||
})
|
||||
const pageList = mockList.filter(
|
||||
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
|
||||
)
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 列表接口
|
||||
{
|
||||
url: '/example/list',
|
||||
|
||||
102
package.json
102
package.json
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "vue-element-plus-admin",
|
||||
"version": "1.9.9",
|
||||
"version": "2.0。0",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode base",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:gitee": "vite build --mode gitee",
|
||||
"build:dev": "npm run ts:check && vite build --mode dev",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"build:test": "npm run ts:check && vite build --mode test",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
@@ -26,88 +26,90 @@
|
||||
"p": "plop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/iconify": "^3.1.0",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.0",
|
||||
"@zxcvbn-ts/core": "^3.0.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"echarts": "^5.4.2",
|
||||
"dayjs": "^1.11.9",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "2.3.4",
|
||||
"element-plus": "^2.3.8",
|
||||
"intro.js": "^7.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.36",
|
||||
"pinia": "^2.1.4",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.1",
|
||||
"url": "^0.11.0",
|
||||
"vue": "3.2.47",
|
||||
"qs": "^6.11.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"url": "^0.11.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^5.0.2",
|
||||
"web-storage-cache": "^1.1.1"
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@iconify/json": "^2.2.62",
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@iconify/json": "^2.2.92",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^20.1.1",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"@unocss/transformer-variant-group": "^0.51.12",
|
||||
"@vitejs/plugin-legacy": "^4.0.3",
|
||||
"@vitejs/plugin-vue": "^4.2.1",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@unocss/transformer-variant-group": "^0.53.5",
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue-macros/volar": "^0.9.8",
|
||||
"@vue-macros/volar": "^0.12.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"consola": "^3.1.0",
|
||||
"eslint": "^8.40.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-define-config": "^1.20.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.11.1",
|
||||
"eslint-define-config": "^1.21.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"husky": "^8.0.3",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.2.2",
|
||||
"lint-staged": "^13.2.3",
|
||||
"plop": "^3.1.2",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss": "^8.4.26",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^3.21.5",
|
||||
"stylelint": "^15.6.1",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^3.26.3",
|
||||
"stylelint": "^15.10.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier": "^9.0.5",
|
||||
"stylelint-config-recommended": "^12.0.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.17.2",
|
||||
"typescript": "5.0.4",
|
||||
"unocss": "^0.51.12",
|
||||
"unplugin-vue-define-options": "^1.3.5",
|
||||
"vite": "4.3.5",
|
||||
"terser": "^5.19.1",
|
||||
"typescript": "5.1.6",
|
||||
"unocss": "^0.53.5",
|
||||
"unplugin-vue-define-options": "^1.3.11",
|
||||
"vite": "4.4.4",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-mock": "^3.0.0",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^1.6.4"
|
||||
"vue-tsc": "^1.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAppStore } from '@/store/modules/app'
|
||||
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||
import { isDark } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -16,12 +16,12 @@ const currentSize = computed(() => appStore.getCurrentSize)
|
||||
|
||||
const greyMode = computed(() => appStore.getGreyMode)
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const { getStorage } = useStorage()
|
||||
|
||||
// 根据浏览器当前主题设置系统主题色
|
||||
const setDefaultTheme = () => {
|
||||
if (wsCache.get('isDark') !== null) {
|
||||
appStore.setIsDark(wsCache.get('isDark'))
|
||||
if (getStorage('isDark') !== null) {
|
||||
appStore.setIsDark(getStorage('isDark'))
|
||||
return
|
||||
}
|
||||
const isDarkTheme = isDark()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// 获取所有字典
|
||||
export const getDictApi = (): Promise<IResponse> => {
|
||||
export const getDictApi = () => {
|
||||
return request.get({ url: '/dict/list' })
|
||||
}
|
||||
|
||||
// 模拟获取某个字典
|
||||
export const getDictOneApi = async (): Promise<IResponse> => {
|
||||
export const getDictOneApi = async () => {
|
||||
return request.get({ url: '/dict/one' })
|
||||
}
|
||||
|
||||
30
src/api/department/index.ts
Normal file
30
src/api/department/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import request from '@/config/axios'
|
||||
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
|
||||
|
||||
export const getDepartmentApi = () => {
|
||||
return request.get<DepartmentListResponse>({ url: '/department/list' })
|
||||
}
|
||||
|
||||
export const getUserByIdApi = (params: DepartmentUserParams) => {
|
||||
return request.get<DepartmentUserResponse>({ url: '/department/users', params })
|
||||
}
|
||||
|
||||
export const deleteUserByIdApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/user/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const saveUserApi = (data: any) => {
|
||||
return request.post({ url: '/department/user/save', data })
|
||||
}
|
||||
|
||||
export const saveDepartmentApi = (data: any) => {
|
||||
return request.post({ url: '/department/save', data })
|
||||
}
|
||||
|
||||
export const deleteDepartmentApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const getDepartmentTableApi = (params: any) => {
|
||||
return request.get({ url: '/department/table/list', params })
|
||||
}
|
||||
32
src/api/department/types.ts
Normal file
32
src/api/department/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface DepartmentItem {
|
||||
id: string
|
||||
departmentName: string
|
||||
children?: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentListResponse {
|
||||
list: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentUserParams {
|
||||
pageSize: number
|
||||
pageIndex: number
|
||||
id: string
|
||||
username?: string
|
||||
account?: string
|
||||
}
|
||||
|
||||
export interface DepartmentUserItem {
|
||||
id: string
|
||||
username: string
|
||||
account: string
|
||||
email: string
|
||||
createTime: string
|
||||
role: string
|
||||
department: DepartmentItem
|
||||
}
|
||||
|
||||
export interface DepartmentUserResponse {
|
||||
list: DepartmentUserItem[]
|
||||
total: number
|
||||
}
|
||||
5
src/api/menu/index.ts
Normal file
5
src/api/menu/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getMenuListApi = () => {
|
||||
return request.get({ url: '/menu/list' })
|
||||
}
|
||||
5
src/api/role/index.ts
Normal file
5
src/api/role/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getRoleListApi = () => {
|
||||
return request.get({ url: '/role/table' })
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import request from '@/config/axios'
|
||||
import type { TableData } from './types'
|
||||
|
||||
export const getTableListApi = (params: any): Promise<IResponse> => {
|
||||
export const getTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/list', params })
|
||||
}
|
||||
|
||||
export const getTreeTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/treeList', params })
|
||||
}
|
||||
|
||||
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
|
||||
return request.post({ url: '/example/save', data })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ConfigGlobal from './src/ConfigGlobal.vue'
|
||||
|
||||
export type { ConfigGlobalTypes } from './src/types'
|
||||
|
||||
export { ConfigGlobal }
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { provide, computed, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ElConfigProvider } from 'element-plus'
|
||||
import { ComponentSize, ElConfigProvider } from 'element-plus'
|
||||
import { useLocaleStore } from '@/store/modules/locale'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { setCssVar } from '@/utils'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const props = defineProps({
|
||||
size: propTypes.oneOf<ElementPlusSize>(['default', 'small', 'large']).def('default')
|
||||
size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
|
||||
})
|
||||
|
||||
provide('configGlobal', props)
|
||||
|
||||
5
src/components/ConfigGlobal/src/types/index.ts
Normal file
5
src/components/ConfigGlobal/src/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ComponentSize } from 'element-plus'
|
||||
|
||||
export interface ConfigGlobalTypes {
|
||||
size?: ComponentSize
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ElCard, ElButton } from 'element-plus'
|
||||
import { ElCard } from 'element-plus'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ref, onMounted, defineEmits } from 'vue'
|
||||
import { Sticky } from '@/components/Sticky'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -15,45 +11,15 @@ defineProps({
|
||||
title: propTypes.string.def(''),
|
||||
message: propTypes.string.def('')
|
||||
})
|
||||
const emit = defineEmits(['back'])
|
||||
const offset = ref(85)
|
||||
const contentDetailWrap = ref()
|
||||
onMounted(() => {
|
||||
offset.value = contentDetailWrap.value.getBoundingClientRect().top
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[`${prefixCls}-container`, 'relative bg-[#fff]']" ref="contentDetailWrap">
|
||||
<Sticky :offset="offset">
|
||||
<div
|
||||
:class="[
|
||||
`${prefixCls}-header`,
|
||||
'flex b-b-1 h-50px items-center text-center bg-white pr-10px'
|
||||
]"
|
||||
>
|
||||
<div :class="[`${prefixCls}-header__back`, 'flex pl-10px pr-10px ']">
|
||||
<el-button @click="emit('back')">
|
||||
<Icon icon="ep:arrow-left" class="mr-5px" />
|
||||
{{ t('common.back') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div :class="[`${prefixCls}-header__title`, 'flex flex-1 justify-center']">
|
||||
<slot name="title">
|
||||
<label class="text-16px font-700">{{ title }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div :class="[`${prefixCls}-header__right`, 'flex pl-10px pr-10px']">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
<div :class="[`${prefixCls}-container`, 'relative']">
|
||||
<ElCard :class="[`${prefixCls}-body`, 'mb-20px']" shadow="never">
|
||||
<div class="mb-20px pb-20px" style="border-bottom: 1px solid var(--el-border-color)">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
</Sticky>
|
||||
<div style="padding: var(--app-content-padding)">
|
||||
<ElCard :class="[`${prefixCls}-body`, 'mb-20px']" shadow="never">
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,8 @@ import ContextMenu from './src/ContextMenu.vue'
|
||||
import { ElDropdown } from 'element-plus'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
export type { ContextMenuSchema } from './src/types'
|
||||
|
||||
export interface ContextMenuExpose {
|
||||
elDropdownMenuRef: ComponentRef<typeof ElDropdown>
|
||||
tagItem: RouteLocationNormalizedLoaded
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PropType, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { contextMenuSchema } from '../../../types/contextMenu'
|
||||
import { ContextMenuSchema } from './types'
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('context-menu')
|
||||
@@ -15,7 +15,7 @@ const emit = defineEmits(['visibleChange'])
|
||||
|
||||
const props = defineProps({
|
||||
schema: {
|
||||
type: Array as PropType<contextMenuSchema[]>,
|
||||
type: Array as PropType<ContextMenuSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
trigger: {
|
||||
@@ -28,7 +28,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const command = (item: contextMenuSchema) => {
|
||||
const command = (item: ContextMenuSchema) => {
|
||||
item.command && item.command(item)
|
||||
}
|
||||
|
||||
|
||||
7
src/components/ContextMenu/src/types/index.ts
Normal file
7
src/components/ContextMenu/src/types/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ContextMenuSchema {
|
||||
disabled?: boolean
|
||||
divided?: boolean
|
||||
icon?: string
|
||||
label: string
|
||||
command?: (item: ContextMenuSchema) => void
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Descriptions from './src/Descriptions.vue'
|
||||
|
||||
export type { DescriptionsSchema } from './src/types'
|
||||
|
||||
export { Descriptions }
|
||||
|
||||
@@ -1,134 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
<script lang="tsx">
|
||||
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ref, unref, PropType, computed, useAttrs, useSlots } from 'vue'
|
||||
import { ref, unref, PropType, computed, defineComponent } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { DescriptionsSchema } from '@/types/descriptions'
|
||||
import { DescriptionsSchema } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { get } from 'lodash-es'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const mobile = computed(() => appStore.getMobile)
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
title: propTypes.string.def(''),
|
||||
message: propTypes.string.def(''),
|
||||
collapse: propTypes.bool.def(true),
|
||||
schema: {
|
||||
type: Array as PropType<DescriptionsSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('descriptions')
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
export default defineComponent({
|
||||
name: 'Descriptions',
|
||||
props: {
|
||||
title: propTypes.string.def(''),
|
||||
message: propTypes.string.def(''),
|
||||
collapse: propTypes.bool.def(true),
|
||||
border: propTypes.bool.def(true),
|
||||
column: propTypes.number.def(2),
|
||||
size: propTypes.oneOf(['large', 'default', 'small']).def('default'),
|
||||
direction: propTypes.oneOf(['horizontal', 'vertical']).def('horizontal'),
|
||||
extra: propTypes.string.def(''),
|
||||
schema: {
|
||||
type: Array as PropType<DescriptionsSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
}
|
||||
}
|
||||
return obj
|
||||
})
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
const getBindValue = computed((): any => {
|
||||
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
if (unref(mobile)) {
|
||||
obj.direction = 'vertical'
|
||||
}
|
||||
return obj
|
||||
})
|
||||
|
||||
const getBindItemValue = (item: DescriptionsSchema) => {
|
||||
const delArr: string[] = ['field']
|
||||
const obj = { ...item }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
const getBindItemValue = (item: DescriptionsSchema) => {
|
||||
const delArr: string[] = ['field']
|
||||
const obj = { ...item }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// 折叠
|
||||
const show = ref(true)
|
||||
// 折叠
|
||||
const show = ref(true)
|
||||
|
||||
const toggleClick = () => {
|
||||
if (props.collapse) {
|
||||
show.value = !unref(show)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
const toggleClick = () => {
|
||||
if (props.collapse) {
|
||||
show.value = !unref(show)
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
prefixCls,
|
||||
'bg-[var(--el-color-white)] dark:bg-[var(--el-bg-color)] dark:border-[var(--el-border-color)] dark:border-1px'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="title"
|
||||
:class="[
|
||||
`${prefixCls}-header`,
|
||||
'h-50px flex justify-between items-center b-b-1 border-solid border-[var(--tags-view-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]'
|
||||
]"
|
||||
@click="toggleClick"
|
||||
>
|
||||
<div :class="[`${prefixCls}-header__title`, 'relative font-18px font-bold ml-10px']">
|
||||
<div class="flex items-center">
|
||||
{{ title }}
|
||||
<ElTooltip v-if="message" :content="message" placement="right">
|
||||
<Icon icon="ep:warning" class="ml-5px" />
|
||||
</ElTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<Icon v-if="collapse" :icon="show ? 'ep:arrow-down' : 'ep:arrow-up'" />
|
||||
</div>
|
||||
|
||||
<ElCollapseTransition>
|
||||
<div v-show="show" :class="[`${prefixCls}-content`, 'p-10px']">
|
||||
<ElDescriptions
|
||||
:column="2"
|
||||
border
|
||||
:direction="mobile ? 'vertical' : 'horizontal'"
|
||||
v-bind="getBindValue"
|
||||
return () => {
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
prefixCls,
|
||||
'bg-[var(--el-color-white)] dark:bg-[var(--el-bg-color)] dark:border-[var(--el-border-color)] dark:border-1px'
|
||||
]}
|
||||
>
|
||||
<template v-if="slots['extra']" #extra>
|
||||
<slot name="extra"></slot>
|
||||
</template>
|
||||
<ElDescriptionsItem
|
||||
v-for="item in schema"
|
||||
:key="item.field"
|
||||
v-bind="getBindItemValue(item)"
|
||||
>
|
||||
<template #label>
|
||||
<slot
|
||||
:name="`${item.field}-label`"
|
||||
:row="{
|
||||
label: item.label
|
||||
}"
|
||||
>{{ item.label }}</slot
|
||||
>
|
||||
</template>
|
||||
{props.title ? (
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-header`,
|
||||
'relative h-50px flex justify-between items-center layout-border__bottom px-10px cursor-pointer'
|
||||
]}
|
||||
onClick={toggleClick}
|
||||
>
|
||||
<div class={[`${prefixCls}-header__title`, 'relative font-18px font-bold ml-10px']}>
|
||||
<div class="flex items-center">
|
||||
{props.title}
|
||||
{props.message ? (
|
||||
<ElTooltip content={props.message} placement="right">
|
||||
<Icon icon="bi:question-circle-fill" class="ml-5px" size={14} />
|
||||
</ElTooltip>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{props.collapse ? <Icon icon={show.value ? 'ep:arrow-down' : 'ep:arrow-up'} /> : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<template #default>
|
||||
<slot :name="item.field" :row="data">{{ data[item.field] }}</slot>
|
||||
</template>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
</ElCollapseTransition>
|
||||
</div>
|
||||
</template>
|
||||
<ElCollapseTransition>
|
||||
<div v-show={unref(show)} class={[`${prefixCls}-content`]}>
|
||||
<ElDescriptions {...unref(getBindValue)}>
|
||||
{{
|
||||
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
|
||||
default: () => {
|
||||
return props.schema.map((item) => {
|
||||
return (
|
||||
<ElDescriptionsItem key={item.field} {...getBindItemValue(item)}>
|
||||
{{
|
||||
label: () => (item.slots?.label ? item.slots?.label(item) : item.label),
|
||||
default: () =>
|
||||
item.slots?.default
|
||||
? item.slots?.default(props.data)
|
||||
: get(props.data, item.field)
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
</ElCollapseTransition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-descriptions';
|
||||
|
||||
:deep(.@{elNamespace}-descriptions__header) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.@{prefix-cls}-header {
|
||||
&__title {
|
||||
&::after {
|
||||
|
||||
@@ -8,4 +8,8 @@ export interface DescriptionsSchema {
|
||||
labelAlign?: 'left' | 'center' | 'right'
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
label?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ const props = defineProps({
|
||||
modelValue: propTypes.bool.def(false),
|
||||
title: propTypes.string.def('Dialog'),
|
||||
fullscreen: propTypes.bool.def(true),
|
||||
maxHeight: propTypes.oneOfType([String, Number]).def('500px')
|
||||
maxHeight: propTypes.oneOfType([String, Number]).def('400px')
|
||||
})
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
@@ -50,7 +50,6 @@ watch(
|
||||
)
|
||||
|
||||
const dialogStyle = computed(() => {
|
||||
console.log(unref(dialogHeight))
|
||||
return {
|
||||
height: unref(dialogHeight)
|
||||
}
|
||||
@@ -64,20 +63,34 @@ const dialogStyle = computed(() => {
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
draggable
|
||||
top="0"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<template #header="{ close }">
|
||||
<div class="flex justify-between items-center h-54px pl-15px pr-15px relative">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
<Icon
|
||||
v-if="fullscreen"
|
||||
class="mr-18px cursor-pointer is-hover mt-2px z-10"
|
||||
:icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'"
|
||||
color="var(--el-color-info)"
|
||||
@click="toggleFull"
|
||||
/>
|
||||
<div
|
||||
class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]"
|
||||
>
|
||||
<Icon
|
||||
v-if="fullscreen"
|
||||
class="cursor-pointer is-hover !h-54px mr-10px"
|
||||
:icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
|
||||
color="var(--el-color-info)"
|
||||
hover-color="var(--el-color-primary)"
|
||||
@click="toggleFull"
|
||||
/>
|
||||
<Icon
|
||||
class="cursor-pointer is-hover !h-54px"
|
||||
icon="ep:close"
|
||||
hover-color="var(--el-color-primary)"
|
||||
color="var(--el-color-info)"
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,28 +105,28 @@ const dialogStyle = computed(() => {
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.@{elNamespace}-dialog__header {
|
||||
margin-right: 0 !important;
|
||||
border-bottom: 1px solid var(--tags-view-border-color);
|
||||
.@{elNamespace}-overlay-dialog {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.@{elNamespace}-dialog__footer {
|
||||
border-top: 1px solid var(--tags-view-border-color);
|
||||
}
|
||||
|
||||
.is-hover {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.@{elNamespace}-dialog__header {
|
||||
.@{elNamespace}-dialog {
|
||||
margin: 0 !important;
|
||||
&__header {
|
||||
margin-right: 0 !important;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
.@{elNamespace}-dialog__footer {
|
||||
&__body {
|
||||
padding: 15px !important;
|
||||
}
|
||||
&__footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
&__headerbtn {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -99,7 +99,6 @@ const handleChange = (editor: IDomEditor) => {
|
||||
// 组件销毁时,及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = unref(editorRef.value)
|
||||
if (editor === null) return
|
||||
|
||||
// 销毁,并移除 editor
|
||||
editor?.destroy()
|
||||
@@ -116,12 +115,12 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border-1 border-solid border-[var(--tags-view-border-color)] z-99">
|
||||
<div class="border-1 border-solid border-[var(--el-border-color)] z-10">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar
|
||||
:editor="editorRef"
|
||||
:editorId="editorId"
|
||||
class="b-b-1 border-solid border-[var(--tags-view-border-color)]"
|
||||
class="border-0 b-b-1 border-solid border-[var(--el-border-color)]"
|
||||
/>
|
||||
<!-- 编辑器 -->
|
||||
<Editor
|
||||
|
||||
@@ -1,15 +1,48 @@
|
||||
import Form from './src/Form.vue'
|
||||
import { ElForm } from 'element-plus'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
import type { FormSchema, FormSetProps } from './src/types'
|
||||
export type {
|
||||
ComponentNameEnum,
|
||||
ComponentName,
|
||||
InputComponentProps,
|
||||
AutocompleteComponentProps,
|
||||
InputNumberComponentProps,
|
||||
SelectOption,
|
||||
SelectComponentProps,
|
||||
SelectV2ComponentProps,
|
||||
CascaderComponentProps,
|
||||
SwitchComponentProps,
|
||||
RateComponentProps,
|
||||
ColorPickerComponentProps,
|
||||
TransferComponentProps,
|
||||
RadioOption,
|
||||
RadioGroupComponentProps,
|
||||
RadioButtonComponentProps,
|
||||
CheckboxOption,
|
||||
CheckboxGroupComponentProps,
|
||||
DividerComponentProps,
|
||||
DatePickerComponentProps,
|
||||
DateTimePickerComponentProps,
|
||||
TimePickerComponentProps,
|
||||
TimeSelectComponentProps,
|
||||
ColProps,
|
||||
FormSetProps,
|
||||
FormItemProps,
|
||||
FormSchema,
|
||||
FormProps,
|
||||
PlaceholderModel,
|
||||
InputPasswordComponentProps,
|
||||
TreeSelectComponentProps
|
||||
} from './src/types'
|
||||
|
||||
export interface FormExpose {
|
||||
setValues: (data: Recordable) => void
|
||||
setProps: (props: Recordable) => void
|
||||
delSchema: (field: string) => void
|
||||
addSchema: (formSchema: FormSchema, index?: number) => void
|
||||
setSchema: (schemaProps: FormSetPropsType[]) => void
|
||||
setSchema: (schemaProps: FormSetProps[]) => void
|
||||
formModel: Recordable
|
||||
getElFormRef: () => ComponentRef<typeof ElForm>
|
||||
getComponentExpose: (field: string) => any
|
||||
getFormItemExpose: (field: string) => any
|
||||
}
|
||||
|
||||
export { Form }
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
<script lang="tsx">
|
||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
||||
import { componentMap } from './componentMap'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import {
|
||||
setTextPlaceholder,
|
||||
setGridProp,
|
||||
setComponentProps,
|
||||
setItemComponentSlots,
|
||||
initModel,
|
||||
setFormItemSlots
|
||||
} from './helper'
|
||||
import { useRenderSelect } from './components/useRenderSelect'
|
||||
import { useRenderRadio } from './components/useRenderRadio'
|
||||
import { useRenderCheckbox } from './components/useRenderCheckbox'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { findIndex } from '@/utils'
|
||||
import { set } from 'lodash-es'
|
||||
import { FormProps } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('form')
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Form',
|
||||
props: {
|
||||
// 生成Form的布局结构数组
|
||||
schema: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否需要栅格布局
|
||||
isCol: propTypes.bool.def(true),
|
||||
// 表单数据对象
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 是否自动设置placeholder
|
||||
autoSetPlaceholder: propTypes.bool.def(true),
|
||||
// 是否自定义内容
|
||||
isCustom: propTypes.bool.def(false),
|
||||
// 表单label宽度
|
||||
labelWidth: propTypes.oneOfType([String, Number]).def('auto')
|
||||
},
|
||||
emits: ['register'],
|
||||
setup(props, { slots, expose, emit }) {
|
||||
// element form 实例
|
||||
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
||||
|
||||
// useForm传入的props
|
||||
const outsideProps = ref<FormProps>({})
|
||||
|
||||
const mergeProps = ref<FormProps>({})
|
||||
|
||||
const getProps = computed(() => {
|
||||
const propsObj = { ...props }
|
||||
Object.assign(propsObj, unref(mergeProps))
|
||||
return propsObj
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formModel = ref<Recordable>({})
|
||||
|
||||
onMounted(() => {
|
||||
emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
|
||||
})
|
||||
|
||||
// 对表单赋值
|
||||
const setValues = (data: Recordable = {}) => {
|
||||
formModel.value = Object.assign(unref(formModel), data)
|
||||
}
|
||||
|
||||
const setProps = (props: FormProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const delSchema = (field: string) => {
|
||||
const { schema } = unref(getProps)
|
||||
|
||||
const index = findIndex(schema, (v: FormSchema) => v.field === field)
|
||||
if (index > -1) {
|
||||
schema.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const addSchema = (formSchema: FormSchema, index?: number) => {
|
||||
const { schema } = unref(getProps)
|
||||
if (index !== void 0) {
|
||||
schema.splice(index, 0, formSchema)
|
||||
return
|
||||
}
|
||||
schema.push(formSchema)
|
||||
}
|
||||
|
||||
const setSchema = (schemaProps: FormSetPropsType[]) => {
|
||||
const { schema } = unref(getProps)
|
||||
for (const v of schema) {
|
||||
for (const item of schemaProps) {
|
||||
if (v.field === item.field) {
|
||||
set(v, item.path, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getElFormRef = (): ComponentRef<typeof ElForm> => {
|
||||
return unref(elFormRef) as ComponentRef<typeof ElForm>
|
||||
}
|
||||
|
||||
expose({
|
||||
setValues,
|
||||
formModel,
|
||||
setProps,
|
||||
delSchema,
|
||||
addSchema,
|
||||
setSchema,
|
||||
getElFormRef
|
||||
})
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(getProps).schema,
|
||||
(schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
// 渲染包裹标签,是否使用栅格布局
|
||||
const renderWrap = () => {
|
||||
const { isCol } = unref(getProps)
|
||||
const content = isCol ? (
|
||||
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
|
||||
) : (
|
||||
renderFormItemWrap()
|
||||
)
|
||||
return content
|
||||
}
|
||||
|
||||
// 是否要渲染el-col
|
||||
const renderFormItemWrap = () => {
|
||||
// hidden属性表示隐藏,不做渲染
|
||||
const { schema = [], isCol } = unref(getProps)
|
||||
|
||||
return schema
|
||||
.filter((v) => !v.hidden)
|
||||
.map((item) => {
|
||||
// 如果是 Divider 组件,需要自己占用一行
|
||||
const isDivider = item.component === 'Divider'
|
||||
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
|
||||
return isDivider ? (
|
||||
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
|
||||
) : isCol ? (
|
||||
// 如果需要栅格,需要包裹 ElCol
|
||||
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
|
||||
) : (
|
||||
renderFormItem(item)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染formItem
|
||||
const renderFormItem = (item: FormSchema) => {
|
||||
// 单独给只有options属性的组件做判断
|
||||
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
|
||||
const componentSlots = (item?.componentProps as any)?.slots || {}
|
||||
const slotsMap: Recordable = {
|
||||
...setItemComponentSlots(unref(formModel), componentSlots)
|
||||
}
|
||||
if (
|
||||
item?.component !== 'SelectV2' &&
|
||||
item?.component !== 'Cascader' &&
|
||||
item?.componentProps?.options
|
||||
) {
|
||||
slotsMap.default = () => renderOptions(item)
|
||||
}
|
||||
|
||||
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||
// 如果有 labelMessage,自动使用插槽渲染
|
||||
if (item?.labelMessage) {
|
||||
formItemSlots.label = () => {
|
||||
return (
|
||||
<>
|
||||
<span>{item.label}</span>
|
||||
<ElTooltip placement="right" raw-content>
|
||||
{{
|
||||
content: () => <span v-html={item.labelMessage}></span>,
|
||||
default: () => (
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
size={16}
|
||||
color="var(--el-color-primary)"
|
||||
class="ml-2px relative top-1px"
|
||||
></Icon>
|
||||
)
|
||||
}}
|
||||
</ElTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
|
||||
{{
|
||||
...formItemSlots,
|
||||
default: () => {
|
||||
const Com = componentMap[item.component as string] as ReturnType<
|
||||
typeof defineComponent
|
||||
>
|
||||
|
||||
const { autoSetPlaceholder } = unref(getProps)
|
||||
|
||||
return slots[item.field] ? (
|
||||
getSlot(slots, item.field, formModel.value)
|
||||
) : (
|
||||
<Com
|
||||
vModel={formModel.value[item.field]}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style}
|
||||
{...(notRenderOptions.includes(item?.component as string) &&
|
||||
item?.componentProps?.options
|
||||
? { options: item?.componentProps?.options || [] }
|
||||
: {})}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
|
||||
// 渲染options
|
||||
const renderOptions = (item: FormSchema) => {
|
||||
switch (item.component) {
|
||||
case 'Select':
|
||||
const { renderSelectOptions } = useRenderSelect(slots)
|
||||
return renderSelectOptions(item)
|
||||
case 'Radio':
|
||||
case 'RadioButton':
|
||||
const { renderRadioOptions } = useRenderRadio()
|
||||
return renderRadioOptions(item)
|
||||
case 'Checkbox':
|
||||
case 'CheckboxButton':
|
||||
const { renderCheckboxOptions } = useRenderCheckbox()
|
||||
return renderCheckboxOptions(item)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤传入Form组件的属性
|
||||
const getFormBindValue = () => {
|
||||
// 避免在标签上出现多余的属性
|
||||
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
|
||||
const props = { ...unref(getProps) }
|
||||
for (const key in props) {
|
||||
if (delKeys.indexOf(key) !== -1) {
|
||||
delete props[key]
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
return () => (
|
||||
<ElForm
|
||||
ref={elFormRef}
|
||||
{...getFormBindValue()}
|
||||
model={props.isCustom ? props.model : formModel}
|
||||
class={prefixCls}
|
||||
>
|
||||
{{
|
||||
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
||||
default: () => {
|
||||
const { isCustom } = unref(getProps)
|
||||
return isCustom ? getSlot(slots, 'default') : renderWrap()
|
||||
}
|
||||
}}
|
||||
</ElForm>
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.@{elNamespace}-form.@{namespace}-form .@{elNamespace}-row {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,15 @@
|
||||
<script lang="tsx">
|
||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
||||
import { componentMap } from './componentMap'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRow,
|
||||
ElCol,
|
||||
FormRules,
|
||||
ComponentSize
|
||||
// FormItemProp
|
||||
} from 'element-plus'
|
||||
import { componentMap } from './helper/componentMap'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import {
|
||||
@@ -9,19 +17,27 @@ import {
|
||||
setGridProp,
|
||||
setComponentProps,
|
||||
setItemComponentSlots,
|
||||
initModel,
|
||||
setFormItemSlots
|
||||
initModel
|
||||
} from './helper'
|
||||
import { useRenderSelect } from './components/useRenderSelect'
|
||||
import { useRenderRadio } from './components/useRenderRadio'
|
||||
import { useRenderCheckbox } from './components/useRenderCheckbox'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { findIndex } from '@/utils'
|
||||
import { set } from 'lodash-es'
|
||||
import { get, set } from 'lodash-es'
|
||||
import { FormProps } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
import { ComponentNameEnum, SelectComponentProps } from '@/types/components.d'
|
||||
import {
|
||||
FormSchema,
|
||||
FormSetProps,
|
||||
ComponentNameEnum,
|
||||
SelectComponentProps,
|
||||
RadioGroupComponentProps,
|
||||
CheckboxGroupComponentProps
|
||||
} from './types'
|
||||
|
||||
const { renderSelectOptions } = useRenderSelect()
|
||||
const { renderRadioOptions } = useRenderRadio()
|
||||
const { renderCheckboxOptions } = useRenderCheckbox()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -39,7 +55,7 @@ export default defineComponent({
|
||||
isCol: propTypes.bool.def(true),
|
||||
// 表单数据对象
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 是否自动设置placeholder
|
||||
@@ -47,7 +63,30 @@ export default defineComponent({
|
||||
// 是否自定义内容
|
||||
isCustom: propTypes.bool.def(false),
|
||||
// 表单label宽度
|
||||
labelWidth: propTypes.oneOfType([String, Number]).def('auto')
|
||||
labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
|
||||
rules: {
|
||||
type: Object as PropType<FormRules>,
|
||||
default: () => ({})
|
||||
},
|
||||
labelPosition: propTypes.oneOf(['left', 'right', 'top']).def('right'),
|
||||
labelSuffix: propTypes.string.def(''),
|
||||
hideRequiredAsterisk: propTypes.bool.def(false),
|
||||
requireAsteriskPosition: propTypes.oneOf(['left', 'right']).def('left'),
|
||||
showMessage: propTypes.bool.def(true),
|
||||
inlineMessage: propTypes.bool.def(false),
|
||||
statusIcon: propTypes.bool.def(false),
|
||||
validateOnRuleChange: propTypes.bool.def(true),
|
||||
size: {
|
||||
type: String as PropType<ComponentSize>,
|
||||
default: undefined
|
||||
},
|
||||
disabled: propTypes.bool.def(false),
|
||||
scrollToError: propTypes.bool.def(false),
|
||||
scrollToErrorOffset: propTypes.oneOfType([Boolean, Object]).def(undefined)
|
||||
// onValidate: {
|
||||
// type: Function as PropType<(prop: FormItemProp, isValid: boolean, message: string) => void>,
|
||||
// default: () => {}
|
||||
// }
|
||||
},
|
||||
emits: ['register'],
|
||||
setup(props, { slots, expose, emit }) {
|
||||
@@ -65,8 +104,14 @@ export default defineComponent({
|
||||
return propsObj
|
||||
})
|
||||
|
||||
// 存储表单实例
|
||||
const formComponents = ref({})
|
||||
|
||||
// 存储form-item实例
|
||||
const formItemComponents = ref({})
|
||||
|
||||
// 表单数据
|
||||
const formModel = ref<Recordable>({})
|
||||
const formModel = ref<Recordable>(props.model)
|
||||
|
||||
onMounted(() => {
|
||||
emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
|
||||
@@ -79,6 +124,7 @@ export default defineComponent({
|
||||
|
||||
const setProps = (props: FormProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
// @ts-ignore
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
@@ -100,7 +146,7 @@ export default defineComponent({
|
||||
schema.push(formSchema)
|
||||
}
|
||||
|
||||
const setSchema = (schemaProps: FormSetPropsType[]) => {
|
||||
const setSchema = (schemaProps: FormSetProps[]) => {
|
||||
const { schema } = unref(getProps)
|
||||
for (const v of schema) {
|
||||
for (const item of schemaProps) {
|
||||
@@ -111,8 +157,42 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const getElFormRef = (): ComponentRef<typeof ElForm> => {
|
||||
return unref(elFormRef) as ComponentRef<typeof ElForm>
|
||||
const getOptions = async (fn: Function, item: FormSchema) => {
|
||||
const options = await fn()
|
||||
setSchema([
|
||||
{
|
||||
field: item.field,
|
||||
path:
|
||||
item.component === ComponentNameEnum.TREE_SELECT
|
||||
? 'componentProps.data'
|
||||
: 'componentProps.options',
|
||||
value: options
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取表单组件实例
|
||||
* @param filed 表单字段
|
||||
*/
|
||||
const getComponentExpose = (filed: string) => {
|
||||
return unref(formComponents)[filed]
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取formItem实例
|
||||
* @param filed 表单字段
|
||||
*/
|
||||
const getFormItemExpose = (filed: string) => {
|
||||
return unref(formItemComponents)[filed]
|
||||
}
|
||||
|
||||
const setComponentRefMap = (ref: any, filed: string) => {
|
||||
formComponents.value[filed] = ref
|
||||
}
|
||||
|
||||
const setFormItemRefMap = (ref: any, filed: string) => {
|
||||
formItemComponents.value[filed] = ref
|
||||
}
|
||||
|
||||
expose({
|
||||
@@ -122,7 +202,8 @@ export default defineComponent({
|
||||
delSchema,
|
||||
addSchema,
|
||||
setSchema,
|
||||
getElFormRef
|
||||
getComponentExpose,
|
||||
getFormItemExpose
|
||||
})
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
@@ -154,7 +235,7 @@ export default defineComponent({
|
||||
const { schema = [], isCol } = unref(getProps)
|
||||
|
||||
return schema
|
||||
.filter((v) => !v.hidden)
|
||||
.filter((v) => !v.remove)
|
||||
.map((item) => {
|
||||
// 如果是 Divider 组件,需要自己占用一行
|
||||
const isDivider = item.component === 'Divider'
|
||||
@@ -172,101 +253,121 @@ export default defineComponent({
|
||||
|
||||
// 渲染formItem
|
||||
const renderFormItem = (item: FormSchema) => {
|
||||
// 单独给只有options属性的组件做判断
|
||||
// const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
|
||||
const componentSlots = (item?.componentProps as any)?.slots || {}
|
||||
const slotsMap: Recordable = {
|
||||
...setItemComponentSlots(unref(formModel), componentSlots)
|
||||
// 如果有optionApi,优先使用optionApi
|
||||
if (item.optionApi) {
|
||||
// 内部自动调用接口,不影响其它渲染
|
||||
getOptions(item.optionApi, item)
|
||||
}
|
||||
// 如果是select组件,并且没有自定义模板,自动渲染options
|
||||
if (item.component === ComponentNameEnum.SELECT) {
|
||||
slotsMap.default = !componentSlots.default
|
||||
? () => renderOptions(item)
|
||||
: () => {
|
||||
return componentSlots.default(
|
||||
unref((item?.componentProps as SelectComponentProps)?.options)
|
||||
)
|
||||
const formItemSlots: Recordable = {
|
||||
default: () => {
|
||||
if (item?.formItemProps?.slots?.default) {
|
||||
return item?.formItemProps?.slots?.default(formModel.value)
|
||||
} else {
|
||||
const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
|
||||
|
||||
const { autoSetPlaceholder } = unref(getProps)
|
||||
|
||||
const componentSlots = (item?.componentProps as any)?.slots || {}
|
||||
const slotsMap: Recordable = {
|
||||
...setItemComponentSlots(componentSlots)
|
||||
}
|
||||
// // 如果是select组件,并且没有自定义模板,自动渲染options
|
||||
if (item.component === ComponentNameEnum.SELECT) {
|
||||
slotsMap.default = !componentSlots.default
|
||||
? () => renderSelectOptions(item)
|
||||
: () => {
|
||||
return componentSlots.default(
|
||||
unref((item?.componentProps as SelectComponentProps)?.options)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (
|
||||
// item?.component !== 'SelectV2' &&
|
||||
// item?.component !== 'Cascader' &&
|
||||
// item?.componentProps?.options
|
||||
// ) {
|
||||
// slotsMap.default = () => renderOptions(item)
|
||||
// }
|
||||
|
||||
// const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||
// 如果有 labelMessage,自动使用插槽渲染
|
||||
// if (item?.labelMessage) {
|
||||
// formItemSlots.label = () => {
|
||||
// return (
|
||||
// <>
|
||||
// <span>{item.label}</span>
|
||||
// <ElTooltip placement="right" raw-content>
|
||||
// {{
|
||||
// content: () => <span v-html={item.labelMessage}></span>,
|
||||
// default: () => (
|
||||
// <Icon
|
||||
// icon="ep:warning"
|
||||
// size={16}
|
||||
// color="var(--el-color-primary)"
|
||||
// class="ml-2px relative top-1px"
|
||||
// ></Icon>
|
||||
// )
|
||||
// }}
|
||||
// </ElTooltip>
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
return (
|
||||
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
|
||||
{{
|
||||
default: () => {
|
||||
const Com = componentMap[item.component as string] as ReturnType<
|
||||
typeof defineComponent
|
||||
>
|
||||
// 虚拟列表
|
||||
if (item.component === ComponentNameEnum.SELECT_V2 && componentSlots.default) {
|
||||
slotsMap.default = ({ item }) => {
|
||||
return componentSlots.default(item)
|
||||
}
|
||||
}
|
||||
|
||||
const { autoSetPlaceholder } = unref(getProps)
|
||||
// 单选框组和按钮样式
|
||||
if (
|
||||
item.component === ComponentNameEnum.RADIO_GROUP ||
|
||||
item.component === ComponentNameEnum.RADIO_BUTTON
|
||||
) {
|
||||
slotsMap.default = !componentSlots.default
|
||||
? () => renderRadioOptions(item)
|
||||
: () => {
|
||||
return componentSlots.default(
|
||||
unref((item?.componentProps as CheckboxGroupComponentProps)?.options)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return slots[item.field] ? (
|
||||
getSlot(slots, item.field, formModel.value)
|
||||
) : (
|
||||
// 多选框组和按钮样式
|
||||
if (
|
||||
item.component === ComponentNameEnum.CHECKBOX_GROUP ||
|
||||
item.component === ComponentNameEnum.CHECKBOX_BUTTON
|
||||
) {
|
||||
slotsMap.default = !componentSlots.default
|
||||
? () => renderCheckboxOptions(item)
|
||||
: () => {
|
||||
return componentSlots.default(
|
||||
unref((item?.componentProps as RadioGroupComponentProps)?.options)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Comp = () => {
|
||||
// 如果field是多层路径,需要转换成对象
|
||||
const itemVal = computed({
|
||||
get: () => {
|
||||
return get(formModel.value, item.field)
|
||||
},
|
||||
set: (val) => {
|
||||
set(formModel.value, item.field, val)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Com
|
||||
vModel={formModel.value[item.field]}
|
||||
vModel={itemVal.value}
|
||||
ref={(el: any) => setComponentRefMap(el, item.field)}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style}
|
||||
style={item.componentProps?.style || {}}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
)
|
||||
}
|
||||
}}
|
||||
|
||||
return <>{Comp()}</>
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item?.formItemProps?.slots?.label) {
|
||||
formItemSlots.label = (...args: any[]) => {
|
||||
return (item?.formItemProps?.slots as any)?.label(...args)
|
||||
}
|
||||
}
|
||||
if (item?.formItemProps?.slots?.error) {
|
||||
formItemSlots.error = (...args: any[]) => {
|
||||
return (item?.formItemProps?.slots as any)?.error(...args)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElFormItem
|
||||
v-show={!item.hidden}
|
||||
ref={(el: any) => setFormItemRefMap(el, item.field)}
|
||||
{...(item.formItemProps || {})}
|
||||
prop={item.field}
|
||||
label={item.label || ''}
|
||||
>
|
||||
{formItemSlots}
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
|
||||
// 渲染options
|
||||
const renderOptions = (item: FormSchema) => {
|
||||
switch (item.component) {
|
||||
case ComponentNameEnum.SELECT:
|
||||
const { renderSelectOptions } = useRenderSelect(slots)
|
||||
return renderSelectOptions(item)
|
||||
case 'Radio':
|
||||
case 'RadioButton':
|
||||
const { renderRadioOptions } = useRenderRadio()
|
||||
return renderRadioOptions(item)
|
||||
case 'Checkbox':
|
||||
case 'CheckboxButton':
|
||||
const { renderCheckboxOptions } = useRenderCheckbox()
|
||||
return renderCheckboxOptions(item)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤传入Form组件的属性
|
||||
const getFormBindValue = () => {
|
||||
// 避免在标签上出现多余的属性
|
||||
@@ -277,14 +378,14 @@ export default defineComponent({
|
||||
delete props[key]
|
||||
}
|
||||
}
|
||||
return props
|
||||
return props as FormProps
|
||||
}
|
||||
|
||||
return () => (
|
||||
<ElForm
|
||||
ref={elFormRef}
|
||||
{...getFormBindValue()}
|
||||
model={props.isCustom ? props.model : formModel}
|
||||
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
|
||||
class={prefixCls}
|
||||
>
|
||||
{{
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { FormSchema, ComponentNameEnum, CheckboxGroupComponentProps } from '../types'
|
||||
import { ElCheckbox, ElCheckboxButton } from 'element-plus'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export const useRenderCheckbox = () => {
|
||||
const renderCheckboxOptions = (item: FormSchema) => {
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
const valueAlias = item?.componentProps?.optionsAlias?.valueField
|
||||
const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType<
|
||||
typeof defineComponent
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
const componentProps = item?.componentProps as CheckboxGroupComponentProps
|
||||
const valueAlias = componentProps?.props?.value || 'value'
|
||||
const labelAlias = componentProps?.props?.label || 'label'
|
||||
const disabledAlias = componentProps?.props?.disabled || 'disabled'
|
||||
const Com = (
|
||||
item.component === ComponentNameEnum.CHECKBOX_GROUP ? ElCheckbox : ElCheckboxButton
|
||||
) as ReturnType<typeof defineComponent>
|
||||
return componentProps?.options?.map((option) => {
|
||||
const { value, ...other } = option
|
||||
return (
|
||||
<Com {...other} label={option[valueAlias || 'value']}>
|
||||
<Com
|
||||
{...other}
|
||||
disabled={option[disabledAlias || 'disabled']}
|
||||
label={option[valueAlias || 'value']}
|
||||
>
|
||||
{option[labelAlias || 'label']}
|
||||
</Com>
|
||||
)
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { FormSchema, ComponentNameEnum, RadioGroupComponentProps } from '../types'
|
||||
import { ElRadio, ElRadioButton } from 'element-plus'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export const useRenderRadio = () => {
|
||||
const renderRadioOptions = (item: FormSchema) => {
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
const valueAlias = item?.componentProps?.optionsAlias?.valueField
|
||||
const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType<
|
||||
typeof defineComponent
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
const componentProps = item?.componentProps as RadioGroupComponentProps
|
||||
const valueAlias = componentProps?.props?.value || 'value'
|
||||
const labelAlias = componentProps?.props?.label || 'label'
|
||||
const disabledAlias = componentProps?.props?.disabled || 'disabled'
|
||||
const Com = (
|
||||
item.component === ComponentNameEnum.RADIO_GROUP ? ElRadio : ElRadioButton
|
||||
) as ReturnType<typeof defineComponent>
|
||||
return componentProps?.options?.map((option) => {
|
||||
const { value, ...other } = option
|
||||
return (
|
||||
<Com {...other} label={option[valueAlias || 'value']}>
|
||||
<Com
|
||||
{...other}
|
||||
disabled={option[disabledAlias || 'disabled']}
|
||||
label={option[valueAlias || 'value']}
|
||||
>
|
||||
{option[labelAlias || 'label']}
|
||||
</Com>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { ElOption, ElOptionGroup } from 'element-plus'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { Slots } from 'vue'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { SelectComponentProps, SelectOption } from '@/types/components'
|
||||
import { FormSchema, SelectComponentProps, SelectOption } from '../types'
|
||||
|
||||
export const useRenderSelect = (slots: Slots) => {
|
||||
export const useRenderSelect = () => {
|
||||
// 渲染 select options
|
||||
const renderSelectOptions = (item: FormSchema) => {
|
||||
const componentsProps = item.componentProps as SelectComponentProps
|
||||
const optionGroupDefaultSlot = componentsProps.slots?.optionGroupDefault
|
||||
const componentsProps = item?.componentProps as SelectComponentProps
|
||||
const optionGroupDefaultSlot = componentsProps?.slots?.optionGroupDefault
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = componentsProps?.labelAlias
|
||||
const labelAlias = componentsProps?.props?.label
|
||||
const keyAlias = componentsProps?.props?.key
|
||||
return componentsProps?.options?.map((option) => {
|
||||
if (option?.options?.length) {
|
||||
return optionGroupDefaultSlot ? (
|
||||
optionGroupDefaultSlot(option)
|
||||
) : (
|
||||
<ElOptionGroup label={option[labelAlias || 'label']}>
|
||||
<ElOptionGroup label={option[labelAlias || 'label']} key={option[keyAlias || 'key']}>
|
||||
{{
|
||||
default: () =>
|
||||
option?.options?.map((v) => {
|
||||
@@ -35,17 +33,17 @@ export const useRenderSelect = (slots: Slots) => {
|
||||
const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
|
||||
// 如果有别名,就取别名
|
||||
const componentsProps = item.componentProps as SelectComponentProps
|
||||
const labelAlias = componentsProps?.labelAlias
|
||||
const valueAlias = componentsProps?.valueAlias
|
||||
const labelAlias = componentsProps?.props?.label
|
||||
const valueAlias = componentsProps?.props?.value
|
||||
const keyAlias = componentsProps?.props?.key
|
||||
const optionDefaultSlot = componentsProps.slots?.optionDefault
|
||||
|
||||
const { label, value, ...other } = option
|
||||
|
||||
return (
|
||||
<ElOption
|
||||
{...other}
|
||||
label={labelAlias ? option[labelAlias] : label}
|
||||
value={valueAlias ? option[valueAlias] : value}
|
||||
{...option}
|
||||
key={option[keyAlias || 'key']}
|
||||
label={option[labelAlias || 'label']}
|
||||
value={option[valueAlias || 'value']}
|
||||
>
|
||||
{{
|
||||
default: () => (optionDefaultSlot ? optionDefaultSlot(option) : undefined)
|
||||
|
||||
@@ -16,15 +16,18 @@ import {
|
||||
ElTimeSelect,
|
||||
ElTransfer,
|
||||
ElAutocomplete,
|
||||
ElDivider
|
||||
ElDivider,
|
||||
ElTreeSelect,
|
||||
ElUpload
|
||||
} from 'element-plus'
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { ComponentName } from '@/types/components'
|
||||
import { ComponentName } from '../types'
|
||||
|
||||
const componentMap: Recordable<Component, ComponentName> = {
|
||||
Radio: ElRadioGroup,
|
||||
Checkbox: ElCheckboxGroup,
|
||||
RadioGroup: ElRadioGroup,
|
||||
RadioButton: ElRadioGroup,
|
||||
CheckboxGroup: ElCheckboxGroup,
|
||||
CheckboxButton: ElCheckboxGroup,
|
||||
Input: ElInput,
|
||||
Autocomplete: ElAutocomplete,
|
||||
@@ -41,9 +44,10 @@ const componentMap: Recordable<Component, ComponentName> = {
|
||||
Divider: ElDivider,
|
||||
TimeSelect: ElTimeSelect,
|
||||
SelectV2: ElSelectV2,
|
||||
RadioButton: ElRadioGroup,
|
||||
InputPassword: InputPassword,
|
||||
Editor: Editor
|
||||
Editor: Editor,
|
||||
TreeSelect: ElTreeSelect,
|
||||
Upload: ElUpload
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { unref, type Slots } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { PlaceholderMoel } from './types'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ColProps } from '@/types/components'
|
||||
import { PlaceholderModel, FormSchema, ComponentNameEnum, ColProps } from '../types'
|
||||
import { isFunction } from '@/utils/is'
|
||||
import { firstUpperCase, humpToDash } from '@/utils'
|
||||
import { set, get } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -14,20 +12,32 @@ const { t } = useI18n()
|
||||
* @returns 返回提示信息对象
|
||||
* @description 用于自动设置placeholder
|
||||
*/
|
||||
export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
|
||||
const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
|
||||
const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
|
||||
if (textMap.includes(schema?.component as string)) {
|
||||
export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
|
||||
const textMap = [
|
||||
ComponentNameEnum.INPUT,
|
||||
ComponentNameEnum.AUTOCOMPLETE,
|
||||
ComponentNameEnum.INPUT_NUMBER,
|
||||
ComponentNameEnum.INPUT_PASSWORD
|
||||
]
|
||||
const selectMap = [
|
||||
ComponentNameEnum.SELECT,
|
||||
ComponentNameEnum.TIME_PICKER,
|
||||
ComponentNameEnum.DATE_PICKER,
|
||||
ComponentNameEnum.TIME_SELECT,
|
||||
ComponentNameEnum.SELECT_V2
|
||||
]
|
||||
if (textMap.includes(schema?.component as ComponentNameEnum)) {
|
||||
return {
|
||||
placeholder: t('common.inputText')
|
||||
}
|
||||
}
|
||||
if (selectMap.includes(schema?.component as string)) {
|
||||
if (selectMap.includes(schema?.component as ComponentNameEnum)) {
|
||||
// 一些范围选择器
|
||||
const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']
|
||||
if (
|
||||
twoTextMap.includes(
|
||||
(schema?.componentProps?.type || schema?.componentProps?.isRange) as string
|
||||
((schema?.componentProps as any)?.type ||
|
||||
(schema?.componentProps as any)?.isRange) as string
|
||||
)
|
||||
) {
|
||||
return {
|
||||
@@ -74,14 +84,30 @@ export const setGridProp = (col: ColProps = {}): ColProps => {
|
||||
*/
|
||||
export const setComponentProps = (item: FormSchema): Recordable => {
|
||||
// const notNeedClearable = ['ColorPicker']
|
||||
// 拆分事件并组合
|
||||
const onEvents = (item?.componentProps as any)?.on || {}
|
||||
const newOnEvents: Recordable = {}
|
||||
|
||||
for (const key in onEvents) {
|
||||
if (onEvents[key]) {
|
||||
newOnEvents[`on${firstUpperCase(key)}`] = (...args: any[]) => {
|
||||
onEvents[key](...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const componentProps: Recordable = {
|
||||
clearable: true,
|
||||
...item.componentProps
|
||||
...item.componentProps,
|
||||
...newOnEvents
|
||||
}
|
||||
// 需要删除额外的属性
|
||||
if (componentProps.slots) {
|
||||
delete componentProps.slots
|
||||
}
|
||||
if (componentProps.on) {
|
||||
delete componentProps.on
|
||||
}
|
||||
return componentProps
|
||||
}
|
||||
|
||||
@@ -90,16 +116,16 @@ export const setComponentProps = (item: FormSchema): Recordable => {
|
||||
* @param formModel 表单数据
|
||||
* @param slotsProps 插槽属性
|
||||
*/
|
||||
export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = {}): Recordable => {
|
||||
export const setItemComponentSlots = (slotsProps: Recordable = {}): Recordable => {
|
||||
const slotObj: Recordable = {}
|
||||
for (const key in slotsProps) {
|
||||
if (slotsProps[key]) {
|
||||
if (isFunction(slotsProps[key])) {
|
||||
slotObj[key] = () => {
|
||||
return slotsProps[key]?.(formModel)
|
||||
slotObj[humpToDash(key)] = (...args: any[]) => {
|
||||
return slotsProps[key]?.(...args)
|
||||
}
|
||||
} else {
|
||||
slotObj[key] = () => {
|
||||
slotObj[humpToDash(key)] = () => {
|
||||
return slotsProps[key]
|
||||
}
|
||||
}
|
||||
@@ -118,34 +144,19 @@ export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = {
|
||||
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
|
||||
const model: Recordable = { ...formModel }
|
||||
schema.map((v) => {
|
||||
// 如果是hidden,就删除对应的值
|
||||
if (v.hidden) {
|
||||
if (v.remove) {
|
||||
delete model[v.field]
|
||||
} else if (v.component && v.component !== 'Divider') {
|
||||
const hasField = Reflect.has(model, v.field)
|
||||
} else if (v.component !== 'Divider') {
|
||||
// const hasField = Reflect.has(model, v.field)
|
||||
const hasField = get(model, v.field)
|
||||
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
|
||||
model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : ''
|
||||
set(
|
||||
model,
|
||||
v.field,
|
||||
hasField !== void 0 ? get(model, v.field) : v.value !== void 0 ? v.value : undefined
|
||||
)
|
||||
// model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
|
||||
}
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
/**
|
||||
* @param slots 插槽
|
||||
* @param field 字段名
|
||||
* @returns 返回FormIiem插槽
|
||||
*/
|
||||
export const setFormItemSlots = (slots: Slots, field: string): Recordable => {
|
||||
const slotObj: Recordable = {}
|
||||
if (slots[`${field}-error`]) {
|
||||
slotObj['error'] = (data: Recordable) => {
|
||||
return getSlot(slots, `${field}-error`, data)
|
||||
}
|
||||
}
|
||||
if (slots[`${field}-label`]) {
|
||||
slotObj['label'] = (data: Recordable) => {
|
||||
return getSlot(slots, `${field}-label`, data)
|
||||
}
|
||||
}
|
||||
return slotObj
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
export interface PlaceholderMoel {
|
||||
placeholder?: string
|
||||
startPlaceholder?: string
|
||||
endPlaceholder?: string
|
||||
rangeSeparator?: string
|
||||
}
|
||||
|
||||
export type FormProps = {
|
||||
schema?: FormSchema[]
|
||||
isCol?: boolean
|
||||
model?: Recordable
|
||||
autoSetPlaceholder?: boolean
|
||||
isCustom?: boolean
|
||||
labelWidth?: string | number
|
||||
} & Recordable
|
||||
663
src/components/Form/src/types/index.ts
Normal file
663
src/components/Form/src/types/index.ts
Normal file
@@ -0,0 +1,663 @@
|
||||
import {
|
||||
AutocompleteProps,
|
||||
InputNumberProps,
|
||||
CascaderProps,
|
||||
CascaderNode,
|
||||
CascaderValue,
|
||||
SwitchProps,
|
||||
ComponentSize,
|
||||
InputProps,
|
||||
RateProps,
|
||||
ColorPickerProps,
|
||||
TransferProps,
|
||||
RadioGroupProps,
|
||||
RadioButtonProps,
|
||||
CheckboxGroupProps,
|
||||
DividerProps,
|
||||
DatePickerProps,
|
||||
FormItemProps as ElFormItemProps,
|
||||
FormProps as ElFormProps,
|
||||
ISelectProps,
|
||||
UploadProps
|
||||
} from 'element-plus'
|
||||
import { IEditorConfig } from '@wangeditor/editor'
|
||||
import { CSSProperties } from 'vue'
|
||||
|
||||
export interface PlaceholderModel {
|
||||
placeholder?: string
|
||||
startPlaceholder?: string
|
||||
endPlaceholder?: string
|
||||
rangeSeparator?: string
|
||||
}
|
||||
|
||||
export enum ComponentNameEnum {
|
||||
RADIO_GROUP = 'RadioGroup',
|
||||
RADIO_BUTTON = 'RadioButton',
|
||||
CHECKBOX_GROUP = 'CheckboxGroup',
|
||||
CHECKBOX_BUTTON = 'CheckboxButton',
|
||||
INPUT = 'Input',
|
||||
AUTOCOMPLETE = 'Autocomplete',
|
||||
INPUT_NUMBER = 'InputNumber',
|
||||
SELECT = 'Select',
|
||||
CASCADER = 'Cascader',
|
||||
SWITCH = 'Switch',
|
||||
SLIDER = 'Slider',
|
||||
TIME_PICKER = 'TimePicker',
|
||||
DATE_PICKER = 'DatePicker',
|
||||
RATE = 'Rate',
|
||||
COLOR_PICKER = 'ColorPicker',
|
||||
TRANSFER = 'Transfer',
|
||||
DIVIDER = 'Divider',
|
||||
TIME_SELECT = 'TimeSelect',
|
||||
SELECT_V2 = 'SelectV2',
|
||||
INPUT_PASSWORD = 'InputPassword',
|
||||
EDITOR = 'Editor',
|
||||
TREE_SELECT = 'TreeSelect',
|
||||
UPLOAD = 'Upload'
|
||||
}
|
||||
|
||||
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
|
||||
? K extends string
|
||||
? K extends `${infer A}_${infer B}`
|
||||
? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
|
||||
: Capitalize<Lowercase<K>>
|
||||
: never
|
||||
: never
|
||||
|
||||
export type ComponentName = CamelCaseComponentName
|
||||
|
||||
export interface InputPasswordComponentProps {
|
||||
strength?: boolean
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface InputComponentProps extends Partial<InputProps> {
|
||||
rows?: number
|
||||
on?: {
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
change?: (value: string | number) => void
|
||||
clear?: () => void
|
||||
input?: (value: string | number) => void
|
||||
}
|
||||
slots?: {
|
||||
prefix?: (...args: any[]) => JSX.Element | null
|
||||
suffix?: (...args: any[]) => JSX.Element | null
|
||||
prepend?: (...args: any[]) => JSX.Element | null
|
||||
append?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface AutocompleteComponentProps extends Partial<AutocompleteProps> {
|
||||
on?: {
|
||||
select?: (item: any) => void
|
||||
change?: (value: string | number) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
prefix?: (...args: any[]) => JSX.Element | null
|
||||
suffix?: (...args: any[]) => JSX.Element | null
|
||||
prepend?: (...args: any[]) => JSX.Element | null
|
||||
append?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface InputNumberComponentProps extends Partial<InputNumberProps> {
|
||||
on?: {
|
||||
change?: (currentValue: number | undefined, oldValue: number | undefined) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface SelectOption {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
value?: any
|
||||
key?: string | number
|
||||
options?: SelectOption[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface SelectComponentProps extends Omit<Partial<ISelectProps>, 'options'> {
|
||||
/**
|
||||
* 数据源的字段别名
|
||||
*/
|
||||
props?: {
|
||||
key?: string
|
||||
value?: string
|
||||
label?: string
|
||||
children?: string
|
||||
}
|
||||
on?: {
|
||||
change?: (value: string | number | boolean | Object) => void
|
||||
visibleChange?: (visible: boolean) => void
|
||||
removeTag?: (tag: any) => void
|
||||
clear?: () => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (options: SelectOption[]) => JSX.Element[] | null
|
||||
optionGroupDefault?: (item: SelectOption) => JSX.Element
|
||||
optionDefault?: (option: SelectOption) => JSX.Element | null
|
||||
prefix?: (...args: any[]) => JSX.Element | null
|
||||
empty?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
options?: SelectOption[]
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface SelectV2ComponentProps {
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
valueKey?: string
|
||||
size?: ComponentSize
|
||||
clearable?: boolean
|
||||
clearIcon?: string | JSX.Element | null
|
||||
collapseTags?: boolean
|
||||
multipleLimit?: number
|
||||
name?: string
|
||||
effect?: string
|
||||
autocomplete?: string
|
||||
placeholder?: string
|
||||
filterable?: boolean
|
||||
allowCreate?: boolean
|
||||
reserveKeyword?: boolean
|
||||
noDataText?: string
|
||||
popperClass?: string
|
||||
teleported?: boolean
|
||||
persistent?: boolean
|
||||
popperOptions?: any
|
||||
automaticDropdown?: boolean
|
||||
height?: number
|
||||
scrollbarAlwaysOn?: boolean
|
||||
remote?: boolean
|
||||
remoteMethod?: (query: string) => void
|
||||
validateEvent?: boolean
|
||||
placement?: AutocompleteProps['placement']
|
||||
collapseTagsTooltip?: boolean
|
||||
on?: {
|
||||
change?: (value: string | number | boolean | Object) => void
|
||||
visibleChange?: (visible: boolean) => void
|
||||
removeTag?: (tag: any) => void
|
||||
clear?: () => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
options?: SelectOption[]
|
||||
slots?: {
|
||||
default?: (option: SelectOption) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface CascaderComponentProps {
|
||||
options?: Record<string, unknown>[]
|
||||
props?: CascaderProps
|
||||
size?: ComponentSize
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
showAllLevels?: boolean
|
||||
collapseTags?: boolean
|
||||
collapseTagsTooltip?: boolean
|
||||
separator?: string
|
||||
filterable?: boolean
|
||||
filterMethod?: (node: CascaderNode, keyword: string) => boolean
|
||||
debounce?: number
|
||||
beforeFilter?: (value: string) => boolean
|
||||
popperClass?: string
|
||||
teleported?: boolean
|
||||
tagType?: ElementPlusInfoType
|
||||
validateEvent?: boolean
|
||||
on?: {
|
||||
change?: (value: CascaderValue) => void
|
||||
expandChange?: (value: CascaderValue) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
visibleChange?: (value: boolean) => void
|
||||
removeTag?: (value: CascaderNode['valueByOption']) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
empty?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface SwitchComponentProps extends Partial<SwitchProps> {
|
||||
on?: {
|
||||
change?: (value: boolean | string | number) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface RateComponentProps extends Partial<RateProps> {
|
||||
on?: {
|
||||
change?: (value: number) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface ColorPickerComponentProps extends Partial<ColorPickerProps> {
|
||||
on?: {
|
||||
change?: (value: string) => void
|
||||
activeChange?: (value: string) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface TransferComponentProps extends Partial<TransferProps> {
|
||||
on?: {
|
||||
change?: (
|
||||
value: number | string,
|
||||
direction: 'left' | 'right',
|
||||
movedKeys: string[] | number[]
|
||||
) => void
|
||||
leftCheckChange?: (value: any[]) => void
|
||||
rightCheckChange?: (value: any[]) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
leftFooter?: (...args: any[]) => JSX.Element | null
|
||||
rightFooter?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface RadioOption {
|
||||
label?: string
|
||||
value?: string | number | boolean
|
||||
disabled?: boolean
|
||||
border?: boolean
|
||||
size?: ComponentSize
|
||||
name?: string
|
||||
[key: string]: any
|
||||
}
|
||||
export interface RadioGroupComponentProps extends Partial<RadioGroupProps> {
|
||||
options?: RadioOption[]
|
||||
/**
|
||||
* 数据源的字段别名
|
||||
*/
|
||||
props?: {
|
||||
label?: string
|
||||
value?: string
|
||||
disabled?: string
|
||||
}
|
||||
on?: {
|
||||
change?: (value: string | number | boolean) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element[] | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface RadioButtonComponentProps extends Partial<RadioButtonProps> {
|
||||
options?: RadioOption[]
|
||||
/**
|
||||
* 数据源的字段别名
|
||||
*/
|
||||
props?: {
|
||||
label?: string
|
||||
value?: string
|
||||
disabled?: string
|
||||
}
|
||||
on?: {
|
||||
change?: (value: string | number | boolean) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element[] | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface CheckboxOption {
|
||||
label?: string
|
||||
value?: string | number | boolean
|
||||
disabled?: boolean
|
||||
trueLabel?: string | number
|
||||
falseLabel?: string | number
|
||||
border?: boolean
|
||||
size?: ComponentSize
|
||||
name?: string
|
||||
checked?: boolean
|
||||
indeterminate?: boolean
|
||||
validateEvent?: boolean
|
||||
tabindex?: number | string
|
||||
id?: string
|
||||
controls?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface CheckboxGroupComponentProps extends Partial<CheckboxGroupProps> {
|
||||
options?: CheckboxOption[]
|
||||
/**
|
||||
* 数据源的字段别名
|
||||
*/
|
||||
props?: {
|
||||
label?: string
|
||||
value?: string
|
||||
disabled?: string
|
||||
}
|
||||
on?: {
|
||||
change?: (value: string | number | boolean) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element[] | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface DividerComponentProps extends Partial<DividerProps> {
|
||||
on?: {
|
||||
change?: (value: number) => void
|
||||
input?: (value: number) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface DatePickerComponentProps extends Partial<DatePickerProps> {
|
||||
on?: {
|
||||
change?: (value: string | Date | number | string[]) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
calendarChange?: (val: [Date, Date]) => void
|
||||
panelChange?: (date, mode, view) => void
|
||||
visibleChange?: (visibility: boolean) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
rangeSeparator?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface DateTimePickerComponentProps {
|
||||
readonly?: boolean
|
||||
disabled?: boolean
|
||||
editable?: boolean
|
||||
clearable?: boolean
|
||||
size?: ComponentSize
|
||||
placeholder?: string
|
||||
startPlaceholder?: string
|
||||
endPlaceholder?: string
|
||||
timeArrowControl?: boolean
|
||||
type?: 'year' | 'month' | 'date' | 'datetime' | 'datetimerange' | 'daterange' | 'week'
|
||||
format?: string
|
||||
popperClass?: string
|
||||
rangeSeparator?: string
|
||||
defaultValue?: Date | [Date, Date]
|
||||
defaultTime?: Date | [Date, Date]
|
||||
valueFormat?: string
|
||||
id?: string
|
||||
name?: string
|
||||
unlinkPanels?: boolean
|
||||
prefixIcon?: string | JSX.Element
|
||||
clearIcon?: string | JSX.Element
|
||||
shortcuts?: Array<{ text: string; value: Date | Function }>
|
||||
disabledDate?: (date: Date) => boolean
|
||||
cellClassName?: string | ((date: Date) => string | undefined)
|
||||
teleported?: boolean
|
||||
on?: {
|
||||
change?: (value: string | Date | number | string[]) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
calendarChange?: (val: [Date, Date]) => void
|
||||
visibleChange?: (visibility: boolean) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
rangeSeparator?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface TimePickerComponentProps {
|
||||
readonly?: boolean
|
||||
disabled?: boolean
|
||||
editable?: boolean
|
||||
clearable?: boolean
|
||||
size?: ComponentSize
|
||||
placeholder?: string
|
||||
startPlaceholder?: string
|
||||
endPlaceholder?: string
|
||||
isRange?: boolean
|
||||
arrowControl?: boolean
|
||||
popperClass?: string
|
||||
rangeSeparator?: string
|
||||
format?: string
|
||||
defaultValue?: Date | [Date, Date]
|
||||
id?: string
|
||||
name?: string
|
||||
label?: string
|
||||
prefixIcon?: string | JSX.Element
|
||||
clearIcon?: string | JSX.Element
|
||||
disabledHours?: (role: string, comparingDate?: any) => number[]
|
||||
disabledMinutes?: (hour: number, role: string, comparingDate?: any) => number[]
|
||||
disabledSeconds?: (hour: number, minute: number, role: string, comparingDate?: any) => number[]
|
||||
teleported?: boolean
|
||||
tabindex?: number | string
|
||||
on?: {
|
||||
change: (
|
||||
val: number | string | Date | [number, number] | [string, string] | [Date, Date]
|
||||
) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
visibleChange?: (visibility: boolean) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface TimeSelectComponentProps {
|
||||
disabled?: boolean
|
||||
editable?: boolean
|
||||
clearable?: boolean
|
||||
size?: ComponentSize
|
||||
placeholder?: string
|
||||
name?: string
|
||||
effect?: string
|
||||
prefixIcon?: string | JSX.Element
|
||||
clearIcon?: string | JSX.Element
|
||||
start?: string
|
||||
end?: string
|
||||
step?: string
|
||||
minTime?: string
|
||||
maxTime?: string
|
||||
format?: string
|
||||
on?: {
|
||||
change?: (val: string) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface EditorComponentProps {
|
||||
editorConfig?: IEditorConfig
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface ColProps {
|
||||
span?: number
|
||||
xs?: number
|
||||
sm?: number
|
||||
md?: number
|
||||
lg?: number
|
||||
xl?: number
|
||||
tag?: string
|
||||
}
|
||||
|
||||
export interface FormSetProps {
|
||||
field: string
|
||||
path: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export interface FormItemProps extends Partial<ElFormItemProps> {
|
||||
style?: CSSProperties
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
label?: (...args: any[]) => JSX.Element | null
|
||||
error?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface UploadComponentProps extends Partial<UploadProps> {
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
trigger?: (...args: any[]) => JSX.Element | null
|
||||
tip?: (...args: any[]) => JSX.Element | null
|
||||
file?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface TreeSelectComponentProps
|
||||
extends Omit<Partial<SelectComponentProps>, 'props' | 'on' | 'slots'> {
|
||||
data?: any[]
|
||||
emptyText?: string
|
||||
nodeKey?: string
|
||||
props?: {
|
||||
children?: string
|
||||
label?: string | ((...args: any[]) => string)
|
||||
disabled?: string | ((...args: any[]) => string)
|
||||
isLeaf?: string | ((...args: any[]) => string)
|
||||
class?: string | ((...args: any[]) => string)
|
||||
}
|
||||
renderAfterExpand?: boolean
|
||||
load?: (...args: any[]) => Promise<any>
|
||||
renderContent?: (...args: any[]) => JSX.Element | null
|
||||
highlightCurrent?: boolean
|
||||
defaultExpandAll?: boolean
|
||||
expandOnClickNode?: boolean
|
||||
checkOnClickNode?: boolean
|
||||
autoExpandParent?: boolean
|
||||
defaultExpandedKeys?: any[]
|
||||
showCheckbox?: boolean
|
||||
checkStrictly?: boolean
|
||||
defaultCheckedKeys?: any[]
|
||||
currentNodeKey?: string | number
|
||||
filterNodeMethod?: (...args: any[]) => boolean
|
||||
accordion?: boolean
|
||||
indent?: number
|
||||
icon?: string | ((...args: any[]) => JSX.Element | null)
|
||||
lazy?: boolean
|
||||
draggable?: boolean
|
||||
allowDrag?: (...args: any[]) => boolean
|
||||
allowDrop?: (...args: any[]) => boolean
|
||||
on?: {
|
||||
change?: (value: string | number | boolean | Object) => void
|
||||
visibleChange?: (visible: boolean) => void
|
||||
removeTag?: (tag: any) => void
|
||||
clear?: () => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
nodeClick?: (...args: any[]) => void
|
||||
nodeContextMenu?: (...args: any[]) => void
|
||||
checkChange?: (...args: any[]) => void
|
||||
check?: (...args: any[]) => void
|
||||
currentChange?: (...args: any[]) => void
|
||||
nodeExpand?: (...args: any[]) => void
|
||||
nodeCollapse?: (...args: any[]) => void
|
||||
nodeDragStart?: (...args: any[]) => void
|
||||
nodeDragEnter?: (...args: any[]) => void
|
||||
nodeDragLeave?: (...args: any[]) => void
|
||||
nodeDragOver?: (...args: any[]) => void
|
||||
nodeDragEnd?: (...args: any[]) => void
|
||||
nodeDrop?: (...args: any[]) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | null
|
||||
optionGroupDefault?: (item: SelectOption) => JSX.Element
|
||||
optionDefault?: (option: SelectOption) => JSX.Element | null
|
||||
prefix?: (...args: any[]) => JSX.Element | null
|
||||
empty?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface FormSchema {
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
field: string
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
label?: string
|
||||
|
||||
/**
|
||||
* col组件属性
|
||||
*/
|
||||
colProps?: ColProps
|
||||
|
||||
/**
|
||||
* 表单组件属性,具体可以查看element-plus文档
|
||||
*/
|
||||
componentProps?:
|
||||
| InputComponentProps
|
||||
| AutocompleteComponentProps
|
||||
| InputNumberComponentProps
|
||||
| SelectComponentProps
|
||||
| SelectV2ComponentProps
|
||||
| CascaderComponentProps
|
||||
| SwitchComponentProps
|
||||
| RateComponentProps
|
||||
| ColorPickerComponentProps
|
||||
| TransferComponentProps
|
||||
| RadioGroupComponentProps
|
||||
| RadioButtonComponentProps
|
||||
| DividerComponentProps
|
||||
| DatePickerComponentProps
|
||||
| DateTimePickerComponentProps
|
||||
| TimePickerComponentProps
|
||||
| InputPasswordComponentProps
|
||||
| TreeSelectComponentProps
|
||||
| UploadComponentProps
|
||||
| any
|
||||
|
||||
/**
|
||||
* formItem组件属性,具体可以查看element-plus文档
|
||||
*/
|
||||
formItemProps?: FormItemProps
|
||||
|
||||
/**
|
||||
* 渲染的组件名称
|
||||
*/
|
||||
component?: ComponentName
|
||||
|
||||
/**
|
||||
* 初始值
|
||||
*/
|
||||
value?: any
|
||||
|
||||
/**
|
||||
* 是否隐藏,如果为true,会连同值一同删除,类似v-if
|
||||
*/
|
||||
remove?: boolean
|
||||
|
||||
/**
|
||||
* 样式隐藏,不会把值一同删掉,类似v-show
|
||||
*/
|
||||
hidden?: boolean
|
||||
|
||||
/**
|
||||
* @returns 远程加载下拉项
|
||||
*/
|
||||
optionApi?: any
|
||||
}
|
||||
|
||||
export interface FormProps extends Partial<ElFormProps> {
|
||||
schema?: FormSchema[]
|
||||
isCol?: boolean
|
||||
model?: Recordable
|
||||
autoSetPlaceholder?: boolean
|
||||
isCustom?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Icon from './src/Icon.vue'
|
||||
|
||||
export type { IconTypes } from './src/types'
|
||||
|
||||
export { Icon }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, unref, ref, watch, nextTick } from 'vue'
|
||||
import { computed, unref } from 'vue'
|
||||
import { ElIcon } from 'element-plus'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import Iconify from '@purge-icons/generated'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -15,11 +15,10 @@ const props = defineProps({
|
||||
// icon color
|
||||
color: propTypes.string,
|
||||
// icon size
|
||||
size: propTypes.number.def(16)
|
||||
size: propTypes.number.def(16),
|
||||
hoverColor: propTypes.string
|
||||
})
|
||||
|
||||
const elRef = ref<ElRef>(null)
|
||||
|
||||
const isLocal = computed(() => props.icon.startsWith('svg-icon:'))
|
||||
|
||||
const symbolId = computed(() => {
|
||||
@@ -33,36 +32,6 @@ const getIconifyStyle = computed(() => {
|
||||
color
|
||||
}
|
||||
})
|
||||
|
||||
const updateIcon = async (icon: string) => {
|
||||
if (unref(isLocal)) return
|
||||
|
||||
const el = unref(elRef)
|
||||
if (!el) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
if (!icon) return
|
||||
|
||||
const svg = Iconify.renderSVG(icon, {})
|
||||
if (svg) {
|
||||
el.textContent = ''
|
||||
el.appendChild(svg)
|
||||
} else {
|
||||
const span = document.createElement('span')
|
||||
span.className = 'iconify'
|
||||
span.dataset.icon = icon
|
||||
el.textContent = ''
|
||||
el.appendChild(span)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.icon,
|
||||
(icon: string) => {
|
||||
updateIcon(icon)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,8 +40,20 @@ watch(
|
||||
<use :xlink:href="symbolId" />
|
||||
</svg>
|
||||
|
||||
<span v-else ref="elRef" :class="$attrs.class" :style="getIconifyStyle">
|
||||
<span class="iconify" :data-icon="symbolId"></span>
|
||||
</span>
|
||||
<Icon v-else :icon="icon" :style="getIconifyStyle" />
|
||||
</ElIcon>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-icon';
|
||||
|
||||
.@{prefix-cls},
|
||||
.iconify {
|
||||
&:hover {
|
||||
:deep(svg) {
|
||||
// stylelint-disable-next-line
|
||||
color: v-bind(hoverColor) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,4 +2,5 @@ export interface IconTypes {
|
||||
size?: number
|
||||
color?: string
|
||||
icon: string
|
||||
hoverColor?: string
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export function createImageViewer(options: ImageViewerProps) {
|
||||
initialIndex = 0,
|
||||
infinite = true,
|
||||
hideOnClickModal = false,
|
||||
appendToBody = false,
|
||||
teleported = false,
|
||||
zIndex = 2000,
|
||||
show = true
|
||||
} = options
|
||||
@@ -23,7 +23,7 @@ export function createImageViewer(options: ImageViewerProps) {
|
||||
propsData.initialIndex = initialIndex
|
||||
propsData.infinite = infinite
|
||||
propsData.hideOnClickModal = hideOnClickModal
|
||||
propsData.appendToBody = appendToBody
|
||||
propsData.teleported = teleported
|
||||
propsData.zIndex = zIndex
|
||||
propsData.show = show
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const props = defineProps({
|
||||
initialIndex: propTypes.number.def(0),
|
||||
infinite: propTypes.bool.def(true),
|
||||
hideOnClickModal: propTypes.bool.def(false),
|
||||
appendToBody: propTypes.bool.def(false),
|
||||
teleported: propTypes.bool.def(false),
|
||||
show: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ export interface ImageViewerProps {
|
||||
initialIndex?: number
|
||||
infinite?: boolean
|
||||
hideOnClickModal?: boolean
|
||||
appendToBody?: boolean
|
||||
teleported?: boolean
|
||||
show?: boolean
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Infotip from './src/Infotip.vue'
|
||||
|
||||
export type { InfoTipSchema } from './src/types'
|
||||
|
||||
export { Infotip }
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PropType } from 'vue'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { TipSchema } from '@/types/infoTip'
|
||||
import { InfoTipSchema } from './types'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -12,7 +12,7 @@ const prefixCls = getPrefixCls('infotip')
|
||||
defineProps({
|
||||
title: propTypes.string.def(''),
|
||||
schema: {
|
||||
type: Array as PropType<Array<string | TipSchema>>,
|
||||
type: Array as PropType<Array<string | InfoTipSchema>>,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface TipSchema {
|
||||
export interface InfoTipSchema {
|
||||
label: string
|
||||
keys?: string[]
|
||||
}
|
||||
@@ -32,10 +32,6 @@ const emit = defineEmits(['update:modelValue'])
|
||||
// 设置input的type属性
|
||||
const textType = ref<'password' | 'text'>('password')
|
||||
|
||||
const changeTextType = () => {
|
||||
textType.value = unref(textType) === 'text' ? 'password' : 'text'
|
||||
}
|
||||
|
||||
// 输入框的值
|
||||
const valueRef = ref(props.modelValue)
|
||||
|
||||
@@ -53,19 +49,11 @@ const getPasswordStrength = computed(() => {
|
||||
const zxcvbnRef = zxcvbn(unref(valueRef)) as ZxcvbnResult
|
||||
return value ? zxcvbnRef.score : -1
|
||||
})
|
||||
|
||||
const getIconName = computed(() =>
|
||||
unref(textType) === 'password' ? 'ant-design:eye-invisible-outlined' : 'ant-design:eye-outlined'
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[prefixCls, `${prefixCls}--${configGlobal?.size}`]">
|
||||
<ElInput v-bind="$attrs" v-model="valueRef" :type="textType">
|
||||
<template #suffix>
|
||||
<Icon class="el-input__icon cursor-pointer" :icon="getIconName" @click="changeTextType" />
|
||||
</template>
|
||||
</ElInput>
|
||||
<ElInput v-bind="$attrs" v-model="valueRef" showPassword :type="textType" />
|
||||
<div
|
||||
v-if="strength"
|
||||
:class="`${prefixCls}__bar`"
|
||||
@@ -116,7 +104,9 @@ const getIconName = computed(() =>
|
||||
height: inherit;
|
||||
background-color: transparent;
|
||||
border-radius: inherit;
|
||||
transition: width 0.5s ease-in-out, background 0.25s;
|
||||
transition:
|
||||
width 0.5s ease-in-out,
|
||||
background 0.25s;
|
||||
|
||||
&[data-score='0'] {
|
||||
width: 20%;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import LocaleDropdown from './src/LocaleDropdown.vue'
|
||||
|
||||
export type { Language, LocaleDropdownType } from './src/types'
|
||||
|
||||
export { LocaleDropdown }
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useRenderMenuItem } from './components/useRenderMenuItem'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { LayoutType } from '@/types/layout'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -124,15 +123,15 @@ export default defineComponent({
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-menu';
|
||||
|
||||
.is-active--after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: var(--el-color-primary);
|
||||
content: '';
|
||||
}
|
||||
// .is-active--after {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// width: 4px;
|
||||
// height: 100%;
|
||||
// background-color: var(--el-color-primary);
|
||||
// content: '';
|
||||
// }
|
||||
|
||||
.@{prefix-cls} {
|
||||
position: relative;
|
||||
@@ -182,9 +181,9 @@ export default defineComponent({
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
.is-active--after;
|
||||
}
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
|
||||
// 设置子菜单的背景颜色
|
||||
@@ -205,9 +204,9 @@ export default defineComponent({
|
||||
position: relative;
|
||||
background-color: var(--left-menu-collapse-bg-active-color) !important;
|
||||
|
||||
&:after {
|
||||
.is-active--after;
|
||||
}
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,15 +254,15 @@ export default defineComponent({
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-menu-popper';
|
||||
|
||||
.is-active--after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: var(--el-color-primary);
|
||||
content: '';
|
||||
}
|
||||
// .is-active--after {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// width: 4px;
|
||||
// height: 100%;
|
||||
// background-color: var(--el-color-primary);
|
||||
// content: '';
|
||||
// }
|
||||
|
||||
.@{prefix-cls}--vertical,
|
||||
.@{prefix-cls}--horizontal {
|
||||
@@ -292,9 +291,9 @@ export default defineComponent({
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
}
|
||||
|
||||
&:after {
|
||||
.is-active--after;
|
||||
}
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
4
src/components/Permission/index.ts
Normal file
4
src/components/Permission/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Permission from './src/Permission.vue'
|
||||
import { hasPermi } from './src/utils'
|
||||
|
||||
export { Permission, hasPermi }
|
||||
29
src/components/Permission/src/Permission.vue
Normal file
29
src/components/Permission/src/Permission.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { computed, unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { currentRoute } = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
permission: propTypes.string.def()
|
||||
})
|
||||
|
||||
const currentPermission = computed(() => {
|
||||
return unref(currentRoute)?.meta?.permission || []
|
||||
})
|
||||
|
||||
const hasPermission = computed(() => {
|
||||
const permission = unref(props.permission)
|
||||
if (!permission) {
|
||||
return true
|
||||
}
|
||||
return unref(currentPermission).includes(permission)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="hasPermission">
|
||||
<slot></slot>
|
||||
</template>
|
||||
</template>
|
||||
14
src/components/Permission/src/utils.ts
Normal file
14
src/components/Permission/src/utils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import router from '@/router'
|
||||
|
||||
export const hasPermi = (value: string) => {
|
||||
const { t } = useI18n()
|
||||
const permission = (router.currentRoute.value.meta.permission || []) as string[]
|
||||
if (!value) {
|
||||
throw new Error(t('permission.hasPermission'))
|
||||
}
|
||||
if (permission.includes(value)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Qrcode from './src/Qrcode.vue'
|
||||
|
||||
export type { QrcodeLogo } from './src/types'
|
||||
|
||||
export { Qrcode }
|
||||
|
||||
@@ -6,7 +6,7 @@ import { cloneDeep } from 'lodash-es'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { isString } from '@/utils/is'
|
||||
import { QrcodeLogo } from '@/types/qrcode'
|
||||
import { QrcodeLogo } from '@/components/Qrcode'
|
||||
|
||||
const props = defineProps({
|
||||
// img 或者 canvas,img不支持logo嵌套
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
import { FormSchema, FormSetProps } from '../Form'
|
||||
import Search from './src/Search.vue'
|
||||
|
||||
export type { SearchProps } from './src/types'
|
||||
|
||||
export interface SearchExpose {
|
||||
setValues: (data: Recordable) => void
|
||||
setProps: (props: Recordable) => void
|
||||
delSchema: (field: string) => void
|
||||
addSchema: (formSchema: FormSchema, index?: number) => void
|
||||
setSchema: (schemaProps: FormSetProps[]) => void
|
||||
formModel: Recordable
|
||||
}
|
||||
|
||||
export { Search }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { Form } from '@/components/Form'
|
||||
import { PropType, computed, unref, ref } from 'vue'
|
||||
<script setup lang="tsx">
|
||||
import { Form, FormSchema, FormSetProps } from '@/components/Form'
|
||||
import { PropType, computed, unref, ref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { findIndex } from '@/utils'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
const { t } = useI18n()
|
||||
import { cloneDeep, set } from 'lodash-es'
|
||||
import { initModel } from '@/components/Form/src/helper'
|
||||
import ActionButton from './components/ActionButton.vue'
|
||||
import { SearchProps } from './types'
|
||||
import { FormItemProp } from 'element-plus'
|
||||
import { isObject, isEmptyVal } from '@/utils/is'
|
||||
|
||||
const props = defineProps({
|
||||
// 生成Form的布局结构数组
|
||||
@@ -24,41 +24,72 @@ const props = defineProps({
|
||||
// 操作按钮风格位置
|
||||
layout: propTypes.string.validate((v: string) => ['inline', 'bottom'].includes(v)).def('inline'),
|
||||
// 底部按钮的对齐方式
|
||||
buttomPosition: propTypes.string
|
||||
buttonPosition: propTypes.string
|
||||
.validate((v: string) => ['left', 'center', 'right'].includes(v))
|
||||
.def('center'),
|
||||
showSearch: propTypes.bool.def(true),
|
||||
showReset: propTypes.bool.def(true),
|
||||
// 是否显示伸缩
|
||||
expand: propTypes.bool.def(false),
|
||||
showExpand: propTypes.bool.def(false),
|
||||
// 伸缩的界限字段
|
||||
expandField: propTypes.string.def(''),
|
||||
inline: propTypes.bool.def(true),
|
||||
// 是否去除空值项
|
||||
removeNoValueItem: propTypes.bool.def(true),
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
searchLoading: propTypes.bool.def(false),
|
||||
resetLoading: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search', 'reset'])
|
||||
const emit = defineEmits(['search', 'reset', 'register', 'validate'])
|
||||
|
||||
const visible = ref(true)
|
||||
|
||||
// 表单数据
|
||||
const formModel = ref<Recordable>(props.model)
|
||||
|
||||
const newSchema = computed(() => {
|
||||
let schema: FormSchema[] = cloneDeep(props.schema)
|
||||
if (props.expand && props.expandField && !unref(visible)) {
|
||||
const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)
|
||||
if (index > -1) {
|
||||
const length = schema.length
|
||||
schema.splice(index + 1, length)
|
||||
}
|
||||
const propsComputed = unref(getProps)
|
||||
let schema: FormSchema[] = cloneDeep(propsComputed.schema)
|
||||
if (propsComputed.showExpand && propsComputed.expandField && !unref(visible)) {
|
||||
const index = findIndex(schema, (v: FormSchema) => v.field === propsComputed.expandField)
|
||||
schema.map((v, i) => {
|
||||
if (i >= index) {
|
||||
v.hidden = true
|
||||
} else {
|
||||
v.hidden = false
|
||||
}
|
||||
return v
|
||||
})
|
||||
}
|
||||
if (props.layout === 'inline') {
|
||||
if (propsComputed.layout === 'inline') {
|
||||
schema = schema.concat([
|
||||
{
|
||||
field: 'action',
|
||||
formItemProps: {
|
||||
labelWidth: '0px'
|
||||
labelWidth: '0px',
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<div>
|
||||
<ActionButton
|
||||
showSearch={propsComputed.showSearch}
|
||||
showReset={propsComputed.showReset}
|
||||
showExpand={propsComputed.showExpand}
|
||||
searchLoading={propsComputed.searchLoading}
|
||||
resetLoading={propsComputed.resetLoading}
|
||||
visible={visible.value}
|
||||
onExpand={setVisible}
|
||||
onReset={reset}
|
||||
onSearch={search}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
@@ -66,81 +97,167 @@ const newSchema = computed(() => {
|
||||
return schema
|
||||
})
|
||||
|
||||
const { register, elFormRef, methods } = useForm({
|
||||
model: props.model || {}
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getElFormExpose, getFormData, getFormExpose } = formMethods
|
||||
|
||||
// useSearch传入的props
|
||||
const outsideProps = ref<SearchProps>({})
|
||||
|
||||
const mergeProps = ref<SearchProps>({})
|
||||
|
||||
const getProps = computed(() => {
|
||||
const propsObj = { ...props }
|
||||
Object.assign(propsObj, unref(mergeProps))
|
||||
return propsObj
|
||||
})
|
||||
|
||||
const setProps = (props: SearchProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
// @ts-ignore
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(newSchema),
|
||||
async (schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const filterModel = async () => {
|
||||
const model = await getFormData()
|
||||
if (unref(getProps).removeNoValueItem) {
|
||||
// 使用reduce过滤空值,并返回一个新对象
|
||||
return Object.keys(model).reduce((prev, next) => {
|
||||
const value = model[next]
|
||||
if (!isEmptyVal(value)) {
|
||||
if (isObject(value)) {
|
||||
if (Object.keys(value).length > 0) {
|
||||
prev[next] = value
|
||||
}
|
||||
} else {
|
||||
prev[next] = value
|
||||
}
|
||||
}
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
const search = async () => {
|
||||
await unref(elFormRef)?.validate(async (isValid) => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
await elFormExpose?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
const { getFormData } = methods
|
||||
const model = await getFormData()
|
||||
const model = await filterModel()
|
||||
emit('search', model)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const reset = async () => {
|
||||
unref(elFormRef)?.resetFields()
|
||||
const { getFormData } = methods
|
||||
const model = await getFormData()
|
||||
const elFormExpose = await getElFormExpose()
|
||||
elFormExpose?.resetFields()
|
||||
const model = await filterModel()
|
||||
emit('reset', model)
|
||||
}
|
||||
|
||||
const bottonButtonStyle = computed(() => {
|
||||
const bottomButtonStyle = computed(() => {
|
||||
return {
|
||||
textAlign: props.buttomPosition as unknown as 'left' | 'center' | 'right'
|
||||
textAlign: unref(getProps).buttonPosition as unknown as 'left' | 'center' | 'right'
|
||||
}
|
||||
})
|
||||
|
||||
const setVisible = () => {
|
||||
unref(elFormRef)?.resetFields()
|
||||
const setVisible = async () => {
|
||||
visible.value = !unref(visible)
|
||||
}
|
||||
|
||||
const setSchema = (schemaProps: FormSetProps[]) => {
|
||||
const { schema } = unref(getProps)
|
||||
for (const v of schema) {
|
||||
for (const item of schemaProps) {
|
||||
if (v.field === item.field) {
|
||||
set(v, item.path, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对表单赋值
|
||||
const setValues = async (data: Recordable = {}) => {
|
||||
formModel.value = Object.assign(props.model, unref(formModel), data)
|
||||
const formExpose = await getFormExpose()
|
||||
formExpose?.setValues(data)
|
||||
}
|
||||
|
||||
const delSchema = (field: string) => {
|
||||
const { schema } = unref(getProps)
|
||||
|
||||
const index = findIndex(schema, (v: FormSchema) => v.field === field)
|
||||
if (index > -1) {
|
||||
schema.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const addSchema = (formSchema: FormSchema, index?: number) => {
|
||||
const { schema } = unref(getProps)
|
||||
if (index !== void 0) {
|
||||
schema.splice(index, 0, formSchema)
|
||||
return
|
||||
}
|
||||
schema.push(formSchema)
|
||||
}
|
||||
|
||||
const defaultExpose = {
|
||||
getElFormExpose,
|
||||
setProps,
|
||||
setSchema,
|
||||
setValues,
|
||||
delSchema,
|
||||
addSchema
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emit('register', defaultExpose)
|
||||
})
|
||||
|
||||
defineExpose(defaultExpose)
|
||||
|
||||
const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
|
||||
emit('validate', prop, isValid, message)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
:model="formModel"
|
||||
:is-custom="false"
|
||||
:label-width="labelWidth"
|
||||
:label-width="getProps.labelWidth"
|
||||
hide-required-asterisk
|
||||
:inline="inline"
|
||||
:is-col="isCol"
|
||||
:inline="getProps.inline"
|
||||
:is-col="getProps.isCol"
|
||||
:schema="newSchema"
|
||||
@register="register"
|
||||
>
|
||||
<template #action>
|
||||
<div v-if="layout === 'inline'">
|
||||
<ElButton v-if="showSearch" type="primary" @click="search">
|
||||
<Icon icon="ep:search" class="mr-5px" />
|
||||
{{ t('common.query') }}
|
||||
</ElButton>
|
||||
<ElButton v-if="showReset" @click="reset">
|
||||
<Icon icon="ep:refresh-right" class="mr-5px" />
|
||||
{{ t('common.reset') }}
|
||||
</ElButton>
|
||||
<ElButton v-if="expand" text @click="setVisible">
|
||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||
<Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
@register="formRegister"
|
||||
@validate="onFormValidate"
|
||||
/>
|
||||
|
||||
<template v-if="layout === 'bottom'">
|
||||
<div :style="bottonButtonStyle">
|
||||
<ElButton v-if="showSearch" type="primary" @click="search">
|
||||
<Icon icon="ep:search" class="mr-5px" />
|
||||
{{ t('common.query') }}
|
||||
</ElButton>
|
||||
<ElButton v-if="showReset" @click="reset">
|
||||
<Icon icon="ep:refresh-right" class="mr-5px" />
|
||||
{{ t('common.reset') }}
|
||||
</ElButton>
|
||||
<ElButton v-if="expand" text @click="setVisible">
|
||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||
<Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
|
||||
</ElButton>
|
||||
<div :style="bottomButtonStyle">
|
||||
<ActionButton
|
||||
:show-reset="getProps.showReset"
|
||||
:show-search="getProps.showSearch"
|
||||
:show-expand="getProps.showExpand"
|
||||
:search-loading="getProps.searchLoading"
|
||||
:reset-loading="getProps.resetLoading"
|
||||
@expand="setVisible"
|
||||
@reset="reset"
|
||||
@search="search"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
59
src/components/Search/src/components/ActionButton.vue
Normal file
59
src/components/Search/src/components/ActionButton.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const emit = defineEmits(['search', 'reset', 'expand'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps({
|
||||
showSearch: propTypes.bool.def(true),
|
||||
showReset: propTypes.bool.def(true),
|
||||
showExpand: propTypes.bool.def(false),
|
||||
visible: propTypes.bool.def(true),
|
||||
searchLoading: propTypes.bool.def(false),
|
||||
resetLoading: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
const onSearch = () => {
|
||||
emit('search')
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
const onExpand = () => {
|
||||
emit('expand')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElButton
|
||||
v-if="showSearch"
|
||||
type="primary"
|
||||
:loading="searchLoading"
|
||||
:icon="useIcon({ icon: 'ep:search' })"
|
||||
@click="onSearch"
|
||||
>
|
||||
{{ t('common.query') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="showReset"
|
||||
:loading="resetLoading"
|
||||
:icon="useIcon({ icon: 'ep:refresh-right' })"
|
||||
@click="onReset"
|
||||
>
|
||||
{{ t('common.reset') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="showExpand"
|
||||
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
|
||||
text
|
||||
@click="onExpand"
|
||||
>
|
||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
16
src/components/Search/src/types/index.ts
Normal file
16
src/components/Search/src/types/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FormSchema } from '@/components/Form'
|
||||
|
||||
export interface SearchProps {
|
||||
schema?: FormSchema[]
|
||||
isCol?: boolean
|
||||
labelWidth?: string | number
|
||||
layout?: 'inline' | 'bottom'
|
||||
buttonPosition?: 'left' | 'right' | 'center'
|
||||
showSearch?: boolean
|
||||
showReset?: boolean
|
||||
showExpand?: boolean
|
||||
expandField?: string
|
||||
inline?: boolean
|
||||
removeNoValueItem?: boolean
|
||||
model?: Recordable
|
||||
}
|
||||
@@ -10,10 +10,12 @@ import { trim, setCssVar } from '@/utils'
|
||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { removeStorage } = useStorage()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('setting')
|
||||
@@ -47,7 +49,6 @@ const setHeaderTheme = (color: string) => {
|
||||
setCssVar('--top-header-bg-color', color)
|
||||
setCssVar('--top-header-text-color', textColor)
|
||||
setCssVar('--top-header-hover-color', textHoverColor)
|
||||
setCssVar('--layout-border-color', topToolBorderColor)
|
||||
appStore.setTheme({
|
||||
topHeaderBgColor: color,
|
||||
topHeaderTextColor: textColor,
|
||||
@@ -92,10 +93,6 @@ const setMenuTheme = (color: string) => {
|
||||
appStore.setTheme(theme)
|
||||
appStore.setCssVarTheme()
|
||||
}
|
||||
if (layout.value === 'top' && !appStore.getIsDark) {
|
||||
headerTheme.value = '#fff'
|
||||
setHeaderTheme('#fff')
|
||||
}
|
||||
|
||||
// 监听layout变化,重置一些主题色
|
||||
watch(
|
||||
@@ -191,10 +188,9 @@ const copyConfig = async () => {
|
||||
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
const { wsCache } = useCache()
|
||||
wsCache.delete('layout')
|
||||
wsCache.delete('theme')
|
||||
wsCache.delete('isDark')
|
||||
removeStorage('layout')
|
||||
removeStorage('theme')
|
||||
removeStorage('isDark')
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
@@ -202,7 +198,7 @@ const clear = () => {
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="fixed top-[45%] right-0 w-40px h-40px flex items-center justify-center bg-[var(--el-color-primary)] cursor-pointer"
|
||||
class="fixed top-[45%] right-0 w-40px h-40px flex items-center justify-center bg-[var(--el-color-primary)] cursor-pointer z-10"
|
||||
@click="drawer = true"
|
||||
>
|
||||
<Icon icon="ant-design:setting-outlined" color="#fff" />
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@@ -21,7 +20,7 @@ const appStore = useAppStore()
|
||||
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
|
||||
const setCurrentSize = (size: ElementPlusSize) => {
|
||||
const setCurrentSize = (size: ComponentSize) => {
|
||||
appStore.setCurrentSize(size)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import Sticky from './src/Sticky.vue'
|
||||
|
||||
export { Sticky }
|
||||
@@ -1,141 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ref, onMounted, onActivated, shallowRef } from 'vue'
|
||||
import { useEventListener, useWindowSize, isClient } from '@vueuse/core'
|
||||
import type { CSSProperties } from 'vue'
|
||||
const props = defineProps({
|
||||
// 距离顶部或者底部的距离(单位px)
|
||||
offset: propTypes.number.def(0),
|
||||
// 设置元素的堆叠顺序
|
||||
zIndex: propTypes.number.def(999),
|
||||
// 设置指定的class
|
||||
className: propTypes.string.def(''),
|
||||
// 定位方式,默认为(top),表示距离顶部位置,可以设置为top或者bottom
|
||||
position: {
|
||||
type: String,
|
||||
validator: function (value: string) {
|
||||
return ['top', 'bottom'].indexOf(value) !== -1
|
||||
},
|
||||
default: 'top'
|
||||
}
|
||||
})
|
||||
const width = ref('auto' as string)
|
||||
const height = ref('auto' as string)
|
||||
const isSticky = ref(false)
|
||||
const refSticky = shallowRef<HTMLElement>()
|
||||
const scrollContainer = shallowRef<HTMLElement | Window>()
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
onMounted(() => {
|
||||
height.value = refSticky.value?.getBoundingClientRect().height + 'px'
|
||||
|
||||
scrollContainer.value = getScrollContainer(refSticky.value!, true)
|
||||
useEventListener(scrollContainer, 'scroll', handleScroll)
|
||||
useEventListener('resize', handleReize)
|
||||
handleScroll()
|
||||
})
|
||||
onActivated(() => {
|
||||
handleScroll()
|
||||
})
|
||||
|
||||
const camelize = (str: string): string => {
|
||||
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
|
||||
}
|
||||
|
||||
const getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {
|
||||
if (!isClient || !element || !styleName) return ''
|
||||
|
||||
let key = camelize(styleName)
|
||||
if (key === 'float') key = 'cssFloat'
|
||||
try {
|
||||
const style = element.style[styleName]
|
||||
if (style) return style
|
||||
const computed = document.defaultView?.getComputedStyle(element, '')
|
||||
return computed ? computed[styleName] : ''
|
||||
} catch {
|
||||
return element.style[styleName]
|
||||
}
|
||||
}
|
||||
const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
|
||||
if (!isClient) return false
|
||||
const key = (
|
||||
{
|
||||
undefined: 'overflow',
|
||||
true: 'overflow-y',
|
||||
false: 'overflow-x'
|
||||
} as const
|
||||
)[String(isVertical)]!
|
||||
const overflow = getStyle(el, key)
|
||||
return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
|
||||
}
|
||||
|
||||
const getScrollContainer = (
|
||||
el: HTMLElement,
|
||||
isVertical: boolean
|
||||
): Window | HTMLElement | undefined => {
|
||||
if (!isClient) return
|
||||
let parent = el
|
||||
while (parent) {
|
||||
if ([window, document, document.documentElement].includes(parent)) return window
|
||||
if (isScroll(parent, isVertical)) return parent
|
||||
parent = parent.parentNode as HTMLElement
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
width.value = refSticky.value!.getBoundingClientRect().width! + 'px'
|
||||
if (props.position === 'top') {
|
||||
const offsetTop = refSticky.value?.getBoundingClientRect().top
|
||||
if (offsetTop !== undefined && offsetTop < props.offset) {
|
||||
sticky()
|
||||
return
|
||||
}
|
||||
reset()
|
||||
} else {
|
||||
const offsetBottom = refSticky.value?.getBoundingClientRect().bottom
|
||||
|
||||
if (offsetBottom !== undefined && offsetBottom > windowHeight.value - props.offset) {
|
||||
sticky()
|
||||
return
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
const handleReize = () => {
|
||||
if (isSticky.value && refSticky.value) {
|
||||
width.value = refSticky.value.getBoundingClientRect().width + 'px'
|
||||
}
|
||||
}
|
||||
const sticky = () => {
|
||||
if (isSticky.value) {
|
||||
return
|
||||
}
|
||||
isSticky.value = true
|
||||
}
|
||||
const reset = () => {
|
||||
if (!isSticky.value) {
|
||||
return
|
||||
}
|
||||
width.value = 'auto'
|
||||
isSticky.value = false
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div :style="{ height: height, zIndex: zIndex }" ref="refSticky">
|
||||
<div
|
||||
:class="className"
|
||||
:style="{
|
||||
top: position === 'top' ? offset + 'px' : '',
|
||||
bottom: position !== 'top' ? offset + 'px' : '',
|
||||
zIndex: zIndex,
|
||||
position: isSticky ? 'fixed' : 'static',
|
||||
width: width,
|
||||
height: height
|
||||
}"
|
||||
>
|
||||
<slot>
|
||||
<div>sticky</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,11 +1,20 @@
|
||||
import Table from './src/Table.vue'
|
||||
import { ElTable } from 'element-plus'
|
||||
import { TableSetPropsType } from '@/types/table'
|
||||
import { TableColumn, TableSetProps } from './src/types'
|
||||
|
||||
export type {
|
||||
TableColumn,
|
||||
TableSlotDefault,
|
||||
Pagination,
|
||||
TableSetProps,
|
||||
TableProps
|
||||
} from './src/types'
|
||||
|
||||
export interface TableExpose {
|
||||
setProps: (props: Recordable) => void
|
||||
setColumn: (columnProps: TableSetPropsType[]) => void
|
||||
selections: Recordable[]
|
||||
setColumn: (columnProps: TableSetProps[]) => void
|
||||
addColumn: (column: TableColumn, index?: number) => void
|
||||
delColumn: (field: string) => void
|
||||
elTableRef: ComponentRef<typeof ElTable>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
<script lang="tsx">
|
||||
import { ElTable, ElTableColumn, ElPagination } from 'element-plus'
|
||||
import {
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElPagination,
|
||||
ComponentSize,
|
||||
ElTooltipProps,
|
||||
ElImage
|
||||
} from 'element-plus'
|
||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { setIndex } from './helper'
|
||||
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
|
||||
import { set, get } from 'lodash-es'
|
||||
import { CSSProperties } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import type { TableProps } from './types'
|
||||
import { set } from 'lodash-es'
|
||||
import { TableColumn, TableSlotDefault, Pagination, TableSetPropsType } from '../../../types/table'
|
||||
import TableActions from './components/TableActions.vue'
|
||||
// import Sortable from 'sortablejs'
|
||||
// import { Icon } from '@/components/Icon'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
props: {
|
||||
pageSize: propTypes.number.def(10),
|
||||
currentPage: propTypes.number.def(1),
|
||||
// 是否多选
|
||||
selection: propTypes.bool.def(true),
|
||||
// 是否展示表格的工具栏
|
||||
showAction: propTypes.bool.def(false),
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
showOverflowTooltip: propTypes.bool.def(true),
|
||||
// 表头
|
||||
@@ -23,7 +33,7 @@ export default defineComponent({
|
||||
default: () => []
|
||||
},
|
||||
// 展开行
|
||||
expand: propTypes.bool.def(false),
|
||||
// expand: propTypes.bool.def(false),
|
||||
// 是否展示分页
|
||||
pagination: {
|
||||
type: Object as PropType<Pagination>,
|
||||
@@ -46,10 +56,140 @@ export default defineComponent({
|
||||
data: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
// 是否自动预览
|
||||
preview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// sortable: propTypes.bool.def(false),
|
||||
height: propTypes.oneOfType([Number, String]),
|
||||
maxHeight: propTypes.oneOfType([Number, String]),
|
||||
stripe: propTypes.bool.def(false),
|
||||
border: propTypes.bool.def(true),
|
||||
size: {
|
||||
type: String as PropType<ComponentSize>,
|
||||
validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v)
|
||||
},
|
||||
fit: propTypes.bool.def(true),
|
||||
showHeader: propTypes.bool.def(true),
|
||||
highlightCurrentRow: propTypes.bool.def(false),
|
||||
currentRowKey: propTypes.oneOfType([Number, String]),
|
||||
// row-class-name, 类型为 (row: Recordable, rowIndex: number) => string | string
|
||||
rowClassName: {
|
||||
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
|
||||
default: ''
|
||||
},
|
||||
rowStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: () => undefined
|
||||
},
|
||||
cellClassName: {
|
||||
type: [Function, String] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => string | string
|
||||
>,
|
||||
default: ''
|
||||
},
|
||||
cellStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: () => undefined
|
||||
},
|
||||
headerRowClassName: {
|
||||
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
|
||||
default: ''
|
||||
},
|
||||
headerRowStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: () => undefined
|
||||
},
|
||||
headerCellClassName: {
|
||||
type: [Function, String] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => string | string
|
||||
>,
|
||||
default: ''
|
||||
},
|
||||
headerCellStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: () => undefined
|
||||
},
|
||||
rowKey: propTypes.string.def('id'),
|
||||
emptyText: propTypes.string.def('No Data'),
|
||||
defaultExpandAll: propTypes.bool.def(false),
|
||||
expandRowKeys: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
defaultSort: {
|
||||
type: Object as PropType<{ prop: string; order: string }>,
|
||||
default: () => ({})
|
||||
},
|
||||
tooltipEffect: {
|
||||
type: String as PropType<'dark' | 'light'>,
|
||||
default: 'dark'
|
||||
},
|
||||
tooltipOptions: {
|
||||
type: Object as PropType<
|
||||
Pick<
|
||||
ElTooltipProps,
|
||||
| 'effect'
|
||||
| 'enterable'
|
||||
| 'hideAfter'
|
||||
| 'offset'
|
||||
| 'placement'
|
||||
| 'popperClass'
|
||||
| 'popperOptions'
|
||||
| 'showAfter'
|
||||
| 'showArrow'
|
||||
>
|
||||
>,
|
||||
default: () => ({
|
||||
enterable: true,
|
||||
placement: 'top',
|
||||
showArrow: true,
|
||||
hideAfter: 200,
|
||||
popperOptions: { strategy: 'fixed' }
|
||||
})
|
||||
},
|
||||
showSummary: propTypes.bool.def(false),
|
||||
sumText: propTypes.string.def('Sum'),
|
||||
summaryMethod: {
|
||||
type: Function as PropType<(param: { columns: any[]; data: any[] }) => any[]>,
|
||||
default: () => undefined
|
||||
},
|
||||
spanMethod: {
|
||||
type: Function as PropType<
|
||||
(param: { row: any; column: any; rowIndex: number; columnIndex: number }) => any[]
|
||||
>,
|
||||
default: () => undefined
|
||||
},
|
||||
selectOnIndeterminate: propTypes.bool.def(true),
|
||||
indent: propTypes.number.def(16),
|
||||
lazy: propTypes.bool.def(false),
|
||||
load: {
|
||||
type: Function as PropType<(row: Recordable, treeNode: any, resolve: Function) => void>,
|
||||
default: () => undefined
|
||||
},
|
||||
treeProps: {
|
||||
type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>,
|
||||
default: () => ({ hasChildren: 'hasChildren', children: 'children', label: 'label' })
|
||||
},
|
||||
tableLayout: {
|
||||
type: String as PropType<'auto' | 'fixed'>,
|
||||
default: 'fixed'
|
||||
},
|
||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||
flexible: propTypes.bool.def(false)
|
||||
},
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register'],
|
||||
setup(props, { attrs, slots, emit, expose }) {
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
// 注册
|
||||
@@ -73,12 +213,39 @@ export default defineComponent({
|
||||
return propsObj
|
||||
})
|
||||
|
||||
// const sortableEl = ref()
|
||||
// 初始化拖拽
|
||||
// const initDropTable = () => {
|
||||
// const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
|
||||
// if (!el) return
|
||||
// if (unref(sortableEl)) unref(sortableEl).destroy()
|
||||
|
||||
// sortableEl.value = Sortable.create(el, {
|
||||
// handle: '.table-move',
|
||||
// animation: 180,
|
||||
// onEnd(e: any) {
|
||||
// emit('sortable-change', e)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// watch(
|
||||
// () => getProps.value.sortable,
|
||||
// async (v) => {
|
||||
// await nextTick()
|
||||
// v && initDropTable()
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// )
|
||||
|
||||
const setProps = (props: TableProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = props
|
||||
outsideProps.value = { ...props } as any
|
||||
}
|
||||
|
||||
const setColumn = (columnProps: TableSetPropsType[], columnsChildren?: TableColumn[]) => {
|
||||
const setColumn = (columnProps: TableSetProps[], columnsChildren?: TableColumn[]) => {
|
||||
const { columns } = unref(getProps)
|
||||
for (const v of columnsChildren || columns) {
|
||||
for (const item of columnProps) {
|
||||
@@ -91,16 +258,36 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const selections = ref<Recordable[]>([])
|
||||
const addColumn = (column: TableColumn, index?: number) => {
|
||||
const { columns } = unref(getProps)
|
||||
if (index) {
|
||||
columns.splice(index, 0, column)
|
||||
} else {
|
||||
columns.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
const selectionChange = (selection: Recordable[]) => {
|
||||
selections.value = selection
|
||||
const delColumn = (field: string) => {
|
||||
const { columns } = unref(getProps)
|
||||
const index = columns.findIndex((item) => item.field === field)
|
||||
if (index > -1) {
|
||||
columns.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
const changSize = (size: ComponentSize) => {
|
||||
setProps({ size })
|
||||
}
|
||||
|
||||
expose({
|
||||
setProps,
|
||||
setColumn,
|
||||
selections,
|
||||
delColumn,
|
||||
addColumn,
|
||||
elTableRef
|
||||
})
|
||||
|
||||
@@ -149,44 +336,44 @@ export default defineComponent({
|
||||
)
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const bindValue: Recordable = { ...attrs, ...props }
|
||||
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
|
||||
delete bindValue.columns
|
||||
delete bindValue.data
|
||||
return bindValue
|
||||
})
|
||||
|
||||
const renderTableSelection = () => {
|
||||
const { selection, reserveSelection, align, headerAlign } = unref(getProps)
|
||||
// 渲染多选
|
||||
return selection ? (
|
||||
<ElTableColumn
|
||||
type="selection"
|
||||
reserveSelection={reserveSelection}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
width="50"
|
||||
></ElTableColumn>
|
||||
) : undefined
|
||||
}
|
||||
|
||||
const renderTableExpand = () => {
|
||||
const { align, headerAlign, expand } = unref(getProps)
|
||||
// 渲染展开行
|
||||
return expand ? (
|
||||
<ElTableColumn type="expand" align={align} headerAlign={headerAlign}>
|
||||
{{
|
||||
// @ts-ignore
|
||||
default: (data: TableSlotDefault) => getSlot(slots, 'expand', data)
|
||||
}}
|
||||
</ElTableColumn>
|
||||
) : undefined
|
||||
}
|
||||
|
||||
const rnderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||
const { align, headerAlign, showOverflowTooltip } = unref(getProps)
|
||||
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
|
||||
return columnsChildren.map((v) => {
|
||||
const props = { ...v }
|
||||
if (v.hidden) return null
|
||||
const props = { ...v } as any
|
||||
if (props.children) delete props.children
|
||||
|
||||
const children = v.children
|
||||
|
||||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(args)
|
||||
}
|
||||
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
@@ -195,23 +382,28 @@ export default defineComponent({
|
||||
{...props}
|
||||
prop={v.field}
|
||||
>
|
||||
{{
|
||||
default: (data: TableSlotDefault) =>
|
||||
v.children && v.children.length
|
||||
? rnderTableColumn(v.children)
|
||||
: // @ts-ignore
|
||||
getSlot(slots, v.field, data) ||
|
||||
v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
|
||||
data.row[v.field],
|
||||
// @ts-ignore
|
||||
header: getSlot(slots, `${v.field}-header`)
|
||||
}}
|
||||
{slots}
|
||||
</ElTableColumn>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const rnderTableColumn = (columnsChildren?: TableColumn[]) => {
|
||||
const renderPreview = (url: string) => {
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%] h-100px"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderTableColumn = (columnsChildren?: TableColumn[]) => {
|
||||
const {
|
||||
columns,
|
||||
reserveIndex,
|
||||
@@ -219,80 +411,130 @@ export default defineComponent({
|
||||
currentPage,
|
||||
align,
|
||||
headerAlign,
|
||||
showOverflowTooltip
|
||||
showOverflowTooltip,
|
||||
reserveSelection,
|
||||
preview
|
||||
} = unref(getProps)
|
||||
return [...[renderTableExpand()], ...[renderTableSelection()]].concat(
|
||||
(columnsChildren || columns).map((v) => {
|
||||
// 自定生成序号
|
||||
if (v.type === 'index') {
|
||||
return (
|
||||
<ElTableColumn
|
||||
type="index"
|
||||
index={
|
||||
v.index
|
||||
? v.index
|
||||
: (index) => setIndex(reserveIndex, index, pageSize, currentPage)
|
||||
}
|
||||
align={v.align || align}
|
||||
headerAlign={v.headerAlign || headerAlign}
|
||||
label={v.label}
|
||||
width="65px"
|
||||
></ElTableColumn>
|
||||
)
|
||||
} else {
|
||||
const props = { ...v }
|
||||
if (props.children) delete props.children
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
{...props}
|
||||
prop={v.field}
|
||||
>
|
||||
{{
|
||||
default: (data: TableSlotDefault) =>
|
||||
v.children && v.children.length
|
||||
? rnderTreeTableColumn(v.children)
|
||||
: // @ts-ignore
|
||||
getSlot(slots, v.field, data) ||
|
||||
v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
|
||||
data.row[v.field],
|
||||
// @ts-ignore
|
||||
header: () => getSlot(slots, `${v.field}-header`) || v.label
|
||||
}}
|
||||
</ElTableColumn>
|
||||
)
|
||||
|
||||
return (columnsChildren || columns).map((v) => {
|
||||
if (v.hidden) return null
|
||||
if (v.type === 'index') {
|
||||
return (
|
||||
<ElTableColumn
|
||||
type="index"
|
||||
index={
|
||||
v.index ? v.index : (index) => setIndex(reserveIndex, index, pageSize, currentPage)
|
||||
}
|
||||
align={v.align || align}
|
||||
headerAlign={v.headerAlign || headerAlign}
|
||||
label={v.label}
|
||||
width="65px"
|
||||
></ElTableColumn>
|
||||
)
|
||||
} else if (v.type === 'selection') {
|
||||
return (
|
||||
<ElTableColumn
|
||||
type="selection"
|
||||
reserveSelection={reserveSelection}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
width="50"
|
||||
></ElTableColumn>
|
||||
)
|
||||
} else {
|
||||
const props = { ...v } as any
|
||||
if (props.children) delete props.children
|
||||
|
||||
const children = v.children
|
||||
|
||||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(args)
|
||||
}
|
||||
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
{...props}
|
||||
prop={v.field}
|
||||
>
|
||||
{slots}
|
||||
</ElTableColumn>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
<ElTable
|
||||
// @ts-ignore
|
||||
ref={elTableRef}
|
||||
data={unref(getProps).data}
|
||||
onSelection-change={selectionChange}
|
||||
{...unref(getBindValue)}
|
||||
>
|
||||
{{
|
||||
default: () => rnderTableColumn(),
|
||||
// @ts-ignore
|
||||
append: () => getSlot(slots, 'append')
|
||||
}}
|
||||
</ElTable>
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
v-model:currentPage={currentPageRef.value}
|
||||
class="mt-10px"
|
||||
{...unref(pagination)}
|
||||
></ElPagination>
|
||||
) : undefined}
|
||||
</div>
|
||||
)
|
||||
return () => {
|
||||
const tableSlots = {}
|
||||
if (getSlot(slots, 'empty')) {
|
||||
tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
|
||||
}
|
||||
if (getSlot(slots, 'append')) {
|
||||
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
|
||||
}
|
||||
|
||||
// const { sortable } = unref(getProps)
|
||||
|
||||
// const sortableEl = sortable ? (
|
||||
// <ElTableColumn
|
||||
// className="table-move cursor-move"
|
||||
// type="sortable"
|
||||
// prop="sortable"
|
||||
// width="60px"
|
||||
// align="center"
|
||||
// >
|
||||
// <Icon icon="ant-design:drag-outlined" />
|
||||
// </ElTableColumn>
|
||||
// ) : null
|
||||
|
||||
return (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
v-model:currentPage={currentPageRef.value}
|
||||
class="mt-10px"
|
||||
{...unref(pagination)}
|
||||
></ElPagination>
|
||||
) : undefined}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
151
src/components/Table/src/components/TableActions.vue
Normal file
151
src/components/Table/src/components/TableActions.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, unref, computed, PropType, watch } from 'vue'
|
||||
import {
|
||||
ElTooltip,
|
||||
ElDropdown,
|
||||
ElDropdownMenu,
|
||||
ElDropdownItem,
|
||||
ComponentSize
|
||||
// ElPopover,
|
||||
// ElTree
|
||||
} from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { TableColumn } from '../types'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
// import { eachTree } from '@/utils/tree'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableActions',
|
||||
props: {
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['refresh', 'changSize'],
|
||||
setup(props, { emit }) {
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
const changSize = (size: ComponentSize) => {
|
||||
emit('changSize', size)
|
||||
}
|
||||
|
||||
const columns = computed(() => {
|
||||
return cloneDeep(props.columns).filter((v) => {
|
||||
// 去掉type为selection的列和expand的列
|
||||
if (v.type !== 'selection' && v.type !== 'expand') {
|
||||
return v
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => columns.value,
|
||||
(newColumns) => {
|
||||
console.log('columns change:', newColumns)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="text-right h-28px flex items-center justify-end">
|
||||
<ElTooltip content={t('common.refresh')} placement="top">
|
||||
<span onClick={refresh}>
|
||||
<Icon
|
||||
icon="ant-design:sync-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</span>
|
||||
</ElTooltip>
|
||||
|
||||
<ElTooltip content={t('common.size')} placement="top">
|
||||
<ElDropdown trigger="click" onCommand={changSize}>
|
||||
{{
|
||||
default: () => {
|
||||
return (
|
||||
<span>
|
||||
<Icon
|
||||
icon="ant-design:column-height-outlined"
|
||||
class="cursor-pointer mr-8px ml-8px"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
dropdown: () => {
|
||||
return (
|
||||
<ElDropdownMenu>
|
||||
{{
|
||||
default: () => {
|
||||
return unref(sizeMap).map((v) => {
|
||||
return (
|
||||
<ElDropdownItem key={v} command={v}>
|
||||
{t(`size.${v}`)}
|
||||
</ElDropdownItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDropdownMenu>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElDropdown>
|
||||
</ElTooltip>
|
||||
|
||||
{/* <ElTooltip content={t('common.columnSetting')} placement="top"> */}
|
||||
{/* <ElPopover trigger="click" placement="left">
|
||||
{{
|
||||
default: () => {
|
||||
return (
|
||||
<div>
|
||||
<ElTree
|
||||
data={unref(columns)}
|
||||
show-checkbox
|
||||
default-checked-keys={unref(defaultCheckeds)}
|
||||
draggable
|
||||
node-key="field"
|
||||
allow-drop={(_draggingNode: any, _dropNode: any, type: string) => {
|
||||
if (type === 'inner') {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}}
|
||||
onNode-drag-end={onNodeDragEnd}
|
||||
onCheck-change={onCheckChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
reference: () => {
|
||||
return (
|
||||
<Icon
|
||||
icon="ant-design:setting-outlined"
|
||||
class="cursor-pointer"
|
||||
hoverColor="var(--el-color-primary)"
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElPopover> */}
|
||||
{/* </ElTooltip> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Pagination, TableColumn } from '@/types/table'
|
||||
|
||||
export type TableProps = {
|
||||
pageSize?: number
|
||||
currentPage?: number
|
||||
// 是否多选
|
||||
selection?: boolean
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
showOverflowTooltip?: boolean
|
||||
// 表头
|
||||
columns?: TableColumn[]
|
||||
// 是否展示分页
|
||||
pagination?: Pagination | undefined
|
||||
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
|
||||
reserveSelection?: boolean
|
||||
// 加载状态
|
||||
loading?: boolean
|
||||
// 是否叠加索引
|
||||
reserveIndex?: boolean
|
||||
// 对齐方式
|
||||
align?: 'left' | 'center' | 'right'
|
||||
// 表头对齐方式
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
data?: Recordable
|
||||
expand?: boolean
|
||||
} & Recordable
|
||||
97
src/components/Table/src/types/index.ts
Normal file
97
src/components/Table/src/types/index.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { TableProps as ElTableProps } from 'element-plus'
|
||||
export interface TableColumn {
|
||||
field: string
|
||||
label?: string
|
||||
type?: string
|
||||
/**
|
||||
* 是否隐藏
|
||||
*/
|
||||
hidden?: boolean
|
||||
children?: TableColumn[]
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | JSX.Element[] | null
|
||||
header?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
index?: number | ((index: number) => number)
|
||||
columnKey?: string
|
||||
width?: string | number
|
||||
minWidth?: string | number
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
renderHeader?: (...args: any[]) => JSX.Element | null
|
||||
// sortable?: boolean
|
||||
sortMethod?: (...args: any[]) => number
|
||||
sortBy?: string | string[] | ((...args: any[]) => string | string[])
|
||||
sortOrders?: (string | null)[]
|
||||
resizable?: boolean
|
||||
formatter?: (...args: any[]) => any
|
||||
showOverflowTooltip?: boolean
|
||||
align?: 'left' | 'center' | 'right'
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
selectable?: (...args: any[]) => boolean
|
||||
reserveSelection?: boolean
|
||||
filters?: Array<{ text: string; value: string }>
|
||||
filterPlacement?: string
|
||||
filterMultiple?: boolean
|
||||
filterMethod?: (...args: any[]) => boolean
|
||||
filteredValue?: string[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface TableSlotDefault {
|
||||
row: Recordable
|
||||
column: TableColumn
|
||||
$index: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
small?: boolean
|
||||
background?: boolean
|
||||
pageSize?: number
|
||||
defaultPageSize?: number
|
||||
total?: number
|
||||
pageCount?: number
|
||||
pagerCount?: number
|
||||
currentPage?: number
|
||||
defaultCurrentPage?: number
|
||||
layout?: string
|
||||
pageSizes?: number[]
|
||||
popperClass?: string
|
||||
prevText?: string
|
||||
nextText?: string
|
||||
disabled?: boolean
|
||||
hideOnSinglePage?: boolean
|
||||
}
|
||||
|
||||
export interface TableSetProps {
|
||||
field: string
|
||||
path: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export interface TableProps extends Omit<Partial<ElTableProps<any[]>>, 'data'> {
|
||||
pageSize?: number
|
||||
currentPage?: number
|
||||
showAction?: boolean
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
showOverflowTooltip?: boolean
|
||||
// 表头
|
||||
columns?: TableColumn[]
|
||||
// 是否展示分页
|
||||
pagination?: Pagination | undefined
|
||||
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
|
||||
reserveSelection?: boolean
|
||||
// 加载状态
|
||||
loading?: boolean
|
||||
// 是否叠加索引
|
||||
reserveIndex?: boolean
|
||||
// 对齐方式
|
||||
align?: 'left' | 'center' | 'right'
|
||||
// 表头对齐方式
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
preview?: string[]
|
||||
sortable?: boolean
|
||||
data?: Recordable
|
||||
}
|
||||
@@ -35,6 +35,8 @@ const appStore = useAppStore()
|
||||
|
||||
const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
|
||||
|
||||
const isDark = computed(() => appStore.getIsDark)
|
||||
|
||||
// 初始化tag
|
||||
const initTags = () => {
|
||||
affixTagArr.value = filterAffixTags(unref(routers))
|
||||
@@ -73,7 +75,7 @@ const closeAllTags = () => {
|
||||
toLastView()
|
||||
}
|
||||
|
||||
// 关闭其他
|
||||
// 关闭其它
|
||||
const closeOthersTags = () => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
@@ -270,7 +272,8 @@ watch(
|
||||
>
|
||||
<Icon
|
||||
icon="ep:d-arrow-left"
|
||||
:color="appStore.getIsDark ? 'var(--el-text-color-regular)' : '#333'"
|
||||
color="var(--el-text-color-placeholder)"
|
||||
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
|
||||
/>
|
||||
</span>
|
||||
<div class="overflow-hidden flex-1">
|
||||
@@ -386,7 +389,8 @@ watch(
|
||||
>
|
||||
<Icon
|
||||
icon="ep:d-arrow-right"
|
||||
:color="appStore.getIsDark ? 'var(--el-text-color-regular)' : '#333'"
|
||||
color="var(--el-text-color-placeholder)"
|
||||
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
@@ -396,7 +400,8 @@ watch(
|
||||
>
|
||||
<Icon
|
||||
icon="ant-design:reload-outlined"
|
||||
:color="appStore.getIsDark ? 'var(--el-text-color-regular)' : '#333'"
|
||||
color="var(--el-text-color-placeholder)"
|
||||
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
|
||||
/>
|
||||
</span>
|
||||
<ContextMenu
|
||||
@@ -459,7 +464,8 @@ watch(
|
||||
>
|
||||
<Icon
|
||||
icon="ant-design:setting-outlined"
|
||||
:color="appStore.getIsDark ? 'var(--el-text-color-regular)' : '#333'"
|
||||
color="var(--el-text-color-placeholder)"
|
||||
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
|
||||
/>
|
||||
</span>
|
||||
</ContextMenu>
|
||||
@@ -476,36 +482,29 @@ watch(
|
||||
|
||||
&__tool {
|
||||
position: relative;
|
||||
&:after {
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(~'100% - 1px');
|
||||
border-left: 1px solid var(--layout-border-color);
|
||||
border-left: 1px solid var(--el-border-color);
|
||||
content: '';
|
||||
}
|
||||
|
||||
&--first {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(~'100% - 1px');
|
||||
border-right: 1px solid var(--layout-border-color);
|
||||
border-right: 1px solid var(--el-border-color);
|
||||
border-left: none;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:deep(span) {
|
||||
color: var(--el-color-black) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
@@ -544,7 +543,7 @@ watch(
|
||||
background-color: var(--el-color-primary);
|
||||
border: 1px solid var(--el-color-primary);
|
||||
.@{prefix-cls}__item--close {
|
||||
:deep(span) {
|
||||
:deep(svg) {
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
}
|
||||
@@ -554,14 +553,8 @@ watch(
|
||||
.dark {
|
||||
.@{prefix-cls} {
|
||||
&__tool {
|
||||
&:hover {
|
||||
:deep(span) {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--first {
|
||||
&:after {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -582,7 +575,7 @@ watch(
|
||||
background-color: var(--el-color-primary);
|
||||
border: 1px solid var(--el-color-primary);
|
||||
.@{prefix-cls}__item--close {
|
||||
:deep(span) {
|
||||
:deep(svg) {
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { resetRouter } from '@/router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { loginOutApi } from '@/api/login'
|
||||
@@ -24,7 +24,7 @@ const prefixCls = getPrefixCls('user-info')
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const { clear } = useStorage()
|
||||
|
||||
const { replace } = useRouter()
|
||||
|
||||
@@ -37,7 +37,7 @@ const loginOut = () => {
|
||||
.then(async () => {
|
||||
const res = await loginOutApi().catch(() => {})
|
||||
if (res) {
|
||||
wsCache.clear()
|
||||
clear()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter() // 重置静态路由表
|
||||
replace('/login')
|
||||
@@ -94,7 +94,9 @@ const toDocument = () => {
|
||||
<style scoped lang="less">
|
||||
.fade-bottom-enter-active,
|
||||
.fade-bottom-leave-active {
|
||||
transition: opacity 0.25s, transform 0.3s;
|
||||
transition:
|
||||
opacity 0.25s,
|
||||
transform 0.3s;
|
||||
}
|
||||
|
||||
.fade-bottom-enter-from {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, unref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { Form } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
@@ -54,14 +54,13 @@ const schema: FormSchema[] = reactive([
|
||||
}
|
||||
])
|
||||
|
||||
const { register, formRef, methods } = useForm({
|
||||
schema
|
||||
})
|
||||
const { formRegister, formMethods } = useForm()
|
||||
|
||||
const { getFormData } = methods
|
||||
const { getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const handleLock = () => {
|
||||
unref(formRef)?.validate(async (valid) => {
|
||||
const handleLock = async () => {
|
||||
const formExpose = await getElFormExpose()
|
||||
formExpose?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
dialogVisible.value = false
|
||||
const formData = await getFormData()
|
||||
@@ -86,7 +85,7 @@ const handleLock = () => {
|
||||
<img src="@/assets/imgs/avatar.jpg" alt="" class="w-70px h-70px rounded-[50%]" />
|
||||
<span class="text-14px my-10px text-[var(--top-header-text-color)]">Archer</span>
|
||||
</div>
|
||||
<Form :is-col="false" :rules="rules" @register="register" />
|
||||
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
|
||||
<template #footer>
|
||||
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
|
||||
</template>
|
||||
|
||||
@@ -3,10 +3,10 @@ import { ref } from 'vue'
|
||||
import { ElInput, ElButton } from 'element-plus'
|
||||
import { resetRouter } from '@/router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useNow } from './useNow'
|
||||
import { useNow } from '@/hooks/web/useNow'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { loginOutApi } from '@/api/login'
|
||||
@@ -14,7 +14,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const { clear } = useStorage()
|
||||
|
||||
const { replace } = useRouter()
|
||||
|
||||
@@ -51,7 +51,7 @@ async function unLock() {
|
||||
async function goLogin() {
|
||||
const res = await loginOutApi().catch(() => {})
|
||||
if (res) {
|
||||
wsCache.clear()
|
||||
clear()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter() // 重置静态路由表
|
||||
lockStore.resetLockInfo()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { App } from 'vue'
|
||||
import { Icon } from './Icon'
|
||||
import { Permission } from './Permission'
|
||||
|
||||
export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Icon', Icon)
|
||||
app.component('Permission', Permission)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
AxiosRequestHeaders,
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig
|
||||
} from './type'
|
||||
} from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import config from './config'
|
||||
|
||||
const { defaultHeaders } = config
|
||||
|
||||
const request = (option: any) => {
|
||||
const request = (option: AxiosConfig) => {
|
||||
const { url, method, params, data, headersType, responseType } = option
|
||||
return service.request({
|
||||
url: url,
|
||||
@@ -19,17 +19,17 @@ const request = (option: any) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
get: <T = any>(option: any) => {
|
||||
return request({ method: 'get', ...option }) as unknown as T
|
||||
get: <T = any>(option: AxiosConfig) => {
|
||||
return request({ method: 'get', ...option }) as Promise<IResponse<T>>
|
||||
},
|
||||
post: <T = any>(option: any) => {
|
||||
return request({ method: 'post', ...option }) as unknown as T
|
||||
post: <T = any>(option: AxiosConfig) => {
|
||||
return request({ method: 'post', ...option }) as Promise<IResponse<T>>
|
||||
},
|
||||
delete: <T = any>(option: any) => {
|
||||
return request({ method: 'delete', ...option }) as unknown as T
|
||||
delete: <T = any>(option: AxiosConfig) => {
|
||||
return request({ method: 'delete', ...option }) as Promise<IResponse<T>>
|
||||
},
|
||||
put: <T = any>(option: any) => {
|
||||
return request({ method: 'put', ...option }) as unknown as T
|
||||
put: <T = any>(option: AxiosConfig) => {
|
||||
return request({ method: 'put', ...option }) as Promise<IResponse<T>>
|
||||
},
|
||||
cancelRequest: (url: string | string[]) => {
|
||||
return service.cancelRequest(url)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios'
|
||||
import config, { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
|
||||
|
||||
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './type'
|
||||
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
|
||||
|
||||
const { interceptors, baseUrl } = config
|
||||
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASEPATH]
|
||||
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
|
||||
|
||||
const { requestInterceptors, responseInterceptors } = interceptors
|
||||
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import type { App, Directive, DirectiveBinding } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { intersection } from 'lodash-es'
|
||||
import { isArray } from '@/utils/is'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import router from '@/router'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wsCache } = useCache()
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
// 全部权限
|
||||
const all_permission = ['*.*.*']
|
||||
const hasPermission = (value: string | string[]): boolean => {
|
||||
const permissions = wsCache.get(appStore.getUserInfo).permissions as string[]
|
||||
const hasPermission = (value: string): boolean => {
|
||||
const permission = (router.currentRoute.value.meta.permission || []) as string[]
|
||||
if (!value) {
|
||||
throw new Error(t('permission.hasPermission'))
|
||||
}
|
||||
if (!isArray(value)) {
|
||||
return permissions?.includes(value as string)
|
||||
}
|
||||
if (all_permission[0] === permissions[0]) {
|
||||
if (permission.includes(value)) {
|
||||
return true
|
||||
}
|
||||
return (intersection(value, permissions) as string[]).length > 0
|
||||
return false
|
||||
}
|
||||
function hasPermi(el: Element, binding: DirectiveBinding) {
|
||||
const value = binding.value
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 配置浏览器本地存储的方式,可直接存储对象数组。
|
||||
*/
|
||||
|
||||
import WebStorageCache from 'web-storage-cache'
|
||||
|
||||
type CacheType = 'sessionStorage' | 'localStorage'
|
||||
|
||||
export const useCache = (type: CacheType = 'sessionStorage') => {
|
||||
const wsCache: WebStorageCache = new WebStorageCache({
|
||||
storage: type
|
||||
})
|
||||
|
||||
return {
|
||||
wsCache
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigGlobalTypes } from '@/types/configGlobal'
|
||||
import { ConfigGlobalTypes } from '@/components/ConfigGlobal'
|
||||
import { inject } from 'vue'
|
||||
|
||||
export const useConfigGlobal = () => {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { reactive } from 'vue'
|
||||
import { eachTree, treeMap, filter } from '@/utils/tree'
|
||||
import { findIndex } from '@/utils'
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { AxiosPromise } from 'axios'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { TableColumn } from '@/types/table'
|
||||
import { DescriptionsSchema } from '@/types/descriptions'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { TableColumn } from '@/components/Table'
|
||||
import { DescriptionsSchema } from '@/components/Descriptions'
|
||||
|
||||
export type CrudSchema = Omit<TableColumn, 'children'> & {
|
||||
search?: CrudSearchParams
|
||||
@@ -16,39 +12,25 @@ export type CrudSchema = Omit<TableColumn, 'children'> & {
|
||||
children?: CrudSchema[]
|
||||
}
|
||||
|
||||
type CrudSearchParams = {
|
||||
// 是否显示在查询项
|
||||
show?: boolean
|
||||
// 字典名称,会去取全局的字典
|
||||
dictName?: string
|
||||
// 接口
|
||||
api?: () => Promise<any>
|
||||
// 搜索字段
|
||||
field?: string
|
||||
} & Omit<FormSchema, 'field'>
|
||||
interface CrudSearchParams extends Omit<FormSchema, 'field'> {
|
||||
// 是否隐藏在查询项
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
type CrudTableParams = {
|
||||
// 是否显示表头
|
||||
show?: boolean
|
||||
} & Omit<FormSchema, 'field'>
|
||||
interface CrudTableParams extends Omit<TableColumn, 'field'> {
|
||||
// 是否隐藏表头
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
type CrudFormParams = {
|
||||
// 字典名称,会去取全局的字典
|
||||
dictName?: string
|
||||
// 接口
|
||||
api?: () => Promise<any>
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
} & Omit<FormSchema, 'field'>
|
||||
interface CrudFormParams extends Omit<FormSchema, 'field'> {
|
||||
// 是否隐藏表单项
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
type CrudDescriptionsParams = {
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
} & Omit<DescriptionsSchema, 'field'>
|
||||
|
||||
const dictStore = useDictStoreWithOut()
|
||||
|
||||
const { t } = useI18n()
|
||||
interface CrudDescriptionsParams extends Omit<DescriptionsSchema, 'field'> {
|
||||
// 是否隐藏表单项
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
interface AllSchemas {
|
||||
searchSchema: FormSchema[]
|
||||
@@ -71,13 +53,14 @@ export const useCrudSchemas = (
|
||||
detailSchema: []
|
||||
})
|
||||
|
||||
const searchSchema = filterSearchSchema(crudSchema, allSchemas)
|
||||
const searchSchema = filterSearchSchema(crudSchema)
|
||||
// @ts-ignore
|
||||
allSchemas.searchSchema = searchSchema || []
|
||||
|
||||
const tableColumns = filterTableSchema(crudSchema)
|
||||
allSchemas.tableColumns = tableColumns || []
|
||||
|
||||
const formSchema = filterFormSchema(crudSchema, allSchemas)
|
||||
const formSchema = filterFormSchema(crudSchema)
|
||||
allSchemas.formSchema = formSchema
|
||||
|
||||
const detailSchema = filterDescriptionsSchema(crudSchema)
|
||||
@@ -89,55 +72,26 @@ export const useCrudSchemas = (
|
||||
}
|
||||
|
||||
// 过滤 Search 结构
|
||||
const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
|
||||
const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
||||
const searchSchema: FormSchema[] = []
|
||||
const length = crudSchema.length
|
||||
|
||||
// 获取字典列表队列
|
||||
const searchRequestTask: Array<() => Promise<void>> = []
|
||||
|
||||
eachTree(crudSchema, (schemaItem: CrudSchema) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.search?.show) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const schemaItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!schemaItem?.search?.hidden) {
|
||||
const searchSchemaItem = {
|
||||
// 默认为 input
|
||||
component: schemaItem.search.component || 'Input',
|
||||
componentProps: {},
|
||||
component: schemaItem?.search?.component || 'Input',
|
||||
...schemaItem.search,
|
||||
field: schemaItem?.search?.field || schemaItem.field,
|
||||
label: schemaItem.search?.label || schemaItem.label
|
||||
}
|
||||
|
||||
if (searchSchemaItem.dictName) {
|
||||
// 如果有 dictName 则证明是从字典中获取数据
|
||||
const dictArr = dictStore.getDictObj[searchSchemaItem.dictName]
|
||||
searchSchemaItem.componentProps!.options = filterOptions(dictArr)
|
||||
} else if (searchSchemaItem.api) {
|
||||
searchRequestTask.push(async () => {
|
||||
const res = await (searchSchemaItem.api as () => AxiosPromise)()
|
||||
if (res) {
|
||||
const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => {
|
||||
return v.field === searchSchemaItem.field
|
||||
})
|
||||
if (index !== -1) {
|
||||
allSchemas.searchSchema[index]!.componentProps!.options = filterOptions(
|
||||
res,
|
||||
searchSchemaItem.componentProps.optionsAlias?.labelField
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete searchSchemaItem.show
|
||||
delete searchSchemaItem.dictName
|
||||
delete searchSchemaItem.hidden
|
||||
|
||||
searchSchema.push(searchSchemaItem)
|
||||
}
|
||||
})
|
||||
|
||||
for (const task of searchRequestTask) {
|
||||
task()
|
||||
}
|
||||
|
||||
return searchSchema
|
||||
@@ -147,7 +101,7 @@ const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): F
|
||||
const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
|
||||
const tableColumns = treeMap<CrudSchema>(crudSchema, {
|
||||
conversion: (schema: CrudSchema) => {
|
||||
if (schema?.table?.show !== false) {
|
||||
if (!schema?.table?.hidden) {
|
||||
return {
|
||||
...schema.table,
|
||||
...schema
|
||||
@@ -166,56 +120,28 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
|
||||
}
|
||||
|
||||
// 过滤 form 结构
|
||||
const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
|
||||
const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
||||
const formSchema: FormSchema[] = []
|
||||
const length = crudSchema.length
|
||||
|
||||
// 获取字典列表队列
|
||||
const formRequestTask: Array<() => Promise<void>> = []
|
||||
|
||||
eachTree(crudSchema, (schemaItem: CrudSchema) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.form?.show !== false) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const formItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!formItem?.form?.hidden) {
|
||||
const formSchemaItem = {
|
||||
// 默认为 input
|
||||
component: schemaItem?.form?.component || 'Input',
|
||||
componentProps: {},
|
||||
...schemaItem.form,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.search?.label || schemaItem.label
|
||||
}
|
||||
|
||||
if (formSchemaItem.dictName) {
|
||||
// 如果有 dictName 则证明是从字典中获取数据
|
||||
const dictArr = dictStore.getDictObj[formSchemaItem.dictName]
|
||||
formSchemaItem.componentProps!.options = filterOptions(dictArr)
|
||||
} else if (formSchemaItem.api) {
|
||||
formRequestTask.push(async () => {
|
||||
const res = await (formSchemaItem.api as () => AxiosPromise)()
|
||||
if (res) {
|
||||
const index = findIndex(allSchemas.formSchema, (v: FormSchema) => {
|
||||
return v.field === formSchemaItem.field
|
||||
})
|
||||
if (index !== -1) {
|
||||
allSchemas.formSchema[index]!.componentProps!.options = filterOptions(
|
||||
res,
|
||||
formSchemaItem.componentProps.optionsAlias?.labelField
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
component: formItem?.form?.component || 'Input',
|
||||
...formItem.form,
|
||||
field: formItem.field,
|
||||
label: formItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete formSchemaItem.show
|
||||
delete formSchemaItem.dictName
|
||||
delete formSchemaItem.hidden
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
}
|
||||
})
|
||||
|
||||
for (const task of formRequestTask) {
|
||||
task()
|
||||
}
|
||||
|
||||
return formSchema
|
||||
}
|
||||
|
||||
@@ -224,8 +150,8 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
|
||||
const descriptionsSchema: FormSchema[] = []
|
||||
|
||||
eachTree(crudSchema, (schemaItem: CrudSchema) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.detail?.show !== false) {
|
||||
// 判断是否隐藏
|
||||
if (!schemaItem?.detail?.hidden) {
|
||||
const descriptionsSchemaItem = {
|
||||
...schemaItem.detail,
|
||||
field: schemaItem.field,
|
||||
@@ -233,7 +159,7 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete descriptionsSchemaItem.show
|
||||
delete descriptionsSchemaItem.hidden
|
||||
|
||||
descriptionsSchema.push(descriptionsSchemaItem)
|
||||
}
|
||||
@@ -241,15 +167,3 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
|
||||
|
||||
return descriptionsSchema
|
||||
}
|
||||
|
||||
// 给options添加国际化
|
||||
const filterOptions = (options: Recordable, labelField?: string) => {
|
||||
return options?.map((v: Recordable) => {
|
||||
if (labelField) {
|
||||
v['labelField'] = t(v.labelField)
|
||||
} else {
|
||||
v['label'] = t(v.label)
|
||||
}
|
||||
return v
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Form, FormExpose } from '@/components/Form'
|
||||
import type { ElForm } from 'element-plus'
|
||||
import type { ElForm, ElFormItem } from 'element-plus'
|
||||
import { ref, unref, nextTick } from 'vue'
|
||||
import type { FormProps } from '@/components/Form/src/types'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
|
||||
|
||||
export const useForm = (props?: FormProps) => {
|
||||
export const useForm = () => {
|
||||
// From实例
|
||||
const formRef = ref<typeof Form & FormExpose>()
|
||||
|
||||
@@ -31,6 +30,10 @@ export const useForm = (props?: FormProps) => {
|
||||
|
||||
// 一些内置的方法
|
||||
const methods = {
|
||||
/**
|
||||
* @description 设置form组件的props
|
||||
* @param props form组件的props
|
||||
*/
|
||||
setProps: async (props: FormProps = {}) => {
|
||||
const form = await getForm()
|
||||
form?.setProps(props)
|
||||
@@ -39,20 +42,26 @@ export const useForm = (props?: FormProps) => {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置form的值
|
||||
* @param data 需要设置的数据
|
||||
*/
|
||||
setValues: async (data: Recordable) => {
|
||||
const form = await getForm()
|
||||
form?.setValues(data)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置schema
|
||||
* @param schemaProps 需要设置的schemaProps
|
||||
*/
|
||||
setSchema: async (schemaProps: FormSetPropsType[]) => {
|
||||
setSchema: async (schemaProps: FormSetProps[]) => {
|
||||
const form = await getForm()
|
||||
form?.setSchema(schemaProps)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 新增schema
|
||||
* @param formSchema 需要新增数据
|
||||
* @param index 在哪里新增
|
||||
*/
|
||||
@@ -62,6 +71,7 @@ export const useForm = (props?: FormProps) => {
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 删除schema
|
||||
* @param field 删除哪个数据
|
||||
*/
|
||||
delSchema: async (field: string) => {
|
||||
@@ -70,19 +80,51 @@ export const useForm = (props?: FormProps) => {
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取表单数据
|
||||
* @returns form data
|
||||
*/
|
||||
getFormData: async <T = Recordable>(): Promise<T> => {
|
||||
const form = await getForm()
|
||||
return form?.formModel as T
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取表单组件的实例
|
||||
* @param field 表单项唯一标识
|
||||
* @returns component instance
|
||||
*/
|
||||
getComponentExpose: async (field: string) => {
|
||||
const form = await getForm()
|
||||
return form?.getComponentExpose(field)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取formItem组件的实例
|
||||
* @param field 表单项唯一标识
|
||||
* @returns formItem instance
|
||||
*/
|
||||
getFormItemExpose: async (field: string) => {
|
||||
const form = await getForm()
|
||||
return form?.getFormItemExpose(field) as ComponentRef<typeof ElFormItem>
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取ElForm组件的实例
|
||||
* @returns ElForm instance
|
||||
*/
|
||||
getElFormExpose: async () => {
|
||||
await getForm()
|
||||
return unref(elFormRef)
|
||||
},
|
||||
|
||||
getFormExpose: async () => {
|
||||
await getForm()
|
||||
return unref(formRef)
|
||||
}
|
||||
}
|
||||
|
||||
props && methods.setProps(props)
|
||||
|
||||
return {
|
||||
register,
|
||||
formRef: elFormRef,
|
||||
methods
|
||||
formRegister: register,
|
||||
formMethods: methods
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { h } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { IconTypes } from '@/types/icon'
|
||||
import { Icon, IconTypes } from '@/components/Icon'
|
||||
|
||||
export const useIcon = (props: IconTypes): VNode => {
|
||||
return h(Icon, props)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { dateUtil } from '@/utils/dateUtil'
|
||||
import { reactive, toRefs } from 'vue'
|
||||
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
|
||||
|
||||
export function useNow(immediate = true) {
|
||||
export const useNow = (immediate = true) => {
|
||||
let timer: IntervalHandle
|
||||
|
||||
const state = reactive({
|
||||
91
src/hooks/web/useSearch.ts
Normal file
91
src/hooks/web/useSearch.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { ref, unref, nextTick } from 'vue'
|
||||
import { FormSchema, FormSetProps } from '@/components/Form'
|
||||
import { SearchExpose, SearchProps } from '@/components/Search'
|
||||
|
||||
export const useSearch = () => {
|
||||
// Search实例
|
||||
const searchRef = ref<SearchExpose>()
|
||||
|
||||
/**
|
||||
* @param ref Search实例
|
||||
* @param elRef ElForm实例
|
||||
*/
|
||||
const register = (ref: SearchExpose) => {
|
||||
searchRef.value = ref
|
||||
}
|
||||
|
||||
const getSearch = async () => {
|
||||
await nextTick()
|
||||
const search = unref(searchRef)
|
||||
if (!search) {
|
||||
console.error('The Search is not registered. Please use the register method to register')
|
||||
}
|
||||
return search
|
||||
}
|
||||
|
||||
// 一些内置的方法
|
||||
const methods = {
|
||||
/**
|
||||
* @description 设置search组件的props
|
||||
* @param field FormItem的field
|
||||
*/
|
||||
setProps: async (props: SearchProps = {}) => {
|
||||
const search = await getSearch()
|
||||
search?.setProps(props)
|
||||
if (props.model) {
|
||||
search?.setValues(props.model)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置form的值
|
||||
* @param data 需要设置的数据
|
||||
*/
|
||||
setValues: async (data: Recordable) => {
|
||||
const search = await getSearch()
|
||||
search?.setValues(data)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置schema
|
||||
* @param schemaProps 需要设置的schemaProps
|
||||
*/
|
||||
setSchema: async (schemaProps: FormSetProps[]) => {
|
||||
const search = await getSearch()
|
||||
search?.setSchema(schemaProps)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 新增schema
|
||||
* @param formSchema 需要新增数据
|
||||
* @param index 在哪里新增
|
||||
*/
|
||||
addSchema: async (formSchema: FormSchema, index?: number) => {
|
||||
const search = await getSearch()
|
||||
search?.addSchema(formSchema, index)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 删除schema
|
||||
* @param field 删除哪个数据
|
||||
*/
|
||||
delSchema: async (field: string) => {
|
||||
const search = await getSearch()
|
||||
search?.delSchema(field)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取表单数据
|
||||
* @returns form data
|
||||
*/
|
||||
getFormData: async <T = Recordable>(): Promise<T> => {
|
||||
const search = await getSearch()
|
||||
return search?.formModel as T
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
searchRegister: register,
|
||||
searchMethods: methods
|
||||
}
|
||||
}
|
||||
31
src/hooks/web/useStorage.ts
Normal file
31
src/hooks/web/useStorage.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { isArray, isObject } from '@/utils/is'
|
||||
|
||||
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
|
||||
const setStorage = (key: string, value: any) => {
|
||||
window[type].setItem(key, isArray(value) || isObject(value) ? JSON.stringify(value) : value)
|
||||
}
|
||||
|
||||
const getStorage = (key: string) => {
|
||||
const value = window[type].getItem(key)
|
||||
try {
|
||||
return JSON.parse(value || '')
|
||||
} catch (error) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const removeStorage = (key: string) => {
|
||||
window[type].removeItem(key)
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
window[type].clear()
|
||||
}
|
||||
|
||||
return {
|
||||
setStorage,
|
||||
getStorage,
|
||||
removeStorage,
|
||||
clear
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,57 @@
|
||||
import { Table, TableExpose } from '@/components/Table'
|
||||
import { ElTable, ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { ref, reactive, watch, computed, unref, nextTick } from 'vue'
|
||||
import { get } from 'lodash-es'
|
||||
import type { TableProps } from '@/components/Table/src/types'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { TableSetPropsType } from '@/types/table'
|
||||
import { Table, TableExpose, TableProps, TableSetProps, TableColumn } from '@/components/Table'
|
||||
import { ElTable, ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { ref, watch, unref, nextTick, onMounted } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface TableResponse<T = any> {
|
||||
total: number
|
||||
list: T[]
|
||||
pageNumber: number
|
||||
pageSize: number
|
||||
interface UseTableConfig {
|
||||
/**
|
||||
* 是否初始化的时候请求一次
|
||||
*/
|
||||
immediate?: boolean
|
||||
fetchDataApi: () => Promise<{
|
||||
list: any[]
|
||||
total?: number
|
||||
}>
|
||||
fetchDelApi?: () => Promise<boolean>
|
||||
}
|
||||
|
||||
interface UseTableConfig<T = any> {
|
||||
getListApi: (option: any) => Promise<IResponse<TableResponse<T>>>
|
||||
delListApi?: (option: any) => Promise<IResponse>
|
||||
// 返回数据格式配置
|
||||
response: {
|
||||
list: string
|
||||
total?: string
|
||||
}
|
||||
// 默认传递的参数
|
||||
defaultParams?: Recordable
|
||||
props?: TableProps
|
||||
}
|
||||
export const useTable = (config: UseTableConfig) => {
|
||||
const { immediate = true } = config
|
||||
|
||||
interface TableObject<T = any> {
|
||||
pageSize: number
|
||||
currentPage: number
|
||||
total: number
|
||||
tableList: T[]
|
||||
params: any
|
||||
loading: boolean
|
||||
currentRow: Nullable<T>
|
||||
}
|
||||
|
||||
export const useTable = <T = any>(config?: UseTableConfig<T>) => {
|
||||
const tableObject = reactive<TableObject<T>>({
|
||||
// 页数
|
||||
pageSize: 10,
|
||||
// 当前页
|
||||
currentPage: 1,
|
||||
// 总条数
|
||||
total: 10,
|
||||
// 表格数据
|
||||
tableList: [],
|
||||
// AxiosConfig 配置
|
||||
params: {
|
||||
...(config?.defaultParams || {})
|
||||
},
|
||||
// 加载中
|
||||
loading: true,
|
||||
// 当前行的数据
|
||||
currentRow: null
|
||||
})
|
||||
|
||||
const paramsObj = computed(() => {
|
||||
return {
|
||||
...tableObject.params,
|
||||
pageSize: tableObject.pageSize,
|
||||
pageIndex: tableObject.currentPage
|
||||
}
|
||||
})
|
||||
const loading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const dataList = ref<any[]>([])
|
||||
|
||||
watch(
|
||||
() => tableObject.currentPage,
|
||||
() => currentPage.value,
|
||||
() => {
|
||||
methods.getList()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => tableObject.pageSize,
|
||||
() => pageSize.value,
|
||||
() => {
|
||||
// 当前页不为1时,修改页数后会导致多次调用getList方法
|
||||
if (tableObject.currentPage === 1) {
|
||||
if (unref(currentPage) === 1) {
|
||||
methods.getList()
|
||||
} else {
|
||||
tableObject.currentPage = 1
|
||||
currentPage.value = 1
|
||||
methods.getList()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (immediate) {
|
||||
methods.getList()
|
||||
}
|
||||
})
|
||||
|
||||
// Table实例
|
||||
const tableRef = ref<typeof Table & TableExpose>()
|
||||
|
||||
@@ -106,91 +72,122 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
|
||||
return table
|
||||
}
|
||||
|
||||
const delData = async (ids: string[] | number[]) => {
|
||||
const res = await (config?.delListApi && config?.delListApi(ids))
|
||||
if (res) {
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
|
||||
// 计算出临界点
|
||||
const currentPage =
|
||||
tableObject.total % tableObject.pageSize === ids.length || tableObject.pageSize === 1
|
||||
? tableObject.currentPage > 1
|
||||
? tableObject.currentPage - 1
|
||||
: tableObject.currentPage
|
||||
: tableObject.currentPage
|
||||
|
||||
tableObject.currentPage = currentPage
|
||||
methods.getList()
|
||||
}
|
||||
}
|
||||
|
||||
const methods = {
|
||||
/**
|
||||
* 获取表单数据
|
||||
*/
|
||||
getList: async () => {
|
||||
tableObject.loading = true
|
||||
const res = await config?.getListApi(unref(paramsObj)).finally(() => {
|
||||
tableObject.loading = false
|
||||
})
|
||||
if (res) {
|
||||
tableObject.tableList = get(res.data || {}, config?.response.list as string)
|
||||
tableObject.total = get(res.data || {}, config?.response?.total as string) || 0
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await config?.fetchDataApi()
|
||||
console.log('fetchDataApi res', res)
|
||||
if (res) {
|
||||
dataList.value = res.list
|
||||
total.value = res.total || 0
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('fetchDataApi error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置table组件的props
|
||||
* @param props table组件的props
|
||||
*/
|
||||
setProps: async (props: TableProps = {}) => {
|
||||
const table = await getTable()
|
||||
table?.setProps(props)
|
||||
},
|
||||
setColumn: async (columnProps: TableSetPropsType[]) => {
|
||||
|
||||
/**
|
||||
* @description 设置column
|
||||
* @param columnProps 需要设置的列
|
||||
*/
|
||||
setColumn: async (columnProps: TableSetProps[]) => {
|
||||
const table = await getTable()
|
||||
table?.setColumn(columnProps)
|
||||
},
|
||||
getSelections: async () => {
|
||||
|
||||
/**
|
||||
* @description 新增column
|
||||
* @param tableColumn 需要新增数据
|
||||
* @param index 在哪里新增
|
||||
*/
|
||||
addColumn: async (tableColumn: TableColumn, index?: number) => {
|
||||
const table = await getTable()
|
||||
return (table?.selections || []) as T[]
|
||||
table?.addColumn(tableColumn, index)
|
||||
},
|
||||
// 与Search组件结合
|
||||
setSearchParams: (data: Recordable) => {
|
||||
tableObject.currentPage = 1
|
||||
tableObject.params = Object.assign(tableObject.params, {
|
||||
pageSize: tableObject.pageSize,
|
||||
pageIndex: tableObject.currentPage,
|
||||
...data
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 删除column
|
||||
* @param field 删除哪个数据
|
||||
*/
|
||||
delColumn: async (field: string) => {
|
||||
const table = await getTable()
|
||||
table?.delColumn(field)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取ElTable组件的实例
|
||||
* @returns ElTable instance
|
||||
*/
|
||||
getElTableExpose: async () => {
|
||||
await getTable()
|
||||
return unref(elTableRef)
|
||||
},
|
||||
|
||||
refresh: () => {
|
||||
methods.getList()
|
||||
},
|
||||
|
||||
// sortableChange: (e: any) => {
|
||||
// console.log('sortableChange', e)
|
||||
// const { oldIndex, newIndex } = e
|
||||
// dataList.value.splice(newIndex, 0, dataList.value.splice(oldIndex, 1)[0])
|
||||
// // to do something
|
||||
// }
|
||||
// 删除数据
|
||||
delList: async (ids: string[] | number[], multiple: boolean, message = true) => {
|
||||
const tableRef = await getTable()
|
||||
if (multiple) {
|
||||
if (!tableRef?.selections.length) {
|
||||
ElMessage.warning(t('common.delNoData'))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!tableObject.currentRow) {
|
||||
ElMessage.warning(t('common.delNoData'))
|
||||
return
|
||||
}
|
||||
}
|
||||
if (message) {
|
||||
ElMessageBox.confirm(t('common.delMessage'), t('common.delWarning'), {
|
||||
confirmButtonText: t('common.delOk'),
|
||||
cancelButtonText: t('common.delCancel'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
await delData(ids)
|
||||
})
|
||||
} else {
|
||||
await delData(ids)
|
||||
delList: async (idsLength: number) => {
|
||||
const { fetchDelApi } = config
|
||||
if (!fetchDelApi) {
|
||||
console.warn('fetchDelApi is undefined')
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(t('common.delMessage'), t('common.delWarning'), {
|
||||
confirmButtonText: t('common.delOk'),
|
||||
cancelButtonText: t('common.delCancel'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await fetchDelApi()
|
||||
if (res) {
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
|
||||
// 计算出临界点
|
||||
const current =
|
||||
unref(total) % unref(pageSize) === idsLength || unref(pageSize) === 1
|
||||
? unref(currentPage) > 1
|
||||
? unref(currentPage) - 1
|
||||
: unref(currentPage)
|
||||
: unref(currentPage)
|
||||
|
||||
currentPage.value = current
|
||||
methods.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
config?.props && methods.setProps(config.props)
|
||||
|
||||
return {
|
||||
register,
|
||||
elTableRef,
|
||||
tableObject,
|
||||
methods
|
||||
tableRegister: register,
|
||||
tableMethods: methods,
|
||||
tableState: {
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
dataList,
|
||||
loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,11 @@ export default {
|
||||
delOk: 'OK',
|
||||
delCancel: 'Cancel',
|
||||
delNoData: 'Please select the data to delete',
|
||||
delSuccess: 'Deleted successfully'
|
||||
delSuccess: 'Deleted successfully',
|
||||
refresh: 'Refresh',
|
||||
fullscreen: 'Fullscreen',
|
||||
size: 'Size',
|
||||
columnSetting: 'Column setting'
|
||||
},
|
||||
lock: {
|
||||
lockScreen: 'Lock screen',
|
||||
@@ -154,7 +158,13 @@ export default {
|
||||
role: 'Role management',
|
||||
document: 'Document',
|
||||
inputPassword: 'InputPassword',
|
||||
sticky: 'Sticky'
|
||||
sticky: 'Sticky',
|
||||
treeTable: 'Tree table',
|
||||
PicturePreview: 'Table Image Preview',
|
||||
department: 'Department management',
|
||||
menuManagement: 'Menu management',
|
||||
// 权限测试页面
|
||||
permission: 'Permission test page'
|
||||
},
|
||||
permission: {
|
||||
hasPermission: 'Please set the operation permission value'
|
||||
@@ -241,8 +251,11 @@ export default {
|
||||
transfer: 'Transfer',
|
||||
render: 'Render',
|
||||
radio: 'Radio',
|
||||
radioGroup: 'Radio Group',
|
||||
button: 'Button',
|
||||
checkbox: 'Checkbox',
|
||||
checkboxButton: 'Checkbox Button',
|
||||
checkboxGroup: 'Checkbox Group',
|
||||
slider: 'Slider',
|
||||
datePicker: 'Date Picker',
|
||||
shortcuts: 'Shortcuts',
|
||||
@@ -277,7 +290,26 @@ export default {
|
||||
set: 'Set',
|
||||
subitem: 'Subitem',
|
||||
formValidation: 'Form validation',
|
||||
verifyReset: 'Verify reset'
|
||||
verifyReset: 'Verify reset',
|
||||
// 富文本编辑器
|
||||
richText: 'Rich text',
|
||||
form: 'Form',
|
||||
// 远程加载
|
||||
remoteLoading: 'Remote loading',
|
||||
// 聚焦
|
||||
focus: 'Focus',
|
||||
treeSelect: 'Tree Select',
|
||||
showCheckbox: 'Show Checkbox',
|
||||
selectAnyLevel: 'Select Any Level',
|
||||
multiple: 'Multiple',
|
||||
filterable: 'Filterable',
|
||||
// 自定义节点内容
|
||||
customContent: 'Custom content',
|
||||
// 懒加载
|
||||
lazyLoad: 'Lazy load',
|
||||
upload: 'Upload',
|
||||
// 用户头像
|
||||
userAvatar: 'User avatar'
|
||||
},
|
||||
guideDemo: {
|
||||
guide: 'Guide',
|
||||
@@ -361,7 +393,13 @@ export default {
|
||||
left: 'left',
|
||||
center: 'center',
|
||||
right: 'right',
|
||||
dynamicOptions: 'Dynamic options'
|
||||
dynamicOptions: 'Dynamic options',
|
||||
// 删除单选框
|
||||
deleteRadio: 'Delete radio',
|
||||
// 还原单选框
|
||||
restoreRadio: 'Restore radio',
|
||||
loading: 'Loading',
|
||||
reset: 'Reset'
|
||||
},
|
||||
stickyDemo: {
|
||||
sticky: 'Sticky'
|
||||
@@ -392,7 +430,14 @@ export default {
|
||||
hiddenExpandedRows: 'Hidden expanded rows',
|
||||
changeTitle: 'Change title',
|
||||
header: 'Header',
|
||||
selectAllNone: 'Select all / none'
|
||||
selectAllNone: 'Select all / none',
|
||||
delOrAddAction: 'Delete or add action',
|
||||
showOrHiddenStripe: 'Show or hidden stripe',
|
||||
showOrHiddenBorder: 'Show or hidden border',
|
||||
fixedHeaderOrAuto: 'Fixed header or auto',
|
||||
getSelections: 'Get selections',
|
||||
preview: 'Preview',
|
||||
showOrHiddenSortable: 'Show or hidden sortable'
|
||||
},
|
||||
richText: {
|
||||
richText: 'Rich text',
|
||||
@@ -446,7 +491,45 @@ export default {
|
||||
role: 'Role',
|
||||
remark: 'Remark',
|
||||
remarkMessage1: 'Back end control routing permission',
|
||||
remarkMessage2: 'Front end control routing permission'
|
||||
remarkMessage2: 'Front end control routing permission',
|
||||
// 部门列表
|
||||
departmentList: 'Department list',
|
||||
// 搜索部门
|
||||
searchDepartment: 'Search department',
|
||||
account: 'Account',
|
||||
email: 'Email',
|
||||
createTime: 'Create time',
|
||||
// 所属部门
|
||||
department: 'Department',
|
||||
departmentName: 'Department name',
|
||||
status: 'Status',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable',
|
||||
superiorDepartment: 'Superior department'
|
||||
},
|
||||
menu: {
|
||||
menuName: 'Menu name',
|
||||
icon: 'Icon',
|
||||
// 权限
|
||||
permission: 'Permission',
|
||||
component: 'Component',
|
||||
path: 'Path',
|
||||
status: 'Status',
|
||||
hidden: 'Hidden',
|
||||
alwaysShow: 'Always show',
|
||||
noCache: 'No cache',
|
||||
breadcrumb: 'Breadcrumb',
|
||||
affix: 'Affix',
|
||||
noTagsView: 'No tags view',
|
||||
activeMenu: 'Active menu',
|
||||
canTo: 'Can to',
|
||||
name: 'Name'
|
||||
},
|
||||
role: {
|
||||
roleName: 'Role name',
|
||||
role: 'Role',
|
||||
// 菜单分配
|
||||
menu: 'Menu allocation'
|
||||
},
|
||||
inputPasswordDemo: {
|
||||
title: 'InputPassword',
|
||||
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
closeTab: '关闭标签页',
|
||||
closeTheLeftTab: '关闭左侧标签页',
|
||||
closeTheRightTab: '关闭右侧标签页',
|
||||
closeOther: '关闭其他标签页',
|
||||
closeOther: '关闭其它标签页',
|
||||
closeAll: '关闭全部标签页',
|
||||
prevLabel: '上一步',
|
||||
nextLabel: '下一步',
|
||||
@@ -40,7 +40,11 @@ export default {
|
||||
delOk: '确定',
|
||||
delCancel: '取消',
|
||||
delNoData: '请选择需要删除的数据',
|
||||
delSuccess: '删除成功'
|
||||
delSuccess: '删除成功',
|
||||
refresh: '刷新',
|
||||
fullscreen: '全屏',
|
||||
size: '尺寸',
|
||||
columnSetting: '列设置'
|
||||
},
|
||||
lock: {
|
||||
lockScreen: '锁定屏幕',
|
||||
@@ -102,7 +106,7 @@ export default {
|
||||
register: '注册',
|
||||
checkPassword: '确认密码',
|
||||
login: '登录',
|
||||
otherLogin: '其他登录方式',
|
||||
otherLogin: '其它登录方式',
|
||||
remember: '记住我',
|
||||
hasUser: '已有账号?去登录',
|
||||
forgetPassword: '忘记密码',
|
||||
@@ -154,7 +158,12 @@ export default {
|
||||
role: '角色管理',
|
||||
document: '文档',
|
||||
inputPassword: '密码输入框',
|
||||
sticky: '黏性'
|
||||
sticky: '黏性',
|
||||
treeTable: '树形表格',
|
||||
PicturePreview: '表格图片预览',
|
||||
department: '部门管理',
|
||||
menuManagement: '菜单管理',
|
||||
permission: '权限测试页'
|
||||
},
|
||||
permission: {
|
||||
hasPermission: '请设置操作权限值'
|
||||
@@ -241,8 +250,11 @@ export default {
|
||||
transfer: '穿梭框',
|
||||
render: '渲染器',
|
||||
radio: '单选框',
|
||||
radioGroup: '单选框组',
|
||||
button: '按钮',
|
||||
checkbox: '多选框',
|
||||
checkboxButton: '多选框按钮',
|
||||
checkboxGroup: '多选框组',
|
||||
slider: '滑块',
|
||||
datePicker: '日期选择器',
|
||||
shortcuts: '快捷选项',
|
||||
@@ -276,7 +288,23 @@ export default {
|
||||
set: '设置',
|
||||
subitem: '子项',
|
||||
formValidation: '表单验证',
|
||||
verifyReset: '验证重置'
|
||||
verifyReset: '验证重置',
|
||||
// 富文本编辑器
|
||||
richText: '富文本编辑器',
|
||||
form: '表单',
|
||||
// 远程加载
|
||||
remoteLoading: '远程加载',
|
||||
// 聚焦
|
||||
focus: '聚焦',
|
||||
treeSelect: '树形选择器',
|
||||
showCheckbox: '显示复选框',
|
||||
selectAnyLevel: '选择任意级别',
|
||||
multiple: '多选',
|
||||
filterable: '可筛选',
|
||||
customContent: '自定义内容',
|
||||
lazyLoad: '懒加载',
|
||||
upload: '上传',
|
||||
userAvatar: '用户头像'
|
||||
},
|
||||
guideDemo: {
|
||||
guide: '引导页',
|
||||
@@ -358,7 +386,13 @@ export default {
|
||||
left: '左',
|
||||
center: '中',
|
||||
right: '右',
|
||||
dynamicOptions: '动态选项'
|
||||
dynamicOptions: '动态选项',
|
||||
// 删除单选框
|
||||
deleteRadio: '删除单选框',
|
||||
// 还原单选框
|
||||
restoreRadio: '还原单选框',
|
||||
loading: '加载中',
|
||||
reset: '重置'
|
||||
},
|
||||
stickyDemo: {
|
||||
sticky: '黏性'
|
||||
@@ -389,7 +423,14 @@ export default {
|
||||
hiddenExpandedRows: '隐藏展开行',
|
||||
changeTitle: '修改标题',
|
||||
header: '头部',
|
||||
selectAllNone: '全选/全不选'
|
||||
selectAllNone: '全选/全不选',
|
||||
delOrAddAction: '删除/添加操作列',
|
||||
showOrHiddenStripe: '显示/隐藏斑马纹',
|
||||
showOrHiddenBorder: '显示/隐藏边框',
|
||||
fixedHeaderOrAuto: '固定头部/自动',
|
||||
getSelections: '获取多选数据',
|
||||
preview: '封面',
|
||||
showOrHiddenSortable: '显示/隐藏排序'
|
||||
},
|
||||
richText: {
|
||||
richText: '富文本',
|
||||
@@ -442,7 +483,45 @@ export default {
|
||||
role: '角色',
|
||||
remark: '备注',
|
||||
remarkMessage1: '后端控制路由权限',
|
||||
remarkMessage2: '前端控制路由权限'
|
||||
remarkMessage2: '前端控制路由权限',
|
||||
// 部门列表
|
||||
departmentList: '部门列表',
|
||||
searchDepartment: '搜索部门',
|
||||
account: '账号',
|
||||
email: '邮箱',
|
||||
createTime: '创建时间',
|
||||
// 所属部门
|
||||
department: '所属部门',
|
||||
departmentName: '部门名称',
|
||||
status: '状态',
|
||||
// 启用
|
||||
enable: '启用',
|
||||
// 禁用
|
||||
disable: '禁用',
|
||||
// 上级部门
|
||||
superiorDepartment: '上级部门'
|
||||
},
|
||||
menu: {
|
||||
menuName: '菜单名称',
|
||||
icon: '图标',
|
||||
permission: '权限标识',
|
||||
component: '组件',
|
||||
path: '路径',
|
||||
status: '状态',
|
||||
hidden: '是否隐藏',
|
||||
alwaysShow: '是否一直显示',
|
||||
noCache: '是否清除缓存',
|
||||
breadcrumb: '是否显示面包屑',
|
||||
affix: '是否固定在标签页',
|
||||
noTagsView: '是否隐藏标签页',
|
||||
activeMenu: '高亮菜单',
|
||||
canTo: '是否可跳转',
|
||||
name: '组件名称'
|
||||
},
|
||||
role: {
|
||||
roleName: '角色名称',
|
||||
role: '角色',
|
||||
menu: '菜单分配'
|
||||
},
|
||||
inputPasswordDemo: {
|
||||
title: '密码输入框',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import router from './router'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
@@ -15,7 +15,7 @@ const appStore = useAppStoreWithOut()
|
||||
|
||||
const dictStore = useDictStoreWithOut()
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const { getStorage } = useStorage()
|
||||
|
||||
const { start, done } = useNProgress()
|
||||
|
||||
@@ -26,52 +26,52 @@ const whiteList = ['/login'] // 不重定向白名单
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
start()
|
||||
loadStart()
|
||||
// if (!wsCache.get(appStore.getUserInfo)) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
if (!dictStore.getIsSetDict) {
|
||||
// 获取所有字典
|
||||
const res = await getDictApi()
|
||||
if (res) {
|
||||
dictStore.setDictObj(res.data)
|
||||
dictStore.setIsSetDict(true)
|
||||
}
|
||||
}
|
||||
if (permissionStore.getIsAddRouters) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = wsCache.get('roleRouters') || []
|
||||
const userInfo = wsCache.get(appStore.getUserInfo)
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
if (getStorage(appStore.getUserInfo)) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none')
|
||||
}
|
||||
if (!dictStore.getIsSetDict) {
|
||||
// 获取所有字典
|
||||
const res = await getDictApi()
|
||||
if (res) {
|
||||
dictStore.setDictObj(res.data)
|
||||
dictStore.setIsSetDict(true)
|
||||
}
|
||||
}
|
||||
if (permissionStore.getIsAddRouters) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
const redirectPath = from.query.redirect || to.path
|
||||
const redirect = decodeURIComponent(redirectPath as string)
|
||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
|
||||
permissionStore.setIsAddRouters(true)
|
||||
next(nextData)
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = getStorage('roleRouters') || []
|
||||
const userInfo = getStorage(appStore.getUserInfo)
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none')
|
||||
}
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
const redirectPath = from.query.redirect || to.path
|
||||
const redirect = decodeURIComponent(redirectPath as string)
|
||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
|
||||
permissionStore.setIsAddRouters(true)
|
||||
next(nextData)
|
||||
}
|
||||
} else {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
}
|
||||
}
|
||||
// } else {
|
||||
// if (whiteList.indexOf(to.path) !== -1) {
|
||||
// next()
|
||||
// } else {
|
||||
// next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
// }
|
||||
// }
|
||||
})
|
||||
|
||||
router.afterEach((to) => {
|
||||
|
||||
@@ -148,335 +148,335 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||
meta: {
|
||||
title: t('router.defaultForm')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-form',
|
||||
component: () => import('@/views/Components/Form/UseFormDemo.vue'),
|
||||
name: 'UseForm',
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'use-form',
|
||||
// component: () => import('@/views/Components/Form/UseFormDemo.vue'),
|
||||
// name: 'UseForm',
|
||||
// meta: {
|
||||
// title: 'UseForm'
|
||||
// }
|
||||
// }
|
||||
// {
|
||||
// path: 'ref-form',
|
||||
// component: () => import('@/views/Components/Form/RefForm.vue'),
|
||||
// name: 'RefForm',
|
||||
// meta: {
|
||||
// title: 'RefForm'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: getParentLayout(),
|
||||
redirect: '/components/table/default-table',
|
||||
name: 'TableDemo',
|
||||
meta: {
|
||||
title: t('router.table'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-table',
|
||||
component: () => import('@/views/Components/Table/DefaultTable.vue'),
|
||||
name: 'DefaultTable',
|
||||
meta: {
|
||||
title: t('router.defaultTable')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-table',
|
||||
component: () => import('@/views/Components/Table/UseTableDemo.vue'),
|
||||
name: 'UseTable',
|
||||
meta: {
|
||||
title: 'UseTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tree-table',
|
||||
component: () => import('@/views/Components/Table/TreeTable.vue'),
|
||||
name: 'TreeTable',
|
||||
meta: {
|
||||
title: t('router.treeTable')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'table-image-preview',
|
||||
component: () => import('@/views/Components/Table/TableImagePreview.vue'),
|
||||
name: 'TableImagePreview',
|
||||
meta: {
|
||||
title: t('router.PicturePreview')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'editor-demo',
|
||||
component: getParentLayout(),
|
||||
redirect: '/components/editor-demo/editor',
|
||||
name: 'EditorDemo',
|
||||
meta: {
|
||||
title: t('router.editor'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: () => import('@/views/Components/Editor/Editor.vue'),
|
||||
name: 'Editor',
|
||||
meta: {
|
||||
title: t('router.richText')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: () => import('@/views/Components/Search.vue'),
|
||||
name: 'Search',
|
||||
meta: {
|
||||
title: t('router.search')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'descriptions',
|
||||
component: () => import('@/views/Components/Descriptions.vue'),
|
||||
name: 'Descriptions',
|
||||
meta: {
|
||||
title: t('router.descriptions')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'image-viewer',
|
||||
component: () => import('@/views/Components/ImageViewer.vue'),
|
||||
name: 'ImageViewer',
|
||||
meta: {
|
||||
title: t('router.imageViewer')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
component: () => import('@/views/Components/Dialog.vue'),
|
||||
name: 'Dialog',
|
||||
meta: {
|
||||
title: t('router.dialog')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: () => import('@/views/Components/Icon.vue'),
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: t('router.icon')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'echart',
|
||||
component: () => import('@/views/Components/Echart.vue'),
|
||||
name: 'Echart',
|
||||
meta: {
|
||||
title: t('router.echart')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'count-to',
|
||||
component: () => import('@/views/Components/CountTo.vue'),
|
||||
name: 'CountTo',
|
||||
meta: {
|
||||
title: t('router.countTo')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'qrcode',
|
||||
component: () => import('@/views/Components/Qrcode.vue'),
|
||||
name: 'Qrcode',
|
||||
meta: {
|
||||
title: t('router.qrcode')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'highlight',
|
||||
component: () => import('@/views/Components/Highlight.vue'),
|
||||
name: 'Highlight',
|
||||
meta: {
|
||||
title: t('router.highlight')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'infotip',
|
||||
component: () => import('@/views/Components/Infotip.vue'),
|
||||
name: 'Infotip',
|
||||
meta: {
|
||||
title: t('router.infotip')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'input-password',
|
||||
component: () => import('@/views/Components/InputPassword.vue'),
|
||||
name: 'InputPassword',
|
||||
meta: {
|
||||
title: t('router.inputPassword')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/hooks',
|
||||
component: Layout,
|
||||
redirect: '/hooks/useWatermark',
|
||||
name: 'Hooks',
|
||||
meta: {
|
||||
title: 'hooks',
|
||||
icon: 'ic:outline-webhook',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'useWatermark',
|
||||
component: () => import('@/views/hooks/useWatermark.vue'),
|
||||
name: 'UseWatermark',
|
||||
meta: {
|
||||
title: 'useWatermark'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'table',
|
||||
// component: getParentLayout(),
|
||||
// redirect: '/components/table/default-table',
|
||||
// name: 'TableDemo',
|
||||
// path: 'useOpenTab',
|
||||
// component: () => import('@/views/hooks/useOpenTab.vue'),
|
||||
// name: 'UseOpenTab',
|
||||
// meta: {
|
||||
// title: t('router.table'),
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'default-table',
|
||||
// component: () => import('@/views/Components/Table/DefaultTable.vue'),
|
||||
// name: 'DefaultTable',
|
||||
// meta: {
|
||||
// title: t('router.defaultTable')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'use-table',
|
||||
// component: () => import('@/views/Components/Table/UseTableDemo.vue'),
|
||||
// name: 'UseTable',
|
||||
// meta: {
|
||||
// title: 'UseTable'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'ref-table',
|
||||
// component: () => import('@/views/Components/Table/RefTable.vue'),
|
||||
// name: 'RefTable',
|
||||
// meta: {
|
||||
// title: 'RefTable'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: 'editor-demo',
|
||||
// component: getParentLayout(),
|
||||
// redirect: '/components/editor-demo/editor',
|
||||
// name: 'EditorDemo',
|
||||
// meta: {
|
||||
// title: t('router.editor'),
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'editor',
|
||||
// component: () => import('@/views/Components/Editor/Editor.vue'),
|
||||
// name: 'Editor',
|
||||
// meta: {
|
||||
// title: t('router.richText')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: 'search',
|
||||
// component: () => import('@/views/Components/Search.vue'),
|
||||
// name: 'Search',
|
||||
// meta: {
|
||||
// title: t('router.search')
|
||||
// title: 'useOpenTab'
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// {
|
||||
// path: 'descriptions',
|
||||
// component: () => import('@/views/Components/Descriptions.vue'),
|
||||
// name: 'Descriptions',
|
||||
// path: 'useCrudSchemas',
|
||||
// component: () => import('@/views/hooks/useCrudSchemas.vue'),
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: t('router.descriptions')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'image-viewer',
|
||||
// component: () => import('@/views/Components/ImageViewer.vue'),
|
||||
// name: 'ImageViewer',
|
||||
// meta: {
|
||||
// title: t('router.imageViewer')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'dialog',
|
||||
// component: () => import('@/views/Components/Dialog.vue'),
|
||||
// name: 'Dialog',
|
||||
// meta: {
|
||||
// title: t('router.dialog')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'icon',
|
||||
// component: () => import('@/views/Components/Icon.vue'),
|
||||
// name: 'Icon',
|
||||
// meta: {
|
||||
// title: t('router.icon')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'echart',
|
||||
// component: () => import('@/views/Components/Echart.vue'),
|
||||
// name: 'Echart',
|
||||
// meta: {
|
||||
// title: t('router.echart')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'count-to',
|
||||
// component: () => import('@/views/Components/CountTo.vue'),
|
||||
// name: 'CountTo',
|
||||
// meta: {
|
||||
// title: t('router.countTo')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'qrcode',
|
||||
// component: () => import('@/views/Components/Qrcode.vue'),
|
||||
// name: 'Qrcode',
|
||||
// meta: {
|
||||
// title: t('router.qrcode')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'highlight',
|
||||
// component: () => import('@/views/Components/Highlight.vue'),
|
||||
// name: 'Highlight',
|
||||
// meta: {
|
||||
// title: t('router.highlight')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'infotip',
|
||||
// component: () => import('@/views/Components/Infotip.vue'),
|
||||
// name: 'Infotip',
|
||||
// meta: {
|
||||
// title: t('router.infotip')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'input-password',
|
||||
// component: () => import('@/views/Components/InputPassword.vue'),
|
||||
// name: 'InputPassword',
|
||||
// meta: {
|
||||
// title: t('router.inputPassword')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'sticky',
|
||||
// component: () => import('@/views/Components/Sticky.vue'),
|
||||
// name: 'Sticky',
|
||||
// meta: {
|
||||
// title: t('router.sticky')
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '/hooks',
|
||||
// component: Layout,
|
||||
// redirect: '/hooks/useWatermark',
|
||||
// name: 'Hooks',
|
||||
// meta: {
|
||||
// title: 'hooks',
|
||||
// icon: 'ic:outline-webhook',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'useWatermark',
|
||||
// component: () => import('@/views/hooks/useWatermark.vue'),
|
||||
// name: 'UseWatermark',
|
||||
// meta: {
|
||||
// title: 'useWatermark'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'useCrudSchemas',
|
||||
// component: () => import('@/views/hooks/useCrudSchemas.vue'),
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: '/level',
|
||||
// component: Layout,
|
||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
// name: 'Level',
|
||||
// meta: {
|
||||
// title: t('router.level'),
|
||||
// icon: 'carbon:skill-level-advanced'
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'menu1',
|
||||
// name: 'Menu1',
|
||||
// component: getParentLayout(),
|
||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
// meta: {
|
||||
// title: t('router.menu1')
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'menu1-1',
|
||||
// name: 'Menu11',
|
||||
// component: getParentLayout(),
|
||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
// meta: {
|
||||
// title: t('router.menu11'),
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'menu1-1-1',
|
||||
// name: 'Menu111',
|
||||
// component: () => import('@/views/Level/Menu111.vue'),
|
||||
// meta: {
|
||||
// title: t('router.menu111')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: 'menu1-2',
|
||||
// name: 'Menu12',
|
||||
// component: () => import('@/views/Level/Menu12.vue'),
|
||||
// meta: {
|
||||
// title: t('router.menu12')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: 'menu2',
|
||||
// name: 'Menu2',
|
||||
// component: () => import('@/views/Level/Menu2.vue'),
|
||||
// meta: {
|
||||
// title: t('router.menu2')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// path: '/example',
|
||||
// component: Layout,
|
||||
// redirect: '/example/example-dialog',
|
||||
// name: 'Example',
|
||||
// meta: {
|
||||
// title: t('router.example'),
|
||||
// icon: 'ep:management',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'example-dialog',
|
||||
// component: () => import('@/views/Example/Dialog/ExampleDialog.vue'),
|
||||
// name: 'ExampleDialog',
|
||||
// meta: {
|
||||
// title: t('router.exampleDialog')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'example-page',
|
||||
// component: () => import('@/views/Example/Page/ExamplePage.vue'),
|
||||
// name: 'ExamplePage',
|
||||
// meta: {
|
||||
// title: t('router.examplePage')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'example-add',
|
||||
// component: () => import('@/views/Example/Page/ExampleAdd.vue'),
|
||||
// name: 'ExampleAdd',
|
||||
// meta: {
|
||||
// title: t('router.exampleAdd'),
|
||||
// noTagsView: true,
|
||||
// noCache: true,
|
||||
// hidden: true,
|
||||
// canTo: true,
|
||||
// activeMenu: '/example/example-page'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'example-edit',
|
||||
// component: () => import('@/views/Example/Page/ExampleEdit.vue'),
|
||||
// name: 'ExampleEdit',
|
||||
// meta: {
|
||||
// title: t('router.exampleEdit'),
|
||||
// noTagsView: true,
|
||||
// noCache: true,
|
||||
// hidden: true,
|
||||
// canTo: true,
|
||||
// activeMenu: '/example/example-page'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'example-detail',
|
||||
// component: () => import('@/views/Example/Page/ExampleDetail.vue'),
|
||||
// name: 'ExampleDetail',
|
||||
// meta: {
|
||||
// title: t('router.exampleDetail'),
|
||||
// noTagsView: true,
|
||||
// noCache: true,
|
||||
// hidden: true,
|
||||
// canTo: true,
|
||||
// activeMenu: '/example/example-page'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: '/level',
|
||||
component: Layout,
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: t('router.level'),
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: getParentLayout(),
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: t('router.menu1')
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: getParentLayout(),
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: t('router.menu11'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: () => import('@/views/Level/Menu111.vue'),
|
||||
meta: {
|
||||
title: t('router.menu111')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: () => import('@/views/Level/Menu12.vue'),
|
||||
meta: {
|
||||
title: t('router.menu12')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2',
|
||||
component: () => import('@/views/Level/Menu2.vue'),
|
||||
meta: {
|
||||
title: t('router.menu2')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: Layout,
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
meta: {
|
||||
title: t('router.example'),
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: () => import('@/views/Example/Dialog/ExampleDialog.vue'),
|
||||
name: 'ExampleDialog',
|
||||
meta: {
|
||||
title: t('router.exampleDialog')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: () => import('@/views/Example/Page/ExamplePage.vue'),
|
||||
name: 'ExamplePage',
|
||||
meta: {
|
||||
title: t('router.examplePage')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: () => import('@/views/Example/Page/ExampleAdd.vue'),
|
||||
name: 'ExampleAdd',
|
||||
meta: {
|
||||
title: t('router.exampleAdd'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: () => import('@/views/Example/Page/ExampleEdit.vue'),
|
||||
name: 'ExampleEdit',
|
||||
meta: {
|
||||
title: t('router.exampleEdit'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: () => import('@/views/Example/Page/ExampleDetail.vue'),
|
||||
name: 'ExampleDetail',
|
||||
meta: {
|
||||
title: t('router.exampleDetail'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: Layout,
|
||||
@@ -513,36 +513,80 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/authorization',
|
||||
component: Layout,
|
||||
redirect: '/authorization/user',
|
||||
name: 'Authorization',
|
||||
meta: {
|
||||
title: t('router.authorization'),
|
||||
icon: 'eos-icons:role-binding',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'department',
|
||||
component: () => import('@/views/Authorization/Department/Department.vue'),
|
||||
name: 'Department',
|
||||
meta: {
|
||||
title: t('router.department')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
component: () => import('@/views/Authorization/User/User.vue'),
|
||||
name: 'User',
|
||||
meta: {
|
||||
title: t('router.user')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'menu',
|
||||
component: () => import('@/views/Authorization/Menu/Menu.vue'),
|
||||
name: 'Menu',
|
||||
meta: {
|
||||
title: t('router.menuManagement')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
component: () => import('@/views/Authorization/Role/Role.vue'),
|
||||
name: 'Role',
|
||||
meta: {
|
||||
title: t('router.role')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
component: () => import('@/views/Authorization/Test/Test.vue'),
|
||||
name: 'Test',
|
||||
meta: {
|
||||
title: t('router.permission'),
|
||||
permission: ['add', 'edit', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/dynamic',
|
||||
component: Layout,
|
||||
redirect: '/404',
|
||||
name: 'Dynamic',
|
||||
meta: {
|
||||
hidden: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'tab/:id',
|
||||
component: () => import('@/components/Dynamic/src/Dynamic.vue'),
|
||||
name: 'Dynamic',
|
||||
meta: {
|
||||
title: '详情页'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// path: '/authorization',
|
||||
// component: Layout,
|
||||
// redirect: '/authorization/user',
|
||||
// name: 'Authorization',
|
||||
// meta: {
|
||||
// title: t('router.authorization'),
|
||||
// icon: 'eos-icons:role-binding',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'user',
|
||||
// component: () => import('@/views/Authorization/User.vue'),
|
||||
// name: 'User',
|
||||
// meta: {
|
||||
// title: t('router.user')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'role',
|
||||
// component: () => import('@/views/Authorization/Role.vue'),
|
||||
// name: 'Role',
|
||||
// meta: {
|
||||
// title: t('router.role')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user