WIP account mgmt data-table prototype and modal prototype.

This commit is contained in:
Cindy Chang
2024-06-19 15:55:02 +08:00
parent 50c98892c4
commit b7862ab164
9 changed files with 252 additions and 11 deletions

View File

@@ -4,15 +4,46 @@
<div class="flex w-full justify-end py-2">
<SearchBar/>
</div>
<div id="acct_mgmt_data_grid" class="flex w-full">
<DataTable dataKey="id" tableClass="w-full mt-4 text-sm cursor-pointer relative table-fixed"
<div id="acct_mgmt_data_grid" class="flex w-full overflow-y-auto h-[570px]" @scroll="handleScroll">
<DataTable :value="infiniteAcctData" dataKey="id" tableClass="w-full mt-4 text-sm relative table-fixed"
>
<Column field="account" :header="i18next.t('AcctMgmt.Account')" bodyClass="font-medium" sortable></Column>
<Column field="account" :header="i18next.t('AcctMgmt.Account')" bodyClass="font-medium" sortable>
<template #body="slotProps">
<div @dblclick="onAcctDoubleClick()" class="cursor-pointer">
{{ slotProps.data.account }}
</div>
</template>
</Column>
<Column field="fullName" :header="i18next.t('AcctMgmt.FullName')" bodyClass="text-neutral-500" sortable></Column>
<Column field="adminRights" :header="i18next.t('AcctMgmt.AdminRights')" bodyClass="text-neutral-500"></Column>
<Column field="accountActivation" :header="i18next.t('AcctMgmt.AccountActivation')" bodyClass="text-neutral-500"></Column>
<Column field="detail" :header="i18next.t('AcctMgmt.Detail')" bodyClass="text-neutral-500"></Column>
<Column field="edit" :header="i18next.t('AcctMgmt.Edit')" bodyClass="text-neutral-500"></Column>
<Column field="adminRights" :header="i18next.t('AcctMgmt.AdminRights')" bodyClass="text-neutral-500 flex justify-center"
headerClass="header-center">
<template #body="slotProps">
<img v-if="slotProps.data.adminRights" src="@/assets/radioOn.svg" alt="Radio On" class="cursor-pointer flex"
@click="onAdminRightsBtnClick(true)"
/>
<img v-else src="@/assets/radioOff.svg" alt="Radio Off" class="cursor-pointer flex"
@click="onAdminRightsBtnClick(false)"
/>
</template>
</Column>
<Column field="accountActivation" :header="i18next.t('AcctMgmt.AccountActivation')" bodyClass="text-neutral-500"
headerClass="header-center">
<template #body="slotProps">
<div class="w-full flex justify-center">
<img v-if="slotProps.data.accountActivation" src="@/assets/radioOn.svg" alt="Radio On" class="cursor-pointer flex"/>
<img v-else src="@/assets/radioOff.svg" alt="Radio Off" class="cursor-pointer flex"/>
</div>
</template>
</Column>
<Column field="detail" :header="i18next.t('AcctMgmt.Detail')" bodyClass="text-neutral-500">
<template #body="slotProps">
<img src="@/assets/icon-detail-card.svg" alt="Detail" class="cursor-pointer" @click="onDetailBtnClick(slotProps.data.id)"/>
</template>
</Column>
<Column field="edit" :header="i18next.t('AcctMgmt.Edit')" bodyClass="text-neutral-500">
<template #body="slotProps">
<img src="@/assets/icon-edit.svg" alt="Edit" class="cursor-pointer"/>
</template></Column>
</DataTable>
</div>
</div>
@@ -20,28 +51,125 @@
</template>
<script>
import { storeToRefs, mapState, } from 'pinia';
import { storeToRefs, mapState, mapActions, } from 'pinia';
import LoadingStore from '@/stores/loading.js';
import { useModalStore } from '@/stores/modal.js';
import SearchBar from '../../../components/AccountMenu/SearchBar.vue';
import i18next from '@/i18n/i18n.js';
const ONCE_RENDER_NUM_OF_DATA = 9;
function repeatAccountList(accountList, N) {
const repeatedList = [];
for (let i = 0; i < N; i++) {
// 复制每次的对象并将其添加到新数组中
accountList.forEach(account => {
// 创建一个新的对象,避免直接引用
const newAccount = { ...account };
repeatedList.push(newAccount);
});
}
return repeatedList;
}
const accountList = [
{
id: 12345,
account: "REDACTED-USER1",
fullName: "Alice Zheng",
adminRights: true,
accountActivation: true,
detail: "abcde",
},
{
id: 345,
account: "REDACTED-USER2",
fullName: "Mike Chen",
adminRights: true,
accountActivation: true,
detail: "abcde",
},
{
id: 88,
account: "REDACTED-USER3",
fullName: "Tory Cheng",
adminRights: true,
accountActivation: true,
detail: "abcde",
},
];
const repeatedAccountList = repeatAccountList(accountList, 20);
export default {
setup() {
const loadingStore = LoadingStore();
const modalStore = useModalStore();
const { isLoading } = storeToRefs(loadingStore);
return {
isLoading,
modalStore,
};
},
data() {
return {
i18next: i18next,
repeatedAccountList: repeatedAccountList,
infiniteAcctData: repeatedAccountList.slice(0, ONCE_RENDER_NUM_OF_DATA),
infiniteStart: 0,
isInfiniteFinish: true,
isInfinitMaxItemsMet: false,
};
},
components: {
SearchBar,
},
methods: {
onAcctDoubleClick(){
},
onAdminRightsBtnClick(isOn){
},
/**
* 無限滾動: 監聽 scroll 有沒有滾到底部
* @param {element} event 滾動傳入的事件
*/
handleScroll(event) {
if(this.infinitMaxItems || this.infiniteAcctData.length < ONCE_RENDER_NUM_OF_DATA || this.isInfiniteFinish === false) {
return;
}
const container = event.target;
const smallValue = 4;
const overScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight - smallValue;
if(overScrollHeight){
this.fetchMoreData();
}
},
/**
* 無限滾動: 滾到底後,要載入數據
*/
async fetchMoreData() {
this.isLoading = true;
this.infiniteFinish = false;
this.infiniteStart += ONCE_RENDER_NUM_OF_DATA;
// await this.acctMgmtStore.getAccountDetail();
this.infiniteAcctData = await [...this.infiniteAcctData, ...this.repeatedAccountList.slice(
this.infiniteStart, this.infiniteStart + ONCE_RENDER_NUM_OF_DATA)];
this.isInfiniteFinish = true;
this.isLoading = false;
},
onDetailBtnClick(dataId){
this.openModal();
},
...mapActions(useModalStore, ['openModal']),
},
created() {
},
@@ -50,3 +178,9 @@ export default {
},
};
</script>
<style>
/*為了讓 radio 按鈕可以置中,所以讓欄位的文字也置中 */
.header-center .p-column-header-content{
justify-content: center;
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<div id="modal_container" v-if="modalStore.isModalOpen" class="fixed w-screen h-screen bg-gray-800
flex justify-center items-center">
<button @click="modalStore.openModal" class="flex px-4 py-2 bg-blue-500 text-white rounded">
Open Modal
</button>
<button @click="modalStore.closeModal" class="flex px-4 py-2 bg-blue-500 text-white rounded">
Close Modal
</button>
</div>
</template>
<script>
import { useModalStore } from '@/stores/modal.js';
export default {
setup() {
const modalStore = useModalStore();
return {
modalStore,
};
},
};
</script>
<style>
#modal_container {
z-index: 9999;
background-color: rgb(254,254,254);
}
</style>

