1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2025-05-01 14:49:00 +08:00

Compare commits

..

3 Commits

Author SHA1 Message Date
zclzone
0141c0287e Merge branch '2.x' of https://gitee.com/isme-admin/vue-naive-admin into 2.x 2024-04-01 16:00:52 +08:00
zclzone
8f715925c7 feat: 资源管理新增支持按钮权限 2024-04-01 16:00:44 +08:00
zclzone
763b5f1295 refactor: 取消级联权限树 2024-04-01 15:55:23 +08:00
6 changed files with 170 additions and 105 deletions

View File

@ -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),

View File

@ -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
---------------------------------> --------------------------------->

View File

@ -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 })
} }

View File

@ -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>

View File

@ -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>

View File

@ -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' })