You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
13 KiB
461 lines
13 KiB
<template>
|
|
<el-dialog
|
|
v-model="showDialog"
|
|
:title="popTitle"
|
|
width="500px"
|
|
:destroy-on-close="true"
|
|
>
|
|
<el-form
|
|
:model="formData"
|
|
label-width="90px"
|
|
ref="formRef"
|
|
:rules="formRules"
|
|
class="page-form"
|
|
v-loading="loading"
|
|
>
|
|
<el-form-item :label="t('roleName')" prop="role_name">
|
|
<el-input
|
|
v-model.trim="formData.role_name"
|
|
:placeholder="t('roleNamePlaceholder')"
|
|
clearable
|
|
:disabled="formData.uid"
|
|
class="input-width"
|
|
maxlength="10"
|
|
:show-word-limit="true"
|
|
/>
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="类型">
|
|
<el-radio-group v-model="formData.role_key">
|
|
<span v-for="(item,index) in roleKeyList" class="mr-2">
|
|
<el-radio :label="item.value">{{ item.name }}</el-radio>
|
|
</span>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item :label="t('status')">
|
|
<el-radio-group v-model="formData.status">
|
|
<el-radio :label="1">{{ t('startUsing') }}</el-radio>
|
|
<el-radio :label="0">{{ t('statusDeactivate') }}</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="部门" >
|
|
<el-select
|
|
class="input-width"
|
|
v-model="formData.dept_id"
|
|
clearable
|
|
placeholder="请选择部门"
|
|
>
|
|
<el-option label="请选择" value=""></el-option>
|
|
<el-option
|
|
v-for="(item, index) in parentDepartmentIdList"
|
|
:key="index"
|
|
:label="item['department_name']"
|
|
:value="item['id']"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
|
|
<el-form-item :label="t('permission')" prop="rules">
|
|
<el-tabs v-model="activePermissionTab" class="w-full">
|
|
<!-- PC端权限 -->
|
|
<el-tab-pane label="PC端权限" name="pc">
|
|
<div class="flex items-center justify-between w-11/12">
|
|
<div>
|
|
<el-checkbox v-model="selectAllPC" :label="t('selectAll')"/>
|
|
<el-checkbox v-model="checkStrictlyPC" :label="t('checkStrictly')"/>
|
|
</div>
|
|
<el-button link type="primary" @click="menuActionPC()">{{
|
|
t('foldText')
|
|
}}
|
|
</el-button>
|
|
</div>
|
|
<el-scrollbar height="35vh" class="w-full">
|
|
<el-tree
|
|
:data="menus"
|
|
:props="{ label: 'menu_name' }"
|
|
:default-checked-keys="formData.rules"
|
|
:check-strictly="checkStrictlyPC"
|
|
show-checkbox
|
|
default-expand-all
|
|
@check-change="handleCheckChangePC"
|
|
node-key="menu_key"
|
|
ref="treeRefPC"
|
|
/>
|
|
</el-scrollbar>
|
|
</el-tab-pane>
|
|
|
|
<!-- 移动端权限 -->
|
|
<el-tab-pane label="移动端权限" name="mobile">
|
|
<div class="flex items-center justify-between w-11/12">
|
|
<div>
|
|
<el-checkbox v-model="selectAllMobile" :label="t('selectAll')"/>
|
|
<el-checkbox v-model="checkStrictlyMobile" :label="t('checkStrictly')"/>
|
|
</div>
|
|
<el-button link type="primary" @click="menuActionMobile()">{{
|
|
t('foldText')
|
|
}}
|
|
</el-button>
|
|
</div>
|
|
<el-scrollbar height="35vh" class="w-full">
|
|
<el-tree
|
|
:data="mobileMenus"
|
|
:props="{ label: 'menu_name' }"
|
|
:default-checked-keys="formData.mobile_rules"
|
|
:check-strictly="checkStrictlyMobile"
|
|
show-checkbox
|
|
default-expand-all
|
|
@check-change="handleCheckChangeMobile"
|
|
node-key="menu_key"
|
|
ref="treeRefMobile"
|
|
/>
|
|
</el-scrollbar>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
|
<el-button
|
|
type="primary"
|
|
:loading="loading"
|
|
@click="confirm(formRef)"
|
|
>{{ t('confirm') }}</el-button
|
|
>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script lang="ts" setup async>
|
|
import {ref, reactive, computed, watch, toRaw} from 'vue'
|
|
import {t} from '@/lang'
|
|
import {
|
|
getWithDepartmentsList} from '@/app/api/departments'
|
|
import type {FormInstance} from 'element-plus'
|
|
import {addRole, editRole, getRoleInfo} from '@/app/api/sys'
|
|
import {getAuthMenus, getUniappMenus, getRoleUniappMenus, setRoleUniappMenus} from '@/app/api/auth'
|
|
import {useDictionary} from '@/app/api/dict'
|
|
import {debounce} from '@/utils/common'
|
|
|
|
const showDialog = ref(false)
|
|
const loading = ref(false)
|
|
const isOpen = ref(true)
|
|
|
|
let popTitle: string = ''
|
|
|
|
// 获取权限数据
|
|
const menus = ref<Record<string, any>[]>([])
|
|
const mobileMenus = ref<Record<string, any>[]>([])
|
|
|
|
// 获取PC端菜单
|
|
getAuthMenus({is_button: 0}).then((res) => {
|
|
menus.value = res.data
|
|
})
|
|
|
|
// 获取移动端菜单(UniApp菜单权限)
|
|
const getMobileMenus = async () => {
|
|
try {
|
|
const res = await getUniappMenus()
|
|
if (res.data) {
|
|
mobileMenus.value = res.data.map(item => ({
|
|
menu_key: item.menu_key,
|
|
menu_name: item.menu_name,
|
|
id: item.id,
|
|
children: []
|
|
}))
|
|
}
|
|
} catch (error) {
|
|
console.error('获取移动端菜单失败:', error)
|
|
// 如果获取失败,设置一个空数组
|
|
mobileMenus.value = []
|
|
}
|
|
}
|
|
getMobileMenus()
|
|
|
|
// 当前活动的权限标签页
|
|
const activePermissionTab = ref('pc')
|
|
|
|
// PC端权限相关
|
|
const selectAllPC = ref(false)
|
|
const checkStrictlyPC = ref(false)
|
|
const treeRefPC: Record<string, any> | null = ref(null)
|
|
watch(selectAllPC, () => {
|
|
if (treeRefPC.value) {
|
|
if (selectAllPC.value) {
|
|
treeRefPC.value.setCheckedNodes(toRaw(menus.value))
|
|
} else {
|
|
treeRefPC.value.setCheckedNodes([])
|
|
}
|
|
}
|
|
})
|
|
|
|
// 移动端权限相关
|
|
const selectAllMobile = ref(false)
|
|
const checkStrictlyMobile = ref(false)
|
|
const treeRefMobile: Record<string, any> | null = ref(null)
|
|
watch(selectAllMobile, () => {
|
|
if (treeRefMobile.value) {
|
|
if (selectAllMobile.value) {
|
|
treeRefMobile.value.setCheckedNodes(toRaw(mobileMenus.value))
|
|
} else {
|
|
treeRefMobile.value.setCheckedNodes([])
|
|
}
|
|
}
|
|
})
|
|
|
|
const parentDepartmentIdList = ref([] as any[])
|
|
const setParentDepartmentIdList = async () => {
|
|
parentDepartmentIdList.value = await (await getWithDepartmentsList({})).data
|
|
}
|
|
setParentDepartmentIdList()
|
|
|
|
const roleKeyList = ref([])
|
|
const getRolekeyDictList = async () => {
|
|
roleKeyList.value = await (
|
|
await useDictionary('role_key')
|
|
).data.dictionary
|
|
}
|
|
getRolekeyDictList()
|
|
|
|
// PC端权限处理函数
|
|
const handleCheckChangePC = debounce((e) => {
|
|
if (treeRefPC.value) {
|
|
formData.rules = treeRefPC.value.getCheckedKeys()
|
|
}
|
|
})
|
|
|
|
const menuActionPC = () => {
|
|
if (isOpen.value) {
|
|
collapseAllPC(menus.value)
|
|
isOpen.value = false
|
|
} else {
|
|
unFoldAllPC(menus.value)
|
|
isOpen.value = true
|
|
}
|
|
}
|
|
|
|
// 移动端权限处理函数
|
|
const handleCheckChangeMobile = debounce((e) => {
|
|
if (treeRefMobile.value) {
|
|
formData.mobile_rules = treeRefMobile.value.getCheckedKeys()
|
|
}
|
|
})
|
|
|
|
const menuActionMobile = () => {
|
|
if (isOpen.value) {
|
|
collapseAllMobile(mobileMenus.value)
|
|
isOpen.value = false
|
|
} else {
|
|
unFoldAllMobile(mobileMenus.value)
|
|
isOpen.value = true
|
|
}
|
|
}
|
|
|
|
// PC端全部展开
|
|
const unFoldAllPC = (data: any) => {
|
|
if (treeRefPC.value && treeRefPC.value.store) {
|
|
Object.keys(data).forEach((key: string | any) => {
|
|
if (treeRefPC.value.store.nodesMap[data[key].menu_key]) {
|
|
treeRefPC.value.store.nodesMap[data[key].menu_key].expanded = true
|
|
}
|
|
if (data[key].children && data[key].children.length > 0)
|
|
unFoldAllPC(data[key].children)
|
|
})
|
|
}
|
|
}
|
|
// PC端全部折叠
|
|
const collapseAllPC = (data: any) => {
|
|
if (treeRefPC.value && treeRefPC.value.store) {
|
|
Object.keys(data).forEach((key: string | any) => {
|
|
if (treeRefPC.value.store.nodesMap[data[key].menu_key]) {
|
|
treeRefPC.value.store.nodesMap[data[key].menu_key].expanded = false
|
|
}
|
|
if (data[key].children && data[key].children.length > 0)
|
|
collapseAllPC(data[key].children)
|
|
})
|
|
}
|
|
}
|
|
|
|
// 移动端全部展开
|
|
const unFoldAllMobile = (data: any) => {
|
|
if (treeRefMobile.value && treeRefMobile.value.store) {
|
|
Object.keys(data).forEach((key: string | any) => {
|
|
if (treeRefMobile.value.store.nodesMap[data[key].menu_key]) {
|
|
treeRefMobile.value.store.nodesMap[data[key].menu_key].expanded = true
|
|
}
|
|
if (data[key].children && data[key].children.length > 0)
|
|
unFoldAllMobile(data[key].children)
|
|
})
|
|
}
|
|
}
|
|
// 移动端全部折叠
|
|
const collapseAllMobile = (data: any) => {
|
|
if (treeRefMobile.value && treeRefMobile.value.store) {
|
|
Object.keys(data).forEach((key: string | any) => {
|
|
if (treeRefMobile.value.store.nodesMap[data[key].menu_key]) {
|
|
treeRefMobile.value.store.nodesMap[data[key].menu_key].expanded = false
|
|
}
|
|
if (data[key].children && data[key].children.length > 0)
|
|
collapseAllMobile(data[key].children)
|
|
})
|
|
}
|
|
}
|
|
/**
|
|
* 表单数据
|
|
*/
|
|
const initialFormData = {
|
|
role_id: 0,
|
|
role_name: '',
|
|
status: 1,
|
|
role_key: '',
|
|
dept_id:'',
|
|
rules: [],
|
|
mobile_rules: [], // 新增移动端权限字段
|
|
}
|
|
const formData: Record<string, any> = reactive({...initialFormData})
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
// 表单验证规则
|
|
const formRules = computed(() => {
|
|
return {
|
|
role_name: [
|
|
{required: true, message: t('roleNamePlaceholder'), trigger: 'blur'},
|
|
],
|
|
rules: [
|
|
{
|
|
validator: (rule: any, value: string, callback: any) => {
|
|
if (!value.length) callback(new Error(t('rulesPlaceholder')))
|
|
else callback()
|
|
},
|
|
trigger: 'blur',
|
|
},
|
|
],
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['complete'])
|
|
|
|
/**
|
|
* 确认
|
|
* @param formEl
|
|
*/
|
|
const confirm = async (formEl: FormInstance | undefined) => {
|
|
if (loading.value || !formEl) return
|
|
const save = formData.role_id ? editRole : addRole
|
|
|
|
await formEl.validate(async (valid) => {
|
|
if (valid) {
|
|
loading.value = true
|
|
|
|
const data = Object.assign({}, formData)
|
|
|
|
// PC端权限处理
|
|
if (treeRefPC.value) {
|
|
data.rules = data.rules.concat(treeRefPC.value.getHalfCheckedKeys())
|
|
}
|
|
|
|
// 移动端权限处理
|
|
if (treeRefMobile.value) {
|
|
data.mobile_rules = data.mobile_rules.concat(treeRefMobile.value.getHalfCheckedKeys())
|
|
|
|
// 调用UniApp权限设置API
|
|
if (data.role_id && data.mobile_rules.length >= 0) {
|
|
try {
|
|
await setRoleUniappMenus(data.role_id, data.mobile_rules)
|
|
} catch (error) {
|
|
console.error('设置移动端权限失败:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
save(data)
|
|
.then((res) => {
|
|
loading.value = false
|
|
showDialog.value = false
|
|
|
|
emit('complete')
|
|
})
|
|
.catch(() => {
|
|
loading.value = false
|
|
// showDialog.value = false
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
const setFormData = async (row: any = null) => {
|
|
loading.value = true
|
|
selectAllPC.value = false
|
|
selectAllMobile.value = false
|
|
Object.assign(formData, initialFormData)
|
|
popTitle = t('addRole')
|
|
if (row) {
|
|
popTitle = t('updateRole')
|
|
const data = await (await getRoleInfo(row.role_id)).data
|
|
|
|
// 获取移动端权限数据
|
|
let mobileRulesData = []
|
|
try {
|
|
const mobileRes = await getRoleUniappMenus(row.role_id)
|
|
if (mobileRes.data) {
|
|
mobileRulesData = mobileRes.data.map(item => item.menu_key)
|
|
}
|
|
} catch (error) {
|
|
console.error('获取移动端权限失败:', error)
|
|
}
|
|
|
|
Object.keys(formData).forEach((key: string) => {
|
|
if (data[key] != undefined) {
|
|
if (key == 'rules') {
|
|
const arr = data.rules
|
|
const newArr: any = []
|
|
|
|
Object.keys(data.rules).forEach((i) => {
|
|
checked(data.rules[i], menus.value, newArr)
|
|
})
|
|
formData[key] = newArr
|
|
} else if (key == 'mobile_rules') {
|
|
formData[key] = mobileRulesData
|
|
} else {
|
|
formData[key] = data[key]
|
|
}
|
|
}
|
|
})
|
|
|
|
// 设置移动端权限数据
|
|
formData.mobile_rules = mobileRulesData
|
|
}
|
|
loading.value = false
|
|
}
|
|
|
|
function checked(menuKey: string, data: any, newArr: any) {
|
|
Object.keys(data).forEach((key: string) => {
|
|
const item = data[key]
|
|
if (item.menu_key == menuKey) {
|
|
if (!item.children || item.children.length == 0) {
|
|
newArr.push(item.menu_key)
|
|
}
|
|
} else {
|
|
if (item.children && item.children.length > 0) {
|
|
checked(menuKey, item.children, newArr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
defineExpose({
|
|
showDialog,
|
|
setFormData,
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style>
|
|
|