fix: copy fails in http environment (#43)
* 🐛 fix: copy fails in http environment * 🎨 optimize: web UI
This commit is contained in:
parent
45fd814d77
commit
0be687905f
@ -15,6 +15,7 @@
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
media="print" onload="this.media='all'"
|
||||
/>
|
||||
</head>
|
||||
|
||||
|
@ -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 = () => {
|
||||
<ThemeProvider theme={themes(customization)}>
|
||||
<CssBaseline />
|
||||
<NavigationScroll>
|
||||
<SnackbarProvider autoHideDuration={5000} maxSnack={3} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}>
|
||||
<SnackbarProvider
|
||||
autoHideDuration={5000}
|
||||
maxSnack={3}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
Components={{ copy: CopySnackbar }}
|
||||
>
|
||||
<UserProvider>
|
||||
<StatusProvider>
|
||||
<Routes />
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 86 KiB |
BIN
web/src/assets/images/invite/cover.webp
Normal file
BIN
web/src/assets/images/invite/cover.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
@ -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: {
|
||||
|
33
web/src/ui-component/Snackbar.js
Normal file
33
web/src/ui-component/Snackbar.js
Normal file
@ -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 (
|
||||
<SnackbarContent ref={ref}>
|
||||
<Alert
|
||||
severity="info"
|
||||
variant="filled"
|
||||
sx={{ width: '100%', whiteSpace: 'normal', overflowWrap: 'break-word' }}
|
||||
onClose={() => {
|
||||
closeSnackbar(props.id);
|
||||
}}
|
||||
>
|
||||
{props.message}
|
||||
</Alert>
|
||||
</SnackbarContent>
|
||||
);
|
||||
});
|
||||
|
||||
CopySnackbar.displayName = 'ReportComplete';
|
||||
|
||||
CopySnackbar.propTypes = {
|
||||
props: PropTypes.object,
|
||||
id: PropTypes.number.isRequired, // 添加 id 的验证
|
||||
message: PropTypes.any.isRequired // 添加 message 的验证
|
||||
};
|
||||
|
||||
export default CopySnackbar;
|
@ -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';
|
||||
|
||||
|
@ -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}失败,请手动复制:<br /><br />${text}`;
|
||||
enqueueSnackbar(<SnackbarHTMLContent htmlContent={text} />, getSnackbarOptions('COPY'));
|
||||
return;
|
||||
}
|
||||
showSuccess(`复制${name}成功!`);
|
||||
}
|
||||
|
||||
export async function getOAuthState() {
|
||||
try {
|
||||
const res = await API.get('/api/oauth/state');
|
||||
|
@ -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);
|
||||
|
@ -296,7 +296,7 @@ const RegisterForm = ({ ...others }) => {
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<AnimateButton>
|
||||
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
|
||||
Sign up
|
||||
注册
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Box>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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, '兑换码');
|
||||
}}
|
||||
>
|
||||
复制
|
||||
|
@ -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
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`sk-${item.key}`);
|
||||
showSuccess('已复制到剪贴板!');
|
||||
copy(`sk-${item.key}`, '令牌');
|
||||
}}
|
||||
>
|
||||
复制
|
||||
|
@ -4,7 +4,7 @@ import SubCard from 'ui-component/cards/SubCard';
|
||||
import inviteImage from 'assets/images/invite/cwok_casual_19.webp';
|
||||
import { useState } from 'react';
|
||||
import { API } from 'utils/api';
|
||||
import { showError, showSuccess } from 'utils/common';
|
||||
import { showError, copy } from 'utils/common';
|
||||
|
||||
const InviteCard = () => {
|
||||
const theme = useTheme();
|
||||
@ -12,8 +12,7 @@ const InviteCard = () => {
|
||||
|
||||
const handleInviteUrl = async () => {
|
||||
if (inviteUl) {
|
||||
navigator.clipboard.writeText(inviteUl);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
copy(inviteUl, '邀请链接');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -23,8 +22,7 @@ const InviteCard = () => {
|
||||
if (success) {
|
||||
let link = `${window.location.origin}/register?aff=${data}`;
|
||||
setInviteUrl(link);
|
||||
navigator.clipboard.writeText(link);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
copy(link, '邀请链接');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user