feat: change center-component to components
This commit is contained in:
@@ -6,9 +6,10 @@ import { App as UserAppApp } from './pages/app';
|
||||
import { App as FileApp } from './pages/file';
|
||||
import { App as OrgApp } from './pages/org';
|
||||
import { App as ConfigApp } from './pages/config';
|
||||
import { App as PayApp } from './pages/pay';
|
||||
import { basename } from './modules/basename';
|
||||
import { Redirect } from './modules/Redirect';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { CustomThemeProvider } from '@kevisual/components/theme/index.tsx';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
@@ -78,7 +79,7 @@ export const App = () => {
|
||||
<Route path='/config/*' element={<ConfigApp />} />
|
||||
<Route path='/app/*' element={<UserAppApp />} />
|
||||
<Route path='/file/*' element={<FileApp />} />
|
||||
|
||||
<Route path='/pay/*' element={<PayApp />} />
|
||||
<Route path='/404' element={<div>404</div>} />
|
||||
<Route path='*' element={<div>404</div>} />
|
||||
</Routes>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import 'tailwindcss';
|
||||
@import './assets/styles.css';
|
||||
@import './index.css';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
@import '@kevisual/components/theme/wind-theme.css';
|
||||
|
||||
h1 {
|
||||
@apply text-2xl font-bold;
|
||||
@@ -60,6 +60,9 @@ h3 {
|
||||
.cm-editor {
|
||||
@apply h-full;
|
||||
}
|
||||
.cm-scroller {
|
||||
@apply scrollbar;
|
||||
}
|
||||
#for-message {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import clsx from 'clsx';
|
||||
import { Button, Menu, MenuItem } from '@mui/material';
|
||||
import i18n from 'i18next';
|
||||
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { Languages } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
|
||||
export const message = {
|
||||
success: (message: string) => {
|
||||
toast.success(message);
|
||||
success: (message: string, opts?: ToastOptions) => {
|
||||
toast.success(message, opts);
|
||||
},
|
||||
error: (message: string) => {
|
||||
toast.error(message);
|
||||
error: (message: string, opts?: ToastOptions) => {
|
||||
toast.error(message, opts);
|
||||
},
|
||||
warning: (message: string) => {
|
||||
toast.warning(message);
|
||||
warning: (message: string, opts?: ToastOptions) => {
|
||||
toast.warning(message, opts);
|
||||
},
|
||||
info: (message: string) => {
|
||||
toast.info(message);
|
||||
info: (message: string, opts?: ToastOptions) => {
|
||||
toast.info(message, opts);
|
||||
},
|
||||
loading: (message: string) => {
|
||||
const toastId = toast.loading(message);
|
||||
loading: (message: string, opts?: ToastOptions) => {
|
||||
const toastId = toast.loading(message, opts);
|
||||
return () => {
|
||||
toast.dismiss(toastId);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import FileOutlined from '@ant-design/icons/FileOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { FileUpload } from '../modules/FileUpload';
|
||||
@@ -18,7 +18,7 @@ import { useNewNavigate } from '@/modules';
|
||||
import { Button } from '@mui/material';
|
||||
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useUserAppStore } from '../store';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
@@ -10,14 +10,14 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined';
|
||||
import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
||||
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
|
||||
import { FormControlLabel, Switch } from '@mui/material';
|
||||
import { FormControlLabel, Switch, useTheme } from '@mui/material';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { DialogActions, Tooltip } from '@mui/material';
|
||||
import { marked } from 'marked';
|
||||
import clsx from 'clsx';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Select } from '@kevisual/center-components/select/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { Select } from '@kevisual/components/select/index.tsx';
|
||||
import { iText } from '@kevisual/resources/index.ts';
|
||||
import { PermissionManager } from '@kevisual/resources/pages/file/modules/PermissionManager.tsx';
|
||||
import { Button } from '@mui/material';
|
||||
@@ -72,6 +72,8 @@ const FormModal = () => {
|
||||
reset();
|
||||
};
|
||||
const isEdit = containerStore?.userApp?.id;
|
||||
const theme = useTheme();
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps as any;
|
||||
return (
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
@@ -84,20 +86,25 @@ const FormModal = () => {
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<form className='flex flex-col gap-4 pt-2' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='title' control={control} render={({ field }) => <TextField {...field} label='title' fullWidth />} />
|
||||
<Controller name='title' control={control} render={({ field }) => <TextField {...defaultProps} {...field} label='title' />} />
|
||||
<Controller
|
||||
name='domain'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label='domain' variant='outlined' helperText='域名自定义绑定' />}
|
||||
render={({ field }) => <TextField {...defaultProps} {...field} label='domain' variant='outlined' helperText='域名自定义绑定' />}
|
||||
/>
|
||||
<Controller name='key' control={control} render={({ field }) => <TextField {...field} label='key' fullWidth />} />
|
||||
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='description' multiline rows={4} fullWidth />} />
|
||||
<Controller name='proxy' control={control} render={({ field }) => <Switch {...field} checked={field.value} />} />
|
||||
<Controller name='key' control={control} render={({ field }) => <TextField {...defaultProps} {...field} label='key' fullWidth />} />
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...defaultProps} {...field} label='description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
<Controller name='proxy' control={control} render={({ field }) => <Switch {...defaultProps} {...field} checked={field.value} />} />
|
||||
<Controller
|
||||
name='status'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...defaultProps}
|
||||
{...field}
|
||||
sx={{
|
||||
width: '100%',
|
||||
@@ -110,7 +117,9 @@ const FormModal = () => {
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<Button type='submit'>提交</Button>
|
||||
<Button type='submit' variant='contained'>
|
||||
提交
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
@@ -161,6 +170,8 @@ const ShareModal = () => {
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
console.log('runtime', runtime);
|
||||
const theme = useTheme();
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps as any;
|
||||
return (
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
@@ -305,6 +316,7 @@ export const List = () => {
|
||||
<Tooltip title={'Edit'}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
userAppStore.getUserApp(item.id);
|
||||
userAppStore.setFormData(item);
|
||||
userAppStore.setShowEdit(true);
|
||||
}}>
|
||||
@@ -337,9 +349,17 @@ export const List = () => {
|
||||
if (item.domain) {
|
||||
if (item.domain.startsWith('http://') || item.domain.startsWith('https://')) {
|
||||
baseUri = item.domain;
|
||||
} else {
|
||||
} else if (item.domain.startsWith('//')) {
|
||||
baseUri = new URL(item.domain).origin;
|
||||
} else {
|
||||
baseUri = new URL('https://' + item.domain).toString();
|
||||
}
|
||||
if (baseUri.endsWith('/')) {
|
||||
window.open(baseUri, '_blank');
|
||||
}
|
||||
console.log('baseUri', baseUri);
|
||||
message.success('success');
|
||||
return;
|
||||
}
|
||||
if (DEV_SERVER) {
|
||||
baseUri = 'http://localhost:3005';
|
||||
|
||||
@@ -4,13 +4,13 @@ import { CardBlank } from '@/components/card';
|
||||
import { Button, ButtonGroup, Divider, Drawer, FormControlLabel, Tab, Tabs, Tooltip, useTheme } from '@mui/material';
|
||||
import { Edit, Plus, Save, Share, Trash, X } from 'lucide-react';
|
||||
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
|
||||
import { TextField, TextFieldLabel } from '@kevisual/center-components/input/TextField.tsx';
|
||||
import { TextField, TextFieldLabel } from '@kevisual/components/input/TextField.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { load, dump } from 'js-yaml';
|
||||
import CodeEditor from '@uiw/react-textarea-code-editor';
|
||||
@@ -117,7 +117,7 @@ export const DrawerEdit = () => {
|
||||
onSave(pickValue);
|
||||
};
|
||||
const theme = useTheme();
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps;
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps as any;
|
||||
return (
|
||||
<Drawer
|
||||
open={showEdit}
|
||||
@@ -128,6 +128,8 @@ export const DrawerEdit = () => {
|
||||
sx: {
|
||||
width: '50%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
background: 'red',
|
||||
},
|
||||
},
|
||||
}}>
|
||||
@@ -146,12 +148,13 @@ export const DrawerEdit = () => {
|
||||
</Tabs>
|
||||
{tab === 'base' && (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2 flex flex-col gap-6'>
|
||||
<Controller control={control} name='title' render={({ field }) => <TextField {...field} label='Title' />} />
|
||||
<Controller control={control} name='title' render={({ field }) => <TextField {...defaultProps} {...field} label='Title' />} />
|
||||
<Controller
|
||||
name='key'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...defaultProps}
|
||||
{...field}
|
||||
label={
|
||||
<TextFieldLabel label='Key' tips='Key is the unique identifier for the configuration. if set and id is none will change data by key;' />
|
||||
@@ -159,7 +162,11 @@ export const DrawerEdit = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='Description' multiline rows={4} />} />
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...defaultProps} {...field} label='Description' multiline rows={4} />}
|
||||
/>
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
@@ -179,15 +186,7 @@ export const DrawerEdit = () => {
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
export const MyController = React.forwardRef(
|
||||
({ control, name, Component, componentProps }: { control: any; name: string; Component: (props: any) => JSX.Element; componentProps: any }, ref) => {
|
||||
const theme = useTheme();
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps;
|
||||
console.log(defaultProps, 'defaultProps');
|
||||
const { field } = useController({ control, name });
|
||||
return <Component {...field} {...componentProps} ref={ref} />;
|
||||
},
|
||||
);
|
||||
|
||||
export const List = () => {
|
||||
const { list, getConfigList, setShowEdit, setFormData, deleteConfig, updateData, formData, detectConfig, onOpenKey } = useConfigStore();
|
||||
const [modal, contextHolder] = useModal();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogTitle, DialogContent, Tooltip, Button, ButtonGroup } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { getDirectoryAndName, toFile, uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
@@ -16,12 +16,12 @@ import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { Settings } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||
import { TagsInput } from '@kevisual/components/select/TagsInput.tsx';
|
||||
const DrawEdit = React.lazy(() => import('../module/DrawEdit'));
|
||||
|
||||
const FormModal = () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BaseEditor } from '@kevisual/codemirror/editor/editor.ts';
|
||||
import { Drawer } from '@mui/material';
|
||||
import { Box, Drawer } from '@mui/material';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useContainerStore } from '../store';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { LeftOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
|
||||
export const DrawEdit = () => {
|
||||
@@ -60,8 +60,15 @@ export const DrawEdit = () => {
|
||||
disableEnforceFocus: true, // 允许操作背景内容
|
||||
disableAutoFocus: true, // 防止自动聚焦
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiDrawer-paper': {
|
||||
width: '50%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}}
|
||||
anchor='right'>
|
||||
<div className='bg-secondary w-[600px] h-[48px]'>
|
||||
<div className='bg-secondary w-full h-[48px]'>
|
||||
<div className='text-white flex p-2'>
|
||||
<div className='ml-2 flex gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
@@ -95,7 +102,17 @@ export const DrawEdit = () => {
|
||||
<div className='flex-1 ml-2 flex items-center'>{containerStore.data?.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-primary' style={{ height: 'calc(100% - 50px)' }} ref={editorElRef}></div>
|
||||
<Box
|
||||
className='border-primary '
|
||||
style={{ height: 'calc(100% - 50px)' }}
|
||||
sx={{
|
||||
'& .cm-editor': {
|
||||
height: '100%',
|
||||
},
|
||||
'& .cm-scroller': {},
|
||||
}}>
|
||||
<div className='w-full h-full ' ref={editorElRef}></div>
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ type ContainerStore = {
|
||||
setLoading: (loading: boolean) => void;
|
||||
list: any[];
|
||||
getList: () => Promise<void>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean, refresh?: boolean }) => Promise<any>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean; refresh?: boolean; showTips?: boolean }) => Promise<any>;
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
openDrawEdit: boolean;
|
||||
setOpenDrawEdit: (openDrawEdit: boolean) => void;
|
||||
@@ -54,8 +54,13 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
key: 'update',
|
||||
data,
|
||||
});
|
||||
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
if (opts?.showTips ?? true) {
|
||||
message.success('Success', {
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
set({ formData: res.data });
|
||||
if (refresh) {
|
||||
getList();
|
||||
|
||||
@@ -6,7 +6,7 @@ import prettyBytes from 'pretty-bytes';
|
||||
import clsx from 'clsx';
|
||||
import FileOutlined from '@ant-design/icons/FileOutlined';
|
||||
import FolderOutlined from '@ant-design/icons/FolderOutlined';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { render, unmount } from '@kevisual/resources/pages/Bootstrap.tsx';
|
||||
import UploadOutlined from '@ant-design/icons/lib/icons/UploadOutlined';
|
||||
import { Tooltip } from '@mui/material';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
@@ -16,7 +16,7 @@ import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
@@ -7,14 +7,14 @@ import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { TextField } from '@mui/material';
|
||||
import { Select } from '@kevisual/center-components/select/index.tsx';
|
||||
import { Select } from '@kevisual/components/select/index.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
|
||||
const FormModal = () => {
|
||||
|
||||
91
src/pages/pay/index.tsx
Normal file
91
src/pages/pay/index.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { X, ChevronRight } from 'lucide-react';
|
||||
import { usePayStore } from './store/pay';
|
||||
import { createQrcode } from './modules/create-qrcode';
|
||||
import Panda from '@/assets/panda.png';
|
||||
import Button from '@mui/material/Button/Button';
|
||||
export const App = () => {
|
||||
const [isAgreed, setIsAgreed] = useState(false);
|
||||
const qrcodeRef = useRef<HTMLImageElement>(null);
|
||||
const { getPayUrl, codeUrl, user, init, money, setMoney, subject, setSubject } = usePayStore();
|
||||
|
||||
useEffect(() => {
|
||||
// getPayUrl({ money: money, subject: subject });
|
||||
init();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (qrcodeRef.current) {
|
||||
createQrcode(codeUrl, qrcodeRef.current);
|
||||
}
|
||||
}, [codeUrl]);
|
||||
let username = user?.nickname || user?.username || '--';
|
||||
if (username === 'root') {
|
||||
username = 'kevisual';
|
||||
}
|
||||
const redirectToPay = async () => {
|
||||
const res = await getPayUrl({ money: money, subject: subject });
|
||||
if (res.code === 200) {
|
||||
const newLink = res?.data?.form;
|
||||
if (newLink) {
|
||||
// window.location.href = newLink;
|
||||
window.open(newLink, '_blank');
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='min-h-screen bg-gray-50 flex items-center justify-center border-gray-200'>
|
||||
<div className='w-[600px] h-[400px] bg-white rounded-lg shadow-lg overflow-hidden'>
|
||||
{/* Header */}
|
||||
<div className='bg-white p-3 flex justify-between items-center border-b border-gray-200'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<img src={user?.avatar ?? Panda} alt='User Avatar' className='w-8 h-8 rounded-full' />
|
||||
<div>
|
||||
<h2 className='text-base font-semibold'>{username}</h2>
|
||||
<span className='text-gray-500 text-xs'>会员:已过期</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-3 text-gray-600 text-sm'>
|
||||
<span>赠送好友</span>
|
||||
<span>积分兑换</span>
|
||||
<span>激活码开通</span>
|
||||
<X className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className='p-4 h-[calc(400px-56px)] overflow-y-auto'>
|
||||
{/* Membership Type */}
|
||||
<div className='flex items-center gap-2 mb-6'>
|
||||
<span className='bg-pink-100 text-pink-500 px-2 py-0.5 rounded text-xs'>会员</span>
|
||||
<div className='flex-1'></div>
|
||||
<button className='flex items-center text-gray-600 text-xs'>
|
||||
切换高级套餐
|
||||
<ChevronRight className='w-3 h-3' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Payment QR Code */}
|
||||
<div className='bg-white border border-gray-200 rounded-lg p-4 text-center min-h-[200px] flex flex-col justify-center items-center'>
|
||||
<div className='text-2xl font-bold mb-3'>¥ {(money / 100).toFixed(2)}</div>
|
||||
{/* <QrCode className='w-32 h-32 mx-auto mb-2' /> */}
|
||||
{/* <img id='qrcode' className='w-32 h-32 mx-auto mb-2' ref={qrcodeRef} /> */}
|
||||
{/* <p className='text-gray-600 text-xs'>请使用支付宝扫码支付</p> */}
|
||||
<div className='flex justify-center items-center gap-2'>
|
||||
<div className='text-center px-4 py-2 cursor-pointer rounded-md shadow-md text-gray-600 text-xs border border-gray-300' onClick={redirectToPay}>
|
||||
提交订单
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terms */}
|
||||
<div className='mt-4 text-xs text-gray-500 text-center flex items-center justify-center gap-1'>
|
||||
<input type='checkbox' id='terms' checked={isAgreed} onChange={(e) => setIsAgreed(e.target.checked)} className='w-3 h-3 accent-pink-500' />
|
||||
<label className='text-gray-300 underline' htmlFor='terms'>
|
||||
已阅读《会员服务协议》
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
27
src/pages/pay/modules/create-qrcode.ts
Normal file
27
src/pages/pay/modules/create-qrcode.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
export const createQrcode = (url: string, element: HTMLImageElement) => {
|
||||
if (!url) return;
|
||||
console.log('pay url', url);
|
||||
QRCode.toDataURL(
|
||||
url,
|
||||
{
|
||||
errorCorrectionLevel: 'H',
|
||||
type: 'image/jpeg',
|
||||
margin: 1,
|
||||
|
||||
width: 300,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
},
|
||||
},
|
||||
function (err, url) {
|
||||
if (err) {
|
||||
console.log('err', err);
|
||||
throw err;
|
||||
}
|
||||
element.src = url;
|
||||
},
|
||||
);
|
||||
};
|
||||
46
src/pages/pay/store/pay.ts
Normal file
46
src/pages/pay/store/pay.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { create } from 'zustand';
|
||||
import { query, queryLogin } from '@/modules/query';
|
||||
import { toast } from 'react-toastify';
|
||||
interface PayState {
|
||||
getPayUrl: (data: { money: number; subject: string }) => Promise<any>;
|
||||
codeUrl: string;
|
||||
setCodeUrl: (url: string) => void;
|
||||
user: any;
|
||||
money: number;
|
||||
subject: string;
|
||||
setMoney: (money: number) => void;
|
||||
setSubject: (subject: string) => void;
|
||||
init: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const usePayStore = create<PayState>((set) => ({
|
||||
money: 12800,
|
||||
setMoney: (money) => set({ money }),
|
||||
subject: 'kevisual 会员',
|
||||
setSubject: (subject) => set({ subject }),
|
||||
getPayUrl: async (data) => {
|
||||
const res = await query.post({
|
||||
path: 'alipay',
|
||||
key: 'pay',
|
||||
data,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
if (res.data?.form) {
|
||||
set({ codeUrl: res.data?.form });
|
||||
}
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
codeUrl: '',
|
||||
setCodeUrl: (url) => set({ codeUrl: url }),
|
||||
user: null,
|
||||
init: async () => {
|
||||
const res = await queryLogin.cacheStore.getCurrentUser();
|
||||
console.log(res);
|
||||
if (res) {
|
||||
set({ user: res });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -8,11 +8,11 @@ import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@kevisual/center-components/card/CardBlank.tsx';
|
||||
import { CardBlank } from '@kevisual/components/card/CardBlank.tsx';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
Reference in New Issue
Block a user