vue3 infinite scroll
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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.');
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user