generated from template/vite-react-template
init
This commit is contained in:
parent
8e41c38dde
commit
4a9727dd71
@ -21,10 +21,12 @@
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@kevisual/router": "0.0.10",
|
||||
"@mui/material": "^7.0.1",
|
||||
"antd": "^5.24.6",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
910
pnpm-lock.yaml
generated
910
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@import '@kevisual/components/theme/wind-theme.css';
|
||||
|
||||
.ant-modal {
|
||||
z-index: 10;
|
||||
}
|
||||
|
18
src/main.tsx
18
src/main.tsx
@ -1,10 +1,18 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App, AppRoute } from './pages/App.tsx';
|
||||
import { CustomThemeProvider } from '@kevisual/components/theme/index.tsx';
|
||||
|
||||
console.log('cu',)
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
// import 'react-toastify/dist/ReactToastify.css';
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<CustomThemeProvider>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Modal: {
|
||||
// zIndex: 1000,
|
||||
},
|
||||
},
|
||||
}}>
|
||||
<AppRoute />
|
||||
</CustomThemeProvider>,
|
||||
<ToastContainer />
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { basename } from '../modules/basename';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
console.log('basename', basename);
|
||||
import { App as AppDemo } from './app-demo';
|
||||
import { App as AppVip } from './vip';
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
|
||||
export const App = () => {
|
||||
return <div className='bg-slate-200 w-full h-full border'>123</div>;
|
||||
};
|
||||
@ -10,8 +12,7 @@ export const AppRoute = () => {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path='/' element={<Navigate to='/app-demo/list' />} />
|
||||
<Route path='/app-demo/*' element={<AppDemo />} />
|
||||
<Route path='*' element={<AppVip />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
|
66
src/pages/vip/constants.ts
Normal file
66
src/pages/vip/constants.ts
Normal file
@ -0,0 +1,66 @@
|
||||
export const vipFeatureList = [
|
||||
{
|
||||
level: 'free',
|
||||
levelNumber: 0,
|
||||
title: '免费',
|
||||
description: '满足简单的部署应用需求',
|
||||
features: [
|
||||
{
|
||||
title: '部署10个以内的前端应用',
|
||||
description: '可以部署10个以内的应用,包括网页应用、小程序、H5等',
|
||||
},
|
||||
{
|
||||
title: '资源存储不超过50MB',
|
||||
},
|
||||
{
|
||||
title: '应用下载',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
level: 'love',
|
||||
levelNumber: 1,
|
||||
title: '支持会员',
|
||||
description: '支持一下',
|
||||
features: [
|
||||
{
|
||||
title: '部署30个以内的前端应用',
|
||||
description: '可以部署30个以内的应用,包括网页应用、小程序、H5等',
|
||||
},
|
||||
{
|
||||
title: '资源存储不超过200MB',
|
||||
},
|
||||
{
|
||||
title: '专属支持',
|
||||
description: '专属支持,包括专属客服、专属技术支持等',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
level: 'vip',
|
||||
levelNumber: 5,
|
||||
title: '高级会员',
|
||||
description: '应用定制和专属支持',
|
||||
features: [
|
||||
{
|
||||
title: '无限部署应用',
|
||||
description: '可以部署无限个应用,包括网页应用、小程序、H5等',
|
||||
},
|
||||
{
|
||||
title: '资源存储不超过500MB',
|
||||
},
|
||||
{
|
||||
title: '优先支持',
|
||||
description: '专属支持,包括专属客服、专属技术支持等',
|
||||
},
|
||||
{
|
||||
title: '专属域名',
|
||||
description: '可以申请专属域名,并使用专属域名进行访问',
|
||||
},
|
||||
{
|
||||
title: '私有化部署',
|
||||
description: '可以私有化部署,代理运维。',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
12
src/pages/vip/index.tsx
Normal file
12
src/pages/vip/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Routes, Route } from 'react-router';
|
||||
import { List } from './pages/List';
|
||||
import { VipInfo } from './pages/VipInfo';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route index element={<VipInfo />} />
|
||||
<Route path='/list' element={<List />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
239
src/pages/vip/pages/List.tsx
Normal file
239
src/pages/vip/pages/List.tsx
Normal file
@ -0,0 +1,239 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDemoStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Modal, Space } from 'antd';
|
||||
import { vipLevel, VipCategory } from '../query.ts';
|
||||
import dayjs from 'dayjs';
|
||||
import { Table, Button, Select, DatePicker, Input, Form } from 'antd';
|
||||
import { ColumnType } from 'antd/es/table/interface';
|
||||
|
||||
const defaultValues = {
|
||||
title: '',
|
||||
userId: '',
|
||||
level: 'free',
|
||||
category: 'center',
|
||||
startDate: dayjs(),
|
||||
endDate: dayjs().add(1, 'month'),
|
||||
};
|
||||
export const EditDialog = () => {
|
||||
const [form] = Form.useForm();
|
||||
const store = useDemoStore(
|
||||
useShallow((state) => ({
|
||||
formData: state.formData,
|
||||
setFormData: state.setFormData,
|
||||
showEdit: state.showEdit,
|
||||
setShowEdit: state.setShowEdit,
|
||||
updateData: state.updateData,
|
||||
})),
|
||||
);
|
||||
useEffect(() => {
|
||||
if (store.showEdit) {
|
||||
if (store.formData) {
|
||||
form.setFieldsValue({
|
||||
...store.formData,
|
||||
startDate: dayjs(store.formData.startDate),
|
||||
endDate: dayjs(store.formData.endDate),
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue(defaultValues);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
form.setFieldsValue(defaultValues);
|
||||
};
|
||||
}, [store.formData, store.showEdit]);
|
||||
const onSubmit = async (data: any) => {
|
||||
// 将dayjs对象转换为时间字符串
|
||||
const formattedData = {
|
||||
...data,
|
||||
id: store.formData?.id,
|
||||
startDate: data.startDate ? data.startDate.format('YYYY-MM-DD') : undefined,
|
||||
endDate: data.endDate ? data.endDate.format('YYYY-MM-DD') : undefined,
|
||||
};
|
||||
const res = await store.updateData(formattedData, { refresh: true });
|
||||
if (res.code === 200) {
|
||||
store.setShowEdit(false);
|
||||
store.setFormData(undefined);
|
||||
}
|
||||
};
|
||||
const onCancel = () => {
|
||||
store.setShowEdit(false);
|
||||
store.setFormData(undefined);
|
||||
};
|
||||
const hasId = !!store.formData?.id;
|
||||
return (
|
||||
<Modal
|
||||
title={hasId ? '编辑' : '添加'}
|
||||
footer={null}
|
||||
open={store.showEdit}
|
||||
onCancel={onCancel}
|
||||
style={{
|
||||
top: 40,
|
||||
}}>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 18,
|
||||
}}
|
||||
className='flex flex-col gap-4 pt-4 min-w-[400px]'
|
||||
onFinish={onSubmit}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='标题' rules={[{ required: true, message: '请输入标题' }]}>
|
||||
<Input placeholder='请输入标题' />
|
||||
</Form.Item>
|
||||
<Form.Item name='userId' label='用户ID' rules={[{ required: true, message: '请输入用户ID' }]}>
|
||||
<Input placeholder='请输入用户ID' />
|
||||
</Form.Item>
|
||||
<Form.Item name='level' label='等级' rules={[{ required: true, message: '请选择等级' }]}>
|
||||
<Select options={vipLevel} />
|
||||
</Form.Item>
|
||||
<Form.Item name='category' label='分类' rules={[{ required: true, message: '请选择分类' }]}>
|
||||
<Select options={VipCategory} />
|
||||
</Form.Item>
|
||||
<Form.Item name='startDate' label='开始日期' rules={[{ required: true, message: '请选择开始日期' }]}>
|
||||
<DatePicker format={'YYYY-MM-DD'} type='date' />
|
||||
</Form.Item>
|
||||
<Form.Item name='endDate' label='结束日期' rules={[{ required: true, message: '请选择结束日期' }]}>
|
||||
<DatePicker format={'YYYY-MM-DD'} type='date' />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button htmlType='submit'>提交</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const store = useDemoStore(
|
||||
useShallow((state) => ({
|
||||
list: state.list,
|
||||
pagination: state.pagination,
|
||||
init: state.init,
|
||||
setShowEdit: state.setShowEdit,
|
||||
deleteData: state.deleteData,
|
||||
setFormData: state.setFormData,
|
||||
getList: state.getList,
|
||||
})),
|
||||
);
|
||||
useEffect(() => {
|
||||
store.init();
|
||||
}, []);
|
||||
const columns: ColumnType<any>[] = [
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '等级',
|
||||
dataIndex: 'level',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
dataIndex: 'startDate',
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
return record.startDate ? dayjs(record.startDate).format('YYYY-MM-DD') : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
dataIndex: 'endDate',
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
return record.endDate ? dayjs(record.endDate).format('YYYY-MM-DD') : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type='link'
|
||||
onClick={() => {
|
||||
store.setShowEdit(true);
|
||||
store.setFormData(record);
|
||||
}}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button type='link' onClick={() => store.deleteData(record.id)}>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
const [formSearch] = Form.useForm();
|
||||
const onSearch = (data: any) => {
|
||||
console.log('onSearch', data);
|
||||
store.getList(data);
|
||||
};
|
||||
return (
|
||||
<div className='w-full h-full flex flex-col gap-2 bg-gray-100 p-4'>
|
||||
<div
|
||||
className='overflow-auto scrollbar relative'
|
||||
style={{
|
||||
height: 'calc(100% - 50px)',
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
<Form form={formSearch} onFinish={onSearch} layout='inline' className='sticky top-0 left-0 z-10 flex gap-2 flex-wrap'>
|
||||
<Button type='primary' onClick={() => store.setShowEdit(true)}>
|
||||
添加
|
||||
</Button>
|
||||
<Form.Item className='w-[200px]' name='search' label='标题'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item className='w-[200px]' name='level' label='等级'>
|
||||
<Select options={vipLevel} />
|
||||
</Form.Item>
|
||||
<Form.Item className='w-[22s0px]' name='userId' label='用户ID'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item className='w-[200px]' name='category' label='分类'>
|
||||
<Select options={VipCategory} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
搜索
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Table
|
||||
className='mt-2'
|
||||
columns={columns}
|
||||
rowKey={(record) => record.id}
|
||||
dataSource={store.list}
|
||||
pagination={{
|
||||
pageSize: store.pagination.pageSize,
|
||||
total: store.pagination.total,
|
||||
current: store.pagination.page,
|
||||
onChange: (page, pageSize) => {
|
||||
store.getList({ page, pageSize });
|
||||
console.log('onChange', page, pageSize);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<EditDialog />
|
||||
</div>
|
||||
);
|
||||
};
|
161
src/pages/vip/pages/VipInfo.tsx
Normal file
161
src/pages/vip/pages/VipInfo.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import { useEffect } from 'react';
|
||||
import { queryApi } from '../store';
|
||||
import { create } from 'zustand';
|
||||
import { toast } from 'react-toastify';
|
||||
import { vipFeatureList } from '../constants';
|
||||
import { usePayModalStore, PayModal } from '../pay-modal/PayModal';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
interface VipStore {
|
||||
vipList: any[];
|
||||
setVipList: (vipList: any[]) => void;
|
||||
init: () => Promise<void>;
|
||||
vipInfo?: {
|
||||
level: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
category: string;
|
||||
};
|
||||
setVipInfo: (vipInfo: any) => void;
|
||||
}
|
||||
|
||||
export const useVipStore = create<VipStore>((set) => ({
|
||||
vipList: [],
|
||||
setVipList: (vipList: any[]) => set({ vipList }),
|
||||
vipInfo: {
|
||||
level: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
category: '',
|
||||
},
|
||||
setVipInfo: (vipInfo: any) => set({ vipInfo }),
|
||||
init: async () => {
|
||||
const res = await queryApi.getMeVipList();
|
||||
if (res.code === 200) {
|
||||
const list = res.data.list || [];
|
||||
set({ vipList: list });
|
||||
const vipCenterInfo = list.find((item: any) => item.category === 'center');
|
||||
set({ vipInfo: vipCenterInfo });
|
||||
} else {
|
||||
toast.error(res.message || '获取VIP列表失败');
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
export const VipInfo = () => {
|
||||
const store = useVipStore();
|
||||
const payModalStore = usePayModalStore(
|
||||
useShallow((state) => {
|
||||
return { setOpen: state.setOpen, setMoney: state.setMoney };
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
store.init();
|
||||
}, []);
|
||||
|
||||
const vipPlans = [
|
||||
{
|
||||
type: '普通方案',
|
||||
level: 'free',
|
||||
background: 'bg-white',
|
||||
textColor: 'text-gray-800',
|
||||
description: '满足简单的部署应用需求',
|
||||
price: '免费',
|
||||
buttonText: '进入部署中心',
|
||||
buttonClass: 'bg-white border border-blue-500 text-blue-500 hover:bg-blue-50',
|
||||
},
|
||||
{
|
||||
type: '支持会员',
|
||||
background: 'bg-blue-50',
|
||||
level: 'love',
|
||||
textColor: 'text-gray-800',
|
||||
description: '可以部署更多应用和更多增值服务',
|
||||
price: '1.0',
|
||||
buttonText: '立即开通',
|
||||
buttonClass: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||
},
|
||||
{
|
||||
type: '高级会员',
|
||||
background: 'bg-gray-800',
|
||||
level: 'vip',
|
||||
textColor: 'text-amber-200',
|
||||
description: '应用定制和专属支持',
|
||||
price: '5.0',
|
||||
buttonText: '立即开通',
|
||||
buttonClass: 'bg-amber-100 text-gray-800 hover:bg-amber-200',
|
||||
},
|
||||
];
|
||||
const onBuyVip = (clickLevel: string) => {
|
||||
const currentLevel = store.vipInfo?.level || 'free';
|
||||
const currentLevelNumber = vipFeatureList.find((item) => item.level === currentLevel)?.levelNumber || 0;
|
||||
const clickLevelNumber = vipFeatureList.find((item) => item.level === clickLevel)?.levelNumber || 0;
|
||||
|
||||
if (clickLevel === 'free') {
|
||||
window.location.href = '/root/center/';
|
||||
return;
|
||||
}
|
||||
if (clickLevelNumber <= currentLevelNumber) {
|
||||
toast.info('您已经是该会员等级');
|
||||
return;
|
||||
}
|
||||
// 打开支付弹窗
|
||||
payModalStore.setOpen(true);
|
||||
payModalStore.setMoney(clickLevelNumber);
|
||||
return;
|
||||
};
|
||||
return (
|
||||
<div className='w-full flex flex-col items-center py-10 px-4 bg-gray-50 h-full scrollbar'>
|
||||
{/* 封面部分 */}
|
||||
<div className='w-full max-w-6xl bg-gradient-to-r from-blue-600 to-indigo-800 rounded-xl p-10 mb-16 text-white shadow-lg'>
|
||||
<div className='max-w-3xl'>
|
||||
<h1 className='text-4xl md:text-5xl font-bold mb-4'>一切为了快速运行</h1>
|
||||
<p className='text-xl md:text-2xl font-light opacity-90'>方便部署轻量级的网页app应用,并可以随时随地访问</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className='text-3xl font-bold mb-16 text-gray-800'>功能权益对比</h1>
|
||||
|
||||
<div className='flex flex-wrap justify-center gap-6 w-full max-w-6xl'>
|
||||
{vipPlans.map((plan, index) => {
|
||||
const vipFeatures = vipFeatureList.find((item) => item.level === plan.level);
|
||||
const planIsVip = plan.level === 'vip';
|
||||
const isActive = plan.level === store.vipInfo?.level;
|
||||
let planButtonText = plan.buttonText;
|
||||
if (isActive && plan.level !== 'free') {
|
||||
planButtonText = '已开通';
|
||||
}
|
||||
if (!isActive && plan.level === 'vip' && store.vipInfo?.level === 'love') {
|
||||
planButtonText = '升级';
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index} className={`${plan.background} rounded-lg p-8 flex flex-col items-center w-full max-w-xs`}>
|
||||
<h2 className={`text-3xl font-bold mb-4 ${plan.textColor}`}>{plan.level}</h2>
|
||||
<p className={`text-center mb-8 ${plan.textColor}`}>{plan.description}</p>
|
||||
|
||||
<div className={`flex items-end mb-8 ${plan.textColor}`}>
|
||||
<span className='text-xl'>低至</span>
|
||||
<span className='text-6xl font-semibold mx-2'>{plan.price}</span>
|
||||
{plan.price !== '免费' && <span className='text-xl'>元/月</span>}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`py-3 px-6 w-full rounded-md font-medium cursor-pointer transition-colors ${plan.buttonClass}`}
|
||||
onClick={() => onBuyVip(plan.level)}>
|
||||
{planButtonText}
|
||||
</button>
|
||||
<div className='flex flex-col items-start py-4'>
|
||||
{vipFeatures?.features.map((feature, index) => (
|
||||
<div key={index} className='flex items-center mb-2'>
|
||||
<span className={`${planIsVip ? 'text-white' : 'text-gray-600'} mr-2`}>{index + 1}.</span>
|
||||
<span className={`${planIsVip ? 'text-white' : 'text-gray-800'}`}>{feature.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<PayModal />
|
||||
</div>
|
||||
);
|
||||
};
|
31
src/pages/vip/pay-modal/PayModal.tsx
Normal file
31
src/pages/vip/pay-modal/PayModal.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Modal } from 'antd';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface PayModalStore {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
money: number;
|
||||
setMoney: (money: number) => void;
|
||||
}
|
||||
export const usePayModalStore = create<PayModalStore>((set) => ({
|
||||
open: false,
|
||||
setOpen: (open: boolean) => set({ open }),
|
||||
money: 0,
|
||||
setMoney: (money: number) => set({ money }),
|
||||
}));
|
||||
|
||||
export const PayModal = () => {
|
||||
const { open, setOpen, money } = usePayModalStore();
|
||||
return (
|
||||
<Modal open={open} onCancel={() => setOpen(false)} maskClosable={false} footer={null}>
|
||||
<div className='px-4 py-4 flex flex-col gap-4 select-none'>
|
||||
<h2 className='text-2xl font-bold mb-4'>支付确认</h2>
|
||||
<p className='text-lg'>请确认支付金额:{money} 元</p>
|
||||
<div className='flex gap-2'>
|
||||
<button className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'>微信支付</button>
|
||||
<button className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'>支付宝支付</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
101
src/pages/vip/query.ts
Normal file
101
src/pages/vip/query.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { BaseQuery } from '@kevisual/query';
|
||||
|
||||
export const vipLevel = [
|
||||
{
|
||||
label: '免费会员',
|
||||
value: 'free',
|
||||
},
|
||||
{
|
||||
label: '普通会员',
|
||||
value: 'love',
|
||||
},
|
||||
{
|
||||
label: 'VIP会员',
|
||||
value: 'vip',
|
||||
},
|
||||
];
|
||||
export const VipCategory = [
|
||||
{
|
||||
label: 'Deploy Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'AI Chat',
|
||||
value: 'chat',
|
||||
},
|
||||
];
|
||||
export class QueryApi extends BaseQuery {
|
||||
constructor(options: { query: any }) {
|
||||
super(options);
|
||||
}
|
||||
async getList(params?: any, dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'list',
|
||||
...params,
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
async getDetail(id?: string, dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'get',
|
||||
data: { id },
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
async update(data?: any, dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'update',
|
||||
data,
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
async delete(id?: string, dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'delete',
|
||||
data: { id },
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 获取我的VIP列表
|
||||
* @param dataOpts 数据选项
|
||||
* @returns
|
||||
*/
|
||||
async getMeVipList(dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'me-vip-list',
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 获取我的VIP信息
|
||||
* @param category 分类
|
||||
* @param dataOpts 数据选项
|
||||
* @returns
|
||||
*/
|
||||
async getMeVipInfo(category?: string, dataOpts?: any) {
|
||||
return this.query.post(
|
||||
{
|
||||
path: 'vip',
|
||||
key: 'me',
|
||||
category: category || 'center',
|
||||
},
|
||||
dataOpts,
|
||||
);
|
||||
}
|
||||
}
|
108
src/pages/vip/store.ts
Normal file
108
src/pages/vip/store.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { create } from 'zustand';
|
||||
import { query } from '@/modules/query';
|
||||
import { QueryApi } from './query';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export const queryApi = new QueryApi({ query });
|
||||
type Store = {
|
||||
list: any[];
|
||||
setList: (list: any[]) => void;
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
setPagination: (pagination: { page: number; pageSize: number; total: number }) => void;
|
||||
data: any;
|
||||
setData: (data: any) => void;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
formData: any;
|
||||
setFormData: (data: any) => void;
|
||||
showEdit: boolean;
|
||||
setShowEdit: (showEdit: boolean) => void;
|
||||
getList: (params?: { page?: number; pageSize?: number; category?: string; level?: string }) => Promise<any>;
|
||||
init: () => Promise<void>;
|
||||
getData: (id: string) => Promise<any>;
|
||||
updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
|
||||
deleteData: (id: string, opts?: { refresh?: boolean }) => Promise<any>;
|
||||
};
|
||||
export const useDemoStore = create<Store>((set, get) => ({
|
||||
list: [],
|
||||
setList: (list) => set({ list }),
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 2,
|
||||
total: 0,
|
||||
},
|
||||
setPagination: (pagination) => set({ pagination }),
|
||||
data: null,
|
||||
setData: (data) => set({ data }),
|
||||
loading: false,
|
||||
setLoading: (loading) => set({ loading }),
|
||||
formData: null,
|
||||
setFormData: (formData) => set({ formData }),
|
||||
showEdit: false,
|
||||
setShowEdit: (showEdit) => set({ showEdit }),
|
||||
getList: async (params?: any) => {
|
||||
set({ loading: true });
|
||||
let { page, pageSize, ...rest } = params || {};
|
||||
const res = await queryApi.getList({
|
||||
page: page || 1,
|
||||
pageSize: pageSize || 10,
|
||||
...rest,
|
||||
});
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({
|
||||
list: res.data.list,
|
||||
pagination: res.data.pagination,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
init: async () => {
|
||||
await get().getList();
|
||||
},
|
||||
getData: async (id) => {
|
||||
set({ loading: true });
|
||||
const res = await queryApi.getDetail(id);
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
const data = res.data;
|
||||
set({ data });
|
||||
}
|
||||
return res;
|
||||
},
|
||||
updateData: async (data, opts = { refresh: true }) => {
|
||||
set({ loading: true });
|
||||
const res = await queryApi.update(data);
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({ data: res.data });
|
||||
toast.success('更新成功');
|
||||
} else {
|
||||
toast.error(res.message || '更新失败');
|
||||
}
|
||||
if (opts.refresh) {
|
||||
await get().getList();
|
||||
}
|
||||
return res;
|
||||
},
|
||||
deleteData: async (id, opts = { refresh: true }) => {
|
||||
set({ loading: true });
|
||||
const res = await queryApi.delete(id);
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({ data: null });
|
||||
toast.success('删除成功');
|
||||
} else {
|
||||
toast.error(res.message || '删除失败');
|
||||
}
|
||||
if (opts.refresh) {
|
||||
await get().getList();
|
||||
}
|
||||
return res;
|
||||
},
|
||||
}));
|
@ -27,19 +27,20 @@ if (isDev) {
|
||||
} else {
|
||||
target = 'https://kevisual.cn';
|
||||
}
|
||||
target = 'http://localhost:4006';
|
||||
|
||||
let proxy = {
|
||||
'/root/center/': {
|
||||
target: `https://${target}/root/center/`,
|
||||
target: `${target}/root/center/`,
|
||||
},
|
||||
'/root/system-lib/': {
|
||||
target: `https://${target}/root/system-lib/`,
|
||||
target: `${target}/root/system-lib/`,
|
||||
},
|
||||
'/user/login/': {
|
||||
target: `https://${target}/user/login/`,
|
||||
target: `${target}/user/login/`,
|
||||
},
|
||||
'/api': {
|
||||
target: `https://${target}`,
|
||||
target: `${target}`,
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewriteWsOrigin: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user