vue3 infinite scroll

This commit is contained in:
Cindy Chang
2024-06-24 09:07:11 +08:00
parent 9b0d54bf5e
commit b55cc0a6d6
4 changed files with 90 additions and 46 deletions

View File

@@ -34,7 +34,8 @@
"ActivateNow": "Activate now.",
"DelteQuestion": "Are you sure to delete ?",
"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": {
"timeUsage": "Time Usage",

View File

@@ -5,7 +5,9 @@ export default defineStore('acctMgmtStore', {
state: () => ({
allUserAccoutList: [],
isAcctMenuOpen: false,
currentViewingUser: {}
currentViewingUser: {
detail: null,
}
}),
getters: {
},
@@ -85,10 +87,24 @@ export default defineStore('acctMgmtStore', {
try{
const response = await this.$axios.post(apiCreateAccount, userToCreate);
console.log('TODO: response:', response, response.status);
}
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.');
};
},
/**

View File

@@ -9,7 +9,7 @@
<SearchBar/>
</div>
<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"
>
<Column field="username" :header="i18next.t('AcctMgmt.Account')" bodyClass="font-medium" sortable>
@@ -149,7 +149,8 @@ export default {
const modalStore = useModalStore();
const { isLoading } = storeToRefs(loadingStore);
const loginStore = piniaLoginStore();
const internalInfiniteAcctData = ref([]);
const infiniteAcctData = ref([]);
const infiniteStart = ref(0);
const loginUserData = ref(null);
const allUserAccoutList = computed(() => acctMgmtStore.allUserAccoutList);
@@ -161,13 +162,13 @@ export default {
const moveCurrentLoginUserToFirstRow = () => {
const currentLoginUsername = loginUserData.value.username;
if(internalInfiniteAcctData.value && internalInfiniteAcctData.value.length){
const index = internalInfiniteAcctData.value.findIndex(user => user.username === currentLoginUsername);
if(infiniteAcctData.value && infiniteAcctData.value.length){
const index = infiniteAcctData.value.findIndex(user => user.username === currentLoginUsername);
if (index !== -1) {
// 移除匹配的對象(現正登入的使用者)並將其插入到陣列的第一位"
const [user] = internalInfiniteAcctData.value.splice(index, 1);
internalInfiniteAcctData.value.unshift(user);
const [user] = infiniteAcctData.value.splice(index, 1);
infiniteAcctData.value.unshift(user);
}
}
};
@@ -179,7 +180,7 @@ export default {
const getFirstPageUserData = async() => {
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);
@@ -234,34 +235,45 @@ export default {
/**
* 無限滾動: 監聽 scroll 有沒有滾到底部
* @param {element} event 滾動傳入的事件
scrollTop表示容器的垂直滾動位置。具體來說它是以像素為單位的數值
表示當前內容視窗(可見區域)的頂部距離整個可滾動內容的頂部的距離。
簡單來說scrollTop 指的是滾動條的位置當滾動條在最上面時scrollTop 為 0
當滾動條向下移動時scrollTop 會增加。
可是作為:我們目前已經滾動了多少。
clientHeight表示容器的可見高度不包括滾動條的高度。它是以像素為單位的數值
表示容器內部的可見區域的高度。
與 offsetHeight 不同的是clientHeight 不包含邊框、內邊距和滾動條的高度,只計算內容區域的高度。
scrollHeight表示容器內部的總內容高度。它是以像素為單位的數值
包括看不見的(需要滾動才能看到的)部分。
簡單來說scrollHeight 是整個可滾動內容的總高度,包括可見區域和需要滾動才能看到的部分。
*/
const handleScroll = (event) => {
if(internalInfiniteAcctData.value.length < ONCE_RENDER_NUM_OF_DATA || isInfiniteFinish.value === false) {
return;
}
const container = event.target;
const smallValue = 4;
const smallValue = 3;
const overScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight - smallValue;
if(overScrollHeight){
const isOverScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight - smallValue;
if(isOverScrollHeight){
fetchMoreDataVue3();
}
};
const fetchMoreDataVue3 = () => {
infiniteStart.value += ONCE_RENDER_NUM_OF_DATA;
internalInfiniteAcctData.value = [internalInfiniteAcctData.value, acctMgmtStore.allUserAccoutList.slice(
infiniteStart.value, this.infiniteStart + ONCE_RENDER_NUM_OF_DATA)];
isInfiniteFinish.value = true;
if(infiniteAcctData.value.length < acctMgmtStore.allUserAccoutList.length) {
infiniteAcctData.value = [...infiniteAcctData.value, ...acctMgmtStore.allUserAccoutList.slice(
infiniteStart.value, infiniteStart.value + ONCE_RENDER_NUM_OF_DATA)];
}
};
return {
isLoading,
modalStore,
loginUserData,
internalInfiniteAcctData,
infiniteAcctData,
onCreateNewClick,
handleScroll,
getRowClass,
@@ -286,7 +298,7 @@ export default {
return {
i18next: i18next,
repeatedAccountList: repeatedAccountList,
infiniteAcctData: [],
infiniteAcctDataVue2: [],
infiniteStart: 0,
isInfiniteFinish: true,
isInfinitMaxItemsMet: false,
@@ -311,7 +323,7 @@ export default {
* @param {element} 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;
}
@@ -332,7 +344,7 @@ export default {
this.infiniteFinish = false;
this.infiniteStart += ONCE_RENDER_NUM_OF_DATA;
// 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.isInfiniteFinish = true;
this.isLoading = false;

View File

@@ -119,6 +119,8 @@ import { defineComponent, computed, ref, watch, } from 'vue';
import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal.js';
import { useRouter } from 'vue-router';
import { useToast } from 'vue-toast-notification';
import useAcctMgmtStore from '@/stores/acctMgmt.js';
import ModalHeader from "./ModalHeader.vue";
import IconChecked from "@/components/icons/IconChecked.vue";
@@ -129,6 +131,9 @@ export default defineComponent({
const acctMgmtStore = useAcctMgmtStore();
const modalStore = useModalStore();
const router = useRouter();
const toast = useToast();
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isPwdConfirmEyeOn = ref(false);
@@ -184,27 +189,37 @@ export default defineComponent({
return validateResult && isPwdMatched.value;
}
const onConfirmBtnClick = () => {
const validateResult = validateConfirmPwd();
if(!validateResult){
return;
const onConfirmBtnClick = async () => {
switch(whichCurrentModal.value) {
case MODAL_CREATE_NEW:
const validateResult = validateConfirmPwd();
if(!validateResult){
return;
}
//TODO: 這邊要記得回來加一個判斷帳號是否已經存在的邏輯
checkAccountIsUnique();
console.log('input content to feed in',
inputUserAccount.value,
inputPwd.value,
inputName.value,
isSetAsAdminChecked.value,
isSetActivedChecked.value,
);
await acctMgmtStore.createNewAccount({
username: inputUserAccount.value,
password: inputPwd.value,
name: inputName.value,
is_admin: isSetAsAdminChecked.value,
is_active: isSetActivedChecked.value,
});
await toast.success(i18next.t("AcctMgmt.MsgAccountAdded"));
await modalStore.closeModal();
await router.push('/account/account-admin');
break;
default:
break;
}
//TODO: 這邊要記得回來加一個判斷帳號是否已經存在的邏輯
checkAccountIsUnique();
console.log('input content to feed in',
inputUserAccount.value,
inputPwd.value,
inputName.value,
isSetAsAdminChecked.value,
isSetActivedChecked.value,
);
acctMgmtStore.createNewAccount({
username: inputUserAccount.value,
password: inputPwd.value,
name: inputName.value,
is_admin: isSetAsAdminChecked.value,
is_active: isSetActivedChecked.value,
});
}