Merge branch 'master' into release

This commit is contained in:
kailong321200875 2023-11-12 11:38:28 +08:00
commit 3cc292aa4a
30 changed files with 587 additions and 171 deletions

View File

@ -2,7 +2,7 @@
NODE_ENV=development NODE_ENV=development
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH=base VITE_API_BASE_PATH=
# 打包路径 # 打包路径
VITE_BASE_PATH=/ VITE_BASE_PATH=/

View File

@ -2,7 +2,7 @@
NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH=dev VITE_API_BASE_PATH=
# 打包路径 # 打包路径
VITE_BASE_PATH=/dist-dev/ VITE_BASE_PATH=/dist-dev/

View File

@ -2,7 +2,7 @@
NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH=pro VITE_API_BASE_PATH=
# 打包路径 # 打包路径
VITE_BASE_PATH=/vue-element-plus-admin/ VITE_BASE_PATH=/vue-element-plus-admin/

View File

@ -2,7 +2,7 @@
NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH=pro VITE_API_BASE_PATH=
# 打包路径 # 打包路径
VITE_BASE_PATH=/ VITE_BASE_PATH=/

View File

@ -2,7 +2,7 @@
NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH=test VITE_API_BASE_PATH=
# 打包路径 # 打包路径
VITE_BASE_PATH=/dist-test/ VITE_BASE_PATH=/dist-test/

View File

@ -65,6 +65,7 @@ module.exports = defineConfig({
} }
], ],
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off' 'vue/no-v-html': 'off',
'vue/require-toggle-inside-transition': 'off'
} }
}) })

View File

@ -1,11 +1,11 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"prettier.enable": false, "prettier.enable": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"[vue]": { "[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"i18n-ally.localesPaths": ["src/locales"], "i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",

View File

@ -124,8 +124,6 @@ export default [
email: '@EMAIL', email: '@EMAIL',
// 创建时间 // 创建时间
createTime: '@datetime', createTime: '@datetime',
// 角色
role: '@first',
// 用户id // 用户id
id: toAnyString() id: toAnyString()
}) })

View File

