diff --git a/app/admin/bot/list/page.tsx b/app/admin/bot/list/page.tsx index dee163f..4ba8ec9 100644 --- a/app/admin/bot/list/page.tsx +++ b/app/admin/bot/list/page.tsx @@ -8,6 +8,7 @@ import { getBotListInServer } from '@/app/admin/bot/action'; import { Button, Skeleton, Input, Empty, Dropdown, message, Modal, Upload } from 'antd'; import { useTranslations } from 'next-intl'; import CustomPagination from '@/app/components/CustomPagination'; +import BackToTop from '@/app/components/BackToTop'; import { debounce } from 'lodash'; import { UploadProps } from 'antd'; @@ -180,121 +181,135 @@ const AdminBotList = () => { ]; return ( -
-
-
-

智能体管理

-
- - + + + + + + + +
+
+ +
+ {t('allUsersCanView')} + - - -
+ +
+
+ } + value={searchQuery} + onChange={handleSearchChange} + size="large" + className="rounded-lg" + style={{ + borderRadius: '8px', + border: '1px solid #e5e7eb', + }} + /> +
+ {searchQuery && ( + - - - - -
-
- -
- 所有用户在 - - 页面都可以查看和使用以下的智能体。 -
- -
-
- } - value={searchQuery} - onChange={handleSearchChange} - size="large" - className="rounded-lg" - style={{ - borderRadius: '8px', - border: '1px solid #e5e7eb', - }} - /> + )}
- {searchQuery && ( - - )}
- {isPending ? -
- - - - -
- : - <> - {botList.length === 0 ? ( -
- - {searchQuery ? `未找到包含「${searchQuery}」的智能体` : '暂无智能体'} - - } - > - {!searchQuery && ( - - - - )} - + +
+
+ {isPending ? +
+ {Array.from({ length: pageSize }, (_, index) => ( + + ))}
- ) : ( + : <> -
- {botList.map((item, index) => ( - - ))} -
- + {botList.length === 0 ? ( +
+ + {searchQuery ? `${t('notFoundWithQuery')}「${searchQuery}」的智能体` : t('noBots')} + + } + > + {!searchQuery && ( + + + + )} + +
+ ) : ( +
+ {botList.map((item) => ( + + ))} +
+ )} - )} - - } + } +
+
+ + {/* 翻页组件固定在底部 */} + {!isPending && botList.length > 0 && ( +
+
+ +
+
+ )} + +
) }; diff --git a/app/chat/bot/discover/page.tsx b/app/chat/bot/discover/page.tsx index 9a662a7..9bc1d85 100644 --- a/app/chat/bot/discover/page.tsx +++ b/app/chat/bot/discover/page.tsx @@ -10,6 +10,7 @@ import { useSession } from 'next-auth/react'; import { useLoginModal } from '@/app/contexts/loginModalContext'; import { useTranslations } from 'next-intl'; import CustomPagination from '@/app/components/CustomPagination'; +import BackToTop from '@/app/components/BackToTop'; import { debounce } from 'lodash'; const BotDiscover = () => { @@ -84,94 +85,107 @@ const BotDiscover = () => { }; return ( -
-
-
-

{t('discoverBots')}

- - - -
- -
-
- } - value={searchQuery} - onChange={handleSearchChange} - size="large" - className="rounded-lg" - style={{ - borderRadius: '8px', - border: '1px solid #e5e7eb', - }} - /> +
+
+
+
+

{t('discoverBots')}

+ + + +
+ +
+
+ } + value={searchQuery} + onChange={handleSearchChange} + size="large" + className="rounded-lg" + style={{ + borderRadius: '8px', + border: '1px solid #e5e7eb', + }} + /> +
+ {searchQuery && ( + + )}
- {searchQuery && ( - - )}
- {isPending ? -
- - - - -
- : - <> - {botList.length === 0 ? ( -
- - {searchQuery ? `未找到包含「${searchQuery}」的智能体` : '暂无智能体'} - - } - > - {!searchQuery && ( - - - - )} - +
+
+ {isPending ? +
+ {Array.from({ length: pageSize }, (_, index) => ( + + ))}
- ) : ( + : <> -
- {botList.map((item, index) => ( - - ))} -
- + {botList.length === 0 ? ( +
+ + {searchQuery ? `${t('notFoundWithQuery')}「${searchQuery}」的智能体` : t('noBots')} + + } + > + {!searchQuery && ( + + + + )} + +
+ ) : ( +
+ {botList.map((item) => ( + + ))} +
+ )} - )} - - } + } +
+
+ + {/* 翻页组件固定在底部 */} + {!isPending && botList.length > 0 && ( +
+
+ +
+
+ )} + +
) }; diff --git a/app/components/BackToTop.tsx b/app/components/BackToTop.tsx new file mode 100644 index 0000000..fb435ec --- /dev/null +++ b/app/components/BackToTop.tsx @@ -0,0 +1,112 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { UpOutlined } from '@ant-design/icons'; +import { useTranslations } from 'next-intl'; + +interface BackToTopProps { + scrollThreshold?: number; + target?: string | (() => HTMLElement | null); + className?: string; +} + +const BackToTop: React.FC = ({ + scrollThreshold = 300, + target, + className = '' +}) => { + const t = useTranslations('Chat'); + const [visible, setVisible] = useState(false); + + useEffect(() => { + const getScrollElement = () => { + if (typeof target === 'string') { + return document.querySelector(target) as HTMLElement; + } else if (typeof target === 'function') { + return target(); + } + return window; + }; + + const handleScroll = () => { + const scrollElement = getScrollElement(); + let scrollTop = 0; + + if (scrollElement === window) { + scrollTop = window.pageYOffset || document.documentElement.scrollTop; + } else { + scrollTop = (scrollElement as HTMLElement)?.scrollTop || 0; + } + + setVisible(scrollTop > scrollThreshold); + }; + + const scrollElement = getScrollElement(); + + if (scrollElement) { + scrollElement.addEventListener('scroll', handleScroll); + // Check initial scroll position + handleScroll(); + } + + return () => { + if (scrollElement) { + scrollElement.removeEventListener('scroll', handleScroll); + } + }; + }, [scrollThreshold, target]); + + const scrollToTop = () => { + const getScrollElement = () => { + if (typeof target === 'string') { + return document.querySelector(target) as HTMLElement; + } else if (typeof target === 'function') { + return target(); + } + return window; + }; + + const scrollElement = getScrollElement(); + + if (scrollElement === window) { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } else { + (scrollElement as HTMLElement)?.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } + }; + + if (!visible) { + return null; + } + + return ( + + ); +}; + +export default BackToTop; \ No newline at end of file diff --git a/app/components/CustomPagination.tsx b/app/components/CustomPagination.tsx index 272a41b..8a420a5 100644 --- a/app/components/CustomPagination.tsx +++ b/app/components/CustomPagination.tsx @@ -1,6 +1,7 @@ 'use client'; import { Pagination } from 'antd'; import { useTranslations } from 'next-intl'; +import { useState, useEffect } from 'react'; interface CustomPaginationProps { current: number; @@ -24,6 +25,17 @@ const CustomPagination = ({ showSizeChanger = true }: CustomPaginationProps) => { const t = useTranslations('Common'); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 640); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); if (total <= 0) return null; @@ -31,236 +43,43 @@ const CustomPagination = ({ const endItem = Math.min(current * pageSize, total); return ( -
-
- {t('total')} {total} {t('items')}, - {t('currentDisplay')} {startItem} {t('to')} {endItem} +
+
+ + {t('total')} {total} {t('items')}, + {t('currentDisplay')} {startItem} {t('to')} {endItem} + + + {startItem}-{endItem} / {total} +
-
- `${range[0]}-${range[1]} ${t('itemsPerPage')}` - } + showQuickJumper={false} + showTotal={!isMobile ? (total, range) => `${range[0]}-${range[1]} ${t('itemsPerPage')}` : undefined} + size={isMobile ? 'small' : 'default'} + simple={isMobile && total > pageSize * 10} locale={{ items_per_page: `${t('itemsPerPage')}`, jump_to: t('jumpTo'), jump_to_confirm: t('confirm'), page: t('page'), - prev_page: '上一页', - next_page: '下一页', - prev_5: '向前 5 页', - next_5: '向后 5 页', - prev_3: '向前 3 页', - next_3: '向后 3 页' + prev_page: t('previousPage'), + next_page: t('nextPage'), + prev_5: t('previous5Pages'), + next_5: t('next5Pages'), + prev_3: t('previous3Pages'), + next_3: t('next3Pages') }} /> -
- -
); }; diff --git a/locales/en.json b/locales/en.json index 3249d93..dc9b290 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,7 +14,13 @@ "perPage": "per page", "jumpTo": "Jump to", "page": "page", - "itemsPerPage": "per page" + "itemsPerPage": "per page", + "previousPage": "Previous page", + "nextPage": "Next page", + "previous5Pages": "Previous 5 pages", + "next5Pages": "Next 5 pages", + "previous3Pages": "Previous 3 pages", + "next3Pages": "Next 3 pages" }, "Auth": { "login": "Login", @@ -162,7 +168,17 @@ "editBotTitle": "Edit Bot", "editSuccess": "Edit successful", "editFailed": "Edit failed", - "editFailedRetry": "Edit failed, please retry" + "editFailedRetry": "Edit failed, please retry", + "backToTop": "Back to top", + "clear": "Clear", + "searchPlaceholder": "Search bot name or description...", + "notFoundWithQuery": "No bots found containing", + "noBots": "No bots available", + "createFirstBot": "Create your first bot", + "botManagement": "Bot Management", + "allUsersCanView": "All users can view and use the following bots on the", + "discoverBotsPage": "Discover Bots", + "page": "page" }, "Settings": { "accountSettings": "Account", diff --git a/locales/zh.json b/locales/zh.json index 7349d11..b8d184e 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -14,7 +14,13 @@ "perPage": "条/页", "jumpTo": "跳至", "page": "页", - "itemsPerPage": "条/页" + "itemsPerPage": "条/页", + "previousPage": "上一页", + "nextPage": "下一页", + "previous5Pages": "向前 5 页", + "next5Pages": "向后 5 页", + "previous3Pages": "向前 3 页", + "next3Pages": "向后 3 页" }, "Auth": { "login": "登录", @@ -162,7 +168,17 @@ "editBotTitle": "编辑智能体", "editSuccess": "编辑成功", "editFailed": "编辑失败", - "editFailedRetry": "编辑失败,请重试" + "editFailedRetry": "编辑失败,请重试", + "backToTop": "回到顶部", + "clear": "清除", + "searchPlaceholder": "搜索智能体名称或描述...", + "notFoundWithQuery": "未找到包含", + "noBots": "暂无智能体", + "createFirstBot": "创建第一个智能体", + "botManagement": "智能体管理", + "allUsersCanView": "所有用户在", + "discoverBotsPage": "「发现智能体」", + "page": "页面都可以查看和使用以下的智能体。" }, "Settings": { "accountSettings": "账号设置",