WIP account mgmt data-table prototype and modal prototype.
This commit is contained in:
3
src/assets/icon-detail-card.svg
Normal file
3
src/assets/icon-detail-card.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.75 10.5C18.75 10.6989 18.671 10.8897 18.5303 11.0303C18.3897 11.171 18.1989 11.25 18 11.25H14.25C14.0511 11.25 13.8603 11.171 13.7197 11.0303C13.579 10.8897 13.5 10.6989 13.5 10.5C13.5 10.3011 13.579 10.1103 13.7197 9.96967C13.8603 9.82902 14.0511 9.75 14.25 9.75H18C18.1989 9.75 18.3897 9.82902 18.5303 9.96967C18.671 10.1103 18.75 10.3011 18.75 10.5ZM18 12.75H14.25C14.0511 12.75 13.8603 12.829 13.7197 12.9697C13.579 13.1103 13.5 13.3011 13.5 13.5C13.5 13.6989 13.579 13.8897 13.7197 14.0303C13.8603 14.171 14.0511 14.25 14.25 14.25H18C18.1989 14.25 18.3897 14.171 18.5303 14.0303C18.671 13.8897 18.75 13.6989 18.75 13.5C18.75 13.3011 18.671 13.1103 18.5303 12.9697C18.3897 12.829 18.1989 12.75 18 12.75ZM12.2625 15.5625C12.2878 15.6573 12.2941 15.7562 12.2809 15.8535C12.2677 15.9507 12.2353 16.0444 12.1855 16.129C12.1358 16.2136 12.0698 16.2875 11.9912 16.3463C11.9127 16.4052 11.8233 16.4479 11.7281 16.4719C11.6327 16.4973 11.5332 16.5037 11.4353 16.4906C11.3374 16.4775 11.2431 16.4452 11.1578 16.3955C11.0724 16.3459 10.9977 16.2799 10.9379 16.2013C10.8781 16.1227 10.8344 16.033 10.8094 15.9375C10.6823 15.4576 10.4 15.0333 10.0067 14.7305C9.61326 14.4278 9.13079 14.2636 8.63437 14.2636C8.13796 14.2636 7.65549 14.4278 7.26209 14.7305C6.8687 15.0333 6.58648 15.4576 6.45938 15.9375C6.41615 16.0985 6.32112 16.2409 6.18896 16.3425C6.0568 16.4442 5.89486 16.4995 5.72812 16.5L5.54062 16.4719C5.44547 16.4479 5.35603 16.4052 5.2775 16.3463C5.19898 16.2875 5.13294 16.2136 5.08322 16.129C5.0335 16.0444 5.00109 15.9507 4.98788 15.8535C4.97466 15.7562 4.98091 15.6573 5.00625 15.5625C5.23456 14.676 5.77774 13.9028 6.53437 13.3875C6.10882 12.9704 5.81713 12.436 5.69649 11.8524C5.57586 11.2688 5.63175 10.6626 5.85703 10.1109C6.08232 9.55922 6.46679 9.08714 6.96143 8.75484C7.45608 8.42254 8.03848 8.24507 8.63437 8.24507C9.23027 8.24507 9.81267 8.42254 10.3073 8.75484C10.802 9.08714 11.1864 9.55922 11.4117 10.1109C11.637 10.6626 11.6929 11.2688 11.5723 11.8524C11.4516 12.436 11.1599 12.9704 10.7344 13.3875C11.491 13.9028 12.0342 14.676 12.2625 15.5625ZM8.63437 12.75C8.93105 12.75 9.22106 12.662 9.46773 12.4972C9.7144 12.3324 9.90666 12.0981 10.0202 11.824C10.1337 11.5499 10.1634 11.2483 10.1056 10.9574C10.0477 10.6664 9.90481 10.3991 9.69504 10.1893C9.48526 9.97956 9.21798 9.8367 8.92701 9.77882C8.63604 9.72094 8.33444 9.75065 8.06035 9.86418C7.78626 9.97771 7.55199 10.17 7.38717 10.4166C7.22235 10.6633 7.13438 10.9533 7.13437 11.25C7.13437 11.6478 7.29241 12.0294 7.57372 12.3107C7.85502 12.592 8.23655 12.75 8.63437 12.75ZM21.75 5.25V18.75C21.75 19.1478 21.592 19.5294 21.3107 19.8107C21.0294 20.092 20.6478 20.25 20.25 20.25H3.75C3.35218 20.25 2.97064 20.092 2.68934 19.8107C2.40804 19.5294 2.25 19.1478 2.25 18.75V5.25C2.25 4.85218 2.40804 4.47064 2.68934 4.18934C2.97064 3.90804 3.35218 3.75 3.75 3.75H20.25C20.6478 3.75 21.0294 3.90804 21.3107 4.18934C21.592 4.47064 21.75 4.85218 21.75 5.25ZM20.25 18.75V5.25H3.75V18.75H20.25Z" fill="#64748B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
3
src/assets/icon-edit.svg
Normal file
3
src/assets/icon-edit.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 17H3.4L12.025 8.375L10.625 6.975L2 15.6V17ZM16.3 6.925L12.05 2.725L13.45 1.325C13.8333 0.941667 14.3043 0.75 14.863 0.75C15.421 0.75 15.8917 0.941667 16.275 1.325L17.675 2.725C18.0583 3.10833 18.2583 3.571 18.275 4.113C18.2917 4.65433 18.1083 5.11667 17.725 5.5L16.3 6.925ZM1 19C0.716667 19 0.479333 18.904 0.288 18.712C0.0960001 18.5207 0 18.2833 0 18V15.175C0 15.0417 0.025 14.9127 0.075 14.788C0.125 14.6627 0.2 14.55 0.3 14.45L10.6 4.15L14.85 8.4L4.55 18.7C4.45 18.8 4.33767 18.875 4.213 18.925C4.08767 18.975 3.95833 19 3.825 19H1ZM11.325 7.675L10.625 6.975L12.025 8.375L11.325 7.675Z" fill="#64748B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 723 B |
23
src/assets/radioOff.svg
Normal file
23
src/assets/radioOff.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="40" height="24" viewBox="0 0 40 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3442_4283)">
|
||||
<rect width="40" height="24" rx="12" fill="#C9CDD4"/>
|
||||
<g filter="url(#filter0_d_3442_4283)">
|
||||
<circle cx="12" cy="12" r="10" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="1" y="1" width="38" height="22" rx="11" stroke="#64748B" stroke-width="2"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_3442_4283" x="0" y="2" width="24" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3442_4283"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3442_4283" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_3442_4283">
|
||||
<rect width="40" height="24" rx="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
23
src/assets/radioOn.svg
Normal file
23
src/assets/radioOn.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="40" height="24" viewBox="0 0 40 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3442_4281)">
|
||||
<rect width="40" height="24" rx="12" fill="#0099FF"/>
|
||||
<g filter="url(#filter0_d_3442_4281)">
|
||||
<circle cx="28" cy="12" r="10" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="1" y="1" width="38" height="22" rx="11" stroke="#0080D5" stroke-width="2"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_3442_4281" x="16" y="2" width="24" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3442_4281"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3442_4281" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_3442_4281">
|
||||
<rect width="40" height="24" rx="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
15
src/stores/modal.js
Normal file
15
src/stores/modal.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useModalStore = defineStore('modalStore', {
|
||||
state: () => ({
|
||||
isModalOpen: false,
|
||||
}),
|
||||
actions: {
|
||||
openModal() {
|
||||
this.isModalOpen = true;
|
||||
},
|
||||
closeModal() {
|
||||
this.isModalOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
33
src/views/AccountManagement/ModalContainer.vue
Normal file
33
src/views/AccountManagement/ModalContainer.vue
Normal 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>
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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, [
|
||||
|
||||
Reference in New Issue
Block a user