generated from template/vite-react-template
	init
This commit is contained in:
		@@ -21,10 +21,12 @@
 | 
				
			|||||||
  "author": "abearxiong <xiongxiao@xiongxiao.me>",
 | 
					  "author": "abearxiong <xiongxiao@xiongxiao.me>",
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@ant-design/v5-patch-for-react-19": "^1.0.3",
 | 
				
			||||||
    "@emotion/react": "^11.14.0",
 | 
					    "@emotion/react": "^11.14.0",
 | 
				
			||||||
    "@emotion/styled": "^11.14.0",
 | 
					    "@emotion/styled": "^11.14.0",
 | 
				
			||||||
    "@kevisual/router": "0.0.10",
 | 
					    "@kevisual/router": "0.0.10",
 | 
				
			||||||
    "@mui/material": "^7.0.1",
 | 
					    "@mui/material": "^7.0.1",
 | 
				
			||||||
 | 
					    "antd": "^5.24.6",
 | 
				
			||||||
    "clsx": "^2.1.1",
 | 
					    "clsx": "^2.1.1",
 | 
				
			||||||
    "dayjs": "^1.11.13",
 | 
					    "dayjs": "^1.11.13",
 | 
				
			||||||
    "lodash-es": "^4.17.21",
 | 
					    "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 "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 { createRoot } from 'react-dom/client';
 | 
				
			||||||
import { App, AppRoute } from './pages/App.tsx';
 | 
					import { App, AppRoute } from './pages/App.tsx';
 | 
				
			||||||
import { CustomThemeProvider } from '@kevisual/components/theme/index.tsx';
 | 
					import { ConfigProvider } from 'antd';
 | 
				
			||||||
 | 
					import { ToastContainer } from 'react-toastify';
 | 
				
			||||||
console.log('cu',)
 | 
					// import 'react-toastify/dist/ReactToastify.css';
 | 
				
			||||||
createRoot(document.getElementById('root')!).render(
 | 
					createRoot(document.getElementById('root')!).render(
 | 
				
			||||||
  <CustomThemeProvider>
 | 
					  <ConfigProvider
 | 
				
			||||||
 | 
					    theme={{
 | 
				
			||||||
 | 
					      components: {
 | 
				
			||||||
 | 
					        Modal: {
 | 
				
			||||||
 | 
					          // zIndex: 1000,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }}>
 | 
				
			||||||
    <AppRoute />
 | 
					    <AppRoute />
 | 
				
			||||||
  </CustomThemeProvider>,
 | 
					    <ToastContainer />
 | 
				
			||||||
 | 
					  </ConfigProvider>,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import { basename } from '../modules/basename';
 | 
					import { basename } from '../modules/basename';
 | 
				
			||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
 | 
					import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
 | 
				
			||||||
console.log('basename', basename);
 | 
					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 = () => {
 | 
					export const App = () => {
 | 
				
			||||||
  return <div className='bg-slate-200 w-full h-full border'>123</div>;
 | 
					  return <div className='bg-slate-200 w-full h-full border'>123</div>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -10,8 +12,7 @@ export const AppRoute = () => {
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Router>
 | 
					    <Router>
 | 
				
			||||||
      <Routes>
 | 
					      <Routes>
 | 
				
			||||||
        <Route path='/' element={<Navigate to='/app-demo/list' />} />
 | 
					        <Route path='*' element={<AppVip />} />
 | 
				
			||||||
        <Route path='/app-demo/*' element={<AppDemo />} />
 | 
					 | 
				
			||||||
      </Routes>
 | 
					      </Routes>
 | 
				
			||||||
    </Router>
 | 
					    </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 {
 | 
					} else {
 | 
				
			||||||
  target = 'https://kevisual.cn';
 | 
					  target = 'https://kevisual.cn';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					target = 'http://localhost:4006';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let proxy = {
 | 
					let proxy = {
 | 
				
			||||||
  '/root/center/': {
 | 
					  '/root/center/': {
 | 
				
			||||||
    target: `https://${target}/root/center/`,
 | 
					    target: `${target}/root/center/`,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  '/root/system-lib/': {
 | 
					  '/root/system-lib/': {
 | 
				
			||||||
    target: `https://${target}/root/system-lib/`,
 | 
					    target: `${target}/root/system-lib/`,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  '/user/login/': {
 | 
					  '/user/login/': {
 | 
				
			||||||
    target: `https://${target}/user/login/`,
 | 
					    target: `${target}/user/login/`,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  '/api': {
 | 
					  '/api': {
 | 
				
			||||||
    target: `https://${target}`,
 | 
					    target: `${target}`,
 | 
				
			||||||
    changeOrigin: true,
 | 
					    changeOrigin: true,
 | 
				
			||||||
    ws: true,
 | 
					    ws: true,
 | 
				
			||||||
    rewriteWsOrigin: true,
 | 
					    rewriteWsOrigin: true,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user