Changes MenuGroupRights

This commit is contained in:
Thien Pham Van
2026-01-15 09:02:35 +07:00
parent bc6551bc80
commit f7a35fc76e
2 changed files with 380 additions and 127 deletions

View File

@@ -33,6 +33,26 @@
></SvgIcon> ></SvgIcon>
</span> </span>
</div> </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> </div>
<ul> <ul>
<li class="border-bottom" v-for="x in v.submenu"> <li class="border-bottom" v-for="x in v.submenu">
@@ -43,13 +63,22 @@
</span> </span>
</div> </div>
<div class="control" v-if="option === 'limit'"> <div class="control" v-if="option === 'limit'">
<span class="ml-6 is-clickable" @click="changeTick(x, v)"> <span class="ml-6 is-clickable" @click="changeTick(x, v)" title="Xem thông tin">
<SvgIcon <SvgIcon
v-bind="{ v-bind="{
name: x.checked ? 'checked.svg' : 'uncheck.svg', name: x.checked ? 'checked.svg' : 'uncheck.svg',
type: x.checked ? 'blue' : 'gray', type: x.checked ? 'blue' : 'gray',
size: 25, 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> ></SvgIcon>
</span> </span>
</div> </div>
@@ -62,7 +91,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
const { $getdata, $filter, $find, $insertapi, $deleteapi, $store } = useNuxtApp(); const { $getdata, $filter, $find, $insertapi, $deleteapi, $store, $updateapi } = useNuxtApp();
const props = defineProps({ const props = defineProps({
row: Object, row: Object,
@@ -93,15 +122,20 @@ async function getRights(first) {
const rights = $filter(rows, { category: "topmenu" }); const rights = $filter(rows, { category: "topmenu" });
rights.forEach((parent) => { rights
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
.forEach((parent) => {
const subs = $filter(rows, { const subs = $filter(rows, {
category: "submenu", category: "submenu",
classify: parent.code, classify: parent.code,
}); });
subs.forEach((sub) => { subs
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
.forEach((sub) => {
const found = $find(loanRights, { setting: sub.id }); const found = $find(loanRights, { setting: sub.id });
sub.checked = !!found; sub.checked = !!found;
sub.is_edit = found?.is_edit || null;
sub.rightId = found?.id || null; sub.rightId = found?.id || null;
}); });
@@ -112,18 +146,18 @@ async function getRights(first) {
parent.submenu = subs; parent.submenu = subs;
}); });
topmenu.value = [...rights]; topmenu.value = [...rights];
} }
async function changeTick(x, v) { async function changeTick(x, v) {
// ===== UNCHECK =====
if (x.checked) { if (x.checked) {
x.checked = false; x.checked = false;
const deleteId = x.rightId; const deleteId = x.rightId;
if (deleteId) {
await $deleteapi(props.api, deleteId);
x.rightId = null; x.rightId = null;
}
// Nếu là submenu → kiểm tra parent
if (v) { if (v) {
const stillChecked = v.submenu.some((s) => s.checked); const stillChecked = v.submenu.some((s) => s.checked);
if (!stillChecked) { if (!stillChecked) {
@@ -136,24 +170,25 @@ async function changeTick(x, v) {
} }
} }
} }
if (deleteId) {
await $deleteapi(props.api, deleteId);
}
} }
// ===== CHECK ===== // ===== CHECK =====
else { else {
x.checked = true; x.checked = true;
x.is_edit = false;
const obj = await $insertapi(props.api, { setting: x.id, group: props.row.id }, undefined, 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 (obj) x.rightId = obj.id;
if (v && !v.checked) { if (v && !v.checked) {
v.checked = true; v.checked = true;
v.is_edit = false;
const parentObj = await $insertapi(props.api, { setting: v.id, group: props.row.id }, undefined, 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; if (parentObj) v.rightId = parentObj.id;
} }
@@ -186,5 +221,185 @@ async function changeOption(v) {
} }
} }
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 {
// ===== UNCHECK =====
if (v.checked) {
v.checked = false;
v.is_edit = false;
console.log("v.rightId", v.rightId);
// Xóa quyền parent
if (v.rightId) {
await $deleteapi(props.api, v.rightId);
v.rightId = null;
}
// Xóa quyền submenu
if (v?.submenu?.length) {
await Promise.all(
v.submenu.map(async (item) => {
if (item.rightId) {
await $deleteapi(props.api, item.rightId);
item.rightId = null;
}
item.checked = false;
item.is_edit = false;
})
);
}
}
// ===== CHECK =====
else {
v.checked = true;
v.is_edit = false;
// Tạo quyền cho parent
const parentObj = await $insertapi(
props.api,
{ setting: v.id, group: props.row.id, is_edit: false },
undefined,
false
);
if (parentObj) v.rightId = parentObj.id;
// Tạo quyền cho submenu
if (v?.submenu?.length) {
await Promise.all(
v.submenu.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;
item.checked = true;
item.is_checked = true;
})
);
}
}
// Force Vue reactive update
topmenu.value = [...topmenu.value];
} catch (err) {
console.error("changeViewAll error:", err);
}
}
async function changeEditAll(v) {
try {
// ===== UNCHECK =====
if (v.checked) {
v.checked = true;
v.is_edit = false;
// Xóa quyền submenu
if (v?.submenu?.length) {
await Promise.all(
v.submenu.map(async (item) => {
if (item.rightId) {
await $updateapi(props.api, {
id: item.rightId,
setting: item.id,
group: props.row.id,
is_edit: !item.is_edit,
});
}
item.checked = true;
item.is_edit = !item.is_edit;
})
);
}
}
// ===== CHECK =====
else {
v.checked = true;
v.is_edit = false;
// Tạo quyền cho parent
const parentObj = await $insertapi(
props.api,
{ setting: v.id, group: props.row.id, is_edit: false },
undefined,
false
);
if (parentObj) v.rightId = parentObj.id;
// Tạo quyền cho submenu
if (v?.submenu?.length) {
await Promise.all(
v.submenu.map(async (item) => {
const obj = await $insertapi(
props.api,
{ setting: item.id, group: props.row.id, is_edit: true },
undefined,
false
);
if (obj) item.rightId = obj.id;
item.checked = true;
item.is_edit = true;
})
);
}
}
// Force Vue reactive update
topmenu.value = [...topmenu.value];
} catch (err) {
console.error("changeViewAll error:", err);
}
}
await getRights(true); await getRights(true);
</script> </script>

View File

@@ -1,22 +1,36 @@
<template> <template>
<div class="has-text-black"> <div class="has-text-black">
<div>{{ row.username }} / {{ row.fullname }} {{$lang('access-right')}}:</div> <div>{{ row.username }} / {{ row.fullname }} {{ $lang("access-right") }}:</div>
<div class="mt-2"> <div class="mt-2">
<span class="icon-text mr-6" v-for="v in options"> <span class="icon-text mr-6" v-for="v in options">
<a @click="changeOption(v)"> <a @click="changeOption(v)">
<SvgIcon v-bind="{name: option===v.code? 'radio-checked.svg' : 'radio-unchecked.svg', type: option===v.code? 'blue' : 'gray', size: 25}" /> <SvgIcon
v-bind="{
name: option === v.code ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: option === v.code ? 'blue' : 'gray',
size: 25,
}"
/>
</a> </a>
<b class="fs-18">{{v[$store.lang==='en'? 'en' : 'name']}}</b> <b class="fs-18">{{ v[$store.lang === "en" ? "en" : "name"] }}</b>
</span> </span>
</div> </div>
<aside class="menu"> <aside class="menu">
<ul class="menu-list" v-for="v in topmenu"> <ul class="menu-list" v-for="v in topmenu">
<li> <li>
<div class="field is-grouped has-background-light has-text-black py-2 px-3"> <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 is-expanded">
<div class="control" v-if="v.submenu.length===0 && option==='limit'"> <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)"> <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> <SvgIcon
v-bind="{
name: v.checked ? 'checked.svg' : 'uncheck.svg',
type: v.checked ? 'blue' : 'gray',
size: 25,
}"
></SvgIcon>
</span> </span>
</div> </div>
</div> </div>
@@ -28,9 +42,15 @@
<span>{{ x[$store.lang] }}</span> <span>{{ x[$store.lang] }}</span>
</span> </span>
</div> </div>
<div class="control" v-if="option==='limit'"> <div class="control" v-if="option === 'limit'">
<span class="ml-6 is-clickable" @click="changeTick(x, v)"> <span class="ml-6 is-clickable" @click="changeTick(x, v)">
<SvgIcon v-bind="{name: x.checked? 'checked.svg' : 'uncheck.svg', type: x.checked? 'blue' : 'gray', size: 25}"></SvgIcon> <SvgIcon
v-bind="{
name: x.checked ? 'checked.svg' : 'uncheck.svg',
type: x.checked ? 'blue' : 'gray',
size: 25,
}"
></SvgIcon>
</span> </span>
</div> </div>
</div> </div>
@@ -38,81 +58,99 @@
</ul> </ul>
</li> </li>
</ul> </ul>
</aside> </aside>
</div> </div>
</template> </template>
<script setup> <script setup>
const { $copy, $getdata, $filter, $find, $insertapi, $deleteapi } = useNuxtApp() const { $copy, $getdata, $filter, $find, $insertapi, $deleteapi } = useNuxtApp();
const props = defineProps({ const props = defineProps({
row: Object, row: Object,
api: String, api: String,
setting: String setting: String,
}) });
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 options = [
var option = ref('limit') { code: "all", name: "Tất cả tính năng", en: "All functions" },
const rows = await $getdata(props.setting, {category__in: ['topmenu', 'submenu']}) { code: "limit", name: "Bị giới hạn", en: "Limited functions" },
var topmenu = ref([]) ];
var loanRights = [] var option = ref("limit");
async function getRights(first) { const rows = await $getdata(props.setting, { category__in: ["topmenu", "submenu"] });
loanRights = await $getdata(props.api, {user: props.row.id}) var topmenu = ref([]);
if(loanRights.length==0 && first) option.value = 'all' var loanRights = [];
var rights = $filter(rows, {category: 'topmenu'}) async function getRights(first) {
rights.map(v=>{ loanRights = await $getdata(props.api, { user: props.row.id });
let arr = $filter(rows, {category: 'submenu', classify: v.code}) if (loanRights.length == 0 && first) option.value = "all";
arr.map(x=>{ var rights = $filter(rows, { category: "topmenu" });
let found = $find(loanRights, {setting: x.id}) rights.map((v) => {
if(found) { let arr = $filter(rows, { category: "submenu", classify: v.code });
x.rightId = found.id arr.map((x) => {
x.checked = true let found = $find(loanRights, { setting: x.id });
if (found) {
x.rightId = found.id;
x.checked = true;
} }
}) });
let found = $find(loanRights, {setting: v.id}) let found = $find(loanRights, { setting: v.id });
if(found) v.rightId = found.id if (found) v.rightId = found.id;
if(arr.length===0) { if (arr.length === 0) {
v.checked = found? true : false v.checked = found ? true : false;
} else v.checked = true } else v.checked = true;
v.submenu = arr v.submenu = arr;
}) });
topmenu.value = rights topmenu.value = rights;
}
async function changeTick(x, v) {
if (x.checked) {
if (x.rightId) {
await $deleteapi(props.api, x.rightId);
x.rightId = null;
} }
async function changeTick(x, v) { x.checked = false;
if(x.checked) { if (v ? v.rightId > 0 : false) {
if(x.rightId) { let idx = v.submenu.findIndex((h) => h.checked);
await $deleteapi(props.api, x.rightId) if (idx < 0) {
x.rightId = null await $deleteapi(props.api, v.rightId);
} v.checked = false;
x.checked = false
if(v? v.rightId>0 : false) {
let idx = v.submenu.findIndex(h=>h.checked)
if(idx<0) {
await $deleteapi(props.api, v.rightId)
v.checked = false
} }
} }
} else { } else {
let obj = await $insertapi(props.api, {setting: x.id, user: props.row.id}, undefined, false) let obj = await $insertapi(props.api, { setting: x.id, user: props.row.id }, undefined, false);
if(obj) { if (obj) {
x.checked = true x.checked = true;
x.rightId = obj.id x.rightId = obj.id;
} }
x.checked = true x.checked = true;
if(v? !v.rightId : false) { if (v ? !v.rightId : false) {
let obj = await $insertapi(props.api, {setting: v.id, user: props.row.id}, undefined, false) let obj = await $insertapi(props.api, { setting: v.id, user: props.row.id }, undefined, false);
if(obj) { if (obj) {
v.rightId = obj.rightId v.rightId = obj.rightId;
v.checked = true v.checked = true;
} }
} }
} }
topmenu.value = $copy(topmenu.value) topmenu.value = $copy(topmenu.value);
}
async function changeOption(v) {
option.value = v.code;
loanRights = await $getdata(props.api, { user: props.row.id });
if (v.code === "all") {
if (loanRights?.length > 0) {
await Promise.all(loanRights.map((item) => $deleteapi(props.api, item.id)));
} }
async function changeOption(v) {
option.value = v.code topmenu.value.forEach((p) => {
if(v.code==='all') { p.checked = false;
if(loanRights.length>0) await $deleteapi(props.api, loanRights) p.rightId = null;
p.submenu?.forEach((s) => {
s.checked = false;
s.rightId = null;
});
});
topmenu.value = [...topmenu.value];
} else { } else {
await getRights() await getRights();
} }
} }
await getRights(true) await getRights(true);
</script> </script>