diff --git a/src/assets/core.scss b/src/assets/core.scss index e80fafa..8d2cea6 100644 --- a/src/assets/core.scss +++ b/src/assets/core.scss @@ -362,11 +362,13 @@ .icon-warning{ @apply text-red-500; font-size: 20px; - transform: translateY(5px); + //transform: translateY(5px); margin-right: 20px; } .ant-modal-confirm-title{ - font-size: 20px; + font-size: 1rem; + line-height: 1.5rem; + font-weight: 400; } .ant-modal-confirm-content{ margin-top: 10px; @@ -456,7 +458,20 @@ border-radius: 8px; } } - +.icon-language{ + @apply relative text-gray-500 p-1.5 hover:bg-[#e3eeff] hover:text-gray-600 rounded cursor-pointer text-xl; +} +@keyframes animation_loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +.icon-generating{ + animation: animation_loading 6s linear infinite; +} // 全局按钮 .page-action { @apply fixed right-10 bottom-10 flex flex-col gap-4 z-10; @@ -466,6 +481,7 @@ flex: 1; text-align: left; padding-left: 15px; + margin-right: 10px; } &:disabled { diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index 1303332..283e5ae 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -208,26 +208,26 @@ export const IconUnlock = ({style, className}: IconProps) => ( ) export const IconPlaying = ({style, className}: IconProps) => ( - ) export const IconGenerating = ({style, className}: IconProps) => ( - ) export const IconGenerateFailed = ({style, className}: IconProps) => ( - ) export const IconRegenerate = ({style, className}: IconProps) => ( - diff --git a/src/components/icons/language-switcher.tsx b/src/components/icons/language-switcher.tsx new file mode 100644 index 0000000..c72c0f7 --- /dev/null +++ b/src/components/icons/language-switcher.tsx @@ -0,0 +1,32 @@ +import useGlobalConfig from "@/hooks/useGlobalConfig.ts"; +import {useTranslation} from "react-i18next"; +import React from "react"; +import {useSearchParams} from "react-router-dom"; + +function LanguageSwitcher() { + const {i18n} = useTranslation(); + const [params] = useSearchParams(); + const {globalConfig} = useGlobalConfig() + const handleChangeLang = async () => { + const key = i18n.language == 'zh-CN' ? 'en-US' : 'zh-CN' + await i18n.changeLanguage(key) + globalConfig.i18n = key + localStorage.setItem('ai-human-lang',key) + } + return ( + (params.get('lang') == 'yes' || AppConfig.APP_LANG == 'multiple') ? +
+ + + + + +
: null + ) +} + +export default LanguageSwitcher \ No newline at end of file diff --git a/src/contexts/auth/index.tsx b/src/contexts/auth/index.tsx index f7fcbd5..d0dedbd 100644 --- a/src/contexts/auth/index.tsx +++ b/src/contexts/auth/index.tsx @@ -5,6 +5,10 @@ import {getAuthToken, setAuthToken} from "@/hooks/useAuth.ts"; import {auth} from "@/service/api/user.ts"; import {getAllCategory} from "@/service/api/article.ts"; import {BizError} from "@/service/types.ts"; +import {getRemainingDuration} from "@/service/api/order.ts"; +import {Modal} from "antd"; +import ModalWarning from "@/components/icons/ModalWarning.tsx"; +import {useTranslation} from "react-i18next"; const UserRoleStorageKey = 'user-current-role'; @@ -54,9 +58,9 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => { token } }) - }catch (e){ + } catch (e) { const err = e as BizError; - if(err.code == 1001){ + if (err.code == 1001) { // token失效 setAuthToken(null) dispatch({ diff --git a/src/i18n/translations/en-US.json b/src/i18n/translations/en-US.json index 9baeef3..4e91cd7 100644 --- a/src/i18n/translations/en-US.json +++ b/src/i18n/translations/en-US.json @@ -4,6 +4,7 @@ "cancel": "Cancel", "close": "Close", "confirm": { + "ok": "Confirm", "push_title": "Push Notice", "push_video": "Are you sure editing selected news?", "title": "Notice" @@ -145,6 +146,7 @@ "order_time": "Time stamp", "title": "Title" }, + "remaining_duration_warning": "Unable to generate videos due to insufficient remaining time?", "text": "Orders" }, "recycle": { @@ -185,6 +187,8 @@ "delete_description": "Are you sure you want to delete the video?", "delete_description_count": "Are you sure you want to delete these {{count}} videos?", "delete_empty": "Select the video you want to delete", + "delete_forever_confirm": "Do you want to permutely delete it?", + "delete_forever_confirm_count": "Do you want to permutely delete these videos?", "download": "Download", "generate_failed": "Generate Failed", "generating": "Generating", @@ -195,6 +199,8 @@ "push_failed": "some video streaming failed!", "push_success": "Streaming success,please goto \"Streaming\"!", "push_to_live": "Streaming", + "restore_confirm": "Do you want to restore it to the generating page?", + "restore_confirm_count": "Do you want to restore these videos to the generating page?", "rollback_confirm_title": "Are you sure you want to revert this video?", "sort_modify_confirm": "Are you change video sequence?", "sort_modify_failed": "Video sequence change failed", diff --git a/src/i18n/translations/zh-CN.json b/src/i18n/translations/zh-CN.json index 6ab835b..bc6a2f9 100644 --- a/src/i18n/translations/zh-CN.json +++ b/src/i18n/translations/zh-CN.json @@ -4,6 +4,7 @@ "cancel": "取消", "close": "关闭", "confirm": { + "ok": "确定", "push_title": "推流提示", "push_video": "是否确定一键推流选中新闻视频?", "title": "提示" @@ -145,6 +146,7 @@ "order_time": "下单时间", "title": "标题" }, + "remaining_duration_warning": "视频生成剩余时长为零,将无法生成视频,请尽快充值额度。", "text": "订单记录" }, "recycle": { @@ -185,6 +187,8 @@ "delete_description": "已选择{{count}}条,确定要全部删除吗?", "delete_description_count": "已选择{{count}}条,确定要全部删除吗?", "delete_empty": "请选择要删除的视频", + "delete_forever_confirm": "是否彻底删除选中的视频?
这些视频将无法找回", + "delete_forever_confirm_count": "是否彻底删除选中的视频?
这些视频将无法找回!", "download": "下载视频", "generate_failed": "生成失败", "generating": "生成中", @@ -195,6 +199,8 @@ "push_failed": "选择视频中有部分视频还在生成中无法推送,推流成功视频前往数字人直播间页面查看!", "push_success": "一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!", "push_to_live": "一键推流", + "restore_confirm": "是否将选中视频,还原到视频生成页?", + "restore_confirm_count": "是否将选中视频,还原到视频生成页", "rollback_confirm_title": "您确定要回退此视频吗?", "sort_modify_confirm": "是否采纳移动视频位置操作?", "sort_modify_failed": "调整视频顺序失败,请重试!", diff --git a/src/pages/recycle/index.tsx b/src/pages/recycle/index.tsx index 7d370c5..4ee15f1 100644 --- a/src/pages/recycle/index.tsx +++ b/src/pages/recycle/index.tsx @@ -177,8 +177,8 @@ export default function RecycleIndex() { emptyMessage={t('video.delete_empty')} confirmMessage={} onProcess={remove} >{t('recycle.remove_forever')}} @@ -188,6 +188,11 @@ export default function RecycleIndex() { className='bg-[#4096ff] hover:bg-blue-600 text-white' icon={} onProcess={restore} + confirmMessage={} emptyMessage={t('video.push_empty')} onError={e => { showToast(String((e as BizError).data || e.message), 'error') diff --git a/src/pages/video/components/button-push2room.tsx b/src/pages/video/components/button-push2room.tsx index 3bf973c..bf21931 100644 --- a/src/pages/video/components/button-push2room.tsx +++ b/src/pages/video/components/button-push2room.tsx @@ -32,6 +32,7 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[]; o return } Modal.confirm({ + wrapClassName:'root-modal-confirm', title: , icon: , content: t("video.push_confirm"), diff --git a/src/routes/index.tsx b/src/routes/index.tsx index bd63e34..06fd65f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,6 +1,6 @@ import {createBrowserRouter, RouterProvider,} from "react-router-dom"; -import {Suspense, useEffect,} from "react"; -import {ConfigProvider, App} from "antd"; +import React, {Suspense, useEffect,} from "react"; +import {ConfigProvider, App, Modal} from "antd"; import zhCN from 'antd/locale/zh_CN'; // for date-picker i18n import dayjs from "dayjs"; @@ -11,6 +11,8 @@ import routes from "@/routes/routes.tsx"; import {DocumentTitle} from "@/components/document.tsx"; import {useTranslation} from "react-i18next"; import useGlobalConfig from "@/hooks/useGlobalConfig.ts"; +import {getRemainingDuration} from "@/service/api/order.ts"; +import ModalWarning from "@/components/icons/ModalWarning.tsx"; const router = createBrowserRouter([ @@ -31,16 +33,39 @@ const router = createBrowserRouter([ const AppRouter = () => { const {globalConfig} = useGlobalConfig(); const {t,i18n} = useTranslation(); - + + const initRemainingDuration = () => { + getRemainingDuration().then(remain => { + if(remain > 0){ + Modal.warning({ + wrapClassName:'root-modal-confirm', + title: t('confirm.title'), + icon: , + content: t("order.remaining_duration_warning"), + okText: t('confirm.ok'), + centered: true + }) + } + console.log('remain', remain) + }) + } useEffect(() => { + if(i18n.language){ + if(i18n.language == 'multiple'){ + const lang = localStorage.getItem('ai-human-lang') || (navigator.language.toLocaleLowerCase().indexOf('cn') != -1 ? 'zh-CN' : 'en-US') + i18n.changeLanguage(lang).catch(console.log) + return; + } + } if (i18n && i18n.language == 'zh-CN') { dayjs.locale('zh-cn'); }else{ dayjs.locale('en') } + initRemainingDuration() globalConfig.i18n = i18n.language // i18n.changeLanguage(i18n).then(()=>console.log('change lang to ',i18n)) - }, [i18n]) + }, [i18n.language]) return ( { ) } export const BaseLayout: React.FC = ({children}) => { - const {i18n} = useTranslation(); - const [params] = useSearchParams(); + return (
@@ -93,15 +94,7 @@ export const BaseLayout: React.FC = ({children}) => {
- {(params.get('lang') == 'yes' || AppConfig.APP_LANG == 'multiple') &&
- { - i18n.language == 'zh-CN' ? ( - - ) : ( - - ) - } -
} +
diff --git a/src/service/api/order.ts b/src/service/api/order.ts index 4b0a328..bba99d6 100644 --- a/src/service/api/order.ts +++ b/src/service/api/order.ts @@ -1,11 +1,18 @@ import {post} from "@/service/request.ts"; +type OrderInfoData = DataList & { + remaining_duration: string | number; +} + export function getList(params: OrderSearchParam) { - return post<{ - list: OrderInfo[]; - remaining_duration: string | number; - }>('/order/list', { + return post('/order/list', { page_num: params.pagination.page || 1, page_size: params.pagination.limit || 10, }) +} + + +export async function getRemainingDuration() { + const result = await getList({pagination: {page: 1, limit: 1}}) + return Number(result.remaining_duration) } \ No newline at end of file diff --git a/src/service/request.ts b/src/service/request.ts index e7eeaef..6f3c397 100644 --- a/src/service/request.ts +++ b/src/service/request.ts @@ -12,6 +12,7 @@ const Axios = axios.create({ headers: {'Content-Type': JSON_FORMAT} }) +// eslint-disable-next-line react-hooks/rules-of-hooks const {globalConfig} = useGlobalConfig(); // 请求前拦截 Axios.interceptors.request.use(config => {