417 lines
11 KiB
Vue
417 lines
11 KiB
Vue
<template>
|
|
<div class="has-text-black">
|
|
<div>{{ row.code }} / {{ isVietnamese ? row.name : row.en }} {{ $lang('access-right') }}:</div>
|
|
<div class="mt-2">
|
|
<span class="icon-text mr-6" v-for="v in options" @click="changeOption(v)">
|
|
<a>
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: option === v.code ? 'radio-checked.svg' : 'radio-unchecked.svg',
|
|
type: option === v.code ? 'blue' : 'gray',
|
|
size: 25,
|
|
}"
|
|
/>
|
|
</a>
|
|
<b class="fs-18">{{ v[$store.lang === 'en' ? 'en' : 'name'] }}</b>
|
|
</span>
|
|
</div>
|
|
<aside class="menu">
|
|
<ul class="menu-list" v-for="v in topmenu">
|
|
<li>
|
|
<div class="field is-grouped has-background-light has-text-black py-2 px-3">
|
|
<div class="control is-expanded">
|
|
<b>{{ v[$store.lang] }}</b>
|
|
</div>
|
|
<div class="control" v-if="v.submenu.length === 0 && option === 'limit'">
|
|
<span class="ml-6 is-clickable" @click="changeTick(v)">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: v.checked ? 'checked.svg' : 'uncheck.svg',
|
|
type: v.checked ? 'blue' : 'gray',
|
|
size: 25,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
</div>
|
|
<div v-if="v.submenu.length > 0 && option === 'limit'">
|
|
<span class="ml-6 is-clickable" @click="changeViewAll(v)" title="Chọn tất cả">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: 'view.svg',
|
|
type: 'gray',
|
|
size: 25,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
<span class="ml-6 is-clickable" @click="changeEditAll(v)" title="Chọn tất cả">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: 'edit1.svg',
|
|
type: 'gray',
|
|
size: 25,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<ul>
|
|
<li class="border-bottom" v-for="x in v.submenu">
|
|
<div class="field is-grouped py-1">
|
|
<div class="control is-expanded">
|
|
<span class="icon-text">
|
|
<span>{{ x[$store.lang] }}</span>
|
|
</span>
|
|
</div>
|
|
<div class="control" v-if="option === 'limit'">
|
|
<span class="ml-6 is-clickable" @click="changeTick(x, v)" title="Xem thông tin">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: x.checked ? 'checked.svg' : 'uncheck.svg',
|
|
type: x.checked ? 'blue' : 'gray',
|
|
size: 25,
|
|
}"
|
|
/>
|
|
</span>
|
|
<span class="ml-6 is-clickable" @click="changeEdit(x, v)" title="Chỉnh sửa thông tin">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: x.checked && x.is_edit === true ? 'checked.svg' : 'uncheck.svg',
|
|
type: x.checked && x.is_edit === true ? 'blue' : 'gray',
|
|
size: 25,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
</div>
|
|
</template>
|
|
<script setup>
|
|
const { $getdata, $filter, $find, $insertapi, $deleteapi, $store, $updateapi } = useNuxtApp();
|
|
|
|
const props = defineProps({
|
|
row: Object,
|
|
api: String,
|
|
setting: String,
|
|
});
|
|
|
|
const isVietnamese = computed(() => $store.lang.toLowerCase() === 'vi');
|
|
|
|
const options = [
|
|
{ code: 'all', name: 'Tất cả tính năng', en: 'All functions' },
|
|
{ code: 'limit', name: 'Bị giới hạn', en: 'Limited functions' },
|
|
];
|
|
|
|
const option = ref('limit');
|
|
const topmenu = ref([]);
|
|
let loanRights = [];
|
|
|
|
const rows = await $getdata(props.setting, {
|
|
category__in: ['topmenu', 'submenu'],
|
|
});
|
|
|
|
async function getRights(first) {
|
|
loanRights = await $getdata(props.api, { group: props.row.id });
|
|
if (loanRights.length === 0 && first) {
|
|
option.value = 'all';
|
|
}
|
|
|
|
const rights = $filter(rows, { category: 'topmenu' });
|
|
|
|
rights
|
|
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
|
|
.forEach((parent) => {
|
|
const subs = $filter(rows, {
|
|
category: 'submenu',
|
|
classify: parent.code,
|
|
});
|
|
|
|
subs
|
|
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
|
|
.forEach((sub) => {
|
|
const found = $find(loanRights, { setting: sub.id });
|
|
sub.checked = !!found;
|
|
sub.is_edit = found?.is_edit || null;
|
|
sub.rightId = found?.id || null;
|
|
});
|
|
|
|
const foundParent = $find(loanRights, { setting: parent.id });
|
|
parent.rightId = foundParent?.id || null;
|
|
|
|
parent.checked = subs.length ? subs.some((s) => s.checked) : !!foundParent;
|
|
|
|
parent.submenu = subs;
|
|
});
|
|
topmenu.value = [...rights];
|
|
}
|
|
|
|
async function changeTick(x, v) {
|
|
if (x.checked) {
|
|
x.checked = false;
|
|
const deleteId = x.rightId;
|
|
if (deleteId) {
|
|
await $deleteapi(props.api, deleteId);
|
|
x.rightId = null;
|
|
}
|
|
|
|
if (v) {
|
|
const stillChecked = v.submenu.some((s) => s.checked);
|
|
if (!stillChecked) {
|
|
const parentDelete = v.rightId;
|
|
v.checked = false;
|
|
v.rightId = null;
|
|
|
|
if (parentDelete) {
|
|
await $deleteapi(props.api, parentDelete);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== CHECK =====
|
|
else {
|
|
x.checked = true;
|
|
x.is_edit = false;
|
|
const obj = await $insertapi(props.api, { setting: x.id, group: props.row.id, is_edit: false }, undefined, false);
|
|
|
|
if (obj) x.rightId = obj.id;
|
|
|
|
if (v && !v.checked) {
|
|
v.checked = true;
|
|
v.is_edit = false;
|
|
const parentObj = await $insertapi(
|
|
props.api,
|
|
{ setting: v.id, group: props.row.id, is_edit: false },
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (parentObj) v.rightId = parentObj.id;
|
|
}
|
|
}
|
|
|
|
topmenu.value = [...topmenu.value];
|
|
}
|
|
|
|
async function changeOption(v) {
|
|
option.value = v.code;
|
|
loanRights = await $getdata(props.api, { group: props.row.id });
|
|
|
|
if (v.code === 'all') {
|
|
if (loanRights?.length > 0) {
|
|
await Promise.all(loanRights.map((item) => $deleteapi(props.api, item.id)));
|
|
}
|
|
|
|
topmenu.value.forEach((p) => {
|
|
p.checked = false;
|
|
p.rightId = null;
|
|
p.submenu?.forEach((s) => {
|
|
s.checked = false;
|
|
s.rightId = null;
|
|
});
|
|
});
|
|
|
|
topmenu.value = [...topmenu.value];
|
|
} else {
|
|
await getRights();
|
|
}
|
|
}
|
|
|
|
async function changeEdit(x, v) {
|
|
if (x.checked) {
|
|
const deleteId = x.rightId;
|
|
|
|
if (deleteId) {
|
|
await $updateapi(props.api, {
|
|
id: x.rightId,
|
|
setting: x.id,
|
|
group: props.row.id,
|
|
is_edit: !x.is_edit,
|
|
});
|
|
}
|
|
x.is_edit = !x.is_edit;
|
|
x.rightId = null;
|
|
}
|
|
|
|
// ===== CHECK =====
|
|
else {
|
|
x.checked = true;
|
|
x.is_edit = true;
|
|
const obj = await $insertapi(props.api, { setting: x.id, group: props.row.id, is_edit: true }, undefined, false);
|
|
|
|
if (obj) x.rightId = obj.id;
|
|
|
|
if (v && !v.checked) {
|
|
v.checked = true;
|
|
|
|
const parentObj = await $insertapi(
|
|
props.api,
|
|
{ setting: v.id, group: props.row.id, is_edit: false },
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (parentObj) v.rightId = parentObj.id;
|
|
}
|
|
}
|
|
|
|
topmenu.value = [...topmenu.value];
|
|
}
|
|
|
|
async function changeViewAll(v) {
|
|
try {
|
|
const hasSubmenu = v?.submenu?.length > 0;
|
|
|
|
const isAllChecked = hasSubmenu ? v.submenu.every((s) => s.checked === true) : v.checked;
|
|
|
|
// ================= UNCHECK ALL =================
|
|
if (isAllChecked) {
|
|
v.checked = false;
|
|
v.is_edit = false;
|
|
|
|
// Chỉ delete parent nếu có quyền
|
|
if (v.rightId) {
|
|
await $deleteapi(props.api, v.rightId);
|
|
v.rightId = null;
|
|
}
|
|
|
|
if (hasSubmenu) {
|
|
const needDelete = v.submenu.filter((item) => item.rightId);
|
|
|
|
await Promise.all(needDelete.map((item) => $deleteapi(props.api, item.rightId)));
|
|
|
|
// Update state local
|
|
v.submenu.forEach((item) => {
|
|
item.rightId = null;
|
|
item.checked = false;
|
|
item.is_edit = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
// ================= CHECK ALL =================
|
|
else {
|
|
v.checked = true;
|
|
v.is_edit = false;
|
|
|
|
// Chỉ insert parent nếu chưa có
|
|
if (!v.rightId) {
|
|
const parentObj = await $insertapi(
|
|
props.api,
|
|
{ setting: v.id, group: props.row.id, is_edit: false },
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (parentObj) v.rightId = parentObj.id;
|
|
}
|
|
|
|
if (hasSubmenu) {
|
|
const needInsert = v.submenu.filter((item) => !item.rightId);
|
|
|
|
await Promise.all(
|
|
needInsert.map(async (item) => {
|
|
const obj = await $insertapi(
|
|
props.api,
|
|
{ setting: item.id, group: props.row.id, is_edit: false },
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (obj) item.rightId = obj.id;
|
|
}),
|
|
);
|
|
|
|
// Update state local
|
|
v.submenu.forEach((item) => {
|
|
item.checked = true;
|
|
item.is_edit = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Force reactive
|
|
topmenu.value = [...topmenu.value];
|
|
} catch (err) {
|
|
console.error('changeViewAll error:', err);
|
|
}
|
|
}
|
|
|
|
async function changeEditAll(v) {
|
|
try {
|
|
if (!v?.submenu?.length) return;
|
|
|
|
const isAllEdit = v.submenu.every((item) => item.is_edit === true);
|
|
const newEditState = !isAllEdit;
|
|
|
|
// Parent luôn phải được check
|
|
v.checked = true;
|
|
v.is_edit = false;
|
|
|
|
// Chỉ insert parent nếu chưa có quyền
|
|
if (!v.rightId) {
|
|
const parentObj = await $insertapi(
|
|
props.api,
|
|
{ setting: v.id, group: props.row.id, is_edit: false },
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (parentObj) v.rightId = parentObj.id;
|
|
}
|
|
|
|
// ===== CHỈ LẤY NHỮNG ITEM CẦN THAO TÁC =====
|
|
const needUpdate = v.submenu.filter((item) => item.rightId && item.is_edit !== newEditState);
|
|
|
|
const needInsert = v.submenu.filter((item) => !item.rightId);
|
|
|
|
// Update những item cần đổi is_edit
|
|
await Promise.all(
|
|
needUpdate.map((item) =>
|
|
$updateapi(props.api, {
|
|
id: item.rightId,
|
|
setting: item.id,
|
|
group: props.row.id,
|
|
is_edit: newEditState,
|
|
}),
|
|
),
|
|
);
|
|
|
|
// Insert những item chưa có quyền
|
|
await Promise.all(
|
|
needInsert.map(async (item) => {
|
|
const obj = await $insertapi(
|
|
props.api,
|
|
{
|
|
setting: item.id,
|
|
group: props.row.id,
|
|
is_edit: newEditState,
|
|
},
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
if (obj) item.rightId = obj.id;
|
|
}),
|
|
);
|
|
|
|
// ===== UPDATE STATE LOCAL =====
|
|
v.submenu.forEach((item) => {
|
|
item.checked = true;
|
|
item.is_edit = newEditState;
|
|
});
|
|
|
|
// Force reactive
|
|
topmenu.value = [...topmenu.value];
|
|
} catch (err) {
|
|
console.error('changeEditAll error:', err);
|
|
}
|
|
}
|
|
|
|
await getRights(true);
|
|
</script>
|