mirror of
https://github-proxy.zxj.im/abearxiong/app-show-list
synced 2025-06-28 22:47:52 +08:00
feat: add uploads app
This commit is contained in:
parent
5b1b73fae5
commit
80d2ec49a3
5
.npmrc
Normal file
5
.npmrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||||
|
@abearxiong:registry=https://npm.pkg.github.com
|
||||||
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
|
@build:registry=https://npm.xiongxiao.me
|
||||||
|
@kevisual:registry=https://npm.xiongxiao.me
|
16
README.md
16
README.md
@ -2,3 +2,19 @@
|
|||||||
|
|
||||||
|
|
||||||
`/system/lib/app.js` 包函的模块是 `QueryRouterServer` 和 `Page` 和 `useConfigKey`
|
`/system/lib/app.js` 包函的模块是 `QueryRouterServer` 和 `Page` 和 `useConfigKey`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## deploy app的过程
|
||||||
|
|
||||||
|
- `envision pack -p`
|
||||||
|
- `curl` 请求
|
||||||
|
```sh
|
||||||
|
curl https://envision.xiongxiao.me/api/router?path=micro-app&key=deploy -d '{
|
||||||
|
"data": {
|
||||||
|
"id": "17d105b8-9b6b-4cfc-9447-c815f24fe3d7",
|
||||||
|
"key": "mark"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
- 启动 `https://kevisual.xiongxiao.me/api/router?path=local-apps&key=updateStatus&appKey=mark&status=start`
|
||||||
|
16
package.json
16
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "app-template",
|
"name": "app-show",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
@ -7,8 +7,8 @@
|
|||||||
"dev": "cross-env WEB_DEV=true vite",
|
"dev": "cross-env WEB_DEV=true vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepub": "envision switchOrg user",
|
"prepub": "envision switchOrg system",
|
||||||
"pub": "envision deploy ./dist -k app-template -v 0.0.1"
|
"pub": "envision deploy ./dist -k app-show -v 0.0.1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
@ -19,23 +19,33 @@
|
|||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@kevisual/query": "0.0.7-alpha.3",
|
"@kevisual/query": "0.0.7-alpha.3",
|
||||||
"@kevisual/system-ui": "^0.0.3",
|
"@kevisual/system-ui": "^0.0.3",
|
||||||
|
"@mui/icons-material": "^6.3.0",
|
||||||
"@mui/material": "^6.3.0",
|
"@mui/material": "^6.3.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router": "^7.1.1",
|
||||||
|
"react-router-dom": "^7.1.1",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"zustand": "^5.0.2"
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@build/tailwind": "1.0.2-alpha-2",
|
||||||
"@emotion/css": "^11.13.5",
|
"@emotion/css": "^11.13.5",
|
||||||
"@kevisual/router": "0.0.6-alpha-4",
|
"@kevisual/router": "0.0.6-alpha-4",
|
||||||
"@kevisual/store": "0.0.1-alpha.9",
|
"@kevisual/store": "0.0.1-alpha.9",
|
||||||
"@kevisual/types": "^0.0.5",
|
"@kevisual/types": "^0.0.5",
|
||||||
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/react": "^19.0.2",
|
"@types/react": "^19.0.2",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vite": "^6.0.6"
|
"vite": "^6.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
870
pnpm-lock.yaml
generated
870
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,15 @@
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { BrowserRouter, Route, Routes } from 'react-router';
|
||||||
export const App = () => {
|
import { List } from './pages/List';
|
||||||
|
import { LayoutMain } from './layouts';
|
||||||
|
|
||||||
|
export const ReactApp = () => {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center items-center h-screen bg-gray-200'>
|
<BrowserRouter basename='/system/app-show'>
|
||||||
<h1 className='text-4xl font-bold text-gray-800'>Hello Vite + React!</h1>
|
<Routes>
|
||||||
</div>
|
<Route element={<LayoutMain />}>
|
||||||
|
<Route path='/show-home' element={<List />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(<App />);
|
|
||||||
|
9
src/app.ts
Normal file
9
src/app.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
import { useContextKey } from '@kevisual/store/config';
|
||||||
|
import { Page } from "@kevisual/store/page";
|
||||||
|
|
||||||
|
export const page = useContextKey('page', () => {
|
||||||
|
return new Page({
|
||||||
|
basename: '/system/app-show',
|
||||||
|
});
|
||||||
|
});
|
@ -1,44 +0,0 @@
|
|||||||
import { useStore } from '@/store/app';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { css } from '@emotion/css';
|
|
||||||
|
|
||||||
const containerStyle = css`
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const itemStyle = css`
|
|
||||||
display: flex;
|
|
||||||
min-width: 240px;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
background-color: #fff;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const List = () => {
|
|
||||||
const store = useStore();
|
|
||||||
useEffect(() => {
|
|
||||||
store.init();
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div className={containerStyle}>
|
|
||||||
<div>
|
|
||||||
{store.list.map((item) => {
|
|
||||||
return (
|
|
||||||
<div className={itemStyle} key={item.key}>
|
|
||||||
<span>{item.key}</span>
|
|
||||||
<span>{item.description}</span>
|
|
||||||
<span>{item.status}</span>
|
|
||||||
<span>{item.type}</span>
|
|
||||||
<span>{item.version}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
13
src/layouts/LayoutMain.tsx
Normal file
13
src/layouts/LayoutMain.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { LayoutMenu } from './LayoutMenu';
|
||||||
|
|
||||||
|
export const LayoutMain = () => {
|
||||||
|
return (
|
||||||
|
<div className='bg-slate-200' style={{ display: 'flex', height: '100%' }}>
|
||||||
|
<LayoutMenu />
|
||||||
|
<main style={{ flexGrow: 1, height: '100%', overflow: 'auto' }}>
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
25
src/layouts/LayoutMenu.tsx
Normal file
25
src/layouts/LayoutMenu.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Menu, MenuItem, ListItemIcon, ListItemText } from '@mui/material';
|
||||||
|
import InboxIcon from '@mui/icons-material/Inbox';
|
||||||
|
import MailIcon from '@mui/icons-material/Mail';
|
||||||
|
|
||||||
|
// ...existing code...
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ text: 'Inbox', icon: <InboxIcon /> },
|
||||||
|
{ text: 'Mail', icon: <MailIcon /> },
|
||||||
|
// 可以根据需要添加更多菜单项
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LayoutMenu = () => {
|
||||||
|
return (
|
||||||
|
<Menu open={false} anchorOrigin={{ vertical: 'top', horizontal: 'left' }}>
|
||||||
|
{menuItems.map((item, index) => (
|
||||||
|
<MenuItem key={index}>
|
||||||
|
<ListItemIcon>{item.icon}</ListItemIcon>
|
||||||
|
<ListItemText primary={item.text} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
1
src/layouts/index.ts
Normal file
1
src/layouts/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './LayoutMain'
|
48
src/main.tsx
48
src/main.tsx
@ -1,29 +1,41 @@
|
|||||||
import { useContextKey } from '@kevisual/store/config';
|
import { useContextKey } from '@kevisual/store/config';
|
||||||
import { Page } from '@kevisual/store/page';
|
|
||||||
import { QueryRouterServer } from '@kevisual/router';
|
import { QueryRouterServer } from '@kevisual/router';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { List } from './app/List';
|
import { List } from './pages/List';
|
||||||
|
import { List as UploadAppList } from './pages/upload-apps/List';
|
||||||
|
import { page } from './app';
|
||||||
|
|
||||||
|
import '@build/tailwind/main.css';
|
||||||
|
// import './tailwind.css';
|
||||||
|
|
||||||
|
export const initRoot = (renderRoot?: any) => {
|
||||||
|
return useContextKey('root', () => {
|
||||||
|
if (!renderRoot) {
|
||||||
|
console.error('renderRoot is required');
|
||||||
|
}
|
||||||
|
return createRoot(renderRoot);
|
||||||
|
});
|
||||||
|
};
|
||||||
export const render = ({ renderRoot }) => {
|
export const render = ({ renderRoot }) => {
|
||||||
// renderRoot.innerHTML = `
|
const root = initRoot(renderRoot);
|
||||||
// <h1>Hello, World!</h1>
|
|
||||||
// `;
|
|
||||||
const root = createRoot(renderRoot);
|
|
||||||
// @ts-ignore
|
|
||||||
root.render(<List />);
|
root.render(<List />);
|
||||||
};
|
};
|
||||||
|
|
||||||
const page = useContextKey('page', () => {
|
|
||||||
return new Page({
|
|
||||||
basename: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (page) {
|
if (page) {
|
||||||
page.addPage('/app-template', 'home');
|
initRoot(document.getElementById('ai-root'));
|
||||||
page.subscribe('home', () => {
|
page.addPage('/', 'home');
|
||||||
render({
|
page.addPage('/local-apps', 'local-apps');
|
||||||
renderRoot: document.getElementById('ai-root'),
|
page.subscribe('local-apps', () => {
|
||||||
|
const root = initRoot();
|
||||||
|
root.render(<List />);
|
||||||
});
|
});
|
||||||
|
page.addPage('/upload-apps', 'upload-apps');
|
||||||
|
page.subscribe('upload-apps', () => {
|
||||||
|
const root = initRoot();
|
||||||
|
root.render(<UploadAppList />);
|
||||||
|
});
|
||||||
|
page.subscribe('home', () => {
|
||||||
|
page.navigate('/local-apps');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +46,7 @@ const app = useContextKey('app', () => {
|
|||||||
if (app) {
|
if (app) {
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'app-template',
|
path: 'show-home',
|
||||||
key: 'render',
|
key: 'render',
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
|
105
src/pages/List.tsx
Normal file
105
src/pages/List.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { useStore } from '@/store/app';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { Page } from '@kevisual/store/page';
|
||||||
|
import { page } from '@/app';
|
||||||
|
import { useOperateStore } from '@/store/operate';
|
||||||
|
import { message } from '@/modules/message';
|
||||||
|
|
||||||
|
const containerStyle = css`
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const itemStyle = css`
|
||||||
|
display: flex;
|
||||||
|
min-width: 240px;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #fff;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const List = () => {
|
||||||
|
const store = useStore();
|
||||||
|
const operateStore = useOperateStore();
|
||||||
|
useEffect(() => {
|
||||||
|
store.init();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className={clsx(containerStyle, 'h-full w-full ')}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
page.navigate('/upload-apps');
|
||||||
|
}}>
|
||||||
|
To Upload Apps
|
||||||
|
</div>
|
||||||
|
<h1 className='text-2xl font-bold'>Local Apps</h1>
|
||||||
|
<div>
|
||||||
|
<div className='local-apps-headers flex flex-row-reverse'>
|
||||||
|
<button
|
||||||
|
className='px-4 py-2 mt-2 text-white bg-blue-500 rounded hover:bg-blue-700'
|
||||||
|
onClick={async () => {
|
||||||
|
const res = await operateStore.detect();
|
||||||
|
if (res.code === 200) {
|
||||||
|
store.init();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Detect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row flex-wrap gap-4'>
|
||||||
|
{store.list.map((item) => {
|
||||||
|
return (
|
||||||
|
<div className={itemStyle} key={item.key}>
|
||||||
|
<span>{item.key}</span>
|
||||||
|
<span>{item.description}</span>
|
||||||
|
<span>{item.status}</span>
|
||||||
|
<span>{item.type}</span>
|
||||||
|
<span>{item.version}</span>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<button
|
||||||
|
className='px-4 py-2 mt-2 text-white bg-blue-500 rounded hover:bg-blue-700'
|
||||||
|
onClick={async () => {
|
||||||
|
if (item.status !== 'stop') {
|
||||||
|
const res = await operateStore.updateStatus({ status: 'stop', appKey: item.key });
|
||||||
|
if (res.code === 200) {
|
||||||
|
store.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Stop
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='px-4 py-2 mt-2 text-white bg-blue-500 rounded hover:bg-blue-700'
|
||||||
|
onClick={async () => {
|
||||||
|
if (item.status !== 'running') {
|
||||||
|
const res = await operateStore.updateStatus({ status: 'start', appKey: item.key });
|
||||||
|
if (res.code === 200) {
|
||||||
|
store.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Start
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className='px-4 py-2 mt-2 text-white bg-blue-500 rounded hover:bg-blue-700'
|
||||||
|
onClick={async () => {
|
||||||
|
const res = await store.deleteData(item.key, { refresh: true });
|
||||||
|
//
|
||||||
|
}}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
54
src/pages/upload-apps/List.tsx
Normal file
54
src/pages/upload-apps/List.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { useAppUploadStore } from '@/store/app-upload';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { KeyEditModal } from './modal/Edit';
|
||||||
|
import { page } from '@/app';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
export const List = () => {
|
||||||
|
const store = useAppUploadStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
store.init();
|
||||||
|
}, []);
|
||||||
|
const onDeploy = (item: { id: string; [key: string]: any }) => {
|
||||||
|
console.log(item);
|
||||||
|
store.setFormData({
|
||||||
|
id: item.id,
|
||||||
|
key: '',
|
||||||
|
});
|
||||||
|
store.setShowModal(true);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='p-4'>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
page.navigate('/local-apps');
|
||||||
|
}}>
|
||||||
|
To Local Apps
|
||||||
|
</div>
|
||||||
|
<h1 className='text-2xl font-bold'>Upload Apps</h1>
|
||||||
|
<div className='flex flex-wrap gap-4'>
|
||||||
|
{store.list.map((item) => {
|
||||||
|
return (
|
||||||
|
<div className='p-4 mb-4 border rounded shadow bg-white w-[300px] flex flex-col' key={item.id}>
|
||||||
|
<span>{item.title}</span>
|
||||||
|
<span>{item.description}</span>
|
||||||
|
<span>{item.tags.join(', ')}</span>
|
||||||
|
<span>{item.type}</span>
|
||||||
|
<span>{dayjs(item.updatedTime).format('YYYY-MM-DD HH:mm:ss')}</span>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className='px-4 py-2 mt-2 text-white bg-blue-500 rounded hover:bg-blue-700'
|
||||||
|
onClick={() => {
|
||||||
|
onDeploy(item);
|
||||||
|
}}>
|
||||||
|
Deploy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<KeyEditModal />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
40
src/pages/upload-apps/modal/Edit.tsx
Normal file
40
src/pages/upload-apps/modal/Edit.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useAppUploadStore } from '@/store/app-upload';
|
||||||
|
import { Modal } from '@mui/material';
|
||||||
|
|
||||||
|
export const KeyEditModal = () => {
|
||||||
|
const store = useAppUploadStore();
|
||||||
|
const onSave = () => {
|
||||||
|
console.log('save');
|
||||||
|
store.setShowModal(false);
|
||||||
|
const fromData = store.formData;
|
||||||
|
store.pubApp(fromData);
|
||||||
|
console.log(fromData);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className='fixed flex items-center justify-center'
|
||||||
|
open={store.showModal}
|
||||||
|
onClose={() => store.setShowModal(false)}
|
||||||
|
>
|
||||||
|
<div className='p-4 bg-white w-[400px]'>
|
||||||
|
<h1 className='text-2xl font-bold'>Edit Key</h1>
|
||||||
|
<div className='mt-4'>
|
||||||
|
<label className='block'>Key</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='w-full p-2 border rounded'
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
store.setFormData({ ...store.formData, key: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='mt-4'>
|
||||||
|
<button className='px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-700' onClick={onSave}>
|
||||||
|
Deploy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
107
src/store/app-upload.ts
Normal file
107
src/store/app-upload.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { message } from '@/modules/message';
|
||||||
|
|
||||||
|
type Store = {
|
||||||
|
list: any[];
|
||||||
|
setList: (list: any[]) => void;
|
||||||
|
data: any;
|
||||||
|
setData: (data: any) => void;
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
formData: any;
|
||||||
|
setFormData: (data: any) => void;
|
||||||
|
getList: () => Promise<any>;
|
||||||
|
init: () => Promise<void>;
|
||||||
|
getData: (id: number) => Promise<any>;
|
||||||
|
updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
|
||||||
|
deleteData: (id: number, opts?: { refresh?: boolean }) => Promise<any>;
|
||||||
|
showModal: boolean;
|
||||||
|
setShowModal: (showModal: boolean) => void;
|
||||||
|
pubApp: (data: any) => Promise<any>;
|
||||||
|
};
|
||||||
|
export const useAppUploadStore = create<Store>((set, get) => ({
|
||||||
|
list: [],
|
||||||
|
setList: (list) => set({ list }),
|
||||||
|
data: null,
|
||||||
|
setData: (data) => set({ data }),
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
formData: null,
|
||||||
|
setFormData: (formData) => set({ formData }),
|
||||||
|
getList: async () => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({ path: 'micro-app-upload', key: 'list' });
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ list: res.data });
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
init: async () => {
|
||||||
|
await get().getList();
|
||||||
|
},
|
||||||
|
getData: async (id) => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'micro-app-upload',
|
||||||
|
key: 'get',
|
||||||
|
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 query.post({
|
||||||
|
path: 'micro-app-upload',
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ data: res.data });
|
||||||
|
}
|
||||||
|
if (opts.refresh) {
|
||||||
|
await get().getList();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
deleteData: async (id, opts = { refresh: true }) => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'micro-app-upload',
|
||||||
|
key: 'delete',
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ data: null });
|
||||||
|
}
|
||||||
|
if (opts.refresh) {
|
||||||
|
await get().getList();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
showModal: false,
|
||||||
|
setShowModal: (showModal) => set({ showModal }),
|
||||||
|
pubApp: async (data) => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'micro-app',
|
||||||
|
key: 'deploy',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('发布成功');
|
||||||
|
set({ data: null });
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '发布失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
@ -72,7 +72,7 @@ export const useStore = create<Store>((set, get) => ({
|
|||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'local-apps',
|
path: 'local-apps',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
id,
|
appKey: id,
|
||||||
});
|
});
|
||||||
set({ loading: false });
|
set({ loading: false });
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
|
@ -2,53 +2,66 @@ import { create } from 'zustand';
|
|||||||
import { query } from '@/modules/query';
|
import { query } from '@/modules/query';
|
||||||
import { message } from '@/modules/message';
|
import { message } from '@/modules/message';
|
||||||
interface OperateStore {
|
interface OperateStore {
|
||||||
updateStatus: () => Promise<void>;
|
updateStatus: (data: { status: 'stop' | 'start'; appKey: string }) => Promise<any>;
|
||||||
detect: () => Promise<void>;
|
detect: () => Promise<any>;
|
||||||
download: () => Promise<void>;
|
download: () => Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微应用的上传和卸载和部署
|
* 微应用的上传和卸载和部署
|
||||||
*/
|
*/
|
||||||
upload: () => Promise<void>;
|
upload: () => Promise<any>;
|
||||||
unload: () => Promise<void>;
|
unload: () => Promise<any>;
|
||||||
deploy: () => Promise<void>;
|
deploy: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useOperateStore = create<OperateStore>((set) => ({
|
export const useOperateStore = create<OperateStore>((set) => ({
|
||||||
updateStatus: async () => {
|
updateStatus: async (data) => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'local-apps',
|
path: 'local-apps',
|
||||||
key: 'updateStatus',
|
key: 'updateStatus',
|
||||||
|
status: data.status,
|
||||||
|
appKey: data.appKey,
|
||||||
});
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('操作成功');
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '操作失败');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
detect: async () => {
|
detect: async () => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'local-apps',
|
path: 'local-apps',
|
||||||
key: 'detect',
|
key: 'detect',
|
||||||
});
|
});
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
download: async () => {
|
download: async () => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'local-apps',
|
path: 'local-apps',
|
||||||
key: 'download',
|
key: 'download',
|
||||||
});
|
});
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
upload: async () => {
|
upload: async () => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'micro-apps',
|
path: 'micro-apps',
|
||||||
key: 'upload',
|
key: 'upload',
|
||||||
});
|
});
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
unload: async () => {
|
unload: async () => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'micro-apps',
|
path: 'micro-apps',
|
||||||
key: 'unload',
|
key: 'unload',
|
||||||
});
|
});
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
deploy: async () => {
|
deploy: async () => {
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'micro-apps',
|
path: 'micro-apps',
|
||||||
key: 'deploy',
|
key: 'deploy',
|
||||||
});
|
});
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
4
src/tailwind.css
Normal file
4
src/tailwind.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
47
tailwind.config.js
Normal file
47
tailwind.config.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const root = path.resolve(process.cwd());
|
||||||
|
const contents = ['./src/**/*.{ts,tsx,html}', './src/**/*.css']
|
||||||
|
const content = contents.map((item) => path.join(root, item));
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
// darkMode: ['class'],
|
||||||
|
content: content,
|
||||||
|
plugins: [
|
||||||
|
require('@tailwindcss/aspect-ratio'), //
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
require('tailwindcss-animate'),
|
||||||
|
require('@build/tailwind'),
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族
|
||||||
|
rob: ['Roboto', 'sans-serif'],
|
||||||
|
int: ['Inter', 'sans-serif'],
|
||||||
|
orb: ['Orbitron', 'sans-serif'],
|
||||||
|
din: ['DIN', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
screen: {
|
||||||
|
sm: '640px',
|
||||||
|
// => @media (min-width: 640px) { ... }
|
||||||
|
|
||||||
|
md: '768px',
|
||||||
|
// => @media (min-width: 768px) { ... }
|
||||||
|
|
||||||
|
lg: '1024px',
|
||||||
|
// => @media (min-width: 1024px) { ... }
|
||||||
|
|
||||||
|
xl: '1280px',
|
||||||
|
// => @media (min-width: 1280px) { ... }
|
||||||
|
|
||||||
|
'2xl': '1536px',
|
||||||
|
// => @media (min-width: 1536px) { ... }
|
||||||
|
'3xl': '1920px',
|
||||||
|
// => @media (min-width: 1920) { ... }
|
||||||
|
'4xl': '2560px',
|
||||||
|
// => @media (min-width: 2560) { ... }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -3,6 +3,9 @@ import react from '@vitejs/plugin-react'
|
|||||||
// import basicSsl from '@vitejs/plugin-basic-ssl';
|
// import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import tailwindcss from 'tailwindcss';
|
||||||
|
import autoprefixer from 'autoprefixer';
|
||||||
|
import nesting from 'tailwindcss/nesting';
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
const BUILD_TIME = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const BUILD_TIME = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
@ -10,6 +13,13 @@ console.log('process', isDev, process.env.WEB_DEV)
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// plugins: [basicSsl()],
|
// plugins: [basicSsl()],
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
// @ts-ignore
|
||||||
|
plugins: [nesting, tailwindcss, autoprefixer],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
base: isDev ? '/' : './',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src'),
|
'@': path.resolve(__dirname, './src'),
|
||||||
@ -32,13 +42,13 @@ export default defineConfig({
|
|||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://kevisual.xiongxiao.me',
|
target: 'https://kevisual.xiongxiao.me',
|
||||||
|
// target: 'http://localhost:9787',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
},
|
},
|
||||||
'/system': {
|
'/system/lib': {
|
||||||
target: 'https://kevisual.xiongxiao.me',
|
target: 'https://kevisual.xiongxiao.me',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/system/, '/system'),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user