Files
tehran_tala/apps/admin-panel/src/views/user/Permission.vue
T

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>