mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-05-01 14:49:00 +08:00
Compare commits
3 Commits
961ad6af7b
...
0141c0287e
Author | SHA1 | Date | |
---|---|---|---|
|
0141c0287e | ||
|
8f715925c7 | ||
|
763b5f1295 |
@ -1,7 +1,7 @@
|
|||||||
/**********************************
|
/**********************************
|
||||||
* @Author: Ronnie Zhang
|
* @Author: Ronnie Zhang
|
||||||
* @LastEditor: Ronnie Zhang
|
* @LastEditor: Ronnie Zhang
|
||||||
* @LastEditTime: 2023/12/05 21:28:47
|
* @LastEditTime: 2024/04/01 15:52:04
|
||||||
* @Email: zclzone@outlook.com
|
* @Email: zclzone@outlook.com
|
||||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
**********************************/
|
**********************************/
|
||||||
@ -11,7 +11,7 @@ import { request } from '@/utils'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
getMenuTree: () => request.get('/permission/menu/tree'),
|
getMenuTree: () => request.get('/permission/menu/tree'),
|
||||||
getButtonAndApi: (parentId) => request.get(`/permission/button-and-api/${parentId}`),
|
getButtons: ({ parentId }) => request.get(`/permission/button/${parentId}`),
|
||||||
getComponents: () => axios.get(`${import.meta.env.VITE_PUBLIC_PATH}components.json`),
|
getComponents: () => axios.get(`${import.meta.env.VITE_PUBLIC_PATH}components.json`),
|
||||||
addPermission: (data) => request.post('/permission', data),
|
addPermission: (data) => request.post('/permission', data),
|
||||||
savePermission: (id, data) => request.patch(`/permission/${id}`, data),
|
savePermission: (id, data) => request.patch(`/permission/${id}`, data),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!--------------------------------
|
<!--------------------------------
|
||||||
- @Author: Ronnie Zhang
|
- @Author: Ronnie Zhang
|
||||||
- @LastEditor: Ronnie Zhang
|
- @LastEditor: Ronnie Zhang
|
||||||
- @LastEditTime: 2023/12/05 21:28:59
|
- @LastEditTime: 2024/04/01 15:51:34
|
||||||
- @Email: zclzone@outlook.com
|
- @Email: zclzone@outlook.com
|
||||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
--------------------------------->
|
--------------------------------->
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!--------------------------------
|
<!--------------------------------
|
||||||
- @Author: Ronnie Zhang
|
- @Author: Ronnie Zhang
|
||||||
- @LastEditor: Ronnie Zhang
|
- @LastEditor: Ronnie Zhang
|
||||||
- @LastEditTime: 2023/12/12 09:03:43
|
- @LastEditTime: 2024/04/01 15:52:31
|
||||||
- @Email: zclzone@outlook.com
|
- @Email: zclzone@outlook.com
|
||||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
--------------------------------->
|
--------------------------------->
|
||||||
@ -20,6 +20,7 @@
|
|||||||
<n-tree-select
|
<n-tree-select
|
||||||
v-model:value="modalForm.parentId"
|
v-model:value="modalForm.parentId"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
|
:disabled="parentIdDisabled"
|
||||||
label-field="name"
|
label-field="name"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
placeholder="根菜单"
|
placeholder="根菜单"
|
||||||
@ -39,6 +40,7 @@
|
|||||||
<n-input v-model:value="modalForm.code" />
|
<n-input v-model:value="modalForm.code" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi
|
<n-form-item-gi
|
||||||
|
v-if="modalForm.type === 'MENU'"
|
||||||
:span="12"
|
:span="12"
|
||||||
path="path"
|
path="path"
|
||||||
:rule="{
|
:rule="{
|
||||||
@ -58,7 +60,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-input v-model:value="modalForm.path" />
|
<n-input v-model:value="modalForm.path" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" path="icon">
|
<n-form-item-gi v-if="modalForm.type === 'MENU'" :span="12" path="icon">
|
||||||
<template #label>
|
<template #label>
|
||||||
<QuestionLabel
|
<QuestionLabel
|
||||||
label="菜单图标"
|
label="菜单图标"
|
||||||
@ -67,7 +69,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-select v-model:value="modalForm.icon" :options="iconOptions" clearable filterable />
|
<n-select v-model:value="modalForm.icon" :options="iconOptions" clearable filterable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" path="layout">
|
<n-form-item-gi v-if="modalForm.type === 'MENU'" :span="12" path="layout">
|
||||||
<template #label>
|
<template #label>
|
||||||
<QuestionLabel
|
<QuestionLabel
|
||||||
label="layout"
|
label="layout"
|
||||||
@ -76,7 +78,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-select v-model:value="modalForm.layout" :options="layoutOptions" clearable />
|
<n-select v-model:value="modalForm.layout" :options="layoutOptions" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="24" path="component">
|
<n-form-item-gi v-if="modalForm.type === 'MENU'" :span="24" path="component">
|
||||||
<template #label>
|
<template #label>
|
||||||
<QuestionLabel
|
<QuestionLabel
|
||||||
label="组件路径"
|
label="组件路径"
|
||||||
@ -92,7 +94,7 @@
|
|||||||
/>
|
/>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
|
|
||||||
<n-form-item-gi :span="12" path="show">
|
<n-form-item-gi v-if="modalForm.type === 'MENU'" :span="12" path="show">
|
||||||
<template #label>
|
<template #label>
|
||||||
<QuestionLabel label="显示状态" content="控制是否在菜单栏显示,不影响路由注册" />
|
<QuestionLabel label="显示状态" content="控制是否在菜单栏显示,不影响路由注册" />
|
||||||
</template>
|
</template>
|
||||||
@ -113,7 +115,7 @@
|
|||||||
<template #unchecked>禁用</template>
|
<template #unchecked>禁用</template>
|
||||||
</n-switch>
|
</n-switch>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" path="enable">
|
<n-form-item-gi v-if="modalForm.type === 'MENU'" :span="12" path="enable">
|
||||||
<template #label>
|
<template #label>
|
||||||
<QuestionLabel
|
<QuestionLabel
|
||||||
label="KeepAlive"
|
label="KeepAlive"
|
||||||
@ -126,6 +128,7 @@
|
|||||||
</n-switch>
|
</n-switch>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi
|
<n-form-item-gi
|
||||||
|
v-if="modalForm.type === 'MENU'"
|
||||||
:span="12"
|
:span="12"
|
||||||
label="排序"
|
label="排序"
|
||||||
path="order"
|
path="order"
|
||||||
@ -186,10 +189,12 @@ const [modalFormRef, modalForm, validation] = useForm(defaultForm)
|
|||||||
const [modalRef, okLoading] = useModal()
|
const [modalRef, okLoading] = useModal()
|
||||||
|
|
||||||
const modalAction = ref('')
|
const modalAction = ref('')
|
||||||
|
const parentIdDisabled = ref(false)
|
||||||
function handleOpen(options = {}) {
|
function handleOpen(options = {}) {
|
||||||
const { action, row = {}, ...rest } = options
|
const { action, row = {}, ...rest } = options
|
||||||
modalAction.value = action
|
modalAction.value = action
|
||||||
modalForm.value = { ...row }
|
modalForm.value = { ...row }
|
||||||
|
parentIdDisabled.value = !!row.parentId
|
||||||
modalRef.value.open({ ...rest, onOk: onSave })
|
modalRef.value.open({ ...rest, onOk: onSave })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,12 +22,7 @@
|
|||||||
<template v-if="currentMenu">
|
<template v-if="currentMenu">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<h3 class="mb-12">{{ currentMenu.name }}</h3>
|
<h3 class="mb-12">{{ currentMenu.name }}</h3>
|
||||||
<n-button
|
<n-button size="small" type="primary" @click="handleEdit(currentMenu)">
|
||||||
:disabled="!currentMenu"
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
@click="handleEdit(currentMenu)"
|
|
||||||
>
|
|
||||||
<i class="i-material-symbols:edit-outline mr-4 text-14" />
|
<i class="i-material-symbols:edit-outline mr-4 text-14" />
|
||||||
编辑
|
编辑
|
||||||
</n-button>
|
</n-button>
|
||||||
@ -64,6 +59,22 @@
|
|||||||
{{ currentMenu.order ?? '--' }}
|
{{ currentMenu.order ?? '--' }}
|
||||||
</n-descriptions-item>
|
</n-descriptions-item>
|
||||||
</n-descriptions>
|
</n-descriptions>
|
||||||
|
|
||||||
|
<div class="mt-32 flex justify-between">
|
||||||
|
<h3 class="mb-12">按钮</h3>
|
||||||
|
<n-button size="small" type="primary" @click="handleAddBtn">
|
||||||
|
<i class="i-fe:plus mr-4 text-14" />
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MeCrud
|
||||||
|
ref="$table"
|
||||||
|
:columns="btnsColumns"
|
||||||
|
:scroll-x="-1"
|
||||||
|
:get-data="api.getButtons"
|
||||||
|
:query-items="{ parentId: currentMenu.id }"
|
||||||
|
></MeCrud>
|
||||||
</template>
|
</template>
|
||||||
<n-empty v-else class="h-450 f-c-c" size="large" description="请选择菜单查看详情" />
|
<n-empty v-else class="h-450 f-c-c" size="large" description="请选择菜单查看详情" />
|
||||||
</div>
|
</div>
|
||||||
@ -73,13 +84,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { NButton, NSwitch } from 'naive-ui'
|
||||||
import MenuTree from './components/MenuTree.vue'
|
import MenuTree from './components/MenuTree.vue'
|
||||||
import ResAddOrEdit from './components/ResAddOrEdit.vue'
|
import ResAddOrEdit from './components/ResAddOrEdit.vue'
|
||||||
|
import { MeCrud } from '@/components'
|
||||||
import api from './api'
|
import api from './api'
|
||||||
|
|
||||||
const treeData = ref([])
|
const treeData = ref([])
|
||||||
const treeLoading = ref(false)
|
const treeLoading = ref(false)
|
||||||
async function initData(data) {
|
async function initData(data) {
|
||||||
|
if (data?.type === 'BUTTON') {
|
||||||
|
$table.value.handleSearch()
|
||||||
|
return
|
||||||
|
}
|
||||||
treeLoading.value = true
|
treeLoading.value = true
|
||||||
const res = await api.getMenuTree()
|
const res = await api.getMenuTree()
|
||||||
treeData.value = res?.data || []
|
treeData.value = res?.data || []
|
||||||
@ -100,4 +117,127 @@ function handleEdit(item = {}) {
|
|||||||
okText: '保存',
|
okText: '保存',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const btnsColumns = [
|
||||||
|
{ title: '名称', key: 'name' },
|
||||||
|
{ title: '编码', key: 'code' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'enable',
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NSwitch,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
rubberBand: false,
|
||||||
|
value: row.enable,
|
||||||
|
loading: !!row.enableLoading,
|
||||||
|
onUpdateValue: () => handleEnable(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
checked: () => '启用',
|
||||||
|
unchecked: () => '停用',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 320,
|
||||||
|
align: 'right',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
style: 'margin-left: 12px;',
|
||||||
|
onClick: () => handleEditBtn(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '编辑',
|
||||||
|
icon: () => h('i', { class: 'i-material-symbols:edit-outline text-14' }),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
style: 'margin-left: 12px;',
|
||||||
|
onClick: () => handleDeleteBtn(row.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '删除',
|
||||||
|
icon: () => h('i', { class: 'i-material-symbols:delete-outline text-14' }),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const $table = ref(null)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentMenu.value,
|
||||||
|
async (v) => {
|
||||||
|
await nextTick()
|
||||||
|
if (v) $table.value.handleSearch()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleAddBtn() {
|
||||||
|
modalRef.value?.handleOpen({
|
||||||
|
action: 'add',
|
||||||
|
title: '新增按钮',
|
||||||
|
row: { type: 'BUTTON', parentId: currentMenu.value.id },
|
||||||
|
okText: '保存',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditBtn(row) {
|
||||||
|
modalRef.value?.handleOpen({
|
||||||
|
action: 'edit',
|
||||||
|
title: '编辑按钮 - ' + row.name,
|
||||||
|
row,
|
||||||
|
okText: '保存',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteBtn(id) {
|
||||||
|
const d = $dialog.warning({
|
||||||
|
content: '确定删除?',
|
||||||
|
title: '提示',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
async onPositiveClick() {
|
||||||
|
try {
|
||||||
|
d.loading = true
|
||||||
|
await api.deletePermission(id)
|
||||||
|
$message.success('删除成功')
|
||||||
|
$table.value.handleSearch()
|
||||||
|
d.loading = false
|
||||||
|
} catch (error) {
|
||||||
|
d.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEnable(item) {
|
||||||
|
try {
|
||||||
|
item.enableLoading = true
|
||||||
|
await api.savePermission(item.id, {
|
||||||
|
enable: !item.enable,
|
||||||
|
})
|
||||||
|
$message.success('操作成功')
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
item.enableLoading = false
|
||||||
|
} catch (error) {
|
||||||
|
item.enableLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
<!--------------------------------
|
|
||||||
- @Author: Ronnie Zhang
|
|
||||||
- @LastEditor: Ronnie Zhang
|
|
||||||
- @LastEditTime: 2023/12/05 21:29:32
|
|
||||||
- @Email: zclzone@outlook.com
|
|
||||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
|
||||||
--------------------------------->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-tree
|
|
||||||
:key-field="keyField"
|
|
||||||
:label-field="labelField"
|
|
||||||
:selectable="false"
|
|
||||||
default-expand-all
|
|
||||||
checkable
|
|
||||||
check-on-click
|
|
||||||
cascade
|
|
||||||
:data="treeData"
|
|
||||||
:checked-keys="checkedKeys"
|
|
||||||
:on-update:checked-keys="(keys) => (checkedKeys = keys)"
|
|
||||||
:on-update:indeterminate-keys="(keys) => (halfCheckedKeys = keys)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const props = defineProps({
|
|
||||||
treeData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
labelField: {
|
|
||||||
type: String,
|
|
||||||
default: 'label',
|
|
||||||
},
|
|
||||||
keyField: {
|
|
||||||
type: String,
|
|
||||||
default: 'value',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const emit = defineEmits(['update:value'])
|
|
||||||
|
|
||||||
const halfCheckedKeys = ref([])
|
|
||||||
const checkedKeys = ref([])
|
|
||||||
watch([halfCheckedKeys, checkedKeys], ([v1, v2]) => {
|
|
||||||
emit('update:value', Array.from(new Set([...v1, ...v2])))
|
|
||||||
})
|
|
||||||
onMounted(() => {
|
|
||||||
halfCheckedKeys.value = getHalfCheckedValues(props.value, props.treeData)
|
|
||||||
checkedKeys.value = props.value.filter((item) => !halfCheckedKeys.value.includes(item))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取半选状态的值
|
|
||||||
function getHalfCheckedValues(selectedValues, treeData, halfCheckedValues = []) {
|
|
||||||
function isHalfChecked(node) {
|
|
||||||
// 如果存在子节点没有选中或者子节点是半选状态
|
|
||||||
return node.children.some(
|
|
||||||
(item) =>
|
|
||||||
!selectedValues.includes(item[props.keyField]) ||
|
|
||||||
halfCheckedValues.includes(item[props.keyField])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasGrandson(node) {
|
|
||||||
return node.children.some((item) => !!item.children?.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of treeData) {
|
|
||||||
if (!item.children?.length) continue
|
|
||||||
if (hasGrandson(item)) {
|
|
||||||
// 根据孙节点判断子节点是否是半选
|
|
||||||
getHalfCheckedValues(selectedValues, item.children, halfCheckedValues)
|
|
||||||
isHalfChecked(item) && halfCheckedValues.push(item[props.keyField])
|
|
||||||
} else {
|
|
||||||
isHalfChecked(item) && halfCheckedValues.push(item[props.keyField])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return halfCheckedValues
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,7 +1,7 @@
|
|||||||
<!--------------------------------
|
<!--------------------------------
|
||||||
- @Author: Ronnie Zhang
|
- @Author: Ronnie Zhang
|
||||||
- @LastEditor: Ronnie Zhang
|
- @LastEditor: Ronnie Zhang
|
||||||
- @LastEditTime: 2023/12/05 21:29:38
|
- @LastEditTime: 2024/04/01 15:52:40
|
||||||
- @Email: zclzone@outlook.com
|
- @Email: zclzone@outlook.com
|
||||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
--------------------------------->
|
--------------------------------->
|
||||||
@ -67,11 +67,16 @@
|
|||||||
<n-input v-model:value="modalForm.code" :disabled="modalAction !== 'add'" />
|
<n-input v-model:value="modalForm.code" :disabled="modalAction !== 'add'" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="权限" path="permissionIds">
|
<n-form-item label="权限" path="permissionIds">
|
||||||
<CascadeTree
|
<n-tree
|
||||||
v-model:value="modalForm.permissionIds"
|
|
||||||
:tree-data="permissionTree"
|
|
||||||
label-field="name"
|
|
||||||
key-field="id"
|
key-field="id"
|
||||||
|
label-field="name"
|
||||||
|
:selectable="false"
|
||||||
|
:data="permissionTree"
|
||||||
|
:checked-keys="modalForm.permissionIds"
|
||||||
|
:on-update:checked-keys="(keys) => (modalForm.permissionIds = keys)"
|
||||||
|
default-expand-all
|
||||||
|
checkable
|
||||||
|
check-on-click
|
||||||
class="cus-scroll max-h-200 w-full"
|
class="cus-scroll max-h-200 w-full"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@ -91,7 +96,6 @@ import { NButton, NSwitch } from 'naive-ui'
|
|||||||
import { MeCrud, MeQueryItem, MeModal } from '@/components'
|
import { MeCrud, MeQueryItem, MeModal } from '@/components'
|
||||||
import { useCrud } from '@/composables'
|
import { useCrud } from '@/composables'
|
||||||
import api from './api'
|
import api from './api'
|
||||||
import CascadeTree from './components/CascadeTree.vue'
|
|
||||||
|
|
||||||
defineOptions({ name: 'RoleMgt' })
|
defineOptions({ name: 'RoleMgt' })
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user