vue3 infinite scroll
This commit is contained in:
@@ -34,7 +34,8 @@
|
|||||||
"ActivateNow": "Activate now.",
|
"ActivateNow": "Activate now.",
|
||||||
"DelteQuestion": "Are you sure to delete ?",
|
"DelteQuestion": "Are you sure to delete ?",
|
||||||
"DeleteFirstClause": "deletion is irreversible !",
|
"DeleteFirstClause": "deletion is irreversible !",
|
||||||
"DeleteSecondClause": "You will delete all data and content on this account."
|
"DeleteSecondClause": "You will delete all data and content on this account.",
|
||||||
|
"MsgAccountAdded": "Account added."
|
||||||
},
|
},
|
||||||
"Compare": {
|
"Compare": {
|
||||||
"timeUsage": "Time Usage",
|
"timeUsage": "Time Usage",
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ export default defineStore('acctMgmtStore', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
allUserAccoutList: [],
|
allUserAccoutList: [],
|
||||||
isAcctMenuOpen: false,
|
isAcctMenuOpen: false,
|
||||||
currentViewingUser: {}
|
currentViewingUser: {
|
||||||
|
detail: null,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
},
|
},
|
||||||
@@ -85,10 +87,24 @@ export default defineStore('acctMgmtStore', {
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
const response = await this.$axios.post(apiCreateAccount, userToCreate);
|
const response = await this.$axios.post(apiCreateAccount, userToCreate);
|
||||||
console.log('TODO: response:', response, response.status);
|
|
||||||
}
|
}
|
||||||
catch(error) {
|
catch(error) {
|
||||||
apiError(error, 'Failed to add a new account');
|
apiError(error, 'Failed to add a new account.');
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get user detail by unique username.
|
||||||
|
* @param {string} uniqueUsername
|
||||||
|
*/
|
||||||
|
async getUserDetail(uniqueUsername) {
|
||||||
|
const apiUserDetail = `/api/users/${uniqueUsername}`;
|
||||||
|
|
||||||
|
try{
|
||||||
|
const response = await this.$axios.get(apiUserDetail);
|
||||||
|
this.currentViewingUser.detail = response.data;
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
apiError(error, 'Failed to get user detail.');
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<SearchBar/>
|
<SearchBar/>
|
||||||
</div>
|
</div>
|
||||||
<div id="acct_mgmt_data_grid" class="flex w-full overflow-y-auto h-[570px]" @scroll="handleScroll">
|
<div id="acct_mgmt_data_grid" class="flex w-full overflow-y-auto h-[570px]" @scroll="handleScroll">
|
||||||
<DataTable :value="internalInfiniteAcctData" dataKey="username" tableClass="w-full mt-4 text-sm relative table-fixed"
|
<DataTable :value="infiniteAcctData" dataKey="username" tableClass="w-full mt-4 text-sm relative table-fixed"
|
||||||
:rowClass="getRowClass"
|
:rowClass="getRowClass"
|
||||||
>
|
>
|
||||||
<Column field="username" :header="i18next.t('AcctMgmt.Account')" bodyClass="font-medium" sortable>
|
<Column field="username" :header="i18next.t('AcctMgmt.Account')" bodyClass="font-medium" sortable>
|
||||||
@@ -149,7 +149,8 @@ export default {
|
|||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
const { isLoading } = storeToRefs(loadingStore);
|
const { isLoading } = storeToRefs(loadingStore);
|
||||||
const loginStore = piniaLoginStore();
|
const loginStore = piniaLoginStore();
|
||||||
const internalInfiniteAcctData = ref([]);
|
const infiniteAcctData = ref([]);
|
||||||
|
const infiniteStart = ref(0);
|
||||||
|
|
||||||
const loginUserData = ref(null);
|
const loginUserData = ref(null);
|
||||||
const allUserAccoutList = computed(() => acctMgmtStore.allUserAccoutList);
|
const allUserAccoutList = computed(() => acctMgmtStore.allUserAccoutList);
|
||||||
@@ -161,13 +162,13 @@ export default {
|
|||||||
|
|
||||||
const moveCurrentLoginUserToFirstRow = () => {
|
const moveCurrentLoginUserToFirstRow = () => {
|
||||||
const currentLoginUsername = loginUserData.value.username;
|
const currentLoginUsername = loginUserData.value.username;
|
||||||
if(internalInfiniteAcctData.value && internalInfiniteAcctData.value.length){
|
if(infiniteAcctData.value && infiniteAcctData.value.length){
|
||||||
const index = internalInfiniteAcctData.value.findIndex(user => user.username === currentLoginUsername);
|
const index = infiniteAcctData.value.findIndex(user => user.username === currentLoginUsername);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// 移除匹配的對象(現正登入的使用者)並將其插入到陣列的第一位"
|
// 移除匹配的對象(現正登入的使用者)並將其插入到陣列的第一位"
|
||||||
const [user] = internalInfiniteAcctData.value.splice(index, 1);
|
const [user] = infiniteAcctData.value.splice(index, 1);
|
||||||
internalInfiniteAcctData.value.unshift(user);
|
infiniteAcctData.value.unshift(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -179,7 +180,7 @@ export default {
|
|||||||
|
|
||||||
const getFirstPageUserData = async() => {
|
const getFirstPageUserData = async() => {
|
||||||
await acctMgmtStore.getAllUserAccounts();
|
await acctMgmtStore.getAllUserAccounts();
|
||||||
internalInfiniteAcctData.value = allUserAccoutList.value.slice(0, ONCE_RENDER_NUM_OF_DATA)
|
infiniteAcctData.value = allUserAccoutList.value.slice(0, ONCE_RENDER_NUM_OF_DATA)
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInfiniteFinish = ref(true);
|
const isInfiniteFinish = ref(true);
|
||||||
@@ -234,34 +235,45 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* 無限滾動: 監聽 scroll 有沒有滾到底部
|
* 無限滾動: 監聽 scroll 有沒有滾到底部
|
||||||
* @param {element} event 滾動傳入的事件
|
* @param {element} event 滾動傳入的事件
|
||||||
|
|
||||||
|
scrollTop,表示容器的垂直滾動位置。具體來說,它是以像素為單位的數值,
|
||||||
|
表示當前內容視窗(可見區域)的頂部距離整個可滾動內容的頂部的距離。
|
||||||
|
簡單來說,scrollTop 指的是滾動條的位置:當滾動條在最上面時,scrollTop 為 0;
|
||||||
|
當滾動條向下移動時,scrollTop 會增加。
|
||||||
|
可是作為:我們目前已經滾動了多少。
|
||||||
|
|
||||||
|
clientHeight:表示容器的可見高度(不包括滾動條的高度)。它是以像素為單位的數值,
|
||||||
|
表示容器內部的可見區域的高度。
|
||||||
|
與 offsetHeight 不同的是,clientHeight 不包含邊框、內邊距和滾動條的高度,只計算內容區域的高度。
|
||||||
|
|
||||||
|
scrollHeight:表示容器內部的總內容高度。它是以像素為單位的數值,
|
||||||
|
包括看不見的(需要滾動才能看到的)部分。
|
||||||
|
簡單來說,scrollHeight 是整個可滾動內容的總高度,包括可見區域和需要滾動才能看到的部分。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const handleScroll = (event) => {
|
const handleScroll = (event) => {
|
||||||
if(internalInfiniteAcctData.value.length < ONCE_RENDER_NUM_OF_DATA || isInfiniteFinish.value === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = event.target;
|
const container = event.target;
|
||||||
const smallValue = 4;
|
const smallValue = 3;
|
||||||
|
|
||||||
const overScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight - smallValue;
|
const isOverScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight - smallValue;
|
||||||
|
if(isOverScrollHeight){
|
||||||
if(overScrollHeight){
|
|
||||||
fetchMoreDataVue3();
|
fetchMoreDataVue3();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchMoreDataVue3 = () => {
|
const fetchMoreDataVue3 = () => {
|
||||||
infiniteStart.value += ONCE_RENDER_NUM_OF_DATA;
|
infiniteStart.value += ONCE_RENDER_NUM_OF_DATA;
|
||||||
internalInfiniteAcctData.value = [internalInfiniteAcctData.value, acctMgmtStore.allUserAccoutList.slice(
|
if(infiniteAcctData.value.length < acctMgmtStore.allUserAccoutList.length) {
|
||||||
infiniteStart.value, this.infiniteStart + ONCE_RENDER_NUM_OF_DATA)];
|
infiniteAcctData.value = [...infiniteAcctData.value, ...acctMgmtStore.allUserAccoutList.slice(
|
||||||
isInfiniteFinish.value = true;
|
infiniteStart.value, infiniteStart.value + ONCE_RENDER_NUM_OF_DATA)];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading,
|
isLoading,
|
||||||
modalStore,
|
modalStore,
|
||||||
loginUserData,
|
loginUserData,
|
||||||
internalInfiniteAcctData,
|
infiniteAcctData,
|
||||||
onCreateNewClick,
|
onCreateNewClick,
|
||||||
handleScroll,
|
handleScroll,
|
||||||
getRowClass,
|
getRowClass,
|
||||||
@@ -286,7 +298,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
i18next: i18next,
|
i18next: i18next,
|
||||||
repeatedAccountList: repeatedAccountList,
|
repeatedAccountList: repeatedAccountList,
|
||||||
infiniteAcctData: [],
|
infiniteAcctDataVue2: [],
|
||||||
infiniteStart: 0,
|
infiniteStart: 0,
|
||||||
isInfiniteFinish: true,
|
isInfiniteFinish: true,
|
||||||
isInfinitMaxItemsMet: false,
|
isInfinitMaxItemsMet: false,
|
||||||
@@ -311,7 +323,7 @@ export default {
|
|||||||
* @param {element} event 滾動傳入的事件
|
* @param {element} event 滾動傳入的事件
|
||||||
*/
|
*/
|
||||||
handleScrollVue2(event) {
|
handleScrollVue2(event) {
|
||||||
if(this.infinitMaxItems || this.infiniteAcctData.length < ONCE_RENDER_NUM_OF_DATA || this.isInfiniteFinish === false) {
|
if(this.infinitMaxItems || this.infiniteAcctDataVue2.length < ONCE_RENDER_NUM_OF_DATA || this.isInfiniteFinish === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +344,7 @@ export default {
|
|||||||
this.infiniteFinish = false;
|
this.infiniteFinish = false;
|
||||||
this.infiniteStart += ONCE_RENDER_NUM_OF_DATA;
|
this.infiniteStart += ONCE_RENDER_NUM_OF_DATA;
|
||||||
// await this.acctMgmtStore.getAccountDetail();
|
// await this.acctMgmtStore.getAccountDetail();
|
||||||
this.infiniteAcctData = await [...this.infiniteAcctData, ...this.allUserAccoutList.slice(
|
this.infiniteAcctDataVue2 = await [...this.infiniteAcctDataVue2, ...this.allUserAccoutList.slice(
|
||||||
this.infiniteStart, this.infiniteStart + ONCE_RENDER_NUM_OF_DATA)];
|
this.infiniteStart, this.infiniteStart + ONCE_RENDER_NUM_OF_DATA)];
|
||||||
this.isInfiniteFinish = true;
|
this.isInfiniteFinish = true;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ import { defineComponent, computed, ref, watch, } from 'vue';
|
|||||||
import i18next from "@/i18n/i18n.js";
|
import i18next from "@/i18n/i18n.js";
|
||||||
import { mapActions, } from 'pinia';
|
import { mapActions, } from 'pinia';
|
||||||
import { useModalStore } from '@/stores/modal.js';
|
import { useModalStore } from '@/stores/modal.js';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useToast } from 'vue-toast-notification';
|
||||||
import useAcctMgmtStore from '@/stores/acctMgmt.js';
|
import useAcctMgmtStore from '@/stores/acctMgmt.js';
|
||||||
import ModalHeader from "./ModalHeader.vue";
|
import ModalHeader from "./ModalHeader.vue";
|
||||||
import IconChecked from "@/components/icons/IconChecked.vue";
|
import IconChecked from "@/components/icons/IconChecked.vue";
|
||||||
@@ -129,6 +131,9 @@ export default defineComponent({
|
|||||||
const acctMgmtStore = useAcctMgmtStore();
|
const acctMgmtStore = useAcctMgmtStore();
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
|
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
|
||||||
const isPwdEyeOn = ref(false);
|
const isPwdEyeOn = ref(false);
|
||||||
const isPwdConfirmEyeOn = ref(false);
|
const isPwdConfirmEyeOn = ref(false);
|
||||||
@@ -184,7 +189,10 @@ export default defineComponent({
|
|||||||
return validateResult && isPwdMatched.value;
|
return validateResult && isPwdMatched.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onConfirmBtnClick = () => {
|
const onConfirmBtnClick = async () => {
|
||||||
|
|
||||||
|
switch(whichCurrentModal.value) {
|
||||||
|
case MODAL_CREATE_NEW:
|
||||||
const validateResult = validateConfirmPwd();
|
const validateResult = validateConfirmPwd();
|
||||||
if(!validateResult){
|
if(!validateResult){
|
||||||
return;
|
return;
|
||||||
@@ -198,13 +206,20 @@ export default defineComponent({
|
|||||||
isSetAsAdminChecked.value,
|
isSetAsAdminChecked.value,
|
||||||
isSetActivedChecked.value,
|
isSetActivedChecked.value,
|
||||||
);
|
);
|
||||||
acctMgmtStore.createNewAccount({
|
await acctMgmtStore.createNewAccount({
|
||||||
username: inputUserAccount.value,
|
username: inputUserAccount.value,
|
||||||
password: inputPwd.value,
|
password: inputPwd.value,
|
||||||
name: inputName.value,
|
name: inputName.value,
|
||||||
is_admin: isSetAsAdminChecked.value,
|
is_admin: isSetAsAdminChecked.value,
|
||||||
is_active: isSetActivedChecked.value,
|
is_active: isSetActivedChecked.value,
|
||||||
});
|
});
|
||||||
|
await toast.success(i18next.t("AcctMgmt.MsgAccountAdded"));
|
||||||
|
await modalStore.closeModal();
|
||||||
|
await router.push('/account/account-admin');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user