feat: Improve route handling and dynamic chat navigation in SiderBar
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { lazy, Suspense, useContext, useEffect } from 'react';
|
import React, { lazy, Suspense, useContext, useEffect } from 'react';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||||
import Loading from './components/Loading';
|
import Loading from './components/Loading';
|
||||||
import User from './pages/User';
|
import User from './pages/User';
|
||||||
import { PrivateRoute } from './components/PrivateRoute';
|
import { PrivateRoute } from './components/PrivateRoute';
|
||||||
@@ -8,10 +8,8 @@ import LoginForm from './components/LoginForm';
|
|||||||
import NotFound from './pages/NotFound';
|
import NotFound from './pages/NotFound';
|
||||||
import Setting from './pages/Setting';
|
import Setting from './pages/Setting';
|
||||||
import EditUser from './pages/User/EditUser';
|
import EditUser from './pages/User/EditUser';
|
||||||
import { getLogo, getSystemName } from './helpers';
|
|
||||||
import PasswordResetForm from './components/PasswordResetForm';
|
import PasswordResetForm from './components/PasswordResetForm';
|
||||||
import PasswordResetConfirm from './components/PasswordResetConfirm';
|
import PasswordResetConfirm from './components/PasswordResetConfirm';
|
||||||
import { UserContext } from './context/User';
|
|
||||||
import Channel from './pages/Channel';
|
import Channel from './pages/Channel';
|
||||||
import Token from './pages/Token';
|
import Token from './pages/Token';
|
||||||
import EditChannel from './pages/Channel/EditChannel';
|
import EditChannel from './pages/Channel/EditChannel';
|
||||||
@@ -26,10 +24,6 @@ import Pricing from './pages/Pricing/index.js';
|
|||||||
import Task from "./pages/Task/index.js";
|
import Task from "./pages/Task/index.js";
|
||||||
import Playground from './pages/Playground/Playground.js';
|
import Playground from './pages/Playground/Playground.js';
|
||||||
import OAuth2Callback from "./components/OAuth2Callback.js";
|
import OAuth2Callback from "./components/OAuth2Callback.js";
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { StatusContext } from './context/Status';
|
|
||||||
import { setStatusData } from './helpers/data.js';
|
|
||||||
import { API, showError } from './helpers';
|
|
||||||
import PersonalSetting from './components/PersonalSetting.js';
|
import PersonalSetting from './components/PersonalSetting.js';
|
||||||
|
|
||||||
const Home = lazy(() => import('./pages/Home'));
|
const Home = lazy(() => import('./pages/Home'));
|
||||||
@@ -37,13 +31,15 @@ const Detail = lazy(() => import('./pages/Detail'));
|
|||||||
const About = lazy(() => import('./pages/About'));
|
const About = lazy(() => import('./pages/About'));
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path='/'
|
path='/'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Home />
|
<Home />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -59,7 +55,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/channel/edit/:id'
|
path='/channel/edit/:id'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<EditChannel />
|
<EditChannel />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -67,7 +63,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/channel/add'
|
path='/channel/add'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<EditChannel />
|
<EditChannel />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -107,7 +103,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/user/edit/:id'
|
path='/user/edit/:id'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<EditUser />
|
<EditUser />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -115,7 +111,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/user/edit'
|
path='/user/edit'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<EditUser />
|
<EditUser />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -123,7 +119,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/user/reset'
|
path='/user/reset'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<PasswordResetConfirm />
|
<PasswordResetConfirm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -131,7 +127,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/login'
|
path='/login'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -139,7 +135,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/register'
|
path='/register'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<RegisterForm />
|
<RegisterForm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -147,7 +143,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/reset'
|
path='/reset'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<PasswordResetForm />
|
<PasswordResetForm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -155,7 +151,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/oauth/github'
|
path='/oauth/github'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<OAuth2Callback type='github'></OAuth2Callback>
|
<OAuth2Callback type='github'></OAuth2Callback>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -163,7 +159,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/oauth/linuxdo'
|
path='/oauth/linuxdo'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<OAuth2Callback type='linuxdo'></OAuth2Callback>
|
<OAuth2Callback type='linuxdo'></OAuth2Callback>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -172,7 +168,7 @@ function App() {
|
|||||||
path='/setting'
|
path='/setting'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Setting />
|
<Setting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -182,7 +178,7 @@ function App() {
|
|||||||
path='/personal'
|
path='/personal'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<PersonalSetting />
|
<PersonalSetting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -192,7 +188,7 @@ function App() {
|
|||||||
path='/topup'
|
path='/topup'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<TopUp />
|
<TopUp />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -210,7 +206,7 @@ function App() {
|
|||||||
path='/detail'
|
path='/detail'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Detail />
|
<Detail />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -220,7 +216,7 @@ function App() {
|
|||||||
path='/midjourney'
|
path='/midjourney'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Midjourney />
|
<Midjourney />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -230,7 +226,7 @@ function App() {
|
|||||||
path='/task'
|
path='/task'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Task />
|
<Task />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
@@ -239,7 +235,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/pricing'
|
path='/pricing'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Pricing />
|
<Pricing />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -247,7 +243,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/about'
|
path='/about'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<About />
|
<About />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -255,7 +251,7 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path='/chat/:id?'
|
path='/chat/:id?'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Chat />
|
<Chat />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
@@ -265,7 +261,7 @@ function App() {
|
|||||||
path='/chat2link'
|
path='/chat2link'
|
||||||
element={
|
element={
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<Suspense fallback={<Loading></Loading>}>
|
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||||
<Chat2Link />
|
<Chat2Link />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
|
|||||||
@@ -62,6 +62,25 @@ const iconStyle = (itemKey, selectedKeys) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Define routerMap as a constant outside the component
|
||||||
|
const routerMap = {
|
||||||
|
home: '/',
|
||||||
|
channel: '/channel',
|
||||||
|
token: '/token',
|
||||||
|
redemption: '/redemption',
|
||||||
|
topup: '/topup',
|
||||||
|
user: '/user',
|
||||||
|
log: '/log',
|
||||||
|
midjourney: '/midjourney',
|
||||||
|
setting: '/setting',
|
||||||
|
about: '/about',
|
||||||
|
detail: '/detail',
|
||||||
|
pricing: '/pricing',
|
||||||
|
task: '/task',
|
||||||
|
playground: '/playground',
|
||||||
|
personal: '/personal',
|
||||||
|
};
|
||||||
|
|
||||||
const SiderBar = () => {
|
const SiderBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [styleState, styleDispatch] = useContext(StyleContext);
|
const [styleState, styleDispatch] = useContext(StyleContext);
|
||||||
@@ -76,6 +95,7 @@ const SiderBar = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setTheme = useSetTheme();
|
const setTheme = useSetTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const [routerMapState, setRouterMapState] = useState(routerMap);
|
||||||
|
|
||||||
// 预先计算所有可能的图标样式
|
// 预先计算所有可能的图标样式
|
||||||
const allItemKeys = useMemo(() => {
|
const allItemKeys = useMemo(() => {
|
||||||
@@ -97,25 +117,6 @@ const SiderBar = () => {
|
|||||||
return styles;
|
return styles;
|
||||||
}, [allItemKeys, selectedKeys]);
|
}, [allItemKeys, selectedKeys]);
|
||||||
|
|
||||||
const routerMap = {
|
|
||||||
home: '/',
|
|
||||||
channel: '/channel',
|
|
||||||
token: '/token',
|
|
||||||
redemption: '/redemption',
|
|
||||||
topup: '/topup',
|
|
||||||
user: '/user',
|
|
||||||
log: '/log',
|
|
||||||
midjourney: '/midjourney',
|
|
||||||
setting: '/setting',
|
|
||||||
about: '/about',
|
|
||||||
chat: '/chat',
|
|
||||||
detail: '/detail',
|
|
||||||
pricing: '/pricing',
|
|
||||||
task: '/task',
|
|
||||||
playground: '/playground',
|
|
||||||
personal: '/personal',
|
|
||||||
};
|
|
||||||
|
|
||||||
const workspaceItems = useMemo(
|
const workspaceItems = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -237,21 +238,24 @@ const SiderBar = () => {
|
|||||||
[chatItems, t],
|
[chatItems, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
// Function to update router map with chat routes
|
||||||
const currentPath = location.pathname;
|
const updateRouterMapWithChats = (chats) => {
|
||||||
const matchingKey = Object.keys(routerMap).find(key => routerMap[key] === currentPath);
|
const newRouterMap = { ...routerMap };
|
||||||
|
|
||||||
if (matchingKey) {
|
if (Array.isArray(chats) && chats.length > 0) {
|
||||||
setSelectedKeys([matchingKey]);
|
for (let i = 0; i < chats.length; i++) {
|
||||||
} else if (currentPath.startsWith('/chat/')) {
|
newRouterMap['chat' + i] = '/chat/' + i;
|
||||||
setSelectedKeys(['chat']);
|
}
|
||||||
}
|
}
|
||||||
}, [location.pathname]);
|
|
||||||
|
setRouterMapState(newRouterMap);
|
||||||
|
return newRouterMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the useEffect for chat items
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let chats = localStorage.getItem('chats');
|
let chats = localStorage.getItem('chats');
|
||||||
if (chats) {
|
if (chats) {
|
||||||
// console.log(chats);
|
|
||||||
try {
|
try {
|
||||||
chats = JSON.parse(chats);
|
chats = JSON.parse(chats);
|
||||||
if (Array.isArray(chats)) {
|
if (Array.isArray(chats)) {
|
||||||
@@ -263,19 +267,44 @@ const SiderBar = () => {
|
|||||||
chat.itemKey = 'chat' + i;
|
chat.itemKey = 'chat' + i;
|
||||||
chat.to = '/chat/' + i;
|
chat.to = '/chat/' + i;
|
||||||
}
|
}
|
||||||
// setRouterMap({ ...routerMap, chat: '/chat/' + i })
|
|
||||||
chatItems.push(chat);
|
chatItems.push(chat);
|
||||||
}
|
}
|
||||||
setChatItems(chatItems);
|
setChatItems(chatItems);
|
||||||
|
|
||||||
|
// Update router map with chat routes
|
||||||
|
updateRouterMapWithChats(chats);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
showError('聊天数据解析失败')
|
showError('聊天数据解析失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Update the useEffect for route selection
|
||||||
|
useEffect(() => {
|
||||||
|
const currentPath = location.pathname;
|
||||||
|
let matchingKey = Object.keys(routerMapState).find(key => routerMapState[key] === currentPath);
|
||||||
|
|
||||||
|
// Handle chat routes
|
||||||
|
if (!matchingKey && currentPath.startsWith('/chat/')) {
|
||||||
|
const chatIndex = currentPath.split('/').pop();
|
||||||
|
if (!isNaN(chatIndex)) {
|
||||||
|
matchingKey = 'chat' + chatIndex;
|
||||||
|
} else {
|
||||||
|
matchingKey = 'chat';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found a matching key, update the selected keys
|
||||||
|
if (matchingKey) {
|
||||||
|
setSelectedKeys([matchingKey]);
|
||||||
|
}
|
||||||
|
}, [location.pathname, routerMapState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
setIsCollapsed(styleState.siderCollapsed);
|
setIsCollapsed(styleState.siderCollapsed);
|
||||||
})
|
}, [styleState.siderCollapsed]);
|
||||||
|
|
||||||
// Custom divider style
|
// Custom divider style
|
||||||
const dividerStyle = {
|
const dividerStyle = {
|
||||||
@@ -322,14 +351,14 @@ const SiderBar = () => {
|
|||||||
// 确保在收起侧边栏时有选中的项目,避免不必要的计算
|
// 确保在收起侧边栏时有选中的项目,避免不必要的计算
|
||||||
if (selectedKeys.length === 0) {
|
if (selectedKeys.length === 0) {
|
||||||
const currentPath = location.pathname;
|
const currentPath = location.pathname;
|
||||||
const matchingKey = Object.keys(routerMap).find(key => routerMap[key] === currentPath);
|
const matchingKey = Object.keys(routerMapState).find(key => routerMapState[key] === currentPath);
|
||||||
|
|
||||||
if (matchingKey) {
|
if (matchingKey) {
|
||||||
setSelectedKeys([matchingKey]);
|
setSelectedKeys([matchingKey]);
|
||||||
} else if (currentPath.startsWith('/chat/')) {
|
} else if (currentPath.startsWith('/chat/')) {
|
||||||
setSelectedKeys(['chat']);
|
setSelectedKeys(['chat']);
|
||||||
} else {
|
} else {
|
||||||
setSelectedKeys([]); // 默认选中首页
|
setSelectedKeys(['detail']); // 默认选中首页
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -338,28 +367,10 @@ const SiderBar = () => {
|
|||||||
hoverStyle={navItemHoverStyle}
|
hoverStyle={navItemHoverStyle}
|
||||||
selectedStyle={navItemSelectedStyle}
|
selectedStyle={navItemSelectedStyle}
|
||||||
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
||||||
let chats = localStorage.getItem('chats');
|
|
||||||
if (chats) {
|
|
||||||
chats = JSON.parse(chats);
|
|
||||||
if (Array.isArray(chats) && chats.length > 0) {
|
|
||||||
for (let i = 0; i < chats.length; i++) {
|
|
||||||
routerMap['chat' + i] = '/chat/' + i;
|
|
||||||
}
|
|
||||||
if (chats.length > 1) {
|
|
||||||
// delete /chat
|
|
||||||
if (routerMap['chat']) {
|
|
||||||
delete routerMap['chat'];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// rename /chat to /chat/0
|
|
||||||
routerMap['chat'] = '/chat/0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
style={{ textDecoration: 'none' }}
|
style={{ textDecoration: 'none' }}
|
||||||
to={routerMap[props.itemKey]}
|
to={routerMapState[props.itemKey] || routerMap[props.itemKey]}
|
||||||
>
|
>
|
||||||
{itemElement}
|
{itemElement}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -466,7 +477,7 @@ const SiderBar = () => {
|
|||||||
|
|
||||||
<Nav.Footer
|
<Nav.Footer
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: styleState?.isMobile ? '112px' : '20px',
|
paddingBottom: styleState?.isMobile ? '112px' : '',
|
||||||
}}
|
}}
|
||||||
collapseButton={true}
|
collapseButton={true}
|
||||||
collapseText={(collapsed)=>
|
collapseText={(collapsed)=>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"主页": "Home",
|
"主页": "Home",
|
||||||
|
"文档": "Docs",
|
||||||
"控制台": "Console",
|
"控制台": "Console",
|
||||||
"$%.6f 额度": "$%.6f quota",
|
"$%.6f 额度": "$%.6f quota",
|
||||||
"%d 点额度": "%d point quota",
|
"%d 点额度": "%d point quota",
|
||||||
|
|||||||
Reference in New Issue
Block a user