|
| 1 | +<template> |
| 2 | + <a-modal |
| 3 | + v-model:visible="visible" |
| 4 | + :title="title" |
| 5 | + :mask-closable="false" |
| 6 | + :esc-to-close="true" |
| 7 | + :width="width >= 600 ? 600 : '100%'" |
| 8 | + @close="reset" |
| 9 | + > |
| 10 | + <a-steps :current="current" class="mb-15" @change="onChangeCurrent"> |
| 11 | + <a-step>创建角色</a-step> |
| 12 | + <a-step>功能权限</a-step> |
| 13 | + <a-step>数据权限</a-step> |
| 14 | + </a-steps> |
| 15 | + |
| 16 | + <a-form ref="formRef" :model="form" :rules="rules" size="large" auto-label-width> |
| 17 | + <fieldset v-show="current === 1"> |
| 18 | + <a-form-item label="名称" field="name"> |
| 19 | + <a-input v-model.trim="form.name" placeholder="请输入名称" /> |
| 20 | + </a-form-item> |
| 21 | + <a-form-item label="编码" field="code"> |
| 22 | + <a-input v-model.trim="form.code" placeholder="请输入编码" :disabled="isUpdate" /> |
| 23 | + </a-form-item> |
| 24 | + <a-form-item label="排序" field="sort"> |
| 25 | + <a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" /> |
| 26 | + </a-form-item> |
| 27 | + <a-form-item label="描述" field="description"> |
| 28 | + <a-textarea |
| 29 | + v-model.trim="form.description" |
| 30 | + placeholder="请输入描述" |
| 31 | + show-word-limit |
| 32 | + :max-length="200" |
| 33 | + :auto-size="{ minRows: 3, maxRows: 5 }" |
| 34 | + /> |
| 35 | + </a-form-item> |
| 36 | + </fieldset> |
| 37 | + <fieldset v-show="current === 2"> |
| 38 | + <a-form-item hide-label :disabled="form.isSystem" class="w-full"> |
| 39 | + <a-space> |
| 40 | + <a-checkbox v-model="isMenuExpanded" @change="onExpanded('menu')">展开/折叠</a-checkbox> |
| 41 | + <a-checkbox v-model="isMenuCheckAll" @change="onCheckAll('menu')">全选/全不选</a-checkbox> |
| 42 | + <a-checkbox v-model="isMenuCheckStrictly">父子联动</a-checkbox> |
| 43 | + </a-space> |
| 44 | + <template #extra> |
| 45 | + <a-tree |
| 46 | + ref="menuTreeRef" |
| 47 | + v-model:checked-keys="form.menuIds" |
| 48 | + class="w-full" |
| 49 | + :data="menuList" |
| 50 | + :default-expand-all="isMenuExpanded" |
| 51 | + :check-strictly="!isMenuCheckStrictly" |
| 52 | + :virtual-list-props="{ height: 400 }" |
| 53 | + checkable |
| 54 | + /> |
| 55 | + </template> |
| 56 | + </a-form-item> |
| 57 | + </fieldset> |
| 58 | + <fieldset v-show="current === 3"> |
| 59 | + <a-form-item hide-label field="dataScope"> |
| 60 | + <a-select |
| 61 | + v-model.trim="form.dataScope" |
| 62 | + :options="data_scope_enum" |
| 63 | + placeholder="请选择数据权限" |
| 64 | + :disabled="form.isSystem" |
| 65 | + /> |
| 66 | + </a-form-item> |
| 67 | + <a-form-item v-if="form.dataScope === 5" hide-label :disabled="form.isSystem"> |
| 68 | + <a-space> |
| 69 | + <a-checkbox v-model="isDeptExpanded" @change="onExpanded('dept')">展开/折叠</a-checkbox> |
| 70 | + <a-checkbox v-model="isDeptCheckAll" @change="onCheckAll('dept')">全选/全不选</a-checkbox> |
| 71 | + <a-checkbox v-model="isDeptCheckStrictly">父子联动</a-checkbox> |
| 72 | + </a-space> |
| 73 | + <template #extra> |
| 74 | + <a-tree |
| 75 | + ref="deptTreeRef" |
| 76 | + v-model:checked-keys="form.deptIds" |
| 77 | + class="w-full" |
| 78 | + :data="deptList" |
| 79 | + :default-expand-all="isDeptExpanded" |
| 80 | + :check-strictly="!isDeptCheckStrictly" |
| 81 | + :virtual-list-props="{ height: 350 }" |
| 82 | + checkable |
| 83 | + /> |
| 84 | + </template> |
| 85 | + </a-form-item> |
| 86 | + </fieldset> |
| 87 | + </a-form> |
| 88 | + <template #footer> |
| 89 | + <a-space size="large"> |
| 90 | + <a-button :disabled="current === 1" type="secondary" @click="onPrev"> |
| 91 | + <IconLeft /> |
| 92 | + 上一步 |
| 93 | + </a-button> |
| 94 | + <a-button v-if="current !== 3" :disabled="current === 3" type="primary" @click="onNext"> |
| 95 | + 下一步 |
| 96 | + <IconRight /> |
| 97 | + </a-button> |
| 98 | + <a-button v-if="current === 3" type="primary" @click="onClickOk">确定</a-button> |
| 99 | + </a-space> |
| 100 | + </template> |
| 101 | + </a-modal> |
| 102 | +</template> |
| 103 | + |
| 104 | +<script setup lang="ts"> |
| 105 | +import { type FormInstance, Message, type TreeNodeData } from '@arco-design/web-vue' |
| 106 | +import { useWindowSize } from '@vueuse/core' |
| 107 | +import { addRole, getRole, updateRole } from '@/apis' |
| 108 | +import { useForm } from '@/hooks' |
| 109 | +import { useDept, useDict, useMenu } from '@/hooks/app' |
| 110 | +
|
| 111 | +const emit = defineEmits<{ |
| 112 | + (e: 'save-success'): void |
| 113 | +}>() |
| 114 | +const { width } = useWindowSize() |
| 115 | +const { data_scope_enum } = useDict('data_scope_enum') |
| 116 | +const { deptList, getDeptList } = useDept() |
| 117 | +const { menuList, getMenuList } = useMenu() |
| 118 | +const current = ref<number>(1) |
| 119 | +const dataId = ref('') |
| 120 | +const isUpdate = computed(() => !!dataId.value) |
| 121 | +const title = computed(() => (isUpdate.value ? '修改角色' : '新增角色')) |
| 122 | +const formRef = ref<FormInstance>() |
| 123 | +
|
| 124 | +const rules: FormInstance['rules'] = { |
| 125 | + name: [{ required: true, message: '请输入名称' }], |
| 126 | + code: [{ required: true, message: '请输入编码' }], |
| 127 | + dataScope: [{ required: true, message: '请选择数据权限' }] |
| 128 | +} |
| 129 | +
|
| 130 | +const { form, resetForm } = useForm({ |
| 131 | + sort: 999, |
| 132 | + dataScope: 4 |
| 133 | +}) |
| 134 | +
|
| 135 | +const menuTreeRef = ref() |
| 136 | +const deptTreeRef = ref() |
| 137 | +const isMenuExpanded = ref(false) |
| 138 | +const isDeptExpanded = ref(true) |
| 139 | +const isMenuCheckAll = ref(false) |
| 140 | +const isDeptCheckAll = ref(false) |
| 141 | +const isMenuCheckStrictly = ref(true) |
| 142 | +const isDeptCheckStrictly = ref(true) |
| 143 | +// 重置 |
| 144 | +const reset = () => { |
| 145 | + isMenuExpanded.value = false |
| 146 | + isMenuCheckAll.value = false |
| 147 | + isDeptExpanded.value = true |
| 148 | + isDeptCheckAll.value = false |
| 149 | + menuTreeRef.value?.expandAll(isMenuExpanded.value) |
| 150 | + deptTreeRef.value?.expandAll(isDeptExpanded.value) |
| 151 | + formRef.value?.resetFields() |
| 152 | + current.value = 1 |
| 153 | + resetForm() |
| 154 | +} |
| 155 | +
|
| 156 | +const visible = ref(false) |
| 157 | +// 新增 |
| 158 | +const onAdd = () => { |
| 159 | + if (!menuList.value.length) { |
| 160 | + getMenuList() |
| 161 | + } |
| 162 | + reset() |
| 163 | + isMenuCheckStrictly.value = true |
| 164 | + isDeptCheckStrictly.value = true |
| 165 | + dataId.value = '' |
| 166 | + visible.value = true |
| 167 | + if (!deptList.value.length) { |
| 168 | + getDeptList() |
| 169 | + } |
| 170 | +} |
| 171 | +// 上一步 |
| 172 | +const onPrev = () => { |
| 173 | + current.value = Math.max(1, current.value - 1) |
| 174 | +} |
| 175 | +// 下一步 |
| 176 | +const onNext = async () => { |
| 177 | + try { |
| 178 | + if (current.value === 1) { |
| 179 | + const isInvalid = await formRef.value?.validateField(['name', 'code', 'sort', 'description']) |
| 180 | + if (isInvalid) return |
| 181 | + } |
| 182 | + current.value = Math.min(3, current.value + 1) |
| 183 | + } catch (error) { |
| 184 | + console.error(error) |
| 185 | + } |
| 186 | +} |
| 187 | +// 当前页 |
| 188 | +const onChangeCurrent = (page: number) => { |
| 189 | + current.value = page |
| 190 | +} |
| 191 | +// 修改 |
| 192 | +const onUpdate = async (id: string) => { |
| 193 | + if (!menuList.value.length) { |
| 194 | + await getMenuList() |
| 195 | + } |
| 196 | + if (!deptList.value.length) { |
| 197 | + await getDeptList() |
| 198 | + } |
| 199 | + reset() |
| 200 | + isMenuCheckStrictly.value = false |
| 201 | + isDeptCheckStrictly.value = false |
| 202 | + dataId.value = id |
| 203 | + const res = await getRole(id) |
| 204 | + Object.assign(form, res.data) |
| 205 | + visible.value = true |
| 206 | +} |
| 207 | +
|
| 208 | +// 获取所有选中的菜单 |
| 209 | +const getMenuAllCheckedKeys = () => { |
| 210 | + // 获取目前被选中的菜单 |
| 211 | + const checkedNodes = menuTreeRef.value?.getCheckedNodes() |
| 212 | + const checkedKeys = checkedNodes.map((item: TreeNodeData) => item.key) |
| 213 | + // 获取半选中的菜单 |
| 214 | + const halfCheckedNodes = menuTreeRef.value?.getHalfCheckedNodes() |
| 215 | + const halfCheckedKeys = halfCheckedNodes.map((item: TreeNodeData) => item.key) |
| 216 | + checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys) |
| 217 | + return checkedKeys |
| 218 | +} |
| 219 | +
|
| 220 | +// 获取所有选中的部门 |
| 221 | +const getDeptAllCheckedKeys = () => { |
| 222 | + if (!deptTreeRef.value) { |
| 223 | + return [] |
| 224 | + } |
| 225 | + // 获取目前被选中的部门 |
| 226 | + const checkedNodes = deptTreeRef.value?.getCheckedNodes() |
| 227 | + const checkedKeys = checkedNodes.map((item: TreeNodeData) => item.key) |
| 228 | + // 获取半选中的部门 |
| 229 | + const halfCheckedNodes = deptTreeRef.value?.getHalfCheckedNodes() |
| 230 | + const halfCheckedKeys = halfCheckedNodes.map((item: TreeNodeData) => item.key) |
| 231 | + // eslint-disable-next-line prefer-spread |
| 232 | + checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys) |
| 233 | + return checkedKeys |
| 234 | +} |
| 235 | +
|
| 236 | +// 保存 |
| 237 | +const save = async () => { |
| 238 | + try { |
| 239 | + const isInvalid = await formRef.value?.validate() |
| 240 | + if (isInvalid) return false |
| 241 | + form.menuIds = getMenuAllCheckedKeys() |
| 242 | + form.deptIds = getDeptAllCheckedKeys() |
| 243 | + if (isUpdate.value) { |
| 244 | + await updateRole(form, dataId.value) |
| 245 | + Message.success('修改成功') |
| 246 | + } else { |
| 247 | + await addRole(form) |
| 248 | + Message.success('新增成功') |
| 249 | + } |
| 250 | + emit('save-success') |
| 251 | + return true |
| 252 | + } catch (error) { |
| 253 | + return false |
| 254 | + } |
| 255 | +} |
| 256 | +
|
| 257 | +const handleTreeAction = (type, action) => { |
| 258 | + const refMap = { |
| 259 | + menu: menuTreeRef, |
| 260 | + dept: deptTreeRef |
| 261 | + } |
| 262 | + const ref = refMap[type] |
| 263 | + if (ref && action === 'expand') { |
| 264 | + ref.value?.expandAll(type === 'menu' ? isMenuExpanded.value : isDeptExpanded.value) |
| 265 | + } else if (ref && action === 'check') { |
| 266 | + ref.value?.checkAll(type === 'menu' ? isMenuCheckAll.value : isDeptCheckAll.value) |
| 267 | + } |
| 268 | +} |
| 269 | +
|
| 270 | +// 调用时 |
| 271 | +const onExpanded = (type) => handleTreeAction(type, 'expand') |
| 272 | +const onCheckAll = (type) => handleTreeAction(type, 'check') |
| 273 | +
|
| 274 | +// 确认时 |
| 275 | +const onClickOk = () => { |
| 276 | + if (unref(current) === 3) { |
| 277 | + const isSaved = save() |
| 278 | + if (isSaved) visible.value = false |
| 279 | + } |
| 280 | +} |
| 281 | +defineExpose({ onAdd, onUpdate }) |
| 282 | +</script> |
| 283 | + |
| 284 | +<style lang="scss" scoped> |
| 285 | +fieldset { |
| 286 | + padding: 15px 15px 0 15px; |
| 287 | + margin-bottom: 10px; |
| 288 | + border: 1px solid var(--color-neutral-3); |
| 289 | + border-radius: 3px; |
| 290 | + height: 440px; |
| 291 | +} |
| 292 | +
|
| 293 | +fieldset legend { |
| 294 | + color: rgb(var(--gray-10)); |
| 295 | + padding: 2px 5px 2px 5px; |
| 296 | + border: 1px solid var(--color-neutral-3); |
| 297 | + border-radius: 3px; |
| 298 | +} |
| 299 | +
|
| 300 | +.mb-15 { |
| 301 | + margin-bottom: 15px |
| 302 | +} |
| 303 | +
|
| 304 | +:deep(.arco-form-item-extra) { |
| 305 | + width: 100%; |
| 306 | +} |
| 307 | +:deep(.arco-modal-footer){ |
| 308 | + margin-top: -20px; |
| 309 | +} |
| 310 | +</style> |
0 commit comments