View File

@@ -179,7 +179,11 @@
</div>
<!-- All Files type of grid -->
<ul class="flex justify-start items-start gap-4 flex-wrap overflow-y-scroll overflow-x-hidden max-h-[calc(100vh_-_440px)] scrollbar" v-else>
<li class="w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:bg-primary/10 hover:border-primary duration-300 flex flex-col justify-between cursor-pointer" v-for="(file, index) in allFiles" :key="file.id" :class="{ 'bg-primary/10 border-primary': isActive === index}" @dblclick="enterDiscover(file)" :title="file.name" @contextmenu="onRightClick($event, file)" @click="onGridCardClick(file, index)" :id="'li' + index">
<li class="w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:bg-primary/10 hover:border-primary duration-300
flex flex-col justify-between cursor-pointer"
v-for="(file, index) in allFiles" :key="file.id" :class="{ 'bg-primary/10 border-primary': isActive === index}"
@dblclick="enterDiscover(file)" :title="file.name" @contextmenu="onRightClick($event, file)"
@click="onGridCardClick(file, index)" :id="'li' + index">
<div>
<span class="material-symbols-outlined mb-2 !text-[32px] block">
{{ file.icon }}

View File

@@ -1,10 +1,11 @@
<template>
<ModalContainer/>
<header class="sticky inset-x-0 top-0 w-full bg-neutral-10 z-10">
<Header/>
<Navbar/>
</header>
<main class="w-full">
<Loading v-if="loadingStore.isLoading" />
<Loading v-if="loadingStore.isLoading" />
<router-view></router-view>
</main>
</template>
@@ -21,6 +22,7 @@ import { leaveFilter, leaveConformance } from '@/module/alertModal.js';
import PageAdminStore from '@/stores/pageAdmin.js';
import LoginStore from "@/stores/login.js";
import { getCookie } from "../utils/cookieUtil.js";
import ModalContainer from './AccountManagement/ModalContainer.vue';
export default {
name: 'MainContainer',
@@ -41,7 +43,8 @@ export default {
components: {
Header,
Navbar,
Loading
Loading,
ModalContainer,
},
computed: {
...mapState(PageAdminStore, [