From aa2c2cd2f756dbde033c8a26aebb27b083104b21 Mon Sep 17 00:00:00 2001 From: Germey Date: Sat, 6 Dec 2025 23:49:38 +0800 Subject: [PATCH] Update loading more experience (#292) --- ...-16e884e3-a345-4751-8ca2-d39a462cf2aa.json | 7 +++ src/components/common/ScrollList.vue | 51 +++++++++++++++ src/components/common/TopLoading.vue | 62 +++++++++++++++++++ src/components/nanobanana/RecentPanel.vue | 28 ++++++--- src/i18n/ar/common.json | 6 +- src/i18n/de/common.json | 6 +- src/i18n/el/common.json | 6 +- src/i18n/en/common.json | 6 +- src/i18n/es/common.json | 6 +- src/i18n/fi/common.json | 6 +- src/i18n/fr/common.json | 6 +- src/i18n/it/common.json | 6 +- src/i18n/ja/common.json | 6 +- src/i18n/ko/common.json | 6 +- src/i18n/pl/common.json | 6 +- src/i18n/pt/common.json | 6 +- src/i18n/ru/common.json | 6 +- src/i18n/sr/common.json | 6 +- src/i18n/sv/common.json | 6 +- src/i18n/uk/common.json | 6 +- src/i18n/zh-CN/common.json | 4 ++ src/i18n/zh-TW/common.json | 6 +- src/pages/nanobanana/Index.vue | 60 ++++++++++++++---- src/store/nanobanana/actions.ts | 6 ++ src/store/nanobanana/mutations.ts | 12 +++- 25 files changed, 291 insertions(+), 41 deletions(-) create mode 100644 change/@acedatacloud-nexior-16e884e3-a345-4751-8ca2-d39a462cf2aa.json create mode 100644 src/components/common/ScrollList.vue create mode 100644 src/components/common/TopLoading.vue diff --git a/change/@acedatacloud-nexior-16e884e3-a345-4751-8ca2-d39a462cf2aa.json b/change/@acedatacloud-nexior-16e884e3-a345-4751-8ca2-d39a462cf2aa.json new file mode 100644 index 00000000..89b8fe15 --- /dev/null +++ b/change/@acedatacloud-nexior-16e884e3-a345-4751-8ca2-d39a462cf2aa.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "update loading more", + "packageName": "@acedatacloud/nexior", + "email": "cqc@cuiqingcai.com", + "dependentChangeType": "patch" +} diff --git a/src/components/common/ScrollList.vue b/src/components/common/ScrollList.vue new file mode 100644 index 00000000..5fc770c5 --- /dev/null +++ b/src/components/common/ScrollList.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/common/TopLoading.vue b/src/components/common/TopLoading.vue new file mode 100644 index 00000000..73e5e51b --- /dev/null +++ b/src/components/common/TopLoading.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/components/nanobanana/RecentPanel.vue b/src/components/nanobanana/RecentPanel.vue index 67af2f54..2dbadf73 100644 --- a/src/components/nanobanana/RecentPanel.vue +++ b/src/components/nanobanana/RecentPanel.vue @@ -2,9 +2,15 @@
-
+ -
+
@@ -15,13 +21,21 @@ import { defineComponent } from 'vue'; import TaskPreview from './task/Preview.vue'; import BotPlaceholder from '../common/BotPlaceholder.vue'; import NoTasks from '@/components/common/NoTasks.vue'; +import ScrollList from '@/components/common/ScrollList.vue'; export default defineComponent({ name: 'RecentPanel', components: { TaskPreview, BotPlaceholder, - NoTasks + NoTasks, + ScrollList + }, + props: { + loading: { + type: Boolean, + default: false + } }, emits: ['reach-top'], data() { @@ -38,11 +52,9 @@ export default defineComponent({ } }, methods: { - onHandleScroll() { - const el = this.$refs.panel as HTMLElement; - if (el.scrollTop === 0) { - this.$emit('reach-top'); - } + getScrollElement(): HTMLElement | undefined { + const list = this.$refs.scrollList as any; + return list?.getScrollElement?.(); } } }); diff --git a/src/i18n/ar/common.json b/src/i18n/ar/common.json index 2cde62f6..51cc776a 100644 --- a/src/i18n/ar/common.json +++ b/src/i18n/ar/common.json @@ -187,6 +187,10 @@ "message": "الحالة", "description": "حالة الكيان" }, + "status.loading": { + "message": "جارٍ التحميل...", + "description": "نص الحالة المعروض أثناء تحميل المحتوى" + }, "entity.operation": { "message": "العملية", "description": "عملية الكيان" @@ -575,4 +579,4 @@ "message": "Claude", "description": "نص في شريط التنقل في الموقع لعرض صفحة Claude، يجب أن يبقى كما هو 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/de/common.json b/src/i18n/de/common.json index 7c6ad776..840c10c9 100644 --- a/src/i18n/de/common.json +++ b/src/i18n/de/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "Der Status der Entität" }, + "status.loading": { + "message": "Wird geladen...", + "description": "Statustext, der während des Ladens angezeigt wird" + }, "entity.operation": { "message": "Operation", "description": "Die Operation der Entität" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Text in der Navigationsleiste der Website, um die Claude-Seite anzuzeigen, muss als 'Claude' bleiben" } -} \ No newline at end of file +} diff --git a/src/i18n/el/common.json b/src/i18n/el/common.json index f0c7aba8..6e0d5c61 100644 --- a/src/i18n/el/common.json +++ b/src/i18n/el/common.json @@ -187,6 +187,10 @@ "message": "Κατάσταση", "description": "Η κατάσταση της οντότητας" }, + "status.loading": { + "message": "Φόρτωση...", + "description": "Κείμενο κατάστασης που εμφανίζεται κατά τη φόρτωση περιεχομένου" + }, "entity.operation": { "message": "Λειτουργία", "description": "Η λειτουργία της οντότητας" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Κείμενο στη γραμμή πλοήγησης του ιστότοπου για να εμφανιστεί η σελίδα Claude, πρέπει να παραμείνει ως 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json index e890811f..67121257 100644 --- a/src/i18n/en/common.json +++ b/src/i18n/en/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "The status of the entity" }, + "status.loading": { + "message": "Loading...", + "description": "Status text shown while content is loading" + }, "entity.operation": { "message": "Operation", "description": "The operation of the entity" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Text in the website navigation bar to display the Claude page, must remain as 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/es/common.json b/src/i18n/es/common.json index 1ceee845..91cc8fc2 100644 --- a/src/i18n/es/common.json +++ b/src/i18n/es/common.json @@ -187,6 +187,10 @@ "message": "Estado", "description": "El estado de la entidad" }, + "status.loading": { + "message": "Cargando...", + "description": "Texto de estado que se muestra mientras se carga el contenido" + }, "entity.operation": { "message": "Operación", "description": "La operación de la entidad" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Texto en la barra de navegación del sitio web para mostrar la página de Claude, debe permanecer como 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/fi/common.json b/src/i18n/fi/common.json index 3a221fdf..59311540 100644 --- a/src/i18n/fi/common.json +++ b/src/i18n/fi/common.json @@ -187,6 +187,10 @@ "message": "Tila", "description": "Entiteetin tila" }, + "status.loading": { + "message": "Ladataan...", + "description": "Tilateksti, joka näytetään sisällön latautuessa" + }, "entity.operation": { "message": "Toiminto", "description": "Entiteetin toiminto" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Teksti verkkosivuston navigointipalkissa, joka näyttää Claude-sivun, on säilytettävä 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/fr/common.json b/src/i18n/fr/common.json index 5882aa38..661236e1 100644 --- a/src/i18n/fr/common.json +++ b/src/i18n/fr/common.json @@ -187,6 +187,10 @@ "message": "Statut", "description": "Le statut de l'entité" }, + "status.loading": { + "message": "Chargement...", + "description": "Texte de statut affiché pendant le chargement du contenu" + }, "entity.operation": { "message": "Opération", "description": "L'opération de l'entité" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Texte dans la barre de navigation du site pour afficher la page Claude, doit rester 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/it/common.json b/src/i18n/it/common.json index 7cdd48c8..6cd64028 100644 --- a/src/i18n/it/common.json +++ b/src/i18n/it/common.json @@ -187,6 +187,10 @@ "message": "Stato", "description": "Lo stato dell'entità" }, + "status.loading": { + "message": "Caricamento...", + "description": "Testo di stato mostrato durante il caricamento dei contenuti" + }, "entity.operation": { "message": "Operazione", "description": "L'operazione dell'entità" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Testo nella barra di navigazione del sito web per visualizzare la pagina Claude, deve rimanere come 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/ja/common.json b/src/i18n/ja/common.json index 5d13efa9..4d089e1e 100644 --- a/src/i18n/ja/common.json +++ b/src/i18n/ja/common.json @@ -187,6 +187,10 @@ "message": "ステータス", "description": "エンティティのステータス" }, + "status.loading": { + "message": "読み込み中...", + "description": "コンテンツ読込中に表示されるステータス文言" + }, "entity.operation": { "message": "操作", "description": "エンティティの操作" @@ -575,4 +579,4 @@ "message": "Claude", "description": "ウェブサイトのナビゲーションバーに表示されるClaudeページのテキスト、'Claude'のままにする必要があります" } -} \ No newline at end of file +} diff --git a/src/i18n/ko/common.json b/src/i18n/ko/common.json index 8feb1ddb..fd114c55 100644 --- a/src/i18n/ko/common.json +++ b/src/i18n/ko/common.json @@ -187,6 +187,10 @@ "message": "상태", "description": "엔티티의 상태" }, + "status.loading": { + "message": "로딩 중...", + "description": "콘텐츠가 로딩될 때 표시되는 상태 문구" + }, "entity.operation": { "message": "작업", "description": "엔티티의 작업" @@ -575,4 +579,4 @@ "message": "Claude", "description": "웹사이트 내비게이션 바에서 Claude 페이지를 표시하는 텍스트, 'Claude'로 유지해야 함" } -} \ No newline at end of file +} diff --git a/src/i18n/pl/common.json b/src/i18n/pl/common.json index f95373e7..d18e8a13 100644 --- a/src/i18n/pl/common.json +++ b/src/i18n/pl/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "Status encji" }, + "status.loading": { + "message": "Ładowanie...", + "description": "Tekst statusu wyświetlany podczas wczytywania treści" + }, "entity.operation": { "message": "Operacja", "description": "Operacja encji" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Tekst w pasku nawigacyjnym strony internetowej, aby wyświetlić stronę Claude, musi pozostać jako 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/pt/common.json b/src/i18n/pt/common.json index b5ba88ff..a83aeec2 100644 --- a/src/i18n/pt/common.json +++ b/src/i18n/pt/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "Status da entidade" }, + "status.loading": { + "message": "Carregando...", + "description": "Texto de status exibido enquanto o conteúdo é carregado" + }, "entity.operation": { "message": "Operação", "description": "Operação da entidade" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Texto na barra de navegação do site para exibir a página Claude, deve permanecer como 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/ru/common.json b/src/i18n/ru/common.json index 77c02d68..892eddf8 100644 --- a/src/i18n/ru/common.json +++ b/src/i18n/ru/common.json @@ -187,6 +187,10 @@ "message": "Статус", "description": "Статус сущности" }, + "status.loading": { + "message": "Загрузка...", + "description": "Текст статуса, отображаемый во время загрузки контента" + }, "entity.operation": { "message": "Операция", "description": "Операция сущности" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Текст в навигационной панели сайта для отображения страницы Claude, должен оставаться как 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/sr/common.json b/src/i18n/sr/common.json index 4440e713..aa6c536f 100644 --- a/src/i18n/sr/common.json +++ b/src/i18n/sr/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "Status entiteta" }, + "status.loading": { + "message": "Учитавање...", + "description": "Текст статуса који се приказује док се садржај учитава" + }, "entity.operation": { "message": "Operacija", "description": "Operacija entiteta" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Tekst u navigacionoj traci sajta za prikaz Claude stranice, mora ostati kao 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/sv/common.json b/src/i18n/sv/common.json index ee927487..dd0a2615 100644 --- a/src/i18n/sv/common.json +++ b/src/i18n/sv/common.json @@ -187,6 +187,10 @@ "message": "Status", "description": "Status för enheten" }, + "status.loading": { + "message": "Laddar...", + "description": "Statustext som visas medan innehållet laddas" + }, "entity.operation": { "message": "Operation", "description": "Operationen för enheten" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Text i webbplatsens navigeringsfält för att visa Claude-sidan, måste förbli som 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/uk/common.json b/src/i18n/uk/common.json index d5a262e0..a9963965 100644 --- a/src/i18n/uk/common.json +++ b/src/i18n/uk/common.json @@ -187,6 +187,10 @@ "message": "Статус", "description": "Статус сутності" }, + "status.loading": { + "message": "Завантаження...", + "description": "Текст статусу, що показується під час завантаження контенту" + }, "entity.operation": { "message": "Операція", "description": "Операція сутності" @@ -575,4 +579,4 @@ "message": "Claude", "description": "Текст у навігаційній панелі сайту для відображення сторінки Claude, має залишатися 'Claude'" } -} \ No newline at end of file +} diff --git a/src/i18n/zh-CN/common.json b/src/i18n/zh-CN/common.json index 34ec7ad8..7d3a8c90 100644 --- a/src/i18n/zh-CN/common.json +++ b/src/i18n/zh-CN/common.json @@ -187,6 +187,10 @@ "message": "状态", "description": "实体的状态" }, + "status.loading": { + "message": "加载中...", + "description": "内容加载时显示的状态文案" + }, "entity.operation": { "message": "操作", "description": "实体的操作" diff --git a/src/i18n/zh-TW/common.json b/src/i18n/zh-TW/common.json index 99ad25d0..89a96245 100644 --- a/src/i18n/zh-TW/common.json +++ b/src/i18n/zh-TW/common.json @@ -187,6 +187,10 @@ "message": "狀態", "description": "實體的狀態" }, + "status.loading": { + "message": "載入中...", + "description": "內容載入時顯示的狀態文案" + }, "entity.operation": { "message": "操作", "description": "實體的操作" @@ -575,4 +579,4 @@ "message": "Claude", "description": "網站導航欄中的文本,用於顯示Claude頁面,必須保持為'Claude'" } -} \ No newline at end of file +} diff --git a/src/pages/nanobanana/Index.vue b/src/pages/nanobanana/Index.vue index ab4b7ac3..f4f11661 100644 --- a/src/pages/nanobanana/Index.vue +++ b/src/pages/nanobanana/Index.vue @@ -4,7 +4,7 @@ @@ -25,6 +25,7 @@ const CALLBACK_URL = 'https://webhook.acedata.cloud/nanobanana'; interface IData { task: INanobananaTask | undefined; job: number; + loading: boolean; } export default defineComponent({ @@ -38,13 +39,17 @@ export default defineComponent({ data(): IData { return { task: undefined, - job: 0 + job: 0, + loading: false }; }, computed: { - loading() { + applicationsLoading() { return this.$store.state.nanobanana?.status?.getApplications === Status.Request; }, + tasksLoading() { + return this.$store.state.nanobanana?.status?.getTasks === Status.Request; + }, credential() { return this.$store.state.nanobanana?.credential; }, @@ -90,9 +95,35 @@ export default defineComponent({ methods: { async onReachTop() { console.debug('reached top'); - await this.onGetTasks({ - createdAtMax: this.tasks?.items?.[0]?.created_at - }); + if (this.loading || this.tasksLoading) { + return; + } + const total = this.tasks?.total; + const currentLength = this.tasks?.items?.length || 0; + if (total !== undefined && total <= currentLength) { + return; + } + const oldest = this.tasks?.items?.[0]; + if (!oldest?.created_at) { + return; + } + const panel = this.$refs.recentPanel as any; + const el = panel?.getScrollElement?.() as HTMLElement | undefined; + const previousHeight = el?.scrollHeight || 0; + const previousScrollTop = el?.scrollTop || 0; + this.loading = true; + try { + await this.onGetTasks({ + createdAtMax: oldest.created_at + }); + await this.$nextTick(); + if (el) { + const newHeight = el.scrollHeight; + el.scrollTop = newHeight - previousHeight + previousScrollTop; + } + } finally { + this.loading = false; + } }, async onGetService() { console.debug('start onGetService'); @@ -106,15 +137,14 @@ export default defineComponent({ await this.onGetTasks(); }, async onScrollDown() { - setTimeout(() => { - const el = document.querySelector('.tasks'); - if (el) { - el.scrollTop = el.scrollHeight; - } - }, 1000); + await this.$nextTick(); + const el = this.getTasksScrollElement(); + if (el) { + el.scrollTop = el.scrollHeight; + } }, async onGetTasks(payload?: { limit?: number; createdAtMin?: number; createdAtMax?: number }) { - if (this.loading) { + if (this.applicationsLoading || this.tasksLoading) { console.debug('loading'); return; } @@ -176,6 +206,10 @@ export default defineComponent({ await this.onScrollDown(); }, 1000); }); + }, + getTasksScrollElement(): HTMLElement | undefined { + const panel = this.$refs.recentPanel as any; + return panel?.getScrollElement?.(); } } }); diff --git a/src/store/nanobanana/actions.ts b/src/store/nanobanana/actions.ts index 44631fcc..711c08a5 100644 --- a/src/store/nanobanana/actions.ts +++ b/src/store/nanobanana/actions.ts @@ -129,18 +129,22 @@ export const getTasks = async ( createdAtMax }: { offset?: number; limit?: number; createdAtMin?: number; createdAtMax?: number } ): Promise => { + state.status.getTasks = Status.Request; return new Promise((resolve, reject) => { console.debug('start to get tasks', offset, limit); const credential = state.credential; console.debug('current credential', credential); const token = credential?.token; if (!token) { + state.status.getTasks = Status.Error; return reject('no token'); } nanobananaOperator .tasks( { userId: rootState?.user?.id, + offset, + limit, createdAtMin, createdAtMax, type: 'images' @@ -158,9 +162,11 @@ export const getTasks = async ( const mergedItems = mergeAndSortLists(existingItems, newItems); commit('setTasksItems', mergedItems); commit('setTasksTotal', response.data.count); + state.status.getTasks = Status.Success; resolve(response.data.items); }) .catch((error) => { + state.status.getTasks = Status.Error; return reject(error); }); }); diff --git a/src/store/nanobanana/mutations.ts b/src/store/nanobanana/mutations.ts index 60c40f11..c02b12e3 100644 --- a/src/store/nanobanana/mutations.ts +++ b/src/store/nanobanana/mutations.ts @@ -2,6 +2,12 @@ import { IApplication, ICredential, INanobananaConfig, INanobananaTask, IService import initialState from './state'; import { INanobananaState } from './models'; +const defaultTasks = { + items: undefined, + total: undefined, + active: undefined +}; + export const resetAll = (state: INanobananaState): void => { Object.assign(state, initialState()); }; @@ -28,7 +34,7 @@ export const setConfig = (state: INanobananaState, payload: INanobananaConfig): export const setTasksItems = (state: INanobananaState, payload: INanobananaTask[]): void => { const newPayload = { - ...state.tasks, + ...(state.tasks || defaultTasks), items: payload } as typeof state.tasks; state.tasks = newPayload; @@ -36,7 +42,7 @@ export const setTasksItems = (state: INanobananaState, payload: INanobananaTask[ export const setTasksTotal = (state: INanobananaState, payload: number): void => { const newPayload = { - ...state.tasks, + ...(state.tasks || defaultTasks), total: payload } as typeof state.tasks; state.tasks = newPayload; @@ -44,7 +50,7 @@ export const setTasksTotal = (state: INanobananaState, payload: number): void => export const setTasksActive = (state: INanobananaState, payload: INanobananaTask): void => { const newPayload = { - ...state.tasks, + ...(state.tasks || defaultTasks), active: payload } as typeof state.tasks; state.tasks = newPayload;