feat: 添加余额提醒
This commit is contained in:
parent
4e23bb623f
commit
605a769b89
@ -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 {
|
||||
|
@ -208,26 +208,26 @@ export const IconUnlock = ({style, className}: IconProps) => (
|
||||
)
|
||||
|
||||
export const IconPlaying = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
<svg className={`svg-icon ${className || ''} icon-playing`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 32 30" version="1.1">
|
||||
<path d="M1 11.7057V18.2943M7 6.76424V23.2358M13 1V29M19 7.22275V22.7772M25 11.1114V18.8886M31 13.3528V16.6472"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)
|
||||
export const IconGenerating = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
<svg className={`svg-icon ${className || ''} icon-generating`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 20 20" version="1.1">
|
||||
<path d="M3.463 2.43301C5.27751 0.860592 7.59897 -0.00342947 10 1.02307e-05C15.523 1.02307e-05 20 4.47701 20 10C20 12.136 19.33 14.116 18.19 15.74L15 10H18C18.0001 8.43163 17.5392 6.89781 16.6747 5.58927C15.8101 4.28072 14.5799 3.25517 13.1372 2.64013C11.6944 2.0251 10.1027 1.84771 8.55996 2.13003C7.0172 2.41234 5.59145 3.14191 4.46 4.22801L3.463 2.43301ZM16.537 17.567C14.7225 19.1394 12.401 20.0034 10 20C4.477 20 0 15.523 0 10C0 7.86401 0.67 5.88401 1.81 4.26001L5 10H2C1.99987 11.5684 2.46075 13.1022 3.32534 14.4108C4.18992 15.7193 5.42007 16.7449 6.86282 17.3599C8.30557 17.9749 9.89729 18.1523 11.44 17.87C12.9828 17.5877 14.4085 16.8581 15.54 15.772L16.537 17.567Z" fill="white"/>
|
||||
</svg>
|
||||
)
|
||||
export const IconGenerateFailed = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
<svg className={`svg-icon ${className || ''} icon-generate-fail`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 20 20" version="1.1">
|
||||
<path d="M18 0H2C0.9 0 0.00999999 0.9 0.00999999 2L0 20L4 16H18C19.1 16 20 15.1 20 14V2C20 0.9 19.1 0 18 0ZM11 12H9V10H11V12ZM11 8H9V4H11V8Z" fill="#FFA800"/>
|
||||
</svg>
|
||||
)
|
||||
export const IconRegenerate = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
<svg className={`svg-icon ${className || ''} icon-regenerate`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 24 24" version="1.1">
|
||||
<path d="M20.4728 3.525C19.3618 2.4074 18.0406 1.52056 16.5851 0.915578C15.1297 0.310592 13.5688 -0.000577199 11.9925 8.03759e-07C5.35835 8.03759e-07 0 5.37 0 12C0 18.63 5.35835 24 11.9925 24C17.591 24 22.2589 20.175 23.5947 15H20.4728C19.8545 16.7543 18.7067 18.2736 17.1878 19.3483C15.6688 20.4229 13.8536 21.0001 11.9925 21C7.02439 21 2.98687 16.965 2.98687 12C2.98687 7.035 7.02439 3 11.9925 3C14.4841 3 16.7054 4.035 18.3265 5.67L13.4934 10.5H24V8.03759e-07L20.4728 3.525Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
32
src/components/icons/language-switcher.tsx
Normal file
32
src/components/icons/language-switcher.tsx
Normal file
@ -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') ?
|
||||
<div className="icon-language" onClick={handleChangeLang}>
|
||||
<span className="hover:bg-gray-200">
|
||||
<svg
|
||||
className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em">
|
||||
<path
|
||||
d="M757.205333 473.173333c5.333333 0 10.453333 2.090667 14.250667 5.717334a19.029333 19.029333 0 0 1 5.888 13.738666v58.154667h141.184c11.093333 0 20.138667 8.704 20.138667 19.413333v232.704a19.797333 19.797333 0 0 1-20.138667 19.413334h-141.184v96.981333a19.754667 19.754667 0 0 1-20.138667 19.370667H716.8a20.565333 20.565333 0 0 1-14.250667-5.674667 19.029333 19.029333 0 0 1-5.888-13.696v-96.981333h-141.141333a20.565333 20.565333 0 0 1-14.250667-5.674667 19.029333 19.029333 0 0 1-5.930666-13.738667v-232.704c0-5.12 2.133333-10.112 5.930666-13.738666a20.565333 20.565333 0 0 1 14.250667-5.674667h141.141333v-58.154667c0-5.162667 2.133333-10.112 5.888-13.738666a20.565333 20.565333 0 0 1 14.250667-5.674667h40.362667zM192.597333 628.394667c22.272 0 40.32 17.365333 40.32 38.826666v38.741334c0 40.618667 32.512 74.368 74.624 77.397333l6.058667 0.213333h80.64c21.930667 0.469333 39.424 17.706667 39.424 38.784 0 21.077333-17.493333 38.314667-39.424 38.784H313.6c-89.088 0-161.28-69.461333-161.28-155.178666v-38.741334c0-21.461333 18.005333-38.826667 40.277333-38.826666z m504.106667 0h-80.64v116.394666h80.64v-116.394666z m161.28 0h-80.64v116.394666h80.64v-116.394666zM320.170667 85.333333c8.234667 0 15.658667 4.778667 18.773333 12.202667H338.773333l161.322667 387.84c2.517333 5.973333 1.706667 12.8-2.005333 18.090667a20.394667 20.394667 0 0 1-16.725334 8.533333h-43.52a20.181333 20.181333 0 0 1-18.688-12.202667L375.850667 395.648H210.901333l-43.264 104.149333A20.181333 20.181333 0 0 1 148.906667 512H105.514667a20.394667 20.394667 0 0 1-16.725334-8.533333 18.773333 18.773333 0 0 1-2.005333-18.090667l161.28-387.84A20.181333 20.181333 0 0 1 266.88 85.333333h53.290667zM716.8 162.901333c42.794667 0 83.84 16.341333 114.090667 45.44a152.234667 152.234667 0 0 1 47.232 109.738667v38.741333c-0.469333 21.077333-18.389333 37.930667-40.32 37.930667s-39.808-16.853333-40.32-37.930667v-38.741333c0-20.608-8.490667-40.32-23.637334-54.869333a82.304 82.304 0 0 0-57.045333-22.741334h-80.64c-21.888-0.469333-39.424-17.706667-39.424-38.784 0-21.077333 17.493333-38.314667 39.424-38.784h80.64z m-423.424 34.304L243.2 318.037333h100.48L293.418667 197.205333z"
|
||||
fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
|
||||
export default LanguageSwitcher
|
@ -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({
|
||||
|
@ -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",
|
||||
|
@ -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": "是否彻底删除选中的视频? <br />这些视频将无法找回",
|
||||
"delete_forever_confirm_count": "是否彻底删除选中的视频? <br />这些视频将无法找回!",
|
||||
"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": "调整视频顺序失败,请重试!",
|
||||
|
@ -177,8 +177,8 @@ export default function RecycleIndex() {
|
||||
emptyMessage={t('video.delete_empty')}
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html: checkedIdArray.length == 1
|
||||
? t('video.delete_confirm')
|
||||
: t('video.delete_confirm_count', {count: checkedIdArray.length})
|
||||
? t('video.delete_forever_confirm')
|
||||
: t('video.delete_forever_confirm_count', {count: checkedIdArray.length})
|
||||
}}></span>}
|
||||
onProcess={remove}
|
||||
>{t('recycle.remove_forever')}</ButtonBatch>}
|
||||
@ -188,6 +188,11 @@ export default function RecycleIndex() {
|
||||
className='bg-[#4096ff] hover:bg-blue-600 text-white'
|
||||
icon={<IconArrowRight className={'text-white'}/>}
|
||||
onProcess={restore}
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html: checkedIdArray.length == 1
|
||||
? t('video.restore_confirm')
|
||||
: t('video.restore_confirm_count', {count: checkedIdArray.length})
|
||||
}}></span>}
|
||||
emptyMessage={t('video.push_empty')}
|
||||
onError={e => {
|
||||
showToast(String((e as BizError).data || e.message), 'error')
|
||||
|
@ -32,6 +32,7 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[]; o
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
wrapClassName:'root-modal-confirm',
|
||||
title: <ModalWarning.Title/>,
|
||||
icon: <ModalWarning.Icon/>,
|
||||
content: t("video.push_confirm"),
|
||||
|
@ -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: <ModalWarning.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 (<ConfigProvider
|
||||
locale={i18n?.language?.toString() == 'zh-CN' ? zhCN : undefined}
|
||||
|
@ -13,6 +13,8 @@ import useAuth from "@/hooks/useAuth.ts";
|
||||
import {hidePhone} from "@/util/strings.ts";
|
||||
import {defaultCache} from "@/hooks/useCache.ts";
|
||||
import {IconOrderFill, IconRecycleFill} from "@/components/icons";
|
||||
import useGlobalConfig from "@/hooks/useGlobalConfig.ts";
|
||||
import LanguageSwitcher from "@/components/icons/language-switcher.tsx";
|
||||
|
||||
|
||||
type LayoutProps = {
|
||||
@ -83,8 +85,7 @@ const NavigationUserContainer = () => {
|
||||
</div>)
|
||||
}
|
||||
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||
const {i18n} = useTranslation();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
return (<div className={'dashboard-layout min-h-screen'}>
|
||||
<div className="min-h-screen w-full">
|
||||
<div className="app-header">
|
||||
@ -93,15 +94,7 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||
</div>
|
||||
<DashboardNavigation/>
|
||||
<div className="flex items-center">
|
||||
{(params.get('lang') == 'yes' || AppConfig.APP_LANG == 'multiple') && <div>
|
||||
{
|
||||
i18n.language == 'zh-CN' ? (
|
||||
<Button className="ml-2" onClick={() => i18n.changeLanguage('en-US')}>Change To EN</Button>
|
||||
) : (
|
||||
<Button className="ml-2" onClick={() => i18n.changeLanguage('zh-CN')}>显示中文</Button>
|
||||
)
|
||||
}
|
||||
</div>}
|
||||
<LanguageSwitcher />
|
||||
<NavigationUserContainer/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,18 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
type OrderInfoData = DataList<OrderInfo> & {
|
||||
remaining_duration: string | number;
|
||||
}
|
||||
|
||||
export function getList(params: OrderSearchParam) {
|
||||
return post<{
|
||||
list: OrderInfo[];
|
||||
remaining_duration: string | number;
|
||||
}>('/order/list', {
|
||||
return post<OrderInfoData>('/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)
|
||||
}
|
@ -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 => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user