258 lines
7.7 KiB
Vue
258 lines
7.7 KiB
Vue
<template>
|
|
<!--
|
|
Ported from legacy henza_admin/src/views/user/Permission.vue — the screen
|
|
that administers the permission(rid, userTypeId) matrix built in Phase 1.
|
|
|
|
Legacy's magic ids, preserved:
|
|
84 = the "new access level" sentinel — picking it reveals a name field
|
|
85 = the default selection on load
|
|
104 = a reserved level; unless the signed-in admin belongs to it, both the
|
|
level and the whole "custom" branch of the tree are hidden
|
|
|
|
The RTL checkbox CSS from legacy is carried over verbatim — ant's tick mark
|
|
is drawn with a rotated border that lands wrong under [dir=rtl].
|
|
-->
|
|
<a-form layout="vertical" @submit.prevent="handleSubmit">
|
|
<a-card id="rid" title="دسترسی ها" class="page-card">
|
|
<div class="grid-2">
|
|
<a-form-item
|
|
label="سطح کاربر"
|
|
:validate-status="err.ut ? 'error' : ''"
|
|
:help="err.ut"
|
|
>
|
|
<a-select v-model:value="ut" allow-clear @change="selectChange">
|
|
<a-select-option v-for="t in visibleUserTypes" :key="t.id" :value="String(t.id)">
|
|
{{ t.title }}
|
|
</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
|
|
<a-form-item
|
|
v-if="newUT && hasPermission('user.permission.create')"
|
|
label="نام سطح دسترسی"
|
|
:validate-status="err.user ? 'error' : ''"
|
|
:help="err.user"
|
|
>
|
|
<a-input v-model:value="newTypeName" allow-clear />
|
|
</a-form-item>
|
|
|
|
<a-form-item label="نقش ها" class="tree-item">
|
|
<a-tree
|
|
v-model:checked-keys="rid"
|
|
:check-strictly="false"
|
|
checkable
|
|
style="width: 100%"
|
|
:tree-data="visibleTree"
|
|
:field-names="{ title: 'title', key: 'key', children: 'children' }"
|
|
:auto-expand-parent="autoExpandParent"
|
|
/>
|
|
</a-form-item>
|
|
</div>
|
|
|
|
<a-button
|
|
v-if="!newUT && hasPermission('user.permission.edit')"
|
|
type="primary"
|
|
class="form-submit"
|
|
html-type="submit"
|
|
>
|
|
ثبت تغییرات
|
|
</a-button>
|
|
|
|
<a-button
|
|
v-if="newUT && hasPermission('user.permission.create')"
|
|
type="primary"
|
|
class="form-submit"
|
|
html-type="submit"
|
|
>
|
|
افزودن
|
|
</a-button>
|
|
</a-card>
|
|
</a-form>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
import { message } from 'ant-design-vue';
|
|
import { treeData, type PermissionNode } from './permissionConst';
|
|
import { SUPER_ADMIN_RIDS } from '../../utils/superAdmin';
|
|
import { useUsersStore } from '../../stores/users';
|
|
import { useAuthStore } from '../../stores/auth';
|
|
import api from '../../api/client';
|
|
|
|
const users = useUsersStore();
|
|
const auth = useAuthStore();
|
|
const hasPermission = (rid: string) => Boolean(users.hasPermission(rid));
|
|
|
|
/** legacy sentinels */
|
|
const NEW_TYPE_ID = '84';
|
|
const DEFAULT_TYPE_ID = '85';
|
|
const RESERVED_TYPE_ID = 104;
|
|
|
|
const ut = ref<string | undefined>(DEFAULT_TYPE_ID);
|
|
const newTypeName = ref('');
|
|
const rid = ref<string[]>([]);
|
|
const autoExpandParent = ref(true);
|
|
const userTypes = ref<{ id: number; title: string }[]>([]);
|
|
const err = reactive<Record<string, string>>({});
|
|
|
|
const newUT = computed(() => ut.value === NEW_TYPE_ID);
|
|
|
|
/** legacy: hide the "new" option without the create rid, and hide 104 unless you are in it */
|
|
const visibleUserTypes = computed(() => {
|
|
let list = userTypes.value;
|
|
if (!hasPermission('user.permission.create')) {
|
|
list = list.filter((t) => String(t.id) !== NEW_TYPE_ID);
|
|
}
|
|
if (auth.user?.ut !== RESERVED_TYPE_ID) {
|
|
list = list.filter((t) => t.id !== RESERVED_TYPE_ID);
|
|
}
|
|
return list;
|
|
});
|
|
|
|
/** Drops every super-admin-only node, with its whole subtree. */
|
|
function withoutSuperAdminNodes(nodes: PermissionNode[]): PermissionNode[] {
|
|
return nodes
|
|
.filter((x) => !SUPER_ADMIN_RIDS.has(x.key))
|
|
.map((x) => (x.children ? { ...x, children: withoutSuperAdminNodes(x.children) } : x));
|
|
}
|
|
|
|
/**
|
|
* legacy drops the whole "custom" branch for anyone outside level 104; the
|
|
* super-admin-only rids are shown to the super admin alone.
|
|
*/
|
|
const visibleTree = computed<PermissionNode[]>(() => {
|
|
const tree =
|
|
auth.user?.ut === RESERVED_TYPE_ID ? treeData : treeData.filter((x) => x.key !== 'custom');
|
|
return users.isSuperAdmin ? tree : withoutSuperAdminNodes(tree);
|
|
});
|
|
|
|
/**
|
|
* Super-admin-only rids the selected level already holds. Not on the tree for
|
|
* anyone else, so they are sent back untouched on save instead of being
|
|
* revoked by an admin who cannot see them.
|
|
*/
|
|
const heldHiddenRids = ref<string[]>([]);
|
|
|
|
function setLoadedRids(loaded: string[]) {
|
|
if (users.isSuperAdmin) {
|
|
rid.value = loaded;
|
|
heldHiddenRids.value = [];
|
|
return;
|
|
}
|
|
rid.value = loaded.filter((x) => !SUPER_ADMIN_RIDS.has(x));
|
|
heldHiddenRids.value = loaded.filter((x) => SUPER_ADMIN_RIDS.has(x));
|
|
}
|
|
|
|
async function getData() {
|
|
try {
|
|
const { data } = await api.get('const/__list__ut');
|
|
userTypes.value = data;
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}
|
|
|
|
/** legacy selectChange(): id 84 means "creating a new level", so start empty */
|
|
async function selectChange(id: string | undefined) {
|
|
if (!id) {
|
|
setLoadedRids([]);
|
|
return;
|
|
}
|
|
if (id === NEW_TYPE_ID) {
|
|
setLoadedRids([]);
|
|
return;
|
|
}
|
|
try {
|
|
const { data } = await api.get(`roles/${id}`);
|
|
// The server returns a flat array of rid strings.
|
|
setLoadedRids(data);
|
|
} catch (e) {
|
|
message.error('خطا در دریافت اطلاعات');
|
|
}
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
Object.keys(err).forEach((k) => delete err[k]);
|
|
if (!ut.value) {
|
|
err.ut = 'لطفاسطح دسترسی را انتخاب کنید';
|
|
return;
|
|
}
|
|
if (newUT.value && !newTypeName.value) {
|
|
err.user = 'لطفا نام سطح دسترسی را وارد کنید';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let targetUt = ut.value;
|
|
|
|
if (newUT.value) {
|
|
// legacy: create the level first, then grant the ticked rids to it
|
|
const { data } = await api.post('const', {
|
|
key: '__list__ut',
|
|
val: newTypeName.value,
|
|
});
|
|
targetUt = String(data.id);
|
|
}
|
|
|
|
await api.post('roles/create', {
|
|
ut: targetUt,
|
|
roles: [...new Set([...rid.value, ...heldHiddenRids.value])],
|
|
});
|
|
|
|
await getData();
|
|
if (newUT.value) {
|
|
ut.value = targetUt;
|
|
newTypeName.value = '';
|
|
await selectChange(targetUt);
|
|
}
|
|
message.success('عملیات با موفقیت انجام شد');
|
|
} catch (e: any) {
|
|
message.error(e?.response?.data?.message ?? 'خطا در ثبت اطلاعات');
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await getData();
|
|
// legacy: setFieldsValue({ut:"85"}) then selectChange("85")
|
|
ut.value = DEFAULT_TYPE_ID;
|
|
await selectChange(DEFAULT_TYPE_ID);
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
/*
|
|
Carried over verbatim from legacy. Ant draws the checkbox tick with a rotated
|
|
border, which lands on the wrong edge under [dir=rtl].
|
|
*/
|
|
[dir='rtl'] .ant-tree-checkbox-inner::after {
|
|
right: 55%;
|
|
top: 35%;
|
|
border-left: 0 !important;
|
|
-webkit-transform: rotate(-45deg) scale(0) translate(50%, -50%);
|
|
transform: rotate(-45deg) scale(0) translate(50%, -50%);
|
|
}
|
|
[dir='rtl'] .ant-tree-checkbox-checked .ant-tree-checkbox-inner::after {
|
|
border-left: 0 !important;
|
|
border-right: 2px solid #fff;
|
|
-webkit-transform: rotate(45deg) scale(1) translate(0%, -60%);
|
|
transform: rotate(45deg) scale(1) translate(0%, -60%);
|
|
}
|
|
</style>
|
|
|
|
<style scoped>
|
|
/* .grid-2 now comes from the global utilities.less (dedup) */
|
|
.tree-item {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
@media (max-width: 575px) {
|
|
/* the permission tree nests deep; a smaller indent keeps titles readable */
|
|
.tree-item :deep(.ant-tree-indent-unit) {
|
|
width: 14px;
|
|
}
|
|
.tree-item :deep(.ant-tree-node-content-wrapper) {
|
|
white-space: normal;
|
|
}
|
|
}
|
|
</style>
|