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