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
# 接口前缀
VITE_API_BASE_PATH=base
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/

View File

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

View File

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

View File

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

View File

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

View File

@ -65,6 +65,7 @@ module.exports = defineConfig({
}
],
'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",
"prettier.enable": false,
"prettier.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",

View File

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

View File

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

View File

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

View File

@ -20,10 +20,13 @@ const prop = defineProps({
src: 'src',
height: 'height'
}),
cols: propTypes.number.def(undefined),
loadingText: propTypes.string.def('加载中...'),
loading: 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>()
@ -37,7 +40,7 @@ const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>()
// = /
const cols = ref(0)
const innerCols = ref(0)
const filterData = ref<any[]>([])
@ -49,11 +52,11 @@ const filterWaterfall = async () => {
const container = unref(wrapEl) as HTMLElement
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
for (let i = 0; i < length; i++) {
if (i < unref(cols)) {
if (i < unref(innerCols)) {
heights.value[i] = data[i][props.height as string]
filterData.value.push({
...data[i],
@ -66,7 +69,7 @@ const filterWaterfall = async () => {
let minHeight = heights.value[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) {
minHeight = unref(heights)[j]
index = j
@ -83,14 +86,42 @@ const filterWaterfall = async () => {
}
}
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(
() => prop.data,
async () => {
await nextTick()
filterWaterfall()
() => [prop.data, prop.cols],
() => {
initLayout()
},
{
immediate: true
@ -99,7 +130,7 @@ watch(
onMounted(() => {
if (unref(prop.reset)) {
useEventListener(window, 'resize', debounce(filterWaterfall, 300))
useEventListener(window, 'resize', debounce(initLayout, 300))
}
useIntersectionObserver(
unref(loadMore),
@ -117,22 +148,29 @@ onMounted(() => {
<template>
<div
:class="[prefixCls, 'flex', 'justify-center', 'items-center']"
:class="[
prefixCls,
'flex',
'items-center',
{
'justify-center': autoCenter
}
]"
ref="wrapEl"
:style="{
height: `${wrapHeight + 40}px`
}"
>
<div
class="relative"
:style="{
width: `${wrapWidth}px`,
height: `${wrapHeight + 40}px`
height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
}"
>
<template v-if="layout === 'javascript'">
<div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
<div
v-for="(item, $index) in filterData"
:class="[`${prefixCls}-item__${$index}`, 'absolute']"
:class="[
`${prefixCls}-item__${$index}`,
{
absolute: layout === 'javascript'
}
]"
:key="`water-${$index}`"
:style="{
width: `${width}px`,
@ -153,5 +191,44 @@ onMounted(() => {
{{ end ? endText : loadingText }}
</div>
</div>
</template>
<template v-else-if="layout === 'flex'">
<div
class="relative flex pb-40px"
:style="{
width: cols ? '100%' : 'auto'
}"
>
<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>
</template>
</div>
</template>

View File

@ -8,23 +8,6 @@ import { ElMessage } from 'element-plus'
import qs from 'qs'
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 { ElMessage } from 'element-plus'
const { interceptors, baseUrl } = config
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
const { interceptors } = config
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
const { requestInterceptors, responseInterceptors } = interceptors

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,13 @@ const tableColumns = reactive<TableColumn[]>([
},
{
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',

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',
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',
label: '菜单状态',

View File

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

View File

@ -9,6 +9,7 @@ import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog'
const { t } = useI18n()
@ -36,10 +37,6 @@ const tableColumns = reactive<TableColumn[]>([
field: 'roleName',
label: t('role.roleName')
},
{
field: 'role',
label: t('role.role')
},
{
field: 'status',
label: t('menu.status'),
@ -75,6 +72,9 @@ const tableColumns = reactive<TableColumn[]>([
<ElButton type="primary" onClick={() => action(row, 'edit')}>
{t('exampleDemo.edit')}
</ElButton>
<ElButton type="success" onClick={() => action(row, 'detail')}>
{t('exampleDemo.detail')}
</ElButton>
<ElButton type="danger">{t('exampleDemo.del')}</ElButton>
</>
)
@ -155,6 +155,7 @@ const save = async () => {
<Dialog v-model="dialogVisible" :title="dialogTitle">
<Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
<Detail v-else :current-row="currentRow" />
<template #footer>
<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'),
component: 'Input'
},
{
field: 'role',
label: t('role.role'),
component: 'Input'
},
{
field: 'status',
label: t('menu.status'),
@ -81,10 +76,10 @@ const formSchema = ref<FormSchema[]>([
</ElTree>
</div>
<div class="flex-1">
{unref(currentTreeData) && unref(currentTreeData)?.permission ? (
{unref(currentTreeData) && unref(currentTreeData)?.permissionList ? (
<ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}>
{unref(currentTreeData)?.permission.map((v: string) => {
return <ElCheckbox label={v} />
{unref(currentTreeData)?.permissionList.map((v: any) => {
return <ElCheckbox label={v.value}>{v.label}</ElCheckbox>
})}
</ElCheckboxGroup>
) : null}

View File

@ -11,6 +11,7 @@ import { Search } from '@/components/Search'
import Write from './components/Write.vue'
import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog'
import { getRoleListApi } from '@/api/role'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n()
@ -113,6 +114,22 @@ const crudSchemas = reactive<CrudSchema[]>([
label: t('userDemo.role'),
search: {
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>
<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-1">{{ t('userDemo.departmentList') }}</div>
<ElInput
@ -299,7 +316,16 @@ const save = async () => {
}"
:filter-node-method="filterNode"
@current-change="currentChange"
/>
>
<template #default="{ data }">
<div
:title="data.departmentName"
class="whitespace-nowrap overflow-ellipsis overflow-hidden"
>
{{ data.departmentName }}
</div>
</template>
</ElTree>
</ContentWrap>
<ContentWrap class="flex-[3] ml-20px">
<Search

View File

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

View File

@ -19,7 +19,8 @@ const getList = () => {
width,
height,
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({
// ...UnoCSS options
rules: [
[
/^overflow-ellipsis$/,
([], { rawSelector }) => {
const selector = e(rawSelector)
return `
${selector} {
text-overflow: ellipsis;
}
`
}
],
[
/^custom-hover$/,
([], { rawSelector }) => {