diff --git a/web/public/index.html b/web/public/index.html
index 6f232250..c667ead5 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -15,6 +15,7 @@
diff --git a/web/src/App.js b/web/src/App.js
index fc54c632..d6ac62c3 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -16,6 +16,7 @@ import NavigationScroll from 'layout/NavigationScroll';
import UserProvider from 'contexts/UserContext';
import StatusProvider from 'contexts/StatusContext';
import { SnackbarProvider } from 'notistack';
+import CopySnackbar from 'ui-component/Snackbar';
// ==============================|| APP ||============================== //
@@ -27,7 +28,12 @@ const App = () => {
-
+
diff --git a/web/src/assets/images/invite/cover.jpg b/web/src/assets/images/invite/cover.jpg
deleted file mode 100644
index 93be1a40..00000000
Binary files a/web/src/assets/images/invite/cover.jpg and /dev/null differ
diff --git a/web/src/assets/images/invite/cover.webp b/web/src/assets/images/invite/cover.webp
new file mode 100644
index 00000000..56da826b
Binary files /dev/null and b/web/src/assets/images/invite/cover.webp differ
diff --git a/web/src/constants/SnackbarConstants.js b/web/src/constants/SnackbarConstants.js
index a05c6652..0b84b764 100644
--- a/web/src/constants/SnackbarConstants.js
+++ b/web/src/constants/SnackbarConstants.js
@@ -2,23 +2,34 @@ export const snackbarConstants = {
Common: {
ERROR: {
variant: 'error',
- autoHideDuration: 5000
+ autoHideDuration: 5000,
+ preventDuplicate: true
},
WARNING: {
variant: 'warning',
- autoHideDuration: 10000
+ autoHideDuration: 10000,
+ preventDuplicate: true
},
SUCCESS: {
variant: 'success',
- autoHideDuration: 1500
+ autoHideDuration: 1500,
+ preventDuplicate: true
},
INFO: {
variant: 'info',
- autoHideDuration: 3000
+ autoHideDuration: 3000,
+ preventDuplicate: true
},
NOTICE: {
variant: 'info',
- autoHideDuration: 20000
+ autoHideDuration: 20000,
+ preventDuplicate: true
+ },
+ COPY: {
+ variant: 'copy',
+ persist: true,
+ preventDuplicate: true,
+ allowDownload: true
}
},
Mobile: {
diff --git a/web/src/ui-component/Snackbar.js b/web/src/ui-component/Snackbar.js
new file mode 100644
index 00000000..8b1a69e5
--- /dev/null
+++ b/web/src/ui-component/Snackbar.js
@@ -0,0 +1,33 @@
+import PropTypes from 'prop-types';
+import { forwardRef } from 'react';
+import { SnackbarContent, useSnackbar } from 'notistack';
+import { Alert } from '@mui/material';
+
+const CopySnackbar = forwardRef((props, ref) => {
+ const { closeSnackbar } = useSnackbar();
+
+ return (
+
+ {
+ closeSnackbar(props.id);
+ }}
+ >
+ {props.message}
+
+
+ );
+});
+
+CopySnackbar.displayName = 'ReportComplete';
+
+CopySnackbar.propTypes = {
+ props: PropTypes.object,
+ id: PropTypes.number.isRequired, // 添加 id 的验证
+ message: PropTypes.any.isRequired // 添加 message 的验证
+};
+
+export default CopySnackbar;
diff --git a/web/src/ui-component/cards/UserCard.js b/web/src/ui-component/cards/UserCard.js
index 793064b9..c29920ba 100644
--- a/web/src/ui-component/cards/UserCard.js
+++ b/web/src/ui-component/cards/UserCard.js
@@ -33,7 +33,7 @@ import { Box, Avatar } from '@mui/material';
import { alpha } from '@mui/material/styles';
import Card from '@mui/material/Card';
import shapeAvatar from 'assets/images/icons/shape-avatar.svg';
-import coverAvatar from 'assets/images/invite/cover.jpg';
+import coverAvatar from 'assets/images/invite/cover.webp';
import userAvatar from 'assets/images/users/user-round.svg';
import SvgColor from 'ui-component/SvgColor';
diff --git a/web/src/utils/common.js b/web/src/utils/common.js
index 32128164..e9890fc5 100644
--- a/web/src/utils/common.js
+++ b/web/src/utils/common.js
@@ -69,6 +69,17 @@ export function showInfo(message) {
enqueueSnackbar(message, getSnackbarOptions('INFO'));
}
+export function copy(text, name = '') {
+ try {
+ navigator.clipboard.writeText(text);
+ } catch (error) {
+ text = `复制${name}失败,请手动复制:
${text}`;
+ enqueueSnackbar(, getSnackbarOptions('COPY'));
+ return;
+ }
+ showSuccess(`复制${name}成功!`);
+}
+
export async function getOAuthState() {
try {
const res = await API.get('/api/oauth/state');
diff --git a/web/src/views/Authentication/AuthForms/AuthLogin.js b/web/src/views/Authentication/AuthForms/AuthLogin.js
index 1d13fc4e..a03738df 100644
--- a/web/src/views/Authentication/AuthForms/AuthLogin.js
+++ b/web/src/views/Authentication/AuthForms/AuthLogin.js
@@ -161,8 +161,8 @@ const LoginForm = ({ ...others }) => {
submit: null
}}
validationSchema={Yup.object().shape({
- username: Yup.string().max(255).required('Username is required'),
- password: Yup.string().max(255).required('Password is required')
+ username: Yup.string().max(255).required('用户名/邮箱是必填项'),
+ password: Yup.string().max(255).required('密码是必填项')
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
const { success, message } = await login(values.username, values.password);
diff --git a/web/src/views/Authentication/AuthForms/AuthRegister.js b/web/src/views/Authentication/AuthForms/AuthRegister.js
index 3be43a43..870b5af0 100644
--- a/web/src/views/Authentication/AuthForms/AuthRegister.js
+++ b/web/src/views/Authentication/AuthForms/AuthRegister.js
@@ -296,7 +296,7 @@ const RegisterForm = ({ ...others }) => {
diff --git a/web/src/views/Authentication/AuthForms/ResetPasswordForm.js b/web/src/views/Authentication/AuthForms/ResetPasswordForm.js
index c55618d9..96517168 100644
--- a/web/src/views/Authentication/AuthForms/ResetPasswordForm.js
+++ b/web/src/views/Authentication/AuthForms/ResetPasswordForm.js
@@ -5,7 +5,7 @@ import { useSearchParams } from 'react-router-dom';
import { Button, Stack, Typography, Alert } from '@mui/material';
// assets
-import { showError, showInfo } from 'utils/common';
+import { showError, copy } from 'utils/common';
import { API } from 'utils/api';
// ===========================|| FIREBASE - REGISTER ||=========================== //
@@ -25,8 +25,7 @@ const ResetPasswordForm = () => {
if (success) {
let password = res.data.data;
setNewPassword(password);
- navigator.clipboard.writeText(password);
- showInfo(`新密码已复制到剪贴板:${password}`);
+ copy(password, '新密码');
} else {
showError(message);
}
diff --git a/web/src/views/Channel/component/NameLabel.js b/web/src/views/Channel/component/NameLabel.js
index 14a69c14..061ed617 100644
--- a/web/src/views/Channel/component/NameLabel.js
+++ b/web/src/views/Channel/component/NameLabel.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { Tooltip, Stack, Container } from '@mui/material';
import Label from 'ui-component/Label';
import { styled } from '@mui/material/styles';
-import { showSuccess } from 'utils/common';
+import { copy } from 'utils/common';
const TooltipContainer = styled(Container)({
maxHeight: '250px',
@@ -28,8 +28,7 @@ const NameLabel = ({ name, models }) => {
variant="ghost"
key={index}
onClick={() => {
- navigator.clipboard.writeText(item);
- showSuccess('复制模型名称成功!');
+ copy(item, '模型名称');
}}
>
{item}
diff --git a/web/src/views/Dashboard/component/SupportModels.js b/web/src/views/Dashboard/component/SupportModels.js
index 91636cab..6535af10 100644
--- a/web/src/views/Dashboard/component/SupportModels.js
+++ b/web/src/views/Dashboard/component/SupportModels.js
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import SubCard from 'ui-component/cards/SubCard';
// import { gridSpacing } from 'store/constant';
import { API } from 'utils/api';
-import { showError, showSuccess } from 'utils/common';
+import { showError, copy } from 'utils/common';
import { Typography, Accordion, AccordionSummary, AccordionDetails, Box, Stack } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import Label from 'ui-component/Label';
@@ -50,8 +50,7 @@ const SupportModels = () => {
color="primary"
key={model}
onClick={() => {
- navigator.clipboard.writeText(model);
- showSuccess('复制模型名称成功!');
+ copy(model, '模型名称');
}}
>
{model}
diff --git a/web/src/views/Profile/index.js b/web/src/views/Profile/index.js
index 29e1ea6e..5578dddb 100644
--- a/web/src/views/Profile/index.js
+++ b/web/src/views/Profile/index.js
@@ -19,8 +19,7 @@ import SubCard from 'ui-component/cards/SubCard';
import { IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
import Label from 'ui-component/Label';
import { API } from 'utils/api';
-import { showError, showSuccess } from 'utils/common';
-import { onGitHubOAuthClicked } from 'utils/common';
+import { showError, showSuccess, onGitHubOAuthClicked, copy } from 'utils/common';
import * as Yup from 'yup';
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
import { useSelector } from 'react-redux';
@@ -93,8 +92,7 @@ export default function Profile() {
const { success, message, data } = res.data;
if (success) {
setInputs((inputs) => ({ ...inputs, access_token: data }));
- navigator.clipboard.writeText(data);
- showSuccess(`令牌已重置并已复制到剪贴板`);
+ copy(data, '令牌');
} else {
showError(message);
}
diff --git a/web/src/views/Redemption/component/TableRow.js b/web/src/views/Redemption/component/TableRow.js
index 68c9a505..380af037 100644
--- a/web/src/views/Redemption/component/TableRow.js
+++ b/web/src/views/Redemption/component/TableRow.js
@@ -18,7 +18,7 @@ import {
import Label from 'ui-component/Label';
import TableSwitch from 'ui-component/Switch';
-import { timestamp2string, renderQuota, showSuccess } from 'utils/common';
+import { timestamp2string, renderQuota, copy } from 'utils/common';
import { IconDotsVertical, IconEdit, IconTrash } from '@tabler/icons-react';
@@ -83,8 +83,7 @@ export default function RedemptionTableRow({ item, manageRedemption, handleOpenM
variant="contained"
color="primary"
onClick={() => {
- navigator.clipboard.writeText(item.key);
- showSuccess('已复制到剪贴板!');
+ copy(item.key, '兑换码');
}}
>
复制
diff --git a/web/src/views/Token/component/TableRow.js b/web/src/views/Token/component/TableRow.js
index 323ead86..f8ec1406 100644
--- a/web/src/views/Token/component/TableRow.js
+++ b/web/src/views/Token/component/TableRow.js
@@ -20,7 +20,7 @@ import {
} from '@mui/material';
import TableSwitch from 'ui-component/Switch';
-import { renderQuota, showSuccess, timestamp2string } from 'utils/common';
+import { renderQuota, timestamp2string, copy } from 'utils/common';
import { IconDotsVertical, IconEdit, IconTrash, IconCaretDownFilled } from '@tabler/icons-react';
@@ -141,8 +141,7 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
if (type === 'link') {
window.open(text);
} else {
- navigator.clipboard.writeText(text);
- showSuccess('已复制到剪贴板!');
+ copy(text, '链接');
}
handleCloseMenu();
};
@@ -211,8 +210,7 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set