feat: complete admin panel and backend admin api migration
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<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="دسترسی ها">
|
||||
<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"
|
||||
html-type="submit"
|
||||
>
|
||||
ثبت تغییرات
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
v-if="newUT && hasPermission('user.permission.create')"
|
||||
type="primary"
|
||||
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 { 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;
|
||||
});
|
||||
|
||||
/** legacy drops the whole "custom" branch for anyone outside level 104 */
|
||||
const visibleTree = computed<PermissionNode[]>(() =>
|
||||
auth.user?.ut === RESERVED_TYPE_ID
|
||||
? treeData
|
||||
: treeData.filter((x) => x.key !== 'custom'),
|
||||
);
|
||||
|
||||
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) {
|
||||
rid.value = [];
|
||||
return;
|
||||
}
|
||||
if (id === NEW_TYPE_ID) {
|
||||
rid.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data } = await api.get(`roles/${id}`);
|
||||
// The server returns a flat array of rid strings.
|
||||
rid.value = 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: rid.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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.tree-item {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user