@ -24,6 +24,8 @@ export default [
name: 'Dashboard', name: 'Dashboard',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 1, id: 1,
type: 0,
parentId: undefined,
title: '首页', title: '首页',
meta: { meta: {
title: '首页', title: '首页',
@ -37,10 +39,23 @@ export default [
name: 'Analysis', name: 'Analysis',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 2, id: 2,
type: 1,
parentId: 1,
title: '分析页', title: '分析页',
permissionList: [
{
label: '新增',
value: 'add'
},
{
label: '编辑',
value: 'edit'
}
],
meta: { meta: {
title: '分析页', title: '分析页',
noCache: true noCache: true,
permission: ['add', 'edit']
} }
}, },
{ {
@ -49,7 +64,23 @@ export default [
name: 'Workplace', name: 'Workplace',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 3, id: 3,
type: 1,
parentId: 1,
title: '工作台', title: '工作台',
permissionList: [
{
label: '新增',
value: 'add'
},
{
label: '编辑',
value: 'edit'
},
{
label: '删除',
value: 'delete'
}
],
meta: { meta: {
title: '工作台', title: '工作台',
noCache: true noCache: true
@ -60,7 +91,6 @@ export default [
{ {
path: '/external-link', path: '/external-link',
component: '#', component: '#',
title: '文档',
meta: { meta: {
title: '文档', title: '文档',
icon: 'clarity:document-solid' icon: 'clarity:document-solid'
@ -68,12 +98,17 @@ export default [
name: 'ExternalLink', name: 'ExternalLink',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 4, id: 4,
type: 0,
parentId: undefined,
title: '文档',
children: [ children: [
{ {
path: 'https://element-plus-admin-doc.cn/', path: 'https://element-plus-admin-doc.cn/',
name: 'DocumentLink', name: 'DocumentLink',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 5, id: 5,
type: 1,
parentId: 4,
title: '文档', title: '文档',
meta: { meta: {
title: '文档' title: '文档'
@ -88,6 +123,8 @@ export default [
name: 'Level', name: 'Level',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 6, id: 6,
type: 0,
parentId: undefined,
title: '菜单', title: '菜单',
meta: { meta: {
title: '菜单', title: '菜单',
@ -100,8 +137,10 @@ export default [
component: '##', component: '##',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 7, id: 7,
redirect: '/level/menu1/menu1-1/menu1-1-1', type: 0,
parentId: 6,
title: '菜单1', title: '菜单1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: { meta: {
title: '菜单1' title: '菜单1'
}, },
@ -112,8 +151,10 @@ export default [
component: '##', component: '##',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 8, id: 8,
redirect: '/level/menu1/menu1-1/menu1-1-1', type: 0,
parentId: 7,
title: '菜单1-1', title: '菜单1-1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: { meta: {
title: '菜单1-1', title: '菜单1-1',
alwaysShow: true alwaysShow: true
@ -125,7 +166,8 @@ export default [
component: 'views/Level/Menu111', component: 'views/Level/Menu111',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 9, id: 9,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 8,
title: '菜单1-1-1', title: '菜单1-1-1',
meta: { meta: {
title: '菜单1-1-1' title: '菜单1-1-1'
@ -139,7 +181,8 @@ export default [
component: 'views/Level/Menu12', component: 'views/Level/Menu12',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 10, id: 10,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 7,
title: '菜单1-2', title: '菜单1-2',
meta: { meta: {
title: '菜单1-2' title: '菜单1-2'
@ -153,7 +196,8 @@ export default [
component: 'views/Level/Menu2', component: 'views/Level/Menu2',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 11, id: 11,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 6,
title: '菜单2', title: '菜单2',
meta: { meta: {
title: '菜单2' title: '菜单2'
@ -168,6 +212,8 @@ export default [
name: 'Example', name: 'Example',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 12, id: 12,
type: 0,
parentId: undefined,
title: '综合示例', title: '综合示例',
meta: { meta: {
title: '综合示例', title: '综合示例',
@ -181,11 +227,29 @@ export default [
name: 'ExampleDialog', name: 'ExampleDialog',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 13, id: 13,
type: 1,
parentId: 12,
title: '综合示例-弹窗', title: '综合示例-弹窗',
permission: ['edit', 'add', 'delete'], permissionList: [
{
label: '新增',
value: 'add'
},
{
label: '编辑',
value: 'edit'
},
{
label: '删除',
value: 'delete'
},
{
label: '查看',
value: 'view'
}
],
meta: { meta: {
title: '综合示例-弹窗', title: '综合示例-弹窗'
permission: ['edit', 'add']
} }
}, },
{ {
@ -194,11 +258,29 @@ export default [
name: 'ExamplePage', name: 'ExamplePage',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 14, id: 14,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 12,
title: '综合示例-页面', title: '综合示例-页面',
permissionList: [
{
label: '新增',
value: 'edit'
},
{
label: '编辑',
value: 'edit'
},
{
label: '删除',
value: 'delete'
},
{
label: '查看',
value: 'view'
}
],
meta: { meta: {
title: '综合示例-页面', title: '综合示例-页面'
permission: ['edit', 'add']
} }
}, },
{ {
@ -207,7 +289,8 @@ export default [
name: 'ExampleAdd', name: 'ExampleAdd',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 15, id: 15,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 12,
title: '综合示例-新增', title: '综合示例-新增',
meta: { meta: {
title: '综合示例-新增', title: '综合示例-新增',
@ -215,8 +298,7 @@ export default [
noCache: true, noCache: true,
hidden: true, hidden: true,
showMainRoute: true, showMainRoute: true,
activeMenu: '/example/example-page', activeMenu: '/example/example-page'
permission: ['delete', 'add']
} }
}, },
{ {
@ -225,7 +307,8 @@ export default [
name: 'ExampleEdit', name: 'ExampleEdit',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 16, id: 16,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 12,
title: '综合示例-编辑', title: '综合示例-编辑',
meta: { meta: {
title: '综合示例-编辑', title: '综合示例-编辑',
@ -233,8 +316,7 @@ export default [
noCache: true, noCache: true,
hidden: true, hidden: true,
showMainRoute: true, showMainRoute: true,
activeMenu: '/example/example-page', activeMenu: '/example/example-page'
permission: ['delete', 'add']
} }
}, },
{ {
@ -243,7 +325,8 @@ export default [
name: 'ExampleDetail', name: 'ExampleDetail',
status: Mock.Random.integer(0, 1), status: Mock.Random.integer(0, 1),
id: 17, id: 17,
permission: ['edit', 'add', 'delete'], type: 1,
parentId: 12,
title: '综合示例-详情', title: '综合示例-详情',
meta: { meta: {
title: '综合示例-详情', title: '综合示例-详情',
@ -251,8 +334,7 @@ export default [
noCache: true, noCache: true,
hidden: true, hidden: true,
showMainRoute: true, showMainRoute: true,
activeMenu: '/example/example-page', activeMenu: '/example/example-page'
permission: ['delete', 'edit']
} }
} }
] ]

View File

@ -34,12 +34,12 @@
"@wangeditor/editor-for-vue": "^5.1.10", "@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.5.1", "axios": "^1.6.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"driver.js": "^1.3.0", "driver.js": "^1.3.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "^2.4.0", "element-plus": "^2.4.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
@ -49,62 +49,62 @@
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qs": "^6.11.2", "qs": "^6.11.2",
"url": "^0.11.3", "url": "^0.11.3",
"vue": "3.3.4", "vue": "3.3.8",
"vue-i18n": "9.5.0", "vue-i18n": "9.6.5",
"vue-json-pretty": "^2.2.4", "vue-json-pretty": "^2.2.4",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-types": "^5.1.1" "vue-types": "^5.1.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.2", "@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^18.1.0",
"@iconify/json": "^2.2.128", "@iconify/json": "^2.2.138",
"@intlify/unplugin-vue-i18n": "^1.4.0", "@intlify/unplugin-vue-i18n": "^1.5.0",
"@purge-icons/generated": "^0.9.0", "@purge-icons/generated": "^0.9.0",
"@types/fs-extra": "^11.0.2", "@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.4", "@types/inquirer": "^9.0.7",
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.11",
"@types/node": "^20.8.6", "@types/node": "^20.9.0",
"@types/nprogress": "^0.2.1", "@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.2", "@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.8", "@types/qs": "^6.9.10",
"@types/sortablejs": "^1.15.3", "@types/sortablejs": "^1.15.5",
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.7.5", "@typescript-eslint/parser": "^6.10.0",
"@unocss/transformer-variant-group": "^0.56.5", "@unocss/transformer-variant-group": "^0.57.2",
"@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue": "^4.4.0",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.0.2",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"consola": "^3.2.3", "consola": "^3.2.3",
"eslint": "^8.51.0", "eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-define-config": "^1.24.1", "eslint-define-config": "^1.24.1",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.18.1",
"esno": "^0.17.0", "esno": "^0.17.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"husky": "^8.0.3", "husky": "^8.0.3",
"inquirer": "^9.2.11", "inquirer": "^9.2.12",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^14.0.1", "lint-staged": "^15.0.2",
"plop": "^4.0.0", "plop": "^4.0.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.0.2", "rollup": "^4.3.0",
"stylelint": "^15.10.3", "stylelint": "^15.11.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3", "stylelint-order": "^6.0.3",
"terser": "^5.21.0", "terser": "^5.24.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"unocss": "^0.56.5", "unocss": "^0.57.2",
"vite": "4.4.11", "vite": "4.5.0",
"vite-plugin-ejs": "^1.6.4", "vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "2.9.6", "vite-plugin-mock": "2.9.6",
@ -112,7 +112,7 @@
"vite-plugin-purge-icons": "^0.9.2", "vite-plugin-purge-icons": "^0.9.2",
"vite-plugin-style-import": "2.0.0", "vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.8.19" "vue-tsc": "^1.8.22"
}, },
"engines": { "engines": {
"node": ">= 16.13.0" "node": ">= 16.13.0"

View File

@ -20,10 +20,13 @@ const prop = defineProps({
src: 'src', src: 'src',
height: 'height' height: 'height'
}), }),
cols: propTypes.number.def(undefined),
loadingText: propTypes.string.def('加载中...'), loadingText: propTypes.string.def('加载中...'),
loading: propTypes.bool.def(false), loading: propTypes.bool.def(false),
end: propTypes.bool.def(false), end: propTypes.bool.def(false),
endText: propTypes.string.def('没有更多了') endText: propTypes.string.def('没有更多了'),
autoCenter: propTypes.bool.def(true),
layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
}) })
const wrapEl = ref<HTMLDivElement>() const wrapEl = ref<HTMLDivElement>()
@ -37,7 +40,7 @@ const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>() const loadMore = ref<HTMLDivElement>()
// = / // = /
const cols = ref(0) const innerCols = ref(0)
const filterData = ref<any[]>([]) const filterData = ref<any[]>([])
@ -49,11 +52,11 @@ const filterWaterfall = async () => {
const container = unref(wrapEl) as HTMLElement const container = unref(wrapEl) as HTMLElement
if (!container) return if (!container) return
cols.value = Math.floor(container.clientWidth / (width + gap)) innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length const length = data.length
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
if (i < unref(cols)) { if (i < unref(innerCols)) {
heights.value[i] = data[i][props.height as string] heights.value[i] = data[i][props.height as string]
filterData.value.push({ filterData.value.push({
...data[i], ...data[i],
@ -66,7 +69,7 @@ const filterWaterfall = async () => {
let minHeight = heights.value[0] let minHeight = heights.value[0]
let index = 0 let index = 0
// //
for (let j = 1; j < unref(cols); j++) { for (let j = 1; j < unref(innerCols); j++) {
if (unref(heights)[j] < minHeight) { if (unref(heights)[j] < minHeight) {
minHeight = unref(heights)[j] minHeight = unref(heights)[j]
index = j index = j
@ -83,14 +86,42 @@ const filterWaterfall = async () => {
} }
} }
wrapHeight.value = Math.max(...unref(heights)) wrapHeight.value = Math.max(...unref(heights))
wrapWidth.value = unref(cols) * (width + gap) - gap wrapWidth.value = unref(innerCols) * (width + gap) - gap
}
const flexWaterfall = async () => {
const { width, gap } = prop
const data = prop.data as any[]
await nextTick()
const container = unref(wrapEl) as HTMLElement
if (!container) return
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length
//
const arr = new Array(unref(innerCols)).fill([])
// dataarr
for (let i = 0; i < length; i++) {
const index = i % unref(innerCols)
arr[index] = [...arr[index], data[i]]
}
filterData.value = arr
}
const initLayout = () => {
const { layout } = prop
if (layout === 'javascript') {
filterWaterfall()
} else if (layout === 'flex') {
flexWaterfall()
}
} }
watch( watch(
() => prop.data, () => [prop.data, prop.cols],
async () => { () => {
await nextTick() initLayout()
filterWaterfall()
}, },
{ {
immediate: true immediate: true
@ -99,7 +130,7 @@ watch(
onMounted(() => { onMounted(() => {
if (unref(prop.reset)) { if (unref(prop.reset)) {
useEventListener(window, 'resize', debounce(filterWaterfall, 300)) useEventListener(window, 'resize', debounce(initLayout, 300))
} }
useIntersectionObserver( useIntersectionObserver(
unref(loadMore), unref(loadMore),
@ -117,41 +148,87 @@ onMounted(() => {
<template> <template>
<div <div
:class="[prefixCls, 'flex', 'justify-center', 'items-center']" :class="[
prefixCls,
'flex',
'items-center',
{
'justify-center': autoCenter
}
]"
ref="wrapEl" ref="wrapEl"
:style="{ :style="{
height: `${wrapHeight + 40}px` height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
}" }"
> >
<div <template v-if="layout === 'javascript'">
class="relative" <div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
:style="{ <div
width: `${wrapWidth}px`, v-for="(item, $index) in filterData"
height: `${wrapHeight + 40}px` :class="[
}" `${prefixCls}-item__${$index}`,
> {
absolute: layout === 'javascript'
}
]"
:key="`water-${$index}`"
:style="{
width: `${width}px`,
height: `${item[props.height as string]}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
>
<img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full"
:style="{
top: `${wrapHeight + gap}px`
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</template>
<template v-else-if="layout === 'flex'">
<div <div
v-for="(item, $index) in filterData" class="relative flex pb-40px"
:class="[`${prefixCls}-item__${$index}`, 'absolute']"
:key="`water-${$index}`"
:style="{ :style="{
width: `${width}px`, width: cols ? '100%' : 'auto'
height: `${item[props.height as string]}px`,
top: `${item.top}px`,
left: `${item.left}px`
}" }"
> >
<img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" /> <div
v-for="(item, $index) in filterData"
:key="`waterWrap-${$index}`"
class="flex-1"
:style="{
marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
}"
>
<div
v-for="(child, i) in item"
:key="`waterWrap-${$index}-${i}`"
:style="{
marginBottom: `${gap}px`,
width: cols ? '100%' : `${width}px`,
height: cols ? 'auto' : `${child[props.height as string]}px`
}"
>
<img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
</div>
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full items-center"
:style="{
bottom: 0
}"
>
{{ end ? endText : loadingText }}
</div>
</div> </div>
<div </template>
ref="loadMore"
class="h-40px flex justify-center absolute w-full"
:style="{
top: `${wrapHeight + gap}px`
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</div> </div>
</template> </template>

View File

@ -8,23 +8,6 @@ import { ElMessage } from 'element-plus'
import qs from 'qs' import qs from 'qs'
const config: AxiosConfig = { const config: AxiosConfig = {
/**
* api请求基础路径
*/
baseUrl: {
// 开发环境接口前缀
base: '',
// 打包开发环境接口前缀
dev: '',
// 打包生产环境接口前缀
pro: '',
// 打包测试环境接口前缀
test: ''
},
/** /**
* *
*/ */

View File

@ -4,8 +4,8 @@ import config, { defaultRequestInterceptors, defaultResponseInterceptors } from
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types' import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const { interceptors, baseUrl } = config const { interceptors } = config
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH] export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
const { requestInterceptors, responseInterceptors } = interceptors const { requestInterceptors, responseInterceptors } = interceptors

View File

@ -16,12 +16,6 @@ interface RequestInterceptors<T> {
responseInterceptorsCatch?: (err: any) => any responseInterceptorsCatch?: (err: any) => any
} }
interface AxiosConfig<T = AxiosResponse> { interface AxiosConfig<T = AxiosResponse> {
baseUrl: {
base: string
dev: string
pro: string
test: string
}
code: number code: number
defaultHeaders: AxiosHeaders defaultHeaders: AxiosHeaders
timeout: number timeout: number

View File

@ -521,7 +521,7 @@ export default {
menu: { menu: {
menuName: '菜单名称', menuName: '菜单名称',
icon: '图标', icon: '图标',
permission: '权限标识', permission: '按钮权限',
component: '组件', component: '组件',
path: '路径', path: '路径',
status: '状态', status: '状态',

View File

@ -63,7 +63,7 @@ export const useAppStore = defineStore('app', {
layout: getStorage('layout') || 'classic', // layout布局 layout: getStorage('layout') || 'classic', // layout布局
isDark: getStorage('isDark'), // 是否是暗黑模式 isDark: getStorage('isDark'), // 是否是暗黑模式
currentSize: getStorage('default') || 'default', // 组件尺寸 currentSize: getStorage('currentSize') || 'default', // 组件尺寸
theme: getStorage('theme') || { theme: getStorage('theme') || {
// 主题色 // 主题色
elColorPrimary: '#409eff', elColorPrimary: '#409eff',

View File

@ -166,11 +166,7 @@ const crudSchemas = reactive<CrudSchema[]>([
hidden: true hidden: true
}, },
form: { form: {
component: 'DatePicker', hidden: true
componentProps: {
type: 'datetime',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
} }
}, },
{ {

View File

@ -20,9 +20,7 @@ const props = defineProps({
const rules = reactive({ const rules = reactive({
id: [required()], id: [required()],
status: [required()], status: [required()]
createTime: [required()],
remark: [required()]
}) })
const { formRegister, formMethods } = useForm() const { formRegister, formMethods } = useForm()

View File

@ -35,7 +35,13 @@ const tableColumns = reactive<TableColumn[]>([
}, },
{ {
field: 'meta.title', field: 'meta.title',
label: t('menu.menuName') label: t('menu.menuName'),
slots: {
default: (data: any) => {
const title = data.row.meta.title
return <>{title}</>
}
}
}, },
{ {
field: 'meta.icon', field: 'meta.icon',

View File

@ -0,0 +1,67 @@
<script setup lang="ts">
import { FormSchema, Form } from '@/components/Form'
import { ElDrawer, ElButton } from 'element-plus'
import { reactive } from 'vue'
import { useForm } from '@/hooks/web/useForm'
import { useValidator } from '@/hooks/web/useValidator'
const modelValue = defineModel<boolean>()
const { required } = useValidator()
const formSchema = reactive<FormSchema[]>([
{
field: 'label',
label: 'label',
component: 'Input',
colProps: {
span: 24
}
},
{
field: 'value',
label: 'value',
component: 'Input',
colProps: {
span: 24
}
}
])
const { formRegister, formMethods } = useForm()
const { getFormData, getElFormExpose } = formMethods
const emit = defineEmits(['confirm'])
const rules = reactive({
label: [required()],
value: [required()]
})
const confirm = async () => {
const elFormExpose = await getElFormExpose()
if (!elFormExpose) return
const valid = await elFormExpose?.validate().catch((err) => {
console.log(err)
})
if (valid) {
const formData = await getFormData()
emit('confirm', formData)
modelValue.value = false
}
}
</script>
<template>
<ElDrawer v-model="modelValue" title="新增按钮权限">
<template #default>
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
</template>
<template #footer>
<div>
<ElButton @click="() => (modelValue = false)">取消</ElButton>
<ElButton type="primary" @click="confirm">确认</ElButton>
</div>
</template>
</ElDrawer>
</template>

View File

@ -75,6 +75,24 @@ const detailSchema = ref<DescriptionsSchema[]>([
field: 'meta.activeMenu', field: 'meta.activeMenu',
label: '高亮菜单' label: '高亮菜单'
}, },
{
field: 'permissionList',
label: '按钮权限',
span: 24,
slots: {
default: (data: any) => (
<>
{data?.permissionList?.map((v) => {
return (
<ElTag class="mr-1" key={v.value}>
{v.label}
</ElTag>
)
})}
</>
)
}
},
{ {
field: 'menuState', field: 'menuState',
label: '菜单状态', label: '菜单状态',

View File

@ -1,11 +1,12 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { Form, FormSchema } from '@/components/Form' import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm' import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue' import { PropType, reactive, watch, ref, unref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator' import { useValidator } from '@/hooks/web/useValidator'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { getMenuListApi } from '@/api/menu' import { getMenuListApi } from '@/api/menu'
import { ElTag, ElButton } from 'element-plus' import { ElTag, ElButton } from 'element-plus'
import AddButtonPermission from './AddButtonPermission.vue'
const { t } = useI18n() const { t } = useI18n()
@ -18,6 +19,16 @@ const props = defineProps({
} }
}) })
const handleClose = async (tag: any) => {
const formData = await getFormData()
//
setValues({
permissionList: formData?.permissionList?.filter((v: any) => v.value !== tag.value)
})
}
const showDrawer = ref(false)
const formSchema = reactive<FormSchema[]>([ const formSchema = reactive<FormSchema[]>([
{ {
field: 'type', field: 'type',
@ -50,7 +61,7 @@ const formSchema = reactive<FormSchema[]>([
} }
]) ])
setValues({ setValues({
component: '' component: unref(cacheComponent)
}) })
} else { } else {
setSchema([ setSchema([
@ -104,7 +115,7 @@ const formSchema = reactive<FormSchema[]>([
}) })
} else if (formData.type === 1) { } else if (formData.type === 1) {
setValues({ setValues({
component: '' component: unref(cacheComponent) ?? ''
}) })
} }
} }
@ -127,7 +138,12 @@ const formSchema = reactive<FormSchema[]>([
value: '#', value: '#',
componentProps: { componentProps: {
disabled: true, disabled: true,
placeholder: '#为顶级目录,##为子目录' placeholder: '#为顶级目录,##为子目录',
on: {
change: (val: string) => {
cacheComponent.value = val
}
}
} }
}, },
{ {
@ -176,18 +192,16 @@ const formSchema = reactive<FormSchema[]>([
}, },
formItemProps: { formItemProps: {
slots: { slots: {
default: () => ( default: (data: any) => (
<> <>
<ElTag class="mx-1" closable disableTransitions={false}> {data?.permissionList?.map((v) => {
新增 return (
</ElTag> <ElTag class="mr-1" key={v.value} closable onClose={() => handleClose(v)}>
<ElTag class="mx-1" closable disableTransitions={false}> {v.label}
编辑 </ElTag>
</ElTag> )
<ElTag class="mx-1" closable disableTransitions={false}> })}
删除 <ElButton type="primary" size="small" onClick={() => (showDrawer.value = true)}>
</ElTag>
<ElButton type="primary" size="small" onClick={() => console.log('添加权限')}>
添加权限 添加权限
</ElButton> </ElButton>
</> </>
@ -252,10 +266,47 @@ const submit = async () => {
} }
} }
const cacheComponent = ref('')
watch( watch(
() => props.currentRow, () => props.currentRow,
(currentRow) => { (currentRow) => {
if (!currentRow) return if (!currentRow) return
cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
if (currentRow.parentId === 0) {
setSchema([
{
field: 'component',
path: 'componentProps.disabled',
value: true
}
])
} else {
setSchema([
{
field: 'component',
path: 'componentProps.disabled',
value: false
}
])
}
if (currentRow.type === 1) {
setSchema([
{
field: 'component',
path: 'componentProps.disabled',
value: false
}
])
} else {
setSchema([
{
field: 'component',
path: 'componentProps.disabled',
value: true
}
])
}
setValues(currentRow) setValues(currentRow)
}, },
{ {
@ -267,8 +318,16 @@ watch(
defineExpose({ defineExpose({
submit submit
}) })
const confirm = async (data: any) => {
const formData = await getFormData()
setValues({
permissionList: [...(formData?.permissionList || []), data]
})
}
</script> </script>
<template> <template>
<Form :rules="rules" @register="formRegister" :schema="formSchema" /> <Form :rules="rules" @register="formRegister" :schema="formSchema" />
<AddButtonPermission v-model="showDrawer" @confirm="confirm" />
</template> </template>

View File

@ -9,6 +9,7 @@ import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form' import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue' import Write from './components/Write.vue'
import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog' import { Dialog } from '@/components/Dialog'
const { t } = useI18n() const { t } = useI18n()
@ -36,10 +37,6 @@ const tableColumns = reactive<TableColumn[]>([
field: 'roleName', field: 'roleName',
label: t('role.roleName') label: t('role.roleName')
}, },
{
field: 'role',
label: t('role.role')
},
{ {
field: 'status', field: 'status',
label: t('menu.status'), label: t('menu.status'),
@ -75,6 +72,9 @@ const tableColumns = reactive<TableColumn[]>([
<ElButton type="primary" onClick={() => action(row, 'edit')}> <ElButton type="primary" onClick={() => action(row, 'edit')}>
{t('exampleDemo.edit')} {t('exampleDemo.edit')}
</ElButton> </ElButton>
<ElButton type="success" onClick={() => action(row, 'detail')}>
{t('exampleDemo.detail')}
</ElButton>
<ElButton type="danger">{t('exampleDemo.del')}</ElButton> <ElButton type="danger">{t('exampleDemo.del')}</ElButton>
</> </>
) )
@ -155,6 +155,7 @@ const save = async () => {
<Dialog v-model="dialogVisible" :title="dialogTitle"> <Dialog v-model="dialogVisible" :title="dialogTitle">
<Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" /> <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
<Detail v-else :current-row="currentRow" />
<template #footer> <template #footer>
<ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save"> <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">

View File

@ -0,0 +1,106 @@
<script setup lang="tsx">
import { PropType, ref, unref, nextTick } from 'vue'
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
import { ElTag, ElTree } from 'element-plus'
import { findIndex } from '@/utils'
import { getMenuListApi } from '@/api/menu'
defineProps({
currentRow: {
type: Object as PropType<any>,
default: () => undefined
}
})
const filterPermissionName = (value: string) => {
const index = findIndex(unref(currentTreeData)?.permissionList || [], (item) => {
return item.value === value
})
return (unref(currentTreeData)?.permissionList || [])[index].label ?? ''
}
const renderTag = (enable?: boolean) => {
return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
}
const treeRef = ref<typeof ElTree>()
const currentTreeData = ref()
const nodeClick = (treeData: any) => {
currentTreeData.value = treeData
}
const treeData = ref<any[]>([])
const getMenuList = async () => {
const res = await getMenuListApi()
if (res) {
treeData.value = res.data.list
await nextTick()
}
}
getMenuList()
const detailSchema = ref<DescriptionsSchema[]>([
{
field: 'roleName',
label: '角色名称'
},
{
field: 'status',
label: '状态',
slots: {
default: (data: any) => {
return renderTag(data.status)
}
}
},
{
field: 'remark',
label: '备注',
span: 24
},
{
field: 'permissionList',
label: '菜单分配',
span: 24,
slots: {
default: () => {
return (
<>
<div class="flex w-full">
<div class="flex-1">
<ElTree
ref={treeRef}
node-key="id"
props={{ children: 'children', label: 'title' }}
highlight-current
expand-on-click-node={false}
data={treeData.value}
onNode-click={nodeClick}
>
{{
default: (data) => {
return <span>{data?.data?.title}</span>
}
}}
</ElTree>
</div>
<div class="flex-1">
{unref(currentTreeData)
? unref(currentTreeData)?.meta?.permission?.map((v: string) => {
return <ElTag class="ml-2 mt-2">{filterPermissionName(v)}</ElTag>
})
: null}
</div>
</div>
</>
)
}
}
}
])
</script>
<template>
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
</template>

View File

@ -28,11 +28,6 @@ const formSchema = ref<FormSchema[]>([
label: t('role.roleName'), label: t('role.roleName'),
component: 'Input' component: 'Input'
}, },
{
field: 'role',
label: t('role.role'),
component: 'Input'
},
{ {
field: 'status', field: 'status',
label: t('menu.status'), label: t('menu.status'),
@ -81,10 +76,10 @@ const formSchema = ref<FormSchema[]>([
</ElTree> </ElTree>
</div> </div>
<div class="flex-1"> <div class="flex-1">
{unref(currentTreeData) && unref(currentTreeData)?.permission ? ( {unref(currentTreeData) && unref(currentTreeData)?.permissionList ? (
<ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}> <ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}>
{unref(currentTreeData)?.permission.map((v: string) => { {unref(currentTreeData)?.permissionList.map((v: any) => {
return <ElCheckbox label={v} /> return <ElCheckbox label={v.value}>{v.label}</ElCheckbox>
})} })}
</ElCheckboxGroup> </ElCheckboxGroup>
) : null} ) : null}

View File

@ -11,6 +11,7 @@ import { Search } from '@/components/Search'
import Write from './components/Write.vue' import Write from './components/Write.vue'
import Detail from './components/Detail.vue' import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog' import { Dialog } from '@/components/Dialog'
import { getRoleListApi } from '@/api/role'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() const { t } = useI18n()
@ -113,6 +114,22 @@ const crudSchemas = reactive<CrudSchema[]>([
label: t('userDemo.role'), label: t('userDemo.role'),
search: { search: {
hidden: true hidden: true
},
form: {
component: 'Select',
value: [],
componentProps: {
multiple: true,
collapseTags: true,
maxCollapseTags: 1
},
optionApi: async () => {
const res = await getRoleListApi()
return res.data?.list?.map((v) => ({
label: v.roleName,
value: v.id
}))
}
} }
}, },
{ {
@ -276,7 +293,7 @@ const save = async () => {
<template> <template>
<div class="flex w-100% h-100%"> <div class="flex w-100% h-100%">
<ContentWrap class="flex-1"> <ContentWrap class="w-250px">
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
<div class="flex-1">{{ t('userDemo.departmentList') }}</div> <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
<ElInput <ElInput
@ -299,7 +316,16 @@ const save = async () => {
}" }"
:filter-node-method="filterNode" :filter-node-method="filterNode"
@current-change="currentChange" @current-change="currentChange"
/> >
<template #default="{ data }">
<div
:title="data.departmentName"
class="whitespace-nowrap overflow-ellipsis overflow-hidden"
>
{{ data.departmentName }}
</div>
</template>
</ElTree>
</ContentWrap> </ContentWrap>
<ContentWrap class="flex-[3] ml-20px"> <ContentWrap class="flex-[3] ml-20px">
<Search <Search

View File

@ -21,10 +21,7 @@ const props = defineProps({
const rules = reactive({ const rules = reactive({
username: [required()], username: [required()],
account: [required()], account: [required()],
'department.id': [required()], 'department.id': [required()]
role: [required()],
email: [required()],
createTime: [required()]
}) })
const { formRegister, formMethods } = useForm() const { formRegister, formMethods } = useForm()

View File

@ -19,7 +19,8 @@ const getList = () => {
width, width,
height, height,
id: toAnyString(), id: toAnyString(),
image_uri: Mock.Random.image(`${width}x${height}`) // httphttps
image_uri: Mock.Random.image(`${width}x${height}`).replace('http://', 'https://')
}) })
) )
} }

View File

@ -4,6 +4,17 @@ import transformerVariantGroup from '@unocss/transformer-variant-group'
export default defineConfig({ export default defineConfig({
// ...UnoCSS options // ...UnoCSS options
rules: [ rules: [
[
/^overflow-ellipsis$/,
([], { rawSelector }) => {
const selector = e(rawSelector)
return `
${selector} {
text-overflow: ellipsis;
}
`
}
],
[ [
/^custom-hover$/, /^custom-hover$/,
([], { rawSelector }) => { ([], { rawSelector }) => {