mirror of
https://github.com/fugary/simple-element-plus-template.git
synced 2025-11-12 14:27:49 +00:00
1. 升级依赖版本
This commit is contained in:
4
.env
4
.env
@@ -1,7 +1,7 @@
|
|||||||
# 程序名称
|
# 程序名称
|
||||||
VITE_APP_NAME=Simple Element+
|
VITE_APP_NAME=Simple Element+
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_APP_API_BASE_URL=https://www.fastmock.site/mock/80793bea9d60828fda74202f7017e953/simple
|
VITE_APP_API_BASE_URL=/simple
|
||||||
# 超时配置
|
# 超时配置
|
||||||
VITE_APP_API_TIMEOUT=10000
|
VITE_APP_API_TIMEOUT=10000
|
||||||
|
VITE_APP_SYSTEM_KEY=SIMPLE-ELEMENT
|
||||||
|
|||||||
75
mock/MockCity.js
Normal file
75
mock/MockCity.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import Mock from 'mockjs'
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/simple/city/autoCities',
|
||||||
|
method: 'post',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: function () {
|
||||||
|
let pageSize = 10
|
||||||
|
if (request.body.page) {
|
||||||
|
pageSize = request.body.page.pageSize || 10
|
||||||
|
}
|
||||||
|
const total = 99
|
||||||
|
const pageCount = parseInt((total + pageSize - 1) / pageSize)
|
||||||
|
const result = {
|
||||||
|
page: {
|
||||||
|
pageSize: function () {
|
||||||
|
return pageSize
|
||||||
|
},
|
||||||
|
pageNumber: function () {
|
||||||
|
if (request.body.page) {
|
||||||
|
return request.body.page.pageNumber || 1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
pageCount,
|
||||||
|
totalCount: total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let size = 10
|
||||||
|
if (request.body.page) {
|
||||||
|
size = request.body.page.pageSize
|
||||||
|
}
|
||||||
|
result['cityList|' + size] = [{
|
||||||
|
code: function () {
|
||||||
|
return Mock.Random.string('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 3)
|
||||||
|
},
|
||||||
|
nameCn: function () {
|
||||||
|
return Mock.Random.city()
|
||||||
|
},
|
||||||
|
nameEn: function () {
|
||||||
|
return 'En' + this.nameCn
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
return Mock.mock(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/simple/city/selectCities',
|
||||||
|
method: 'post',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: {
|
||||||
|
'cityList|20-70': [{
|
||||||
|
code: function () {
|
||||||
|
return Mock.Random.string('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 3)
|
||||||
|
},
|
||||||
|
nameCn: function () {
|
||||||
|
return Mock.Random.city()
|
||||||
|
},
|
||||||
|
nameEn: function () {
|
||||||
|
return 'En' + this.nameCn
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
31
mock/MockLogin.js
Normal file
31
mock/MockLogin.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
*/
|
||||||
|
export default [{
|
||||||
|
url: '/simple/login',
|
||||||
|
method: 'post',
|
||||||
|
response: (request) => {
|
||||||
|
return {
|
||||||
|
success: function () {
|
||||||
|
return request.body.userName === 'admin' && request.body.userPassword === '123456'
|
||||||
|
},
|
||||||
|
message: function () {
|
||||||
|
return this.success ? '登录成功' : '用户名或密码错误'
|
||||||
|
},
|
||||||
|
resultData: function () {
|
||||||
|
if (this.success) {
|
||||||
|
return {
|
||||||
|
account: {
|
||||||
|
userNameEN: 'Tom',
|
||||||
|
userNameCN: '汤姆',
|
||||||
|
gender: 'male',
|
||||||
|
email: 'tomcat@fugary.com'
|
||||||
|
},
|
||||||
|
accessToken: 'abcdefghijklmn'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
159
mock/MockMenus.js
Normal file
159
mock/MockMenus.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
const allMenus = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
iconCls: 'setting',
|
||||||
|
nameCn: '系统管理',
|
||||||
|
nameEn: 'System'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'user',
|
||||||
|
nameCn: '用户管理',
|
||||||
|
nameEn: 'Users',
|
||||||
|
menuUrl: '/admin/users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'GroupOutlined',
|
||||||
|
nameCn: '角色管理',
|
||||||
|
nameEn: 'Roles',
|
||||||
|
menuUrl: '/admin/roles'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'lock',
|
||||||
|
nameCn: '权限管理',
|
||||||
|
nameEn: 'Authority',
|
||||||
|
menuUrl: '/admin/authority'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'GroupsOutlined',
|
||||||
|
nameCn: '用户组管理',
|
||||||
|
nameEn: 'Groups',
|
||||||
|
menuUrl: '/admin/groups'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'SupervisedUserCircleOutlined',
|
||||||
|
nameCn: '租户管理',
|
||||||
|
nameEn: 'Tenants',
|
||||||
|
menuUrl: '/admin/tenants'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
parentId: 1,
|
||||||
|
iconCls: 'menu',
|
||||||
|
nameCn: '菜单管理',
|
||||||
|
nameEn: 'Menus',
|
||||||
|
menuUrl: '/admin/menus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
iconCls: 'BuildFilled',
|
||||||
|
nameCn: '常用工具',
|
||||||
|
nameEn: 'Tools'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
parentId: 2,
|
||||||
|
iconCls: 'InsertEmoticonOutlined',
|
||||||
|
nameCn: '图标管理',
|
||||||
|
nameEn: 'Icons',
|
||||||
|
menuUrl: '/icons'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
parentId: 2,
|
||||||
|
iconCls: 'TableRowsFilled',
|
||||||
|
nameCn: '表单示例',
|
||||||
|
nameEn: 'Forms',
|
||||||
|
menuUrl: '/forms'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 23,
|
||||||
|
parentId: 2,
|
||||||
|
iconCls: 'Grid',
|
||||||
|
nameCn: '表格示例',
|
||||||
|
nameEn: 'Tables',
|
||||||
|
menuUrl: '/tables'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 24,
|
||||||
|
parentId: 2,
|
||||||
|
iconCls: 'TipsAndUpdatesOutlined',
|
||||||
|
nameCn: '其他示例',
|
||||||
|
nameEn: 'Others',
|
||||||
|
menuUrl: '/tests'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default [{
|
||||||
|
url: '/simple/api/menus',
|
||||||
|
method: 'post',
|
||||||
|
response: () => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: {
|
||||||
|
menuList: allMenus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
url: '/simple/api/searchMenus',
|
||||||
|
method: 'post',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: function () {
|
||||||
|
const menuList = allMenus
|
||||||
|
let pageSize = 10
|
||||||
|
let pageNumber = 1
|
||||||
|
if (request.body.page) {
|
||||||
|
pageSize = +request.body.page.pageSize || 10
|
||||||
|
pageNumber = +request.body.page.pageNumber || 1
|
||||||
|
}
|
||||||
|
const total = menuList.length
|
||||||
|
const pageCount = (total + pageSize - 1) / pageSize
|
||||||
|
const result = {
|
||||||
|
page: {
|
||||||
|
pageSize: function () {
|
||||||
|
return pageSize
|
||||||
|
},
|
||||||
|
pageNumber: function ({ request }) {
|
||||||
|
if (request.body.page) {
|
||||||
|
return request.body.page.pageNumber || 1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
pageCount,
|
||||||
|
totalCount: total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.menuList = menuList.slice((pageNumber - 1) * pageSize, pageNumber * pageSize)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
url: '/simple/api/menus/:id',
|
||||||
|
method: 'get',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: function () {
|
||||||
|
return {
|
||||||
|
menu: allMenus.filter(menu => menu.id === +request.query.id)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
74
mock/MockUsers.js
Normal file
74
mock/MockUsers.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import Mock from 'mockjs'
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/simple/api/users',
|
||||||
|
method: 'post',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: function () {
|
||||||
|
let pageSize = 10
|
||||||
|
if (request.body.page) {
|
||||||
|
pageSize = +request.body.page.pageSize || 10
|
||||||
|
}
|
||||||
|
const total = 999
|
||||||
|
const pageCount = (total + pageSize - 1) / pageSize
|
||||||
|
const result = {
|
||||||
|
page: {
|
||||||
|
pageSize: function () {
|
||||||
|
return pageSize
|
||||||
|
},
|
||||||
|
pageNumber: function () {
|
||||||
|
if (request.body.page) {
|
||||||
|
return request.body.page.pageNumber || 1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
pageCount,
|
||||||
|
totalCount: total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let size = 10
|
||||||
|
if (request.body.page) {
|
||||||
|
size = request.body.page.pageSize
|
||||||
|
}
|
||||||
|
result['userList|' + size] = [{
|
||||||
|
id: '@id',
|
||||||
|
'gender|1': ['male', 'female'],
|
||||||
|
nameCn: '@cname',
|
||||||
|
nameEn: '@name',
|
||||||
|
address: function () {
|
||||||
|
return Mock.Random.city(true)
|
||||||
|
},
|
||||||
|
birthday: '@date'
|
||||||
|
}]
|
||||||
|
return Mock.mock(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
url: '/simple/api/users/:id',
|
||||||
|
method: 'get',
|
||||||
|
response: request => {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
resultData: {
|
||||||
|
user: {
|
||||||
|
id: function () {
|
||||||
|
return request.query.id
|
||||||
|
},
|
||||||
|
'gender|1': ['male', 'female'],
|
||||||
|
nameCn: '@cname',
|
||||||
|
nameEn: '@name',
|
||||||
|
address: function () {
|
||||||
|
return Mock.Random.city(true)
|
||||||
|
},
|
||||||
|
birthday: '@date'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
1445
package-lock.json
generated
1445
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -11,27 +11,29 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vueuse/core": "^10.7.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"axios": "^1.6.3",
|
"axios": "^1.6.8",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"element-plus": "^2.4.4",
|
"element-plus": "^2.6.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"vue": "^3.3.13",
|
"vite-plugin-mock": "^3.0.1",
|
||||||
"vue-i18n": "^9.8.0",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.2.5",
|
"vue-i18n": "^9.10.2",
|
||||||
|
"vue-router": "^4.3.0",
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.8"
|
"vue-virtual-scroller": "^2.0.0-beta.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.6.1",
|
"@rushstack/eslint-patch": "^1.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||||
"@typescript-eslint/parser": "^6.18.1",
|
"@typescript-eslint/parser": "^7.4.0",
|
||||||
"@vitejs/plugin-vue": "^4.5.2",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-standard": "^8.0.1",
|
"@vue/eslint-config-standard": "^8.0.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.19.2",
|
"eslint-plugin-vue": "^9.24.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.3",
|
||||||
"vite": "^5.0.10"
|
"vite": "^5.2.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,23 +19,62 @@ html, body, #app, .index-container {
|
|||||||
border-right: 0 none;
|
border-right: 0 none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.common-el-tooltip {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-dropdown .el-dropdown-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown-link:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.el-menu-left:not(.el-menu--collapse) {
|
.el-menu-left:not(.el-menu--collapse) {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-padding {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-5 {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-10 {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-15 {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-main {
|
||||||
|
padding: var(--el-main-padding);
|
||||||
|
}
|
||||||
|
|
||||||
.padding-left1 {
|
.padding-left1 {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
.padding-left2 {
|
.padding-left2 {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
.padding-left3 {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
.padding-right1 {
|
.padding-right1 {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
.padding-right2 {
|
.padding-right2 {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
.padding-right3 {
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.padding-top1 {
|
.padding-top1 {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
@@ -43,12 +82,59 @@ html, body, #app, .index-container {
|
|||||||
.padding-top2 {
|
.padding-top2 {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
.padding-top3 {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
.padding-bottom1 {
|
.padding-bottom1 {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
.padding-bottom2 {
|
.padding-bottom2 {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.padding-bottom3 {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-bottom1 {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.margin-bottom2 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.margin-bottom3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-left1 {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.margin-left2 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.margin-left3 {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-right1 {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.margin-right2 {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.margin-right3 {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top1 {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.margin-top2 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.margin-top3 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -94,6 +180,10 @@ html, body, #app, .index-container {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.container-center {
|
.container-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -101,22 +191,63 @@ html, body, #app, .index-container {
|
|||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.common-form.el-form--inline .el-input{
|
.reason-code-container .el-radio__label{
|
||||||
--el-input-width: 220px;
|
white-space: break-spaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.common-tabs .el-tabs__new-tab {
|
||||||
|
width: 50%;
|
||||||
|
border: none;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__item.is-active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form.el-form--inline .el-select,
|
||||||
|
.common-subform.el-form--inline .el-select{
|
||||||
|
--el-select-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form.el-form--inline .el-input,
|
||||||
.common-subform.el-form--inline .el-input{
|
.common-subform.el-form--inline .el-input{
|
||||||
--el-input-width: 220px;
|
--el-input-width: 200px;
|
||||||
|
--el-date-editor-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-small.common-form.el-form--inline .el-select{
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-small.common-form.el-form--inline .el-input{
|
||||||
|
--el-input-width: 160px;
|
||||||
|
--el-date-editor-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-edit-width-70 {
|
.form-edit-width-70 {
|
||||||
width:70%
|
width:70%
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-edit-width-90 {
|
||||||
|
width:90%
|
||||||
|
}
|
||||||
|
|
||||||
.form-edit-width-100 {
|
.form-edit-width-100 {
|
||||||
width:100%
|
width:100%
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-edit-width-70 .el-select:not(:is(.el-form--inline .el-select,.el-pagination .el-select)),
|
||||||
|
.form-edit-width-90 .el-select:not(:is(.el-form--inline .el-select,.el-pagination .el-select)),
|
||||||
|
.form-edit-width-100 .el-select:not(:is(.el-form--inline .el-select,.el-pagination .el-select)){
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.common-autocomplete .el-popover__title{
|
.common-autocomplete .el-popover__title{
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -133,6 +264,8 @@ html, body, #app, .index-container {
|
|||||||
|
|
||||||
.common-autocomplete .common-select-page .el-tabs__item {
|
.common-autocomplete .common-select-page .el-tabs__item {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
padding-left: 10px !important;
|
||||||
|
padding-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.common-autocomplete .common-select-page .common-select-page-btn {
|
.common-autocomplete .common-select-page .common-select-page-btn {
|
||||||
@@ -146,6 +279,90 @@ html, body, #app, .index-container {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.common-hide-expand.el-table .el-table__expand-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exchange-button {
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-card .el-card__header{
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-card.el-card {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card .el-card__body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-book-card .el-card__body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-card .card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-data-row label {
|
||||||
|
font-weight: 600 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-data-row .el-col {
|
||||||
|
padding: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown+.el-button {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment-label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-auto {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-auto .el-form-item {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-auto .el-form-item__content {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-auto .el-form--inline .el-input,
|
||||||
|
.common-form-auto.el-form--inline .el-input,
|
||||||
|
.common-form-auto .el-form--inline .el-select,
|
||||||
|
.common-form-auto.el-form--inline .el-select {
|
||||||
|
--el-input-width: 100%;
|
||||||
|
--el-date-editor-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form-auto.el-form--inline .el-form-item,
|
||||||
|
.common-form-auto .el-form--inline .el-form-item {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item.is-required .common-form-label .common-form-label-text:before {
|
||||||
|
content: "*";
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow2 {
|
||||||
|
flex-grow: 2;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* slide-fade动画
|
* slide-fade动画
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { debounce, isEmpty, isObject } from 'lodash'
|
import { debounce, isEmpty, isObject, cloneDeep, chunk } from 'lodash'
|
||||||
import { onClickOutside, onKeyStroke, useVModel } from '@vueuse/core'
|
import { onClickOutside, onKeyStroke, useVModel } from '@vueuse/core'
|
||||||
import chunk from 'lodash/chunk'
|
|
||||||
import { UPDATE_MODEL_EVENT, CHANGE_EVENT, useFormItem } from 'element-plus'
|
import { UPDATE_MODEL_EVENT, CHANGE_EVENT, useFormItem } from 'element-plus'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +50,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
inputWidth: {
|
inputWidth: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '200px'
|
default: ''
|
||||||
},
|
},
|
||||||
autocompleteConfig: {
|
autocompleteConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -128,10 +127,13 @@ const selectPageData = ref({})
|
|||||||
const selectPageTab = ref(null)
|
const selectPageTab = ref(null)
|
||||||
const popoverVisible = ref(false)
|
const popoverVisible = ref(false)
|
||||||
const autocompletePopover = ref()
|
const autocompletePopover = ref()
|
||||||
const autoPage = ref({
|
const defaultAutoPage = {
|
||||||
pageSize: 8,
|
pageSize: props.autocompleteConfig?.pageSize || 8,
|
||||||
pageNumber: 1
|
pageNumber: 1
|
||||||
})
|
}
|
||||||
|
const autoPage = ref(props.autocompleteConfig?.frontendPaging
|
||||||
|
? null
|
||||||
|
: cloneDeep(defaultAutoPage))
|
||||||
const loadingData = ref(false)
|
const loadingData = ref(false)
|
||||||
|
|
||||||
const idProp = computed(() => props.autocompleteConfig?.idProp || props.idProp)
|
const idProp = computed(() => props.autocompleteConfig?.idProp || props.idProp)
|
||||||
@@ -194,10 +196,21 @@ const onInputKeywords = debounce((input) => {
|
|||||||
}
|
}
|
||||||
}, props.debounceTime)
|
}, props.debounceTime)
|
||||||
|
|
||||||
|
const calcDefaultLabelFunc = () => {
|
||||||
|
if (!props.useIdModel) {
|
||||||
|
const value = props.modelValue
|
||||||
|
return value && isObject(value) ? value[labelProp.value] : ''
|
||||||
|
}
|
||||||
|
return props.defaultLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcDefaultLabel = computed(calcDefaultLabelFunc)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onClickOutside(autocompletePopover.value?.popperRef?.contentRef, () => {
|
onClickOutside(autocompletePopover.value?.popperRef?.contentRef, () => {
|
||||||
popoverVisible.value = false
|
popoverVisible.value = false
|
||||||
})
|
})
|
||||||
|
setAutocompleteLabel(calcDefaultLabel.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => popoverVisible.value, (val) => {
|
watch(() => popoverVisible.value, (val) => {
|
||||||
@@ -211,7 +224,6 @@ watch(() => popoverVisible.value, (val) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.modelValue, (value) => {
|
watch(() => props.modelValue, (value) => {
|
||||||
console.info('=====================value', value)
|
|
||||||
if (!props.useIdModel) {
|
if (!props.useIdModel) {
|
||||||
setAutocompleteLabel(value && isObject(value) ? value[labelProp.value] : '')
|
setAutocompleteLabel(value && isObject(value) ? value[labelProp.value] : '')
|
||||||
if (isEmpty(value)) {
|
if (isEmpty(value)) {
|
||||||
@@ -220,13 +232,7 @@ watch(() => props.modelValue, (value) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => {
|
watch(calcDefaultLabelFunc, (label) => {
|
||||||
if (!props.useIdModel) {
|
|
||||||
const value = props.modelValue
|
|
||||||
return value && isObject(value) ? value[labelProp.value] : ''
|
|
||||||
}
|
|
||||||
return props.defaultLabel
|
|
||||||
}, (label) => {
|
|
||||||
setAutocompleteLabel(label)
|
setAutocompleteLabel(label)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -287,7 +293,6 @@ const moveSelection = function (down) {
|
|||||||
currentOnIndex.value = -1
|
currentOnIndex.value = -1
|
||||||
currentOnRow.value = null
|
currentOnRow.value = null
|
||||||
}
|
}
|
||||||
console.info('=================', tableRef.value.table, currentOnIndex.value, currentOnRow.value)
|
|
||||||
tableRef.value.table?.setCurrentRow(currentOnRow.value)
|
tableRef.value.table?.setCurrentRow(currentOnRow.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +354,15 @@ watch(() => props.selectPageConfig, () => {
|
|||||||
selectPageTab.value = null
|
selectPageTab.value = null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => props.autocompleteConfig, (autocompleteConfig) => {
|
||||||
|
defaultAutoPage.pageSize = autocompleteConfig.pageSize || 8
|
||||||
|
if (autocompleteConfig.frontendPaging) {
|
||||||
|
autoPage.value = null
|
||||||
|
} else {
|
||||||
|
autoPage.value = cloneDeep(defaultAutoPage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -443,6 +457,8 @@ watch(() => props.selectPageConfig, () => {
|
|||||||
:empty-text="autocompleteConfig.emptyMessage"
|
:empty-text="autocompleteConfig.emptyMessage"
|
||||||
:data="dataList"
|
:data="dataList"
|
||||||
:page-attrs="pageAttrs"
|
:page-attrs="pageAttrs"
|
||||||
|
:frontend-paging="autocompleteConfig.frontendPaging"
|
||||||
|
:frontend-page-size="defaultAutoPage.pageSize"
|
||||||
@row-click="onSelectData($event)"
|
@row-click="onSelectData($event)"
|
||||||
@current-page-change="onInputKeywords(false)"
|
@current-page-change="onInputKeywords(false)"
|
||||||
>
|
>
|
||||||
|
|||||||
12
src/components/common-autocomplete/public.d.ts
vendored
12
src/components/common-autocomplete/public.d.ts
vendored
@@ -19,6 +19,18 @@ export interface CommonSelectPageOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonAutocompleteOption {
|
export interface CommonAutocompleteOption {
|
||||||
|
/** id属性名 */
|
||||||
|
labelProp?: string;
|
||||||
|
/** label属性名 */
|
||||||
|
idProp?: string;
|
||||||
|
/**
|
||||||
|
* 分页数
|
||||||
|
*/
|
||||||
|
pageSize: number;
|
||||||
|
/**
|
||||||
|
* 前端分页模式
|
||||||
|
*/
|
||||||
|
frontendPaging: boolean;
|
||||||
/** 自动完成表格列配置 */
|
/** 自动完成表格列配置 */
|
||||||
columns: Array<CommonTableColumn>;
|
columns: Array<CommonTableColumn>;
|
||||||
/** 空数据提示信息 */
|
/** 空数据提示信息 */
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useTabsViewStore } from '@/stores/TabsViewStore'
|
import { useTabsViewStore } from '@/stores/TabsViewStore'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useMenuInfo, useMenuName } from '@/components/utils'
|
import { parsePathParams, useMenuInfo, useMenuName } from '@/components/utils'
|
||||||
|
|
||||||
const tabsViewStore = useTabsViewStore()
|
const tabsViewStore = useTabsViewStore()
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ const breadcrumbs = computed(() => {
|
|||||||
icon = item.meta.icon
|
icon = item.meta.icon
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
path: item.path,
|
path: parsePathParams(item.path, route.params),
|
||||||
menuName: useMenuName(item),
|
menuName: useMenuName(item),
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,11 @@ const breadcrumbs = computed(() => {
|
|||||||
return notExist && !item.menuName.endsWith('Base')
|
return notExist && !item.menuName.endsWith('Base')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-breadcrumb
|
<el-breadcrumb
|
||||||
v-bind="$attrs"
|
|
||||||
class="common-breadcrumb"
|
class="common-breadcrumb"
|
||||||
>
|
>
|
||||||
<el-breadcrumb-item
|
<el-breadcrumb-item
|
||||||
|
|||||||
136
src/components/common-form-control/common-filter-control.vue
Normal file
136
src/components/common-form-control/common-filter-control.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<script setup>
|
||||||
|
import { get, isArray, isObject, set } from 'lodash'
|
||||||
|
import { computed, onMounted, useSlots } from 'vue'
|
||||||
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* @type {CommonFormOption}
|
||||||
|
*/
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
labelWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '150px'
|
||||||
|
},
|
||||||
|
prop: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
unlimitedEnable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const calcFilterType = computed(() => props.option.type || 'checkbox-group')
|
||||||
|
|
||||||
|
const isUnlimited = computed(() => {
|
||||||
|
const option = props.option
|
||||||
|
const value = get(props.model, option.prop)
|
||||||
|
const filterType = calcFilterType.value
|
||||||
|
if (filterType === 'common-tab-filter') {
|
||||||
|
if (isObject(value) && option.attrs?.tabs && option.attrs?.tabs.length) {
|
||||||
|
let unlimited = true
|
||||||
|
option.attrs.tabs.forEach(tab => {
|
||||||
|
const tabVal = get(value, tab.prop)
|
||||||
|
if (unlimited) {
|
||||||
|
if (isArray(tabVal)) {
|
||||||
|
if (tabVal.length) {
|
||||||
|
unlimited = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unlimited = !tabVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return unlimited
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !value || !value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const setUnlimited = () => {
|
||||||
|
const option = props.option
|
||||||
|
const filterType = calcFilterType.value
|
||||||
|
let value
|
||||||
|
if (filterType.includes('checkbox')) {
|
||||||
|
value = []
|
||||||
|
} else if (filterType === 'common-tab-filter') {
|
||||||
|
value = {}
|
||||||
|
}
|
||||||
|
set(props.model, option.prop, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initFilterModel = () => {
|
||||||
|
const filterType = calcFilterType.value
|
||||||
|
if (filterType === 'common-tab-filter') {
|
||||||
|
const value = get(props.model, props.option.prop)
|
||||||
|
if (!value) {
|
||||||
|
set(props.model, props.option.prop, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initFilterModel()
|
||||||
|
})
|
||||||
|
|
||||||
|
const newOption = computed(() => {
|
||||||
|
const option = cloneDeep(props.option)
|
||||||
|
option.type = calcFilterType.value
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
|
||||||
|
const slots = computed(() => {
|
||||||
|
const tmpSlots = cloneDeep(useSlots())
|
||||||
|
delete tmpSlots.afterLabel
|
||||||
|
return tmpSlots
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<common-form-control
|
||||||
|
:label-width="labelWidth"
|
||||||
|
:model="model"
|
||||||
|
:option="newOption"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="unlimitedEnable"
|
||||||
|
#afterLabel
|
||||||
|
>
|
||||||
|
<slot name="afterLabel" />
|
||||||
|
<el-link
|
||||||
|
class="margin-left1 margin-top1"
|
||||||
|
type="primary"
|
||||||
|
:underline="false"
|
||||||
|
@click="setUnlimited()"
|
||||||
|
>
|
||||||
|
<el-tag
|
||||||
|
size="small"
|
||||||
|
:effect="isUnlimited?'dark':'light'"
|
||||||
|
>
|
||||||
|
{{ $t('common.label.unlimited') }}
|
||||||
|
</el-tag>
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-for="(slot, slotKey) in slots"
|
||||||
|
:key="slotKey"
|
||||||
|
#[slotKey]
|
||||||
|
>
|
||||||
|
<slot :name="slotKey" />
|
||||||
|
</template>
|
||||||
|
</common-form-control>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
23
src/components/common-form-control/common-form-label.vue
Normal file
23
src/components/common-form-control/common-form-label.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
component: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="common-form-label">
|
||||||
|
<slot />
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
v-if="component"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
89
src/components/common-form-control/common-tab-filter.vue
Normal file
89
src/components/common-form-control/common-tab-filter.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import ControlChild from '@/components/common-form-control/control-child.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useInputType } from '@/components/utils'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'checkbox-group'
|
||||||
|
},
|
||||||
|
defaultIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'CaretBottom'
|
||||||
|
},
|
||||||
|
iconSize: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const vModel = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
|
const childTypeMapping = { // 自动映射子元素类型,配置的时候可以不写type
|
||||||
|
'checkbox-group': 'checkbox',
|
||||||
|
'radio-group': 'radio',
|
||||||
|
select: 'option'
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcTabs = computed(() => {
|
||||||
|
return props.tabs.map(tab => {
|
||||||
|
tab.children = (tab.children || []).map(child => {
|
||||||
|
child.type = child.type || childTypeMapping[props.type]
|
||||||
|
return child
|
||||||
|
})
|
||||||
|
if (!tab.icon) {
|
||||||
|
tab.icon = props.defaultIcon
|
||||||
|
}
|
||||||
|
return tab
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputType = computed(() => useInputType({ type: props.type }))
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-tabs
|
||||||
|
type="border-card"
|
||||||
|
class="form-edit-width-100"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="(tab, index) in calcTabs"
|
||||||
|
:key="tab.label + index"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
{{ tab.label }}
|
||||||
|
<common-icon
|
||||||
|
:size="tab.iconSize||iconSize"
|
||||||
|
:icon="tab.icon"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<component
|
||||||
|
:is="inputType"
|
||||||
|
v-if="vModel"
|
||||||
|
v-model="vModel[tab.prop]"
|
||||||
|
@change="vModel._tabFilter=true"
|
||||||
|
>
|
||||||
|
<control-child
|
||||||
|
v-for="(childItem, childIdx) in tab.children"
|
||||||
|
:key="childIdx"
|
||||||
|
:option="childItem"
|
||||||
|
/>
|
||||||
|
</component>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { $i18nBundle } from '@/messages'
|
import { toLabelByKey, useInputType } from '@/components/utils'
|
||||||
import { useInputType } from '@/components/utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {{option:CommonFormOption}}
|
* @type {{option:CommonFormOption}}
|
||||||
@@ -21,31 +20,17 @@ const inputType = computed(() => useInputType(props.option))
|
|||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
const option = props.option
|
const option = props.option
|
||||||
if (option.labelKey) {
|
if (option.labelKey) {
|
||||||
return $i18nBundle(option.labelKey)
|
return toLabelByKey(option.labelKey)
|
||||||
}
|
}
|
||||||
return option.label
|
return option.label
|
||||||
})
|
})
|
||||||
/**
|
|
||||||
* element-plus的复选框和单选框没有value值,只有label用于存储值,因此特殊处理
|
|
||||||
* @type {string[]}
|
|
||||||
*/
|
|
||||||
const labelAsValueKeys = ['el-checkbox', 'el-radio', 'el-checkbox-button', 'el-radio-button']
|
|
||||||
|
|
||||||
const labelOrValue = computed(() => {
|
|
||||||
const option = props.option
|
|
||||||
if (labelAsValueKeys.includes(inputType.value)) {
|
|
||||||
return option.value
|
|
||||||
}
|
|
||||||
return label.value
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="inputType"
|
:is="inputType"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
:label="labelOrValue"
|
:label="label"
|
||||||
v-bind="option.attrs"
|
v-bind="option.attrs"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, isVNode, ref, watch } from 'vue'
|
||||||
import { $i18nBundle } from '@/messages'
|
import { $i18nBundle } from '@/messages'
|
||||||
import ControlChild from '@/components/common-form-control/control-child.vue'
|
import ControlChild from '@/components/common-form-control/control-child.vue'
|
||||||
import { useInputType } from '@/components/utils'
|
import { toLabelByKey, useInputType } from '@/components/utils'
|
||||||
import cloneDeep from 'lodash/cloneDeep'
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
import { get, set } from 'lodash'
|
import { get, isFunction, set } from 'lodash'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {{option:CommonFormOption}}
|
* @type {{option:CommonFormOption}}
|
||||||
@@ -24,13 +25,27 @@ const props = defineProps({
|
|||||||
labelWidth: {
|
labelWidth: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
addInfo: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const inputType = computed(() => useInputType(props.option))
|
const calcOption = computed(() => {
|
||||||
|
let option = props.option
|
||||||
|
if (isFunction(option.dynamicOption)) {
|
||||||
|
option = { ...option, ...option.dynamicOption(props.model, option, props.addInfo) }
|
||||||
|
} else if (isFunction(option.dynamicAttrs)) {
|
||||||
|
option = { ...option, attrs: { ...option.attrs, ...option.dynamicAttrs(props.model, option, props.addInfo) } }
|
||||||
|
}
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputType = computed(() => useInputType(calcOption.value))
|
||||||
|
|
||||||
const modelAttrs = computed(() => {
|
const modelAttrs = computed(() => {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
const attrs = { ...option.attrs }
|
const attrs = { ...option.attrs }
|
||||||
if (attrs.clearable === undefined && ['el-input', 'el-select', 'el-select-v2', 'common-autocomplete', 'el-autocomplete', 'el-cascader', 'el-tree-select'].includes(inputType.value)) {
|
if (attrs.clearable === undefined && ['el-input', 'el-select', 'el-select-v2', 'common-autocomplete', 'el-autocomplete', 'el-cascader', 'el-tree-select'].includes(inputType.value)) {
|
||||||
attrs.clearable = true
|
attrs.clearable = true
|
||||||
@@ -38,33 +53,67 @@ const modelAttrs = computed(() => {
|
|||||||
if (inputType.value === 'common-autocomplete' && option.getAutocompleteLabel) {
|
if (inputType.value === 'common-autocomplete' && option.getAutocompleteLabel) {
|
||||||
attrs.defaultLabel = option.getAutocompleteLabel(props.model, option)
|
attrs.defaultLabel = option.getAutocompleteLabel(props.model, option)
|
||||||
}
|
}
|
||||||
|
if (inputType.value === 'el-date-picker') {
|
||||||
|
attrs.disabledDate = (date) => {
|
||||||
|
const option = calcOption.value
|
||||||
|
let result = false
|
||||||
|
if (option.minDate) {
|
||||||
|
result = date.getTime() < dayjs(option.minDate).startOf('d').toDate().getTime()
|
||||||
|
}
|
||||||
|
if (!result && option.maxDate) {
|
||||||
|
result = date.getTime() > dayjs(option.maxDate).startOf('d').toDate().getTime()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const defaultValue = modelValue.value || option.minDate
|
||||||
|
if (defaultValue) {
|
||||||
|
attrs.defaultValue = dayjs(defaultValue).toDate()
|
||||||
|
}
|
||||||
return attrs
|
return attrs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => [inputType.value, calcOption.value.minDate, calcOption.value.maxDate], ([type, minDate, maxDate]) => {
|
||||||
|
const option = calcOption.value
|
||||||
|
const date = modelValue.value
|
||||||
|
if (type === 'el-date-picker' && date && !option.disabled && option.clearInvalidDate !== false) {
|
||||||
|
let invalid = false
|
||||||
|
if (minDate) {
|
||||||
|
invalid = dayjs(date).isBefore(dayjs(option.minDate).startOf('d'))
|
||||||
|
}
|
||||||
|
if (invalid && maxDate) {
|
||||||
|
invalid = dayjs(date).isAfter(dayjs(option.maxDate).startOf('d'))
|
||||||
|
}
|
||||||
|
if (invalid) {
|
||||||
|
modelValue.value = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
if (option.labelKey) {
|
if (option.labelKey) {
|
||||||
return $i18nBundle(option.labelKey)
|
return toLabelByKey(option.labelKey)
|
||||||
}
|
}
|
||||||
return option.label
|
return option.label
|
||||||
})
|
})
|
||||||
|
|
||||||
const showLabel = computed(() => {
|
const showLabel = computed(() => {
|
||||||
return props.option.showLabel !== false && props.labelWidth !== '0'
|
return calcOption.value.showLabel !== false && props.labelWidth !== '0'
|
||||||
})
|
})
|
||||||
|
|
||||||
const formModel = computed(() => props.option.model || props.model)
|
const formModel = computed(() => calcOption.value.model || props.model)
|
||||||
|
|
||||||
const modelValue = computed({
|
const modelValue = computed({
|
||||||
get () {
|
get () {
|
||||||
if (formModel.value && props.option.prop) {
|
if (formModel.value && calcOption.value.prop) {
|
||||||
return get(formModel.value, props.option.prop)
|
return get(formModel.value, calcOption.value.prop)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
set (val) {
|
set (val) {
|
||||||
if (formModel.value && props.option.prop) {
|
if (formModel.value && calcOption.value.prop) {
|
||||||
set(formModel.value, props.option.prop, val)
|
set(formModel.value, calcOption.value.prop, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -76,7 +125,7 @@ const childTypeMapping = { // 自动映射子元素类型,配置的时候可
|
|||||||
}
|
}
|
||||||
|
|
||||||
const children = computed(() => {
|
const children = computed(() => {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
const result = option.children || [] // 初始化一些默认值
|
const result = option.children || [] // 初始化一些默认值
|
||||||
result.forEach(childItem => {
|
result.forEach(childItem => {
|
||||||
if (!childItem.type) {
|
if (!childItem.type) {
|
||||||
@@ -89,11 +138,11 @@ const children = computed(() => {
|
|||||||
const formItemRef = ref()
|
const formItemRef = ref()
|
||||||
|
|
||||||
const rules = computed(() => {
|
const rules = computed(() => {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
let _rules = cloneDeep(option.rules || [])
|
let _rules = cloneDeep(option.rules || [])
|
||||||
if (option.prop) {
|
if (option.prop) {
|
||||||
if (option.required !== undefined) {
|
if (option.required !== undefined) {
|
||||||
const label = option.label || $i18nBundle(option.labelKey)
|
const label = option.label || toLabelByKey(option.labelKey)
|
||||||
_rules = [{
|
_rules = [{
|
||||||
trigger: option.trigger,
|
trigger: option.trigger,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
@@ -101,7 +150,7 @@ const rules = computed(() => {
|
|||||||
}, ..._rules]
|
}, ..._rules]
|
||||||
}
|
}
|
||||||
if (option.pattern !== undefined) {
|
if (option.pattern !== undefined) {
|
||||||
const label = option.label || $i18nBundle(option.labelKey)
|
const label = option.label || toLabelByKey(option.labelKey)
|
||||||
_rules = [{
|
_rules = [{
|
||||||
pattern: option.pattern,
|
pattern: option.pattern,
|
||||||
message: option.patternMsg || $i18nBundle('common.msg.patternInvalid', [label])
|
message: option.patternMsg || $i18nBundle('common.msg.patternInvalid', [label])
|
||||||
@@ -114,7 +163,7 @@ const rules = computed(() => {
|
|||||||
|
|
||||||
const initFormModel = () => {
|
const initFormModel = () => {
|
||||||
if (formModel.value) {
|
if (formModel.value) {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
if (option.prop) {
|
if (option.prop) {
|
||||||
const defaultVal = get(formModel.value, option.prop)
|
const defaultVal = get(formModel.value, option.prop)
|
||||||
set(formModel.value, option.prop, defaultVal ?? option.value ?? undefined)
|
set(formModel.value, option.prop, defaultVal ?? option.value ?? undefined)
|
||||||
@@ -124,23 +173,48 @@ const initFormModel = () => {
|
|||||||
|
|
||||||
initFormModel()
|
initFormModel()
|
||||||
|
|
||||||
watch(() => props.option, initFormModel, { deep: true })
|
watch(() => calcOption.value, initFormModel, { deep: true })
|
||||||
|
|
||||||
const hasModelText = computed(() => {
|
const hasModelText = computed(() => {
|
||||||
return !!(modelAttrs.value.modelText || modelAttrs.value.modelTextFunc)
|
return modelAttrs.value.modelText || calcOption.value.formatter
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['change'])
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
const controlChange = (...args) => {
|
const controlChange = (...args) => {
|
||||||
const option = props.option
|
const option = calcOption.value
|
||||||
if (option.change) {
|
if (option.change) {
|
||||||
option.change(...args)
|
option.change(...args)
|
||||||
}
|
}
|
||||||
emit('change', ...args)
|
emit('change', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formItemEnabled = computed(() => props.option.enabled !== false)
|
const formItemEnabled = computed(() => calcOption.value.enabled !== false)
|
||||||
|
|
||||||
|
const controlLabelWidth = computed(() => {
|
||||||
|
const option = calcOption.value
|
||||||
|
const labelWidth = props.labelWidth
|
||||||
|
return option.labelWidth || modelAttrs.value.labelWidth || labelWidth
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatResult = computed(() => {
|
||||||
|
if (hasModelText.value) {
|
||||||
|
if (modelAttrs.value.modelText) {
|
||||||
|
return {
|
||||||
|
modelText: modelAttrs.value.modelText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const option = calcOption.value
|
||||||
|
if (option.formatter) {
|
||||||
|
const result = option.formatter(modelValue.value, calcOption.value)
|
||||||
|
return {
|
||||||
|
modelText: result,
|
||||||
|
vnode: isVNode(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -149,26 +223,30 @@ const formItemEnabled = computed(() => props.option.enabled !== false)
|
|||||||
v-if="formItemEnabled"
|
v-if="formItemEnabled"
|
||||||
ref="formItemRef"
|
ref="formItemRef"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
:prop="option.prop"
|
:prop="calcOption.prop"
|
||||||
:label-width="labelWidth"
|
:style="calcOption.style"
|
||||||
|
:label-width="controlLabelWidth"
|
||||||
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="showLabel"
|
v-if="showLabel"
|
||||||
#label
|
#label
|
||||||
>
|
>
|
||||||
<span>{{ label }}</span>
|
<slot name="beforeLabel" />
|
||||||
|
<span :class="calcOption.labelCls">{{ label }}</span>
|
||||||
|
<slot name="afterLabel" />
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="option.tooltip||option.tooltipFunc"
|
v-if="calcOption.tooltip||calcOption.tooltipFunc"
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
:disabled="!option.tooltip"
|
:disabled="!calcOption.tooltip"
|
||||||
:content="option.tooltip"
|
:content="calcOption.tooltip"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<el-link
|
<el-link
|
||||||
:underline="false"
|
:underline="false"
|
||||||
@click="option.tooltipFunc"
|
@click="calcOption.tooltipFunc"
|
||||||
>
|
>
|
||||||
<common-icon
|
<common-icon
|
||||||
icon="QuestionFilled"
|
icon="QuestionFilled"
|
||||||
@@ -181,17 +259,27 @@ const formItemEnabled = computed(() => props.option.enabled !== false)
|
|||||||
:is="inputType"
|
:is="inputType"
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
v-bind="modelAttrs"
|
v-bind="modelAttrs"
|
||||||
:placeholder="option.placeholder"
|
:placeholder="calcOption.placeholder"
|
||||||
:disabled="option.disabled"
|
:disabled="calcOption.disabled"
|
||||||
:readonly="option.readonly"
|
:readonly="calcOption.readonly"
|
||||||
@change="controlChange"
|
@change="controlChange"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="hasModelText"
|
v-if="hasModelText&&formatResult"
|
||||||
#default
|
#default
|
||||||
>
|
>
|
||||||
{{ modelAttrs.modelText || modelAttrs.modelTextFunc(modelValue) }}
|
<span
|
||||||
|
v-if="formatResult.modelText&&!formatResult.vnode"
|
||||||
|
class="common-form-label-text"
|
||||||
|
v-html="formatResult.modelText"
|
||||||
|
/>
|
||||||
|
<component
|
||||||
|
:is="formatResult.modelText"
|
||||||
|
v-if="formatResult.vnode"
|
||||||
|
class="common-form-label-text"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<slot name="childBefore" />
|
||||||
<template v-if="children&&children.length">
|
<template v-if="children&&children.length">
|
||||||
<control-child
|
<control-child
|
||||||
v-for="(childItem, index) in children"
|
v-for="(childItem, index) in children"
|
||||||
@@ -199,7 +287,9 @@ const formItemEnabled = computed(() => props.option.enabled !== false)
|
|||||||
:option="childItem"
|
:option="childItem"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<slot name="childAfter" />
|
||||||
</component>
|
</component>
|
||||||
|
<slot name="after" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { inject, ref, onMounted, isRef, watchEffect } from 'vue'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +23,17 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
inline: {
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: 'common-form'
|
||||||
|
},
|
||||||
|
buttonStyle: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
validateOnRuleChange: {
|
validateOnRuleChange: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -35,6 +46,10 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
disableSubmitIfNotValid: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
submitLabel: {
|
submitLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -66,22 +81,37 @@ const emit = defineEmits(['submitForm', 'update:model'])
|
|||||||
const formModel = useVModel(props, 'model', emit)
|
const formModel = useVModel(props, 'model', emit)
|
||||||
|
|
||||||
//= ============form暴露============//
|
//= ============form暴露============//
|
||||||
|
|
||||||
const form = ref()
|
const form = ref()
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
form
|
form
|
||||||
})
|
})
|
||||||
|
onMounted(() => {
|
||||||
|
const windowFormRef = inject('commonWindowForm', null)
|
||||||
|
if (isRef(windowFormRef)) {
|
||||||
|
windowFormRef.value = form.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单校验不通过时禁止点击提交按钮
|
||||||
|
*/
|
||||||
|
const disableSubmit = ref(false)
|
||||||
|
watchEffect(async () => {
|
||||||
|
if (!props.disableSubmitIfNotValid) { return false }
|
||||||
|
if (!form.value) { return true }
|
||||||
|
await form.value.validate((ok) => { disableSubmit.value = !ok })
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form
|
<el-form
|
||||||
ref="form"
|
ref="form"
|
||||||
class="common-form"
|
:inline="inline"
|
||||||
|
:class="className"
|
||||||
:model="formModel"
|
:model="formModel"
|
||||||
:label-width="labelWidth"
|
:label-width="labelWidth"
|
||||||
v-bind="$attrs"
|
|
||||||
:validate-on-rule-change="validateOnRuleChange"
|
:validate-on-rule-change="validateOnRuleChange"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
@@ -90,13 +120,13 @@ defineExpose({
|
|||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
v-if="option.slot"
|
v-if="option.slot"
|
||||||
name="option.slot"
|
:name="option.slot"
|
||||||
:option="option"
|
:option="option"
|
||||||
:form="form"
|
:form="form"
|
||||||
:model="formModel"
|
:model="formModel"
|
||||||
/>
|
/>
|
||||||
<common-form-control
|
<common-form-control
|
||||||
v-if="option.enabled!==false"
|
v-if="!option.slot&&option.enabled!==false"
|
||||||
:model="formModel"
|
:model="formModel"
|
||||||
:option="option"
|
:option="option"
|
||||||
/>
|
/>
|
||||||
@@ -106,9 +136,13 @@ defineExpose({
|
|||||||
:model="formModel"
|
:model="formModel"
|
||||||
name="default"
|
name="default"
|
||||||
/>
|
/>
|
||||||
<el-form-item v-if="showButtons">
|
<el-form-item
|
||||||
|
v-if="showButtons"
|
||||||
|
:style="buttonStyle"
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="showSubmit"
|
v-if="showSubmit"
|
||||||
|
:disabled="disableSubmit"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="$emit('submitForm', form)"
|
@click="$emit('submitForm', form)"
|
||||||
>
|
>
|
||||||
|
|||||||
148
src/components/common-form/public.d.ts
vendored
148
src/components/common-form/public.d.ts
vendored
@@ -1,19 +1,114 @@
|
|||||||
import { RuleItem } from 'async-validator/dist-types/interface'
|
import { RuleItem } from 'async-validator/dist-types/interface'
|
||||||
import { FormInstance, FormProps } from 'element-plus'
|
import {
|
||||||
|
FormInstance, FormProps, DialogProps,
|
||||||
|
InputProps, InputNumberProps, CascaderProps,
|
||||||
|
RadioGroupProps, RadioProps, RadioButtonProps,
|
||||||
|
CheckboxProps, CheckboxGroupProps, CheckboxButtonProps,
|
||||||
|
DatePickerProps, timePickerDefaultProps, SwitchProps, SliderProps, TransferProps
|
||||||
|
} from 'element-plus'
|
||||||
|
import { SelectProps as SelectV1Props } from 'element-plus/es/components/select/src/select'
|
||||||
|
import SelectV2Props from 'element-plus/es/components/select-v2/src/select-v2/defaults'
|
||||||
|
import { CommonAutocompleteProps } from '../common-autocomplete/public'
|
||||||
|
import { TreeComponentProps } from 'element-plus/es/components/tree/src/tree.type'
|
||||||
|
import { ExtractPropTypes, CSSProperties, VNode } from 'vue'
|
||||||
|
|
||||||
export interface CommonFormOption {
|
export type TimePickerProps = ExtractPropTypes<typeof timePickerDefaultProps>
|
||||||
/** 表单类型 */
|
export type SelectProps = ExtractPropTypes<typeof SelectV1Props>
|
||||||
type: 'input' | 'input-number' | 'cascader' | 'radio'
|
export interface OptionProps {
|
||||||
| 'radio-group' | 'radio-button' | 'checkbox' | 'checkbox-group' | 'checkbox-button' | 'date-picker'
|
label: string;
|
||||||
| 'time-picker' | 'switch' | 'select' | 'option' | 'slider' | 'transfer' | 'upload' | 'common-icon-select' | 'common-autocomplete' | 'tree-select';
|
value: any;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IconSelectProps {
|
||||||
|
dialogAttrs: DialogProps,
|
||||||
|
colSize: number,
|
||||||
|
dialogHeight: string,
|
||||||
|
dialogWidth: string,
|
||||||
|
disabled: boolean,
|
||||||
|
readonly: boolean,
|
||||||
|
placeholder: string,
|
||||||
|
clearable: boolean,
|
||||||
|
validateEvent: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonFormLabelProps {
|
||||||
|
/**
|
||||||
|
* 显示文本
|
||||||
|
*/
|
||||||
|
modelText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonTabFilterProps {
|
||||||
|
tabs: Array<{
|
||||||
|
icon: string,
|
||||||
|
label: string,
|
||||||
|
prop: string,
|
||||||
|
children: OptionProps[]
|
||||||
|
}>,
|
||||||
|
type: 'checkbox-group' | 'radio-group',
|
||||||
|
defaultIcon: string,
|
||||||
|
iconSize: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'input' | 'input-number' | 'cascader' | 'radio'
|
||||||
|
* | 'radio-group' | 'radio-button' | 'checkbox' | 'checkbox-group' | 'checkbox-button' | 'date-picker'
|
||||||
|
* | 'time-picker' | 'switch' | 'select' | 'select-v2' | 'option' | 'slider' | 'transfer' | 'upload'
|
||||||
|
* | 'common-tab-filter' | 'common-icon-select' | 'common-autocomplete' | 'common-form-label'
|
||||||
|
* | 'tree-select'
|
||||||
|
*/
|
||||||
|
export type PropsMap = {
|
||||||
|
'input': InputProps,
|
||||||
|
'input-number': InputNumberProps,
|
||||||
|
'cascader': CascaderProps,
|
||||||
|
'radio': RadioProps,
|
||||||
|
'radio-group': RadioGroupProps,
|
||||||
|
'radio-button': RadioButtonProps,
|
||||||
|
'checkbox': CheckboxProps,
|
||||||
|
'checkbox-group': CheckboxGroupProps,
|
||||||
|
'checkbox-button': CheckboxButtonProps,
|
||||||
|
'date-picker': DatePickerProps,
|
||||||
|
'time-picker': TimePickerProps,
|
||||||
|
'switch': SwitchProps,
|
||||||
|
'select': SelectProps,
|
||||||
|
'select-v2': SelectV2Props,
|
||||||
|
'option': OptionProps,
|
||||||
|
'slider': SliderProps,
|
||||||
|
'transfer': TransferProps,
|
||||||
|
'upload': TransferProps,
|
||||||
|
'tree-select': SelectProps & TreeComponentProps,
|
||||||
|
'common-tab-filter': CommonTabFilterProps,
|
||||||
|
'common-form-label': CommonFormLabelProps,
|
||||||
|
'common-icon-select': IconSelectProps,
|
||||||
|
'common-autocomplete': CommonAutocompleteProps,
|
||||||
|
[key:string]: InputProps
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormControlTypeOption = {
|
||||||
|
[Type in keyof PropsMap]: {
|
||||||
|
type?: Type,
|
||||||
|
attrs?: PropsMap[Type]
|
||||||
|
}
|
||||||
|
}[keyof PropsMap]
|
||||||
|
|
||||||
|
export interface CommonFormOption extends FormControlTypeOption {
|
||||||
/** 数据值 */
|
/** 数据值 */
|
||||||
value?: any;
|
value?: any;
|
||||||
/** 属性名 */
|
/** 属性名 */
|
||||||
prop: string | string[];
|
prop?: string | string[];
|
||||||
/** 表单标签 */
|
/** 表单标签 */
|
||||||
label?: string;
|
label?: string;
|
||||||
/** 用于国际化的label */
|
/** 用于国际化的label */
|
||||||
labelKey?: string;
|
labelKey?: string;
|
||||||
|
/**
|
||||||
|
* 样式自定义
|
||||||
|
*/
|
||||||
|
labelCls?: string;
|
||||||
|
/**
|
||||||
|
* item样式
|
||||||
|
*/
|
||||||
|
style: CSSProperties;
|
||||||
/** 是否必填,后面解析成为rules的一部分 */
|
/** 是否必填,后面解析成为rules的一部分 */
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
/** 正则表达式验证,解析成为rules的一部分 */
|
/** 正则表达式验证,解析成为rules的一部分 */
|
||||||
@@ -22,27 +117,42 @@ export interface CommonFormOption {
|
|||||||
patternMsg?: string;
|
patternMsg?: string;
|
||||||
/** 是否禁用 */
|
/** 是否禁用 */
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
/** 是否显示 */
|
||||||
|
enabled?: boolean;
|
||||||
/** 是否只读 */
|
/** 是否只读 */
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
/** 占位提示符 */
|
/** 占位提示符 */
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
/** 其他可用属性 */
|
|
||||||
attrs?: {
|
|
||||||
showPassword: boolean,
|
|
||||||
[key: string]: any
|
|
||||||
};
|
|
||||||
/** 有些控件柚子节点 */
|
/** 有些控件柚子节点 */
|
||||||
children?: Array<CommonFormOption>;
|
children?: Array<CommonFormOption>;
|
||||||
/** async-validator验证器 */
|
/** async-validator验证器 */
|
||||||
rules: Array<RuleItem>;
|
rules?: Array<RuleItem>;
|
||||||
/** change事件 */
|
/** change事件 */
|
||||||
change: (val: any) => void;
|
change?: (val: any) => void;
|
||||||
/** 提示信息 */
|
/** 提示信息 */
|
||||||
tooltip: string;
|
tooltip?: string;
|
||||||
/** 提示函数 */
|
/** 提示函数 */
|
||||||
tooltipFunc: () => void;
|
tooltipFunc?: () => void;
|
||||||
|
/**
|
||||||
|
* common-form-label格式化
|
||||||
|
* @param modelValue 数据
|
||||||
|
* @param option 选项
|
||||||
|
*/
|
||||||
|
formatter?: (modelValue:any, option: CommonFormOption) => string|VNode;
|
||||||
/** 自定义slot名称 */
|
/** 自定义slot名称 */
|
||||||
slot: string;
|
slot?: string;
|
||||||
|
/**
|
||||||
|
* 根据model数据动态计算Option值
|
||||||
|
* @param model 表单model
|
||||||
|
* @param option 原始选项
|
||||||
|
*/
|
||||||
|
dynamicOption?: (model: any, option: CommonFormOption, addInfo?: any) => CommonFormOption;
|
||||||
|
/**
|
||||||
|
* 根据model数据动态计算attrs的值
|
||||||
|
* @param model 表单model
|
||||||
|
* @param option 原始选项
|
||||||
|
*/
|
||||||
|
dynamicAttrs?: (model: any, option: CommonFormOption, addInfo?: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonFormProps extends FormProps {
|
export interface CommonFormProps extends FormProps {
|
||||||
@@ -64,10 +174,14 @@ export interface CommonFormProps extends FormProps {
|
|||||||
showButtons: boolean;
|
showButtons: boolean;
|
||||||
/** 是否显示提交按钮 */
|
/** 是否显示提交按钮 */
|
||||||
showSubmit: boolean;
|
showSubmit: boolean;
|
||||||
|
/** 当校验不通过时提交按钮不可点击,默认为 false: 校验不通过也可直接提交 */
|
||||||
|
disableSubmitIfNotValid: boolean;
|
||||||
/** 是否显示重置按钮 */
|
/** 是否显示重置按钮 */
|
||||||
showReset: boolean;
|
showReset: boolean;
|
||||||
/** 提交逻辑 */
|
/** 提交逻辑 */
|
||||||
submitForm: (form: FormInstance) => void;
|
submitForm: (form: FormInstance) => void;
|
||||||
/** 返回地址 */
|
/** 返回地址 */
|
||||||
backUrl: string;
|
backUrl: string;
|
||||||
|
/** 行级排列 */
|
||||||
|
inline: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { filterIconsByKeywords } from '@/services/icon/IconService'
|
import { filterIconsByKeywords } from '@/services/icon/IconService'
|
||||||
import { useVModel } from '@vueuse/core'
|
|
||||||
import { UPDATE_MODEL_EVENT, CHANGE_EVENT, useFormItem } from 'element-plus'
|
import { UPDATE_MODEL_EVENT, CHANGE_EVENT, useFormItem } from 'element-plus'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
dialogAttrs: {
|
dialogAttrs: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default () {
|
default () {
|
||||||
@@ -60,7 +55,10 @@ const filterIcons = computed(() => {
|
|||||||
|
|
||||||
const emit = defineEmits([UPDATE_MODEL_EVENT, CHANGE_EVENT])
|
const emit = defineEmits([UPDATE_MODEL_EVENT, CHANGE_EVENT])
|
||||||
|
|
||||||
const vModel = useVModel(props, 'modelValue', emit)
|
const vModel = defineModel({
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
})
|
||||||
|
|
||||||
const { formItem } = useFormItem()
|
const { formItem } = useFormItem()
|
||||||
|
|
||||||
@@ -91,76 +89,76 @@ const selectIcon = icon => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
:disabled="disabled||readonly"
|
:disabled="disabled||readonly"
|
||||||
size="small"
|
size="small"
|
||||||
@click="iconSelectVisible = true"
|
@click.prevent="iconSelectVisible = true"
|
||||||
>
|
>
|
||||||
{{ $t('common.label.select') }}
|
{{ $t('common.label.select') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</label>
|
<el-button
|
||||||
<el-button
|
v-if="clearable&&vModel"
|
||||||
v-if="clearable&&vModel"
|
type="danger"
|
||||||
type="danger"
|
:disabled="disabled||readonly"
|
||||||
:disabled="disabled||readonly"
|
size="small"
|
||||||
size="small"
|
@click.prevent="selectIcon()"
|
||||||
@click="selectIcon()"
|
|
||||||
>
|
|
||||||
{{ $t('common.label.clear') }}
|
|
||||||
</el-button>
|
|
||||||
<el-dialog
|
|
||||||
v-model="iconSelectVisible"
|
|
||||||
:width="dialogWidth"
|
|
||||||
v-bind="dialogAttrs"
|
|
||||||
draggable
|
|
||||||
class="icon-dialog"
|
|
||||||
:title="$t('common.msg.pleaseSelectIcon')"
|
|
||||||
>
|
|
||||||
<el-container
|
|
||||||
style="overflow: auto;"
|
|
||||||
:style="{ height: dialogHeight }"
|
|
||||||
class="icon-container"
|
|
||||||
>
|
>
|
||||||
<el-header height="40px">
|
{{ $t('common.label.clear') }}
|
||||||
<el-form label-width="120px">
|
</el-button>
|
||||||
<el-form-item :label="$t('common.label.keywords')">
|
<el-dialog
|
||||||
<el-input
|
v-model="iconSelectVisible"
|
||||||
v-model="keyWords"
|
:width="dialogWidth"
|
||||||
:placeholder="$t('common.msg.inputKeywords')"
|
v-bind="dialogAttrs"
|
||||||
/>
|
draggable
|
||||||
</el-form-item>
|
class="icon-dialog"
|
||||||
</el-form>
|
:title="$t('common.msg.pleaseSelectIcon')"
|
||||||
</el-header>
|
>
|
||||||
<el-main class="icon-area">
|
<el-container
|
||||||
<recycle-scroller
|
style="overflow: auto;"
|
||||||
v-slot="{ item }"
|
:style="{ height: dialogHeight }"
|
||||||
class="scroller icon-list"
|
class="icon-container"
|
||||||
:items="filterIcons"
|
>
|
||||||
:item-size="80"
|
<el-header height="40px">
|
||||||
key-field="id"
|
<el-form label-width="120px">
|
||||||
>
|
<el-form-item :label="$t('common.label.keywords')">
|
||||||
<el-row>
|
<el-input
|
||||||
<el-col
|
v-model="keyWords"
|
||||||
v-for="icon in item.icons"
|
:placeholder="$t('common.msg.inputKeywords')"
|
||||||
:key="icon"
|
/>
|
||||||
:span="24/colSize"
|
</el-form-item>
|
||||||
class="text-center"
|
</el-form>
|
||||||
>
|
</el-header>
|
||||||
<a
|
<el-main class="icon-area">
|
||||||
class="el-button el-button--large is-text icon-a"
|
<recycle-scroller
|
||||||
@click="selectIcon(icon)"
|
v-slot="{ item }"
|
||||||
|
class="scroller icon-list"
|
||||||
|
:items="filterIcons"
|
||||||
|
:item-size="80"
|
||||||
|
key-field="id"
|
||||||
|
>
|
||||||
|
<el-row>
|
||||||
|
<el-col
|
||||||
|
v-for="icon in item.icons"
|
||||||
|
:key="icon"
|
||||||
|
:span="24/colSize"
|
||||||
|
class="text-center"
|
||||||
>
|
>
|
||||||
<div>
|
<a
|
||||||
<common-icon
|
class="el-button el-button--large is-text icon-a"
|
||||||
size="20"
|
@click.prevent="selectIcon(icon)"
|
||||||
:icon="icon"
|
>
|
||||||
/><br>
|
<div>
|
||||||
<span class="icon-text">{{ icon }}</span>
|
<common-icon
|
||||||
</div>
|
size="20"
|
||||||
</a>
|
:icon="icon"
|
||||||
</el-col>
|
/><br>
|
||||||
</el-row>
|
<span class="icon-text">{{ icon }}</span>
|
||||||
</recycle-scroller>
|
</div>
|
||||||
</el-main>
|
</a>
|
||||||
</el-container>
|
</el-col>
|
||||||
</el-dialog>
|
</el-row>
|
||||||
|
</recycle-scroller>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-dialog>
|
||||||
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import kebabCase from 'lodash/kebabCase'
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const calcIcon = computed(() => {
|
const calcIcon = computed(() => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useGlobalConfigStore } from '@/stores/GlobalConfigStore'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const globalConfigStore = useGlobalConfigStore()
|
||||||
/**
|
/**
|
||||||
* @type {CommonMenuItemProps}
|
* @type {CommonMenuItemProps}
|
||||||
*/
|
*/
|
||||||
@@ -15,7 +18,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
index: {
|
index: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
required: false
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const isSubMenu = computed(() => {
|
const isSubMenu = computed(() => {
|
||||||
@@ -35,7 +38,7 @@ const menuCls = computed(() => {
|
|||||||
})
|
})
|
||||||
const dropdownClick = (menuItem, $event) => {
|
const dropdownClick = (menuItem, $event) => {
|
||||||
if (menuItem.click) {
|
if (menuItem.click) {
|
||||||
menuItem.click($event)
|
menuItem.click($event, menuItem)
|
||||||
} else {
|
} else {
|
||||||
const route = menuItem.route || menuItem.index
|
const route = menuItem.route || menuItem.index
|
||||||
if (route) {
|
if (route) {
|
||||||
@@ -44,35 +47,39 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkShowMenuIcon = menuItem => {
|
||||||
|
return menuItem.icon && (globalConfigStore.showMenuIcon || (!menuItem.labelKey && !menuItem.label))
|
||||||
|
}
|
||||||
|
|
||||||
|
const showMenuIcon = computed(() => {
|
||||||
|
return checkShowMenuIcon(props.menuItem)
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="menuItem.isSplit"
|
v-if="menuItem.isSplit"
|
||||||
:key="menuItem.index||index"
|
|
||||||
:class="menuCls"
|
:class="menuCls"
|
||||||
>
|
>
|
||||||
<slot name="split" />
|
<slot name="split" />
|
||||||
</div>
|
</div>
|
||||||
<el-sub-menu
|
<el-sub-menu
|
||||||
v-else-if="isSubMenu"
|
v-else-if="isSubMenu"
|
||||||
:key="menuItem.index||index"
|
|
||||||
:index="`${menuItem.index||index}`"
|
:index="`${menuItem.index||index}`"
|
||||||
:class="menuCls"
|
:class="menuCls"
|
||||||
|
:disabled="menuItem.disabled"
|
||||||
v-bind="menuItem.attrs"
|
v-bind="menuItem.attrs"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<common-icon
|
<common-icon
|
||||||
|
v-if="showMenuIcon"
|
||||||
:size="menuItem.iconSize"
|
:size="menuItem.iconSize"
|
||||||
:icon="menuItem.icon"
|
:icon="menuItem.icon"
|
||||||
/>
|
/>
|
||||||
<span v-if="menuItem.labelKey||menuItem.label">
|
<span v-if="menuItem.labelKey||menuItem.label">
|
||||||
{{ menuItem.labelKey?$t(menuItem.labelKey):menuItem.label }}
|
{{ menuItem.labelKey?$t(menuItem.labelKey):menuItem.label }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
|
||||||
v-if="menuItem.html"
|
|
||||||
v-html="menuItem.html"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<common-menu-item
|
<common-menu-item
|
||||||
v-for="(childMenu, childIdx) in menuItem.children"
|
v-for="(childMenu, childIdx) in menuItem.children"
|
||||||
@@ -83,13 +90,14 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
v-else-if="isDropdown"
|
v-else-if="isDropdown"
|
||||||
:key="menuItem.index||index"
|
|
||||||
:class="menuCls"
|
:class="menuCls"
|
||||||
@click="menuItem.click&&menuItem.click($event)"
|
:disabled="menuItem.disabled"
|
||||||
|
@click="menuItem.click&&menuItem.click($event, menuItem)"
|
||||||
>
|
>
|
||||||
<el-dropdown class="common-dropdown">
|
<el-dropdown class="common-dropdown">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<common-icon
|
<common-icon
|
||||||
|
v-if="showMenuIcon"
|
||||||
:size="menuItem.iconSize"
|
:size="menuItem.iconSize"
|
||||||
:icon="menuItem.icon"
|
:icon="menuItem.icon"
|
||||||
/>
|
/>
|
||||||
@@ -104,6 +112,7 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
@click="dropdownClick(childMenu, $event)"
|
@click="dropdownClick(childMenu, $event)"
|
||||||
>
|
>
|
||||||
<common-icon
|
<common-icon
|
||||||
|
v-if="checkShowMenuIcon(childMenu)"
|
||||||
:size="childMenu.iconSize"
|
:size="childMenu.iconSize"
|
||||||
:icon="childMenu.icon"
|
:icon="childMenu.icon"
|
||||||
/>
|
/>
|
||||||
@@ -117,12 +126,14 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
<el-menu-item
|
<el-menu-item
|
||||||
v-else
|
v-else
|
||||||
:class="menuCls"
|
:class="menuCls"
|
||||||
|
:disabled="menuItem.disabled"
|
||||||
:route="menuItem.route"
|
:route="menuItem.route"
|
||||||
v-bind="menuItem.attrs"
|
v-bind="menuItem.attrs"
|
||||||
:index="menuItem.index"
|
:index="menuItem.index"
|
||||||
@click="menuItem.click&&menuItem.click(menuItem, $event)"
|
@click="menuItem.click&&menuItem.click($event,menuItem)"
|
||||||
>
|
>
|
||||||
<common-icon
|
<common-icon
|
||||||
|
v-if="showMenuIcon"
|
||||||
:size="menuItem.iconSize"
|
:size="menuItem.iconSize"
|
||||||
:icon="menuItem.icon"
|
:icon="menuItem.icon"
|
||||||
/>
|
/>
|
||||||
@@ -130,10 +141,6 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
<span v-if="menuItem.labelKey||menuItem.label">
|
<span v-if="menuItem.labelKey||menuItem.label">
|
||||||
{{ menuItem.labelKey?$t(menuItem.labelKey):menuItem.label }}
|
{{ menuItem.labelKey?$t(menuItem.labelKey):menuItem.label }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
|
||||||
v-if="menuItem.html"
|
|
||||||
v-html="menuItem.html"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -142,7 +149,4 @@ const dropdownClick = (menuItem, $event) => {
|
|||||||
.common-dropdown {
|
.common-dropdown {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.common-dropdown .el-icon {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed, ref, watchEffect } from 'vue'
|
||||||
import { filterMenus, useParentRoute } from '@/components/utils'
|
import { processMenus, useParentRoute } from '@/components/utils'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
menus: {
|
menus: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
defaultActivePath: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const menuItems = computed(() => {
|
const route = useRoute()
|
||||||
return filterMenus(props.menus)
|
const menuItems = ref([])
|
||||||
|
watchEffect(() => {
|
||||||
|
menuItems.value = processMenus(props.menus)
|
||||||
})
|
})
|
||||||
const activeRoutePath = computed(() => {
|
const activeRoutePath = computed(() => {
|
||||||
let route = useRoute()
|
if (props.defaultActivePath) {
|
||||||
route = useParentRoute(route)
|
return props.defaultActivePath
|
||||||
return route && route.path !== '/' ? route.path : ''
|
}
|
||||||
|
const current = useParentRoute(route)
|
||||||
|
return current && current.path !== '/' ? current.path : ''
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
8
src/components/common-menu/public.d.ts
vendored
8
src/components/common-menu/public.d.ts
vendored
@@ -8,6 +8,14 @@ export interface CommonMenuItem {
|
|||||||
isDropdown?: boolean;
|
isDropdown?: boolean;
|
||||||
/** 是否是分割元素 */
|
/** 是否是分割元素 */
|
||||||
isSplit?: boolean;
|
isSplit?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否禁用,禁用状态仍然是显示的
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否启用,默认true,设置false不显示
|
||||||
|
*/
|
||||||
|
enabled?: boolean;
|
||||||
/** 自定义样式 */
|
/** 自定义样式 */
|
||||||
menuCls?: string;
|
menuCls?: string;
|
||||||
/** 路由地址 */
|
/** 路由地址 */
|
||||||
|
|||||||
101
src/components/common-sort/index.vue
Normal file
101
src/components/common-sort/index.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { toLabelByKey } from '@/components/utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同时只能有一个字段被排序
|
||||||
|
* 排序的数据会被放到vModel中
|
||||||
|
* 如 { "deptTime": "DESC", "price": "", "duration": "" }
|
||||||
|
* @type {{modelValue:SortProps}} 可以传入默认排序值,但是最多只能有一个字段有值 如:{ "deptTime": "DESC", "price": "", "duration": "" }
|
||||||
|
* @type {{options:SortOption[]}} 所有要排序的字段和要显示的值
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { }
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const vModel = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 根据传入的option和初始vmodel设置vModel
|
||||||
|
const model = {}
|
||||||
|
for (const option of props.options) {
|
||||||
|
const prop = option.prop
|
||||||
|
if (vModel.value[prop] !== undefined) {
|
||||||
|
model[prop] = vModel.value[prop]
|
||||||
|
} else {
|
||||||
|
model[option.prop] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vModel.value = model
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算要显示的label
|
||||||
|
* @param {SortOption} option
|
||||||
|
*/
|
||||||
|
const calLabel = (option) => {
|
||||||
|
if (option.labelKey) {
|
||||||
|
return toLabelByKey(option.labelKey)
|
||||||
|
}
|
||||||
|
if (option.label) {
|
||||||
|
return option.label
|
||||||
|
}
|
||||||
|
return 'undefined'
|
||||||
|
}
|
||||||
|
const handleClick = (key, fixedValue) => {
|
||||||
|
const newValue = { ...vModel.value }
|
||||||
|
for (const k in newValue) {
|
||||||
|
if (k === key) { continue }
|
||||||
|
newValue[k] = ''
|
||||||
|
}
|
||||||
|
if (fixedValue) {
|
||||||
|
newValue[key] = fixedValue
|
||||||
|
} else {
|
||||||
|
newValue[key] = newValue[key] === 'ASC' ? 'DESC' : 'ASC'
|
||||||
|
}
|
||||||
|
vModel.value = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-button-group
|
||||||
|
class="ml-4"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="option in props.options"
|
||||||
|
:key="option.prop"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
:type="vModel[option.prop]===''?'':'primary'"
|
||||||
|
@click="handleClick(option.prop, option.fixedValue)"
|
||||||
|
>
|
||||||
|
{{ calLabel(option) }}
|
||||||
|
<template v-if="option.showIcon!==false">
|
||||||
|
<common-icon
|
||||||
|
v-if="vModel[option.prop]==='ASC'"
|
||||||
|
icon="SortUp"
|
||||||
|
/>
|
||||||
|
<common-icon
|
||||||
|
v-if="vModel[option.prop]==='DESC'"
|
||||||
|
icon="SortDown"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-button-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
17
src/components/common-sort/public.d.ts
vendored
Normal file
17
src/components/common-sort/public.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export interface SortOption {
|
||||||
|
prop: string; // 排序字段,如price,该值会作为
|
||||||
|
labelKey?: string;// 国际化资源key,首选该属性,不存在才使用label
|
||||||
|
label?: string;
|
||||||
|
showIcon?: boolean; // 控制某些排序不显示图标
|
||||||
|
fixedValue?: 'ASC' | 'DESC' // 固定排序模式
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序结果
|
||||||
|
* key是排序字段,来自SortOption的prop,value是排序方式
|
||||||
|
* 'ASC':升序 'DESC':降序 '':不生效
|
||||||
|
* 如 { "deptTime": "DESC", "price": "", "duration": "" }
|
||||||
|
*/
|
||||||
|
export interface SortProps {
|
||||||
|
[key: string]: 'ASC' | 'DESC' | ''
|
||||||
|
}
|
||||||
136
src/components/common-table-form/index.vue
Normal file
136
src/components/common-table-form/index.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import TableFormControl from '@/components/common-table-form/table-form-control.vue'
|
||||||
|
import { toLabelByKey } from '@/components/utils'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dataListKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'items'
|
||||||
|
},
|
||||||
|
showOperation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataList = computed(() => {
|
||||||
|
return props.model[props.dataListKey] || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['delete', 'change'])
|
||||||
|
|
||||||
|
const deleteItem = (item, index) => {
|
||||||
|
emit('delete', {
|
||||||
|
item, index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formChange = ($event, row, $index, option) => {
|
||||||
|
const args = [$event, {
|
||||||
|
model: row,
|
||||||
|
index: $index,
|
||||||
|
option
|
||||||
|
}]
|
||||||
|
if (option.formChange) { // 动态表单change事件
|
||||||
|
option.formChange(...args)
|
||||||
|
}
|
||||||
|
emit('change', ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
return props.formOptions.filter(option => option.enabled !== false)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-table
|
||||||
|
:data="dataList"
|
||||||
|
border
|
||||||
|
class="common-table-form"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="(option, index) in options"
|
||||||
|
:key="index"
|
||||||
|
:label="option.label||toLabelByKey(option.labelKey)"
|
||||||
|
:width="option.width"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="option.headerSlot"
|
||||||
|
#header="scope"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
v-bind="scope"
|
||||||
|
:name="option.headerSlot"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!--用于自定义显示属性-->
|
||||||
|
<template
|
||||||
|
v-if="option.slot"
|
||||||
|
#default="scope"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
v-bind="scope"
|
||||||
|
:item="scope.row"
|
||||||
|
:name="option.slot"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else
|
||||||
|
#default="{row, $index}"
|
||||||
|
>
|
||||||
|
<table-form-control
|
||||||
|
:model="row"
|
||||||
|
label-width="0"
|
||||||
|
:option="option"
|
||||||
|
:prop="`${dataListKey}.${$index}.${option.prop}`"
|
||||||
|
@change="formChange($event, row, $index, option)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="showOperation"
|
||||||
|
:label="$t('common.label.operation')"
|
||||||
|
>
|
||||||
|
<template #default="{row, $index}">
|
||||||
|
<div class="el-form-item">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
:underline="false"
|
||||||
|
@click="deleteItem(row, $index)"
|
||||||
|
>
|
||||||
|
<common-icon
|
||||||
|
icon="Delete"
|
||||||
|
/>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty="scope">
|
||||||
|
<slot
|
||||||
|
name="empty"
|
||||||
|
v-bind="scope"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #append="scope">
|
||||||
|
<slot
|
||||||
|
name="append"
|
||||||
|
v-bind="scope"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
38
src/components/common-table-form/table-form-control.vue
Normal file
38
src/components/common-table-form/table-form-control.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
/**
|
||||||
|
* @type {CommonFormOption}
|
||||||
|
*/
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
labelWidth: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
prop: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<common-form-control
|
||||||
|
:model="model"
|
||||||
|
:label-width="labelWidth"
|
||||||
|
:option="option"
|
||||||
|
:prop="prop"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { formatDate } from '@/components/utils'
|
import { formatDate, toLabelByKey } from '@/components/utils'
|
||||||
import { computed } from 'vue'
|
import { computed, useSlots } from 'vue'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,22 +36,24 @@ const formatter = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const getPropertyData = (row) => {
|
const getPropertyData = (row) => {
|
||||||
return get(row, props.column.property)
|
return get(row, props.column.prop || props.column.property)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="!column.isOperation"
|
v-if="!column.isOperation"
|
||||||
:label="column.label || $t(column.labelKey)"
|
:label="column.label || toLabelByKey(column.labelKey)"
|
||||||
:prop="column.prop||column.property"
|
:prop="column.prop||column.property"
|
||||||
:width="column.width"
|
:width="column.width"
|
||||||
v-bind="column.attrs"
|
v-bind="column.attrs"
|
||||||
:formatter="formatter"
|
:formatter="formatter"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="column.slot||column.click"
|
v-if="column.click"
|
||||||
#default="scope"
|
#default="scope"
|
||||||
>
|
>
|
||||||
<el-link
|
<el-link
|
||||||
@@ -62,16 +64,22 @@ const getPropertyData = (row) => {
|
|||||||
>
|
>
|
||||||
{{ formatter?formatter(scope.row, scope):getPropertyData(scope.row) }}
|
{{ formatter?formatter(scope.row, scope):getPropertyData(scope.row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-for="(slot, slotKey) in slots"
|
||||||
|
:key="slotKey"
|
||||||
|
#[slotKey]="scope"
|
||||||
|
>
|
||||||
<slot
|
<slot
|
||||||
|
:name="slotKey"
|
||||||
v-bind="scope"
|
v-bind="scope"
|
||||||
:column-conf="column"
|
:column-conf="column"
|
||||||
name="default"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="column.isOperation"
|
v-if="column.isOperation"
|
||||||
:label="column.label || $t(column.labelKey)"
|
:label="column.label || toLabelByKey(column.labelKey)"
|
||||||
:width="column.width"
|
:width="column.width"
|
||||||
v-bind="column.attrs"
|
v-bind="column.attrs"
|
||||||
>
|
>
|
||||||
@@ -80,7 +88,7 @@ const getPropertyData = (row) => {
|
|||||||
>
|
>
|
||||||
<template v-for="(button, index) in column.buttons">
|
<template v-for="(button, index) in column.buttons">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!button.buttonIf||button.buttonIf(scope.row, scope)"
|
v-if="(!button.buttonIf||button.buttonIf(scope.row, scope))&&button.enabled!==false"
|
||||||
:key="index"
|
:key="index"
|
||||||
:type="button.type"
|
:type="button.type"
|
||||||
:icon="button.icon"
|
:icon="button.icon"
|
||||||
@@ -90,7 +98,7 @@ const getPropertyData = (row) => {
|
|||||||
:circle="button.circle"
|
:circle="button.circle"
|
||||||
@click="button.click&&button.click(scope.row, scope)"
|
@click="button.click&&button.click(scope.row, scope)"
|
||||||
>
|
>
|
||||||
{{ button.label || $t(button.labelKey) }}
|
{{ button.label || toLabelByKey(button.labelKey) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<slot
|
<slot
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import CommonTableColumn from '@/components/common-table/common-table-column.vue'
|
import CommonTableColumn from '@/components/common-table/common-table-column.vue'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, onMounted, watch } from 'vue'
|
||||||
|
import { useIntersectionObserver } from '@vueuse/core'
|
||||||
|
import { getFrontendPage } from '@/components/utils'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type CommonTableProps
|
* @type CommonTableProps
|
||||||
@@ -11,7 +17,7 @@ const props = defineProps({
|
|||||||
*/
|
*/
|
||||||
columns: {
|
columns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
default: () => []
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 显示数据
|
* 显示数据
|
||||||
@@ -36,7 +42,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* el-button
|
* el-button
|
||||||
* @type [ButtonProps]
|
* @type [TableButtonProps]
|
||||||
*/
|
*/
|
||||||
buttons: {
|
buttons: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -84,6 +90,26 @@ const props = defineProps({
|
|||||||
loadingText: {
|
loadingText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
expandTable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
hideExpandBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
frontendPaging: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
frontendPageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
infinitePaging: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +118,15 @@ const props = defineProps({
|
|||||||
*/
|
*/
|
||||||
const calcColumns = computed(() => {
|
const calcColumns = computed(() => {
|
||||||
let _columns = props.columns
|
let _columns = props.columns
|
||||||
|
if (props.expandTable) {
|
||||||
|
_columns = [{
|
||||||
|
slot: 'expand',
|
||||||
|
attrs: {
|
||||||
|
type: 'expand',
|
||||||
|
width: props.hideExpandBtn ? 1 : 0
|
||||||
|
}
|
||||||
|
}, ..._columns]
|
||||||
|
}
|
||||||
if (props.buttons.length || props.buttonsSlot) {
|
if (props.buttons.length || props.buttonsSlot) {
|
||||||
const buttonColumn = {
|
const buttonColumn = {
|
||||||
labelKey: 'common.label.operation',
|
labelKey: 'common.label.operation',
|
||||||
@@ -116,6 +151,74 @@ const currentPageChange = (pageNumber) => {
|
|||||||
emit('currentPageChange', pageNumber)
|
emit('currentPageChange', pageNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calcData = ref([])
|
||||||
|
const frontendPage = ref(getFrontendPage(0, props.frontendPageSize))
|
||||||
|
const infiniteRef = ref(null)
|
||||||
|
const isInfiniteEnd = ref(false)
|
||||||
|
|
||||||
|
function checkInfiniteEnd (pageVal) {
|
||||||
|
if (props.infinitePaging) {
|
||||||
|
isInfiniteEnd.value = pageVal ? pageVal.pageNumber >= pageVal.pageCount : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcFrontEndPageData () {
|
||||||
|
if (props.frontendPaging) {
|
||||||
|
calcData.value = props.data?.slice((frontendPage.value.pageNumber - 1) * frontendPage.value.pageSize,
|
||||||
|
frontendPage.value.pageNumber * frontendPage.value.pageSize) // 展示数据
|
||||||
|
checkInfiniteEnd(frontendPage.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcTableDataAndPage () {
|
||||||
|
if (props.frontendPaging) { // 前端分页模式
|
||||||
|
frontendPage.value = getFrontendPage(props.data?.length, frontendPage.value.pageSize, frontendPage.value.pageNumber) // 前端分页信息
|
||||||
|
calcFrontEndPageData()
|
||||||
|
} else { // 后端分页模式
|
||||||
|
if (props.infinitePaging) { // 无限加载模式
|
||||||
|
if (!calcData.value.length && props.page && props.page.pageNumber > 1) { // 如果进来就是后面的页码,重新查询
|
||||||
|
emit('update:page', { ...props.page, pageNumber: 1 }) // 仅更新pageNumber
|
||||||
|
} else {
|
||||||
|
calcData.value = [...calcData.value, ...props.data]
|
||||||
|
}
|
||||||
|
checkInfiniteEnd(props.page)
|
||||||
|
} else {
|
||||||
|
calcData.value = props.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
calcTableDataAndPage()
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
watch(() => frontendPage, () => {
|
||||||
|
calcFrontEndPageData()
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.infinitePaging) {
|
||||||
|
console.info('================================mounted', infiniteRef.value)
|
||||||
|
useIntersectionObserver(infiniteRef, onInfiniteLoad, {
|
||||||
|
threshold: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onInfiniteLoad = (args) => {
|
||||||
|
const isIntersecting = args[0].isIntersecting
|
||||||
|
console.info('===========================infinite', isIntersecting, ...args)
|
||||||
|
if (isIntersecting && calcData.value?.length) {
|
||||||
|
if (props.frontendPaging) {
|
||||||
|
frontendPage.value = getFrontendPage(props.data?.length,
|
||||||
|
frontendPage.value.pageSize + props.frontendPageSize,
|
||||||
|
frontendPage.value.pageNumber)
|
||||||
|
} else if (props.page) {
|
||||||
|
currentPageChange(props.page.pageNumber + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const table = ref()
|
const table = ref()
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -135,7 +238,8 @@ defineExpose({
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:highlight-current-row="highlightCurrentRow"
|
:highlight-current-row="highlightCurrentRow"
|
||||||
:stripe="stripe"
|
:stripe="stripe"
|
||||||
:data="data"
|
:data="calcData"
|
||||||
|
:class="{'common-hide-expand': hideExpandBtn}"
|
||||||
:border="border"
|
:border="border"
|
||||||
>
|
>
|
||||||
<common-table-column
|
<common-table-column
|
||||||
@@ -144,23 +248,50 @@ defineExpose({
|
|||||||
:column="column"
|
:column="column"
|
||||||
:button-size="buttonSize"
|
:button-size="buttonSize"
|
||||||
>
|
>
|
||||||
|
<template
|
||||||
|
v-if="column.headerSlot"
|
||||||
|
#header="scope"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
v-bind="scope"
|
||||||
|
:name="column.headerSlot"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<!--用于自定义显示属性-->
|
<!--用于自定义显示属性-->
|
||||||
<template
|
<template
|
||||||
|
v-if="column.slot"
|
||||||
#default="scope"
|
#default="scope"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
v-if="column.slot"
|
v-bind="scope"
|
||||||
:row="scope.row"
|
|
||||||
:column="scope.column"
|
|
||||||
:item="scope.row"
|
:item="scope.row"
|
||||||
:column-conf="scope.columnConf"
|
|
||||||
:name="column.slot"
|
:name="column.slot"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</common-table-column>
|
</common-table-column>
|
||||||
|
<template #append="scope">
|
||||||
|
<slot
|
||||||
|
name="append"
|
||||||
|
v-bind="scope"
|
||||||
|
/>
|
||||||
|
<el-container
|
||||||
|
v-show="infinitePaging&&!isInfiniteEnd"
|
||||||
|
ref="infiniteRef"
|
||||||
|
v-loading="true"
|
||||||
|
class="container-center"
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
<template #empty="scope">
|
||||||
|
<slot
|
||||||
|
name="empty"
|
||||||
|
v-bind="scope"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-if="page&&page.pageCount"
|
v-if="!infinitePaging&&!frontendPaging&&page&&page.pageCount"
|
||||||
class="common-pagination"
|
class="common-pagination"
|
||||||
v-bind="pageAttrs"
|
v-bind="pageAttrs"
|
||||||
:total="page.totalCount"
|
:total="page.totalCount"
|
||||||
@@ -169,6 +300,16 @@ defineExpose({
|
|||||||
@size-change="pageSizeChange($event)"
|
@size-change="pageSizeChange($event)"
|
||||||
@current-change="currentPageChange($event)"
|
@current-change="currentPageChange($event)"
|
||||||
/>
|
/>
|
||||||
|
<el-pagination
|
||||||
|
v-if="!infinitePaging&&frontendPaging&&frontendPage&&frontendPage.pageCount"
|
||||||
|
class="common-pagination"
|
||||||
|
v-bind="pageAttrs"
|
||||||
|
:total="frontendPage.totalCount"
|
||||||
|
:page-size="frontendPage.pageSize"
|
||||||
|
:current-page="frontendPage.pageNumber"
|
||||||
|
@size-change="frontendPage.pageSize=$event"
|
||||||
|
@current-change="frontendPage.pageNumber=$event"
|
||||||
|
/>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
82
src/components/common-table/public.d.ts
vendored
82
src/components/common-table/public.d.ts
vendored
@@ -1,34 +1,52 @@
|
|||||||
import { ButtonProps, LinkProps, TableProps, PaginationProps } from 'element-plus'
|
import { ButtonProps, LinkProps, TableProps, PaginationProps } from 'element-plus'
|
||||||
|
import tableColumnProps from 'element-plus/es/components/table/src/table-column/defaults'
|
||||||
import { CommonPage } from '../public'
|
import { CommonPage } from '../public'
|
||||||
|
import { ExtractPropTypes } from 'vue'
|
||||||
|
|
||||||
|
export type TableColumnProps = ExtractPropTypes<typeof tableColumnProps>
|
||||||
|
|
||||||
|
export type TableButtonProps = {
|
||||||
|
/**
|
||||||
|
* 计算是否显示按钮
|
||||||
|
* @param data 表格数据
|
||||||
|
*/
|
||||||
|
buttonIf: (data: any) => boolean
|
||||||
|
} & ButtonProps
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表格列定义
|
* 表格列定义
|
||||||
*/
|
*/
|
||||||
export interface CommonTableColumn {
|
export interface CommonTableColumn {
|
||||||
// 表格头
|
/** 是否显示 */
|
||||||
label: string;
|
enabled?: boolean;
|
||||||
// 表格头国际化key
|
/** 表格头 */
|
||||||
labelKey: string;
|
label?: string;
|
||||||
// 属性名
|
/** 表格头国际化key */
|
||||||
property: string;
|
labelKey?: string;
|
||||||
// 属性名,同property
|
/** 属性名 */
|
||||||
prop: string;
|
property?: string;
|
||||||
// 宽度
|
/** 属性名,同property */
|
||||||
width: string;
|
prop?: string;
|
||||||
// 是否是可操作列
|
/** 宽度 */
|
||||||
isOperation: boolean;
|
width?: string;
|
||||||
// 自定义插槽名称,用于自定义显示数据
|
/** 是否是可操作列 */
|
||||||
slot: string;
|
isOperation?: boolean;
|
||||||
// 自定义按钮
|
/** 自定义插槽名称,用于自定义显示数据 */
|
||||||
buttons: Array<ButtonProps>
|
slot?: string;
|
||||||
// 可选属性
|
/** 自定义插槽名称,用于自定义显示Label */
|
||||||
attrs: any;
|
headerSlot?: string;
|
||||||
// 链接可选属性
|
/** 自定义按钮 */
|
||||||
linkAttrs: LinkProps;
|
buttons?: Array<TableButtonProps>
|
||||||
// 点击事件
|
/** 可选属性 */
|
||||||
click: (data: any) => any;
|
attrs?: TableColumnProps;
|
||||||
// 格式化函数
|
/** 链接可选属性 */
|
||||||
formatter: (data: any, scope: any) => string;
|
linkAttrs?: LinkProps;
|
||||||
|
/** 点击事件 */
|
||||||
|
click?: (data: any) => any;
|
||||||
|
/** 格式化函数 */
|
||||||
|
formatter?: (data: any, scope: any) => string;
|
||||||
|
/** 日期格式化 */
|
||||||
|
dateFormat?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,15 +64,13 @@ export interface CommonTableProps extends TableProps<any> {
|
|||||||
/** 边框配置 */
|
/** 边框配置 */
|
||||||
border: boolean;
|
border: boolean;
|
||||||
/** 自定义按钮配置 */
|
/** 自定义按钮配置 */
|
||||||
buttons?: Array<ButtonProps>;
|
buttons?: Array<TableButtonProps>;
|
||||||
/** buttons插槽 */
|
/** buttons插槽 */
|
||||||
buttonsSlot?: string;
|
buttonsSlot?: string;
|
||||||
/** 默认的按钮大小 */
|
/** 默认的按钮大小 */
|
||||||
buttonSize?: string;
|
buttonSize?: string;
|
||||||
/** 按钮列配置 */
|
/** 按钮列配置 */
|
||||||
buttonsColumnAttrs?: {
|
buttonsColumnAttrs?: TableColumnProps;
|
||||||
[key: string]: any
|
|
||||||
};
|
|
||||||
/** 分页配置 */
|
/** 分页配置 */
|
||||||
page?: CommonPage;
|
page?: CommonPage;
|
||||||
/** 分页对齐 */
|
/** 分页对齐 */
|
||||||
@@ -65,6 +81,16 @@ export interface CommonTableProps extends TableProps<any> {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
/** loading显示消息 */
|
/** loading显示消息 */
|
||||||
loadingText?: string;
|
loadingText?: string;
|
||||||
|
/** 可以展开的table */
|
||||||
|
expandTable: boolean;
|
||||||
|
/** 是否隐藏展开按钮 */
|
||||||
|
hideExpandBtn: boolean;
|
||||||
|
/** 是否是前台分页**/
|
||||||
|
frontendPaging: boolean;
|
||||||
|
/** 前端模式分页数量 **/
|
||||||
|
frontendPageSize: number;
|
||||||
|
/** 是否是无限加载分页**/
|
||||||
|
infinitePaging: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonTableColumnProps {
|
export interface CommonTableColumnProps {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { computed } from 'vue'
|
import { computed, ref, provide } from 'vue'
|
||||||
import { UPDATE_MODEL_EVENT } from 'element-plus'
|
import { UPDATE_MODEL_EVENT } from 'element-plus'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -8,10 +8,6 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
draggable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -24,6 +20,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '800px'
|
default: '800px'
|
||||||
},
|
},
|
||||||
|
defaultCls: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
@@ -63,19 +63,44 @@ const props = defineProps({
|
|||||||
closeClick: {
|
closeClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
destroyOnClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
overflow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
closeOnClickModal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
closeOnPressEscape: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
appendToBody: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits([UPDATE_MODEL_EVENT])
|
const emit = defineEmits([UPDATE_MODEL_EVENT])
|
||||||
const showDialog = useVModel(props, 'modelValue', emit) // 自动响应v-model
|
const showDialog = useVModel(props, 'modelValue', emit) // 自动响应v-model
|
||||||
|
const windowForm = ref(null) // 如果common-window下面有common-form,注册到这里
|
||||||
|
provide('commonWindowForm', windowForm)
|
||||||
|
|
||||||
const okButtonClick = $event => {
|
const okButtonClick = $event => {
|
||||||
if (!props.okClick || props.okClick($event) !== false) {
|
if (!props.okClick || props.okClick({ $event, form: windowForm.value }) !== false) {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cancelButtonClick = $event => {
|
const cancelButtonClick = $event => {
|
||||||
if (!props.cancelClick || props.cancelClick($event) !== false) {
|
if (!props.cancelClick || props.cancelClick({ $event, form: windowForm.value }) !== false) {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +110,7 @@ const calcBeforeClose = computed(() => {
|
|||||||
return props.beforeClose
|
return props.beforeClose
|
||||||
} else if (props.closeClick) {
|
} else if (props.closeClick) {
|
||||||
return done => {
|
return done => {
|
||||||
if (props.closeClick() !== false) {
|
if (props.closeClick({ form: windowForm.value }) !== false) {
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,9 +127,14 @@ const calcBeforeClose = computed(() => {
|
|||||||
:before-close="calcBeforeClose"
|
:before-close="calcBeforeClose"
|
||||||
:width="width"
|
:width="width"
|
||||||
:draggable="draggable"
|
:draggable="draggable"
|
||||||
v-bind="$attrs"
|
:overflow="true"
|
||||||
|
:destroy-on-close="destroyOnClose"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:close-on-press-escape="closeOnPressEscape"
|
||||||
|
:append-to-body="appendToBody"
|
||||||
>
|
>
|
||||||
<el-container
|
<el-container
|
||||||
|
:class="defaultCls"
|
||||||
:style="{ height:height }"
|
:style="{ height:height }"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
@@ -135,7 +165,7 @@ const calcBeforeClose = computed(() => {
|
|||||||
:disabled="button.disabled"
|
:disabled="button.disabled"
|
||||||
:round="button.round"
|
:round="button.round"
|
||||||
:circle="button.circle"
|
:circle="button.circle"
|
||||||
@click="button.click&&button.click($event)"
|
@click="button.click&&button.click({$event, form:windowForm})"
|
||||||
>
|
>
|
||||||
{{ button.label || $t(button.labelKey) }}
|
{{ button.label || $t(button.labelKey) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|||||||
52
src/components/directives/CommonTooltip.vue
Normal file
52
src/components/directives/CommonTooltip.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const triggerRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'el-tooltip'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const propConfig = ref({
|
||||||
|
placement: 'top-start',
|
||||||
|
rawContent: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const setConfig = (config) => {
|
||||||
|
Object.assign(propConfig.value, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showOrHideTooltip = (show) => {
|
||||||
|
visible.value = show
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
triggerRef,
|
||||||
|
setConfig,
|
||||||
|
showOrHideTooltip
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="type"
|
||||||
|
v-if="triggerRef"
|
||||||
|
:disabled="!propConfig.content"
|
||||||
|
:popper-class="`common-${type}`"
|
||||||
|
:visible="visible"
|
||||||
|
:virtual-ref="triggerRef"
|
||||||
|
virtual-triggering
|
||||||
|
v-bind="propConfig"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div v-html="propConfig.content" />
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
56
src/components/directives/CommonTooltipDirective.js
Normal file
56
src/components/directives/CommonTooltipDirective.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { isObject, isString } from 'lodash'
|
||||||
|
import CommonTooltip from '@/components/directives/CommonTooltip.vue'
|
||||||
|
import { DynamicHelper } from '@/components/directives/index'
|
||||||
|
|
||||||
|
const calcTooltipConfig = (binding) => {
|
||||||
|
let config = {}
|
||||||
|
if (isObject(binding.value)) {
|
||||||
|
config = { ...config, ...binding.value }
|
||||||
|
} else if (isString(binding.value)) {
|
||||||
|
config.content = binding.value
|
||||||
|
}
|
||||||
|
if (binding.arg) {
|
||||||
|
config.placement = binding.arg
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTooltipVnode = (el, binding, props) => {
|
||||||
|
const dynamicHelper = new DynamicHelper()
|
||||||
|
const tooltipVnode = dynamicHelper.createAndRender(CommonTooltip, props)
|
||||||
|
el.tooltipVnode = tooltipVnode
|
||||||
|
el.tooltipDynamicHelper = dynamicHelper
|
||||||
|
if (!el.tooltipConfig) {
|
||||||
|
el.tooltipConfig = calcTooltipConfig(binding)
|
||||||
|
}
|
||||||
|
if (tooltipVnode.component?.exposed?.triggerRef) {
|
||||||
|
tooltipVnode.component.exposed.triggerRef.value = el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTooltipDirective = (props) => {
|
||||||
|
return {
|
||||||
|
mounted (el, binding) {
|
||||||
|
el.addEventListener('mouseenter', () => {
|
||||||
|
if (!el.tooltipVnode) {
|
||||||
|
initTooltipVnode(el, binding, props)
|
||||||
|
}
|
||||||
|
el?.tooltipVnode?.component?.exposed?.setConfig(el.tooltipConfig)
|
||||||
|
el.tooltipVnode?.component?.exposed?.showOrHideTooltip(true)
|
||||||
|
})
|
||||||
|
el.addEventListener('mouseleave', () => {
|
||||||
|
el.tooltipVnode?.component?.exposed?.showOrHideTooltip(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updated (el, binding) {
|
||||||
|
el.tooltipConfig = calcTooltipConfig(binding)
|
||||||
|
},
|
||||||
|
unmounted (el) {
|
||||||
|
el.tooltipDynamicHelper?.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CommonTooltipDirective = getTooltipDirective({ type: 'el-tooltip' })
|
||||||
|
|
||||||
|
export const CommonPopoverDirective = getTooltipDirective({ type: 'el-popover' })
|
||||||
43
src/components/directives/index.js
Normal file
43
src/components/directives/index.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { CommonPopoverDirective, CommonTooltipDirective } from '@/components/directives/CommonTooltipDirective'
|
||||||
|
import { h, render } from 'vue'
|
||||||
|
|
||||||
|
export class DynamicHelper {
|
||||||
|
constructor () {
|
||||||
|
this.appDivId = 'app'
|
||||||
|
this.context = DynamicHelper.app._context
|
||||||
|
this.container = DynamicHelper.createContainer()
|
||||||
|
this.destroy = DynamicHelper.getDestroyFunc(this.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
static createContainer () {
|
||||||
|
return document.createElement('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDestroyFunc (container) {
|
||||||
|
return () => {
|
||||||
|
if (container) {
|
||||||
|
render(null, container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndRender (...args) {
|
||||||
|
const container = this.container
|
||||||
|
const vnode = h(...args)
|
||||||
|
vnode.appContext = this.context
|
||||||
|
render(vnode, container)
|
||||||
|
const appDiv = document.getElementById(this.appDivId)
|
||||||
|
if (appDiv && container.firstElementChild) {
|
||||||
|
appDiv.appendChild(container.firstElementChild)
|
||||||
|
}
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install (Vue) {
|
||||||
|
DynamicHelper.app = Vue
|
||||||
|
Vue.directive('common-tooltip', CommonTooltipDirective)
|
||||||
|
Vue.directive('common-popover', CommonPopoverDirective)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ import CommonIcon from '@/components/common-icon/index.vue'
|
|||||||
import CommonIconSelect from '@/components/common-icon-select/index.vue'
|
import CommonIconSelect from '@/components/common-icon-select/index.vue'
|
||||||
import CommonForm from '@/components/common-form/index.vue'
|
import CommonForm from '@/components/common-form/index.vue'
|
||||||
import CommonFormControl from '@/components/common-form-control/index.vue'
|
import CommonFormControl from '@/components/common-form-control/index.vue'
|
||||||
|
import CommonFilterControl from '@/components/common-form-control/common-filter-control.vue'
|
||||||
|
import CommonTabFilter from '@/components/common-form-control/common-tab-filter.vue'
|
||||||
import CommonFormLabel from '@/components/common-form-control/common-form-label.vue'
|
import CommonFormLabel from '@/components/common-form-control/common-form-label.vue'
|
||||||
import CommonMenu from '@/components/common-menu/index.vue'
|
import CommonMenu from '@/components/common-menu/index.vue'
|
||||||
import CommonMenuItem from '@/components/common-menu-item/index.vue'
|
import CommonMenuItem from '@/components/common-menu-item/index.vue'
|
||||||
@@ -11,6 +13,8 @@ import CommonTableForm from '@/components/common-table-form/index.vue'
|
|||||||
import CommonBreadcrumb from '@/components/common-breadcrumb/index.vue'
|
import CommonBreadcrumb from '@/components/common-breadcrumb/index.vue'
|
||||||
import CommonWindow from '@/components/common-window/index.vue'
|
import CommonWindow from '@/components/common-window/index.vue'
|
||||||
import CommonAutocomplete from '@/components/common-autocomplete/index.vue'
|
import CommonAutocomplete from '@/components/common-autocomplete/index.vue'
|
||||||
|
import CommonSort from '@/components/common-sort/index.vue'
|
||||||
|
import CommonDirectives from '@/components/directives'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义通用组件自动注册
|
* 自定义通用组件自动注册
|
||||||
@@ -24,6 +28,8 @@ export default {
|
|||||||
Vue.component('CommonIconSelect', CommonIconSelect)
|
Vue.component('CommonIconSelect', CommonIconSelect)
|
||||||
Vue.component('CommonForm', CommonForm)
|
Vue.component('CommonForm', CommonForm)
|
||||||
Vue.component('CommonFormControl', CommonFormControl)
|
Vue.component('CommonFormControl', CommonFormControl)
|
||||||
|
Vue.component('CommonFilterControl', CommonFilterControl)
|
||||||
|
Vue.component('CommonTabFilter', CommonTabFilter)
|
||||||
Vue.component('CommonFormLabel', CommonFormLabel)
|
Vue.component('CommonFormLabel', CommonFormLabel)
|
||||||
Vue.component('CommonMenu', CommonMenu)
|
Vue.component('CommonMenu', CommonMenu)
|
||||||
Vue.component('CommonMenuItem', CommonMenuItem)
|
Vue.component('CommonMenuItem', CommonMenuItem)
|
||||||
@@ -33,5 +39,7 @@ export default {
|
|||||||
Vue.component('CommonBreadcrumb', CommonBreadcrumb)
|
Vue.component('CommonBreadcrumb', CommonBreadcrumb)
|
||||||
Vue.component('CommonWindow', CommonWindow)
|
Vue.component('CommonWindow', CommonWindow)
|
||||||
Vue.component('CommonAutocomplete', CommonAutocomplete)
|
Vue.component('CommonAutocomplete', CommonAutocomplete)
|
||||||
|
Vue.component('CommonSort', CommonSort)
|
||||||
|
Vue.use(CommonDirectives)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { $i18nBundle } from '@/messages'
|
import { $i18nBundle, $i18nKey } from '@/messages'
|
||||||
|
import { isArray, isObject } from 'lodash'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
export const getFrontendPage = (totalCount, pageSize, pageNumber = 1) => {
|
||||||
|
const pageCount = Math.floor((totalCount + pageSize - 1) / pageSize)
|
||||||
|
if (pageNumber > pageCount && pageCount > 0) {
|
||||||
|
pageNumber = pageCount
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pageNumber,
|
||||||
|
pageSize,
|
||||||
|
totalCount,
|
||||||
|
pageCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const calcWithIf = menuItem => {
|
const calcWithIf = menuItem => {
|
||||||
['icon', 'labelKey', 'label', 'html'].forEach(key => {
|
['icon', 'labelKey', 'label', 'html'].forEach(key => {
|
||||||
const keyIf = menuItem[`${key}If`]
|
const keyIf = menuItem[`${key}If`]
|
||||||
@@ -11,6 +25,10 @@ const calcWithIf = menuItem => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CommonFormOption} option
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
export const useInputType = (option) => {
|
export const useInputType = (option) => {
|
||||||
const inType = option.type || 'input'
|
const inType = option.type || 'input'
|
||||||
if (inType.startsWith('common-') || inType.startsWith('el-')) {
|
if (inType.startsWith('common-') || inType.startsWith('el-')) {
|
||||||
@@ -28,6 +46,15 @@ export const useMenuInfo = item => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toLabelByKey = labelKey => {
|
||||||
|
if (isArray(labelKey)) {
|
||||||
|
return $i18nKey(...labelKey)
|
||||||
|
}
|
||||||
|
if (labelKey) {
|
||||||
|
return $i18nBundle(labelKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useMenuName = item => {
|
export const useMenuName = item => {
|
||||||
const menuInfo = useMenuInfo(item)
|
const menuInfo = useMenuInfo(item)
|
||||||
if (menuInfo) {
|
if (menuInfo) {
|
||||||
@@ -35,23 +62,50 @@ export const useMenuName = item => {
|
|||||||
return menuInfo.label
|
return menuInfo.label
|
||||||
}
|
}
|
||||||
if (menuInfo.labelKey) {
|
if (menuInfo.labelKey) {
|
||||||
return $i18nBundle(menuInfo.labelKey)
|
return toLabelByKey(menuInfo.labelKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.meta && item.meta.labelKey) {
|
if (item.meta && item.meta.labelKey) {
|
||||||
return $i18nBundle(item.meta.labelKey)
|
return toLabelByKey(item.meta.labelKey)
|
||||||
}
|
}
|
||||||
return item.name || 'No Name'
|
return item.name || 'No Name'
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 外部链接判断
|
||||||
|
* @param path
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isExternalLink = (path) => {
|
||||||
|
return /^(https?:|mailto:|tel:)/.test(path)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 外部菜单类型判断
|
||||||
|
* @param menuItem
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isExternalMenu = menuItem => {
|
||||||
|
return menuItem.external || isExternalLink(menuItem.index)
|
||||||
|
}
|
||||||
|
|
||||||
export const filterMenus = menus => menus.filter(menu => !menu.disabled)
|
/**
|
||||||
|
* @param menus {[CommonMenuItem] }菜单列表
|
||||||
|
* @return {[CommonMenuItem]}
|
||||||
|
*/
|
||||||
|
export const processMenus = menus => menus.filter(menu => menu.enabled !== false)
|
||||||
.map(menu => {
|
.map(menu => {
|
||||||
calcWithIf(menu)
|
calcWithIf(menu)
|
||||||
if (menu.index) { // 把菜单存储下来,后面需要使用名字
|
if (menu.index) { // 把菜单存储下来,后面需要使用名字
|
||||||
MENU_INFO_LIST.value[menu.index] = menu
|
MENU_INFO_LIST.value[menu.index] = menu
|
||||||
}
|
}
|
||||||
if (menu.children && menu.children.length) {
|
if (menu.children && menu.children.length) {
|
||||||
menu.children = filterMenus(menu.children)
|
menu.children = processMenus(menu.children)
|
||||||
|
}
|
||||||
|
if (isExternalMenu(menu) && !menu.click) { // 跳转外部链接
|
||||||
|
const url = menu.index
|
||||||
|
menu.index = ''
|
||||||
|
menu.click = () => {
|
||||||
|
window.open(url, menu.target || '_blank')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return menu
|
return menu
|
||||||
})
|
})
|
||||||
@@ -62,7 +116,7 @@ export const filterMenus = menus => menus.filter(menu => !menu.disabled)
|
|||||||
* @param route {RouteRecordMultipleViewsWithChildren} 路由信息
|
* @param route {RouteRecordMultipleViewsWithChildren} 路由信息
|
||||||
*/
|
*/
|
||||||
export const useParentRoute = function (route) {
|
export const useParentRoute = function (route) {
|
||||||
const parentName = route.meta?.replaceTabHistory
|
const parentName = route?.meta?.replaceTabHistory
|
||||||
if (parentName) {
|
if (parentName) {
|
||||||
const routes = route.matched || []
|
const routes = route.matched || []
|
||||||
for (let i = routes.length - 1; i > 0; i--) {
|
for (let i = routes.length - 1; i > 0; i--) {
|
||||||
@@ -75,6 +129,48 @@ export const useParentRoute = function (route) {
|
|||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parsePathParams = (path, params) => {
|
||||||
|
if (path && path.includes(':') && isObject(params)) {
|
||||||
|
Object.keys(params).forEach(key => {
|
||||||
|
path = path.replace(new RegExp(`:${key}`, 'g'), params[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义表单选项,带有jsdoc注解,方便代码提示
|
||||||
|
* @param {CommonFormOption[]} formOptions 表单选项
|
||||||
|
* @return {CommonFormOption[]} 表单选项配置
|
||||||
|
*/
|
||||||
|
export const defineFormOptions = (formOptions) => {
|
||||||
|
return formOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义表格选项,带有jsdoc注解,方便代码提示
|
||||||
|
* @param {CommonTableColumn[]} tableColumns 表单的列
|
||||||
|
* @return {CommonTableColumn[]} 表单的列配置
|
||||||
|
*/
|
||||||
|
export const defineTableColumns = (tableColumns) => {
|
||||||
|
return tableColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义表格的按钮,带有jsdoc注解,方便代码提示
|
||||||
|
* @param {TableButtonProps[]} tableButtons 表格的按钮
|
||||||
|
* @return {TableButtonProps[]} 表格的按钮配置
|
||||||
|
*/
|
||||||
|
export const defineTableButtons = (tableButtons) => {
|
||||||
|
return tableButtons
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param menuItems {CommonMenuItem[]} 菜单配置项
|
||||||
|
* @return {CommonMenuItem[]} 菜单配置项
|
||||||
|
*/
|
||||||
|
export const defineMenuItems = (menuItems) => {
|
||||||
|
return menuItems
|
||||||
|
}
|
||||||
export const formatDate = (date, format) => {
|
export const formatDate = (date, format) => {
|
||||||
if (date) {
|
if (date) {
|
||||||
return dayjs(date).format(format || 'YYYY-MM-DD HH:mm:ss')
|
return dayjs(date).format(format || 'YYYY-MM-DD HH:mm:ss')
|
||||||
@@ -86,3 +182,4 @@ export const formatDay = (date, format) => {
|
|||||||
return dayjs(date).format(format || 'YYYY-MM-DD')
|
return dayjs(date).format(format || 'YYYY-MM-DD')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,15 @@ export const changeMessages = locale => {
|
|||||||
export const $changeLocale = locale => {
|
export const $changeLocale = locale => {
|
||||||
useGlobalConfigStore().changeLocale(locale)
|
useGlobalConfigStore().changeLocale(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const $isLocale = locale => {
|
||||||
|
return useGlobalConfigStore().currentLocale === locale
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param cn
|
* @param cn 中文字段
|
||||||
* @param en
|
* @param en 英文字段
|
||||||
* @param {boolean} replaceEmpty 为空是否用不为空的数据代替
|
* @param {boolean} replaceEmpty 为空是否用不为空的数据代替
|
||||||
* @returns {*}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
export const $i18nMsg = (cn, en, replaceEmpty = true) => {
|
export const $i18nMsg = (cn, en, replaceEmpty = true) => {
|
||||||
const { currentLocale } = useGlobalConfigStore()
|
const { currentLocale } = useGlobalConfigStore()
|
||||||
@@ -48,16 +52,50 @@ export const $i18nMsg = (cn, en, replaceEmpty = true) => {
|
|||||||
}
|
}
|
||||||
return replaceEmpty ? (en || cn) : en
|
return replaceEmpty ? (en || cn) : en
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {String} key 国际化资源key
|
||||||
|
* @param {String[]=} params 可选参数
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
export const $i18nBundle = i18n.global.t
|
export const $i18nBundle = i18n.global.t
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key和locale返回数据<br>
|
||||||
|
* vue-i18n似乎有bug,按照官方文档传locale得不到正确的消息:<br>
|
||||||
|
* <code>$t('ab.c', 'zh-CN')</code><br>
|
||||||
|
* https://vue-i18n.intlify.dev/api/injection.html#t-key-locale
|
||||||
|
* @param key
|
||||||
|
* @param locale
|
||||||
|
* @param [args]
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
export const $i18nByLocale = (key, locale, args) => {
|
||||||
|
return i18n.global.t(key, locale, {
|
||||||
|
locale,
|
||||||
|
list: args || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方便多个资源key解析
|
||||||
|
* @param {String} key 国际化资源key
|
||||||
|
* @param {String} args 可选参数,也是资源key,方便多个资源key解析
|
||||||
|
*/
|
||||||
|
export const $i18nKey = (key, ...args) => {
|
||||||
|
args = args.map(argKey => $i18nBundle(argKey))
|
||||||
|
return $i18nBundle(key, args)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install (app) {
|
install (app) {
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
Object.assign(app.config.globalProperties, {
|
Object.assign(app.config.globalProperties, {
|
||||||
$changeLocale,
|
$changeLocale,
|
||||||
$i18nMsg,
|
$i18nMsg,
|
||||||
$i18nBundle
|
$i18nKey,
|
||||||
|
$i18nBundle,
|
||||||
|
$isLocale,
|
||||||
|
$i18nByLocale
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import piniaPluginPersistedState from 'pinia-plugin-persistedstate'
|
import { createPersistedState } from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合式api的$reset需要自己实现
|
||||||
|
*
|
||||||
|
* @param store
|
||||||
|
*/
|
||||||
|
const piniaPluginResetStore = ({ store }) => {
|
||||||
|
const initialState = JSON.parse(JSON.stringify(store.$state)) // deep clone(store.$state)
|
||||||
|
store.$reset = () => {
|
||||||
|
store.$state = JSON.parse(JSON.stringify(initialState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install (app) {
|
install (app) {
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
pinia.use(piniaPluginPersistedState)
|
pinia.use(piniaPluginResetStore)
|
||||||
|
pinia.use(createPersistedState({
|
||||||
|
key: key => {
|
||||||
|
const systemKey = import.meta.env.VITE_APP_SYSTEM_KEY
|
||||||
|
return `__${systemKey}__${key}`
|
||||||
|
}
|
||||||
|
}))
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
return pinia
|
return pinia
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed, ref } from 'vue'
|
|||||||
import { useCityAutocompleteConfig, useCitySelectPageConfig } from '@/services/city/CityService'
|
import { useCityAutocompleteConfig, useCitySelectPageConfig } from '@/services/city/CityService'
|
||||||
import { $i18nMsg } from '@/messages'
|
import { $i18nMsg } from '@/messages'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { defineFormOptions } from '@/components/utils'
|
||||||
|
|
||||||
const defaultCity = ref({})
|
const defaultCity = ref({})
|
||||||
|
|
||||||
@@ -14,11 +15,53 @@ setTimeout(() => {
|
|||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
const getGenderOptions = (type) => {
|
||||||
|
type = type || 'radio'
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '男',
|
||||||
|
value: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '女',
|
||||||
|
value: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '保密',
|
||||||
|
value: 'unknown'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHobbyOptions = (type) => {
|
||||||
|
type = type || 'checkbox'
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '编程',
|
||||||
|
value: 'program'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '吃饭',
|
||||||
|
value: 'eat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
label: '睡觉',
|
||||||
|
value: 'sleep'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {[CommonFormOption]}
|
* @type {[CommonFormOption]}
|
||||||
*/
|
*/
|
||||||
const formOptions = computed(() => {
|
const formOptions = computed(() => {
|
||||||
return [{
|
return defineFormOptions([{
|
||||||
label: '用户名',
|
label: '用户名',
|
||||||
prop: 'userName',
|
prop: 'userName',
|
||||||
value: '',
|
value: '',
|
||||||
@@ -52,22 +95,14 @@ const formOptions = computed(() => {
|
|||||||
label: '兴趣爱好',
|
label: '兴趣爱好',
|
||||||
type: 'checkbox-group',
|
type: 'checkbox-group',
|
||||||
prop: 'hobby',
|
prop: 'hobby',
|
||||||
value: '',
|
|
||||||
required: true,
|
required: true,
|
||||||
children: [
|
children: getHobbyOptions()
|
||||||
{
|
}, {
|
||||||
label: '编程',
|
label: '兴趣爱好',
|
||||||
value: 'program'
|
type: 'checkbox-group',
|
||||||
},
|
prop: 'hobby',
|
||||||
{
|
required: true,
|
||||||
label: '吃饭',
|
children: getHobbyOptions('checkbox-button')
|
||||||
value: 'eat'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '睡觉',
|
|
||||||
value: 'sleep'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
}, {
|
||||||
label: '职业',
|
label: '职业',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
@@ -94,20 +129,14 @@ const formOptions = computed(() => {
|
|||||||
prop: 'gender',
|
prop: 'gender',
|
||||||
value: '',
|
value: '',
|
||||||
required: true,
|
required: true,
|
||||||
children: [
|
children: getGenderOptions()
|
||||||
{
|
}, {
|
||||||
label: '男',
|
label: '性别',
|
||||||
value: 'male'
|
type: 'radio-group',
|
||||||
},
|
prop: 'gender',
|
||||||
{
|
value: '',
|
||||||
label: '女',
|
required: true,
|
||||||
value: 'female'
|
children: getGenderOptions('radio-button')
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '保密',
|
|
||||||
value: 'unknown'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
}, {
|
||||||
label: '图标',
|
label: '图标',
|
||||||
prop: 'icon',
|
prop: 'icon',
|
||||||
@@ -124,6 +153,9 @@ const formOptions = computed(() => {
|
|||||||
change: (city) => {
|
change: (city) => {
|
||||||
defaultCity.value = city
|
defaultCity.value = city
|
||||||
},
|
},
|
||||||
|
getAutocompleteLabel: () => {
|
||||||
|
return $i18nMsg(defaultCity.value?.nameCN, defaultCity.value?.nameEN)
|
||||||
|
},
|
||||||
attrs: {
|
attrs: {
|
||||||
defaultLabel: $i18nMsg(defaultCity.value?.nameCn, defaultCity.value?.nameEn),
|
defaultLabel: $i18nMsg(defaultCity.value?.nameCn, defaultCity.value?.nameEn),
|
||||||
autocompleteConfig: useCityAutocompleteConfig(),
|
autocompleteConfig: useCityAutocompleteConfig(),
|
||||||
@@ -146,9 +178,11 @@ const formOptions = computed(() => {
|
|||||||
prop: 'address',
|
prop: 'address',
|
||||||
value: '',
|
value: '',
|
||||||
attrs: {
|
attrs: {
|
||||||
type: 'textarea'
|
type: 'textarea',
|
||||||
|
maxlength: 100,
|
||||||
|
showWordLimit: true
|
||||||
}
|
}
|
||||||
}]
|
}])
|
||||||
})
|
})
|
||||||
const userDto = ref({
|
const userDto = ref({
|
||||||
contacts: [{
|
contacts: [{
|
||||||
@@ -195,7 +229,7 @@ const submitForm = (form) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<el-container class="flex-column container-center">
|
||||||
<common-form
|
<common-form
|
||||||
:model="userDto"
|
:model="userDto"
|
||||||
:options="formOptions"
|
:options="formOptions"
|
||||||
@@ -246,7 +280,7 @@ const submitForm = (form) => {
|
|||||||
<div>
|
<div>
|
||||||
{{ userDto }}
|
{{ userDto }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ const tableData = ref([])
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const usersResult = await loadUsersResult({ page: page.value })
|
const usersResult = await loadUsersResult({ page: page.value }).finally(() => (loading.value = false))
|
||||||
loading.value = false
|
|
||||||
if (usersResult.success && usersResult.resultData) {
|
if (usersResult.success && usersResult.resultData) {
|
||||||
const resultData = usersResult.resultData
|
const resultData = usersResult.resultData
|
||||||
tableData.value = resultData.userList
|
tableData.value = resultData.userList
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { fileURLToPath, URL } from 'node:url'
|
|||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { viteMockServe } from 'vite-plugin-mock'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteMockServe({
|
||||||
|
mockPath: './mock'
|
||||||
|
})
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
|||||||
Reference in New Issue
Block a user