mirror of
https://github-proxy.zxj.im/abearxiong/app-show-list
synced 2025-06-28 14:37:53 +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`
|
||||
|
||||
|
||||
|
||||
## 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",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
@ -7,8 +7,8 @@
|
||||
"dev": "cross-env WEB_DEV=true vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepub": "envision switchOrg user",
|
||||
"pub": "envision deploy ./dist -k app-template -v 0.0.1"
|
||||
"prepub": "envision switchOrg system",
|
||||
"pub": "envision deploy ./dist -k app-show -v 0.0.1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
@ -19,23 +19,33 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@kevisual/query": "0.0.7-alpha.3",
|
||||
"@kevisual/system-ui": "^0.0.3",
|
||||
"@mui/icons-material": "^6.3.0",
|
||||
"@mui/material": "^6.3.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router": "^7.1.1",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@build/tailwind": "1.0.2-alpha-2",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@kevisual/router": "0.0.6-alpha-4",
|
||||
"@kevisual/store": "0.0.1-alpha.9",
|
||||
"@kevisual/types": "^0.0.5",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"react": "^19.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"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';
|
||||
export const App = () => {
|
||||
import { BrowserRouter, Route, Routes } from 'react-router';
|
||||
import { List } from './pages/List';
|
||||
import { LayoutMain } from './layouts';
|
||||
|
||||
export const ReactApp = () => {
|
||||
return (
|
||||
<div className='flex justify-center items-center h-screen bg-gray-200'>
|
||||
<h1 className='text-4xl font-bold text-gray-800'>Hello Vite + React!</h1>
|
||||
</div>
|
||||
<BrowserRouter basename='/system/app-show'>
|
||||
<Routes>
|
||||
<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 { Page } from '@kevisual/store/page';
|
||||
import { QueryRouterServer } from '@kevisual/router';
|
||||
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 }) => {
|
||||
// renderRoot.innerHTML = `
|
||||
// <h1>Hello, World!</h1>
|
||||
// `;
|
||||
const root = createRoot(renderRoot);
|
||||
// @ts-ignore
|
||||
const root = initRoot(renderRoot);
|
||||
root.render(<List />);
|
||||
};
|
||||
|
||||
const page = useContextKey('page', () => {
|
||||
return new Page({
|
||||
basename: '',
|
||||
});
|
||||
});
|
||||
|
||||
if (page) {
|
||||
page.addPage('/app-template', 'home');
|
||||
initRoot(document.getElementById('ai-root'));
|
||||
page.addPage('/', 'home');
|
||||
page.addPage('/local-apps', 'local-apps');
|
||||
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', () => {
|
||||
render({
|
||||
renderRoot: document.getElementById('ai-root'),
|
||||
});
|
||||
page.navigate('/local-apps');
|
||||
});
|
||||
}
|
||||
|
||||
@ -34,7 +46,7 @@ const app = useContextKey('app', () => {
|
||||
if (app) {
|
||||
app
|
||||
.route({
|
||||
path: 'app-template',
|
||||
path: 'show-home',
|
||||
key: 'render',
|
||||
})
|
||||
.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({
|
||||
path: 'local-apps',
|
||||
key: 'delete',
|
||||
id,
|
||||
appKey: id,
|
||||
});
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
|
@ -2,53 +2,66 @@ import { create } from 'zustand';
|
||||
import { query } from '@/modules/query';
|
||||
import { message } from '@/modules/message';
|
||||
interface OperateStore {
|
||||
updateStatus: () => Promise<void>;
|
||||
detect: () => Promise<void>;
|
||||
download: () => Promise<void>;
|
||||
updateStatus: (data: { status: 'stop' | 'start'; appKey: string }) => Promise<any>;
|
||||
detect: () => Promise<any>;
|
||||
download: () => Promise<any>;
|
||||
|
||||
/**
|
||||
* 微应用的上传和卸载和部署
|
||||
*/
|
||||
upload: () => Promise<void>;
|
||||
unload: () => Promise<void>;
|
||||
deploy: () => Promise<void>;
|
||||
upload: () => Promise<any>;
|
||||
unload: () => Promise<any>;
|
||||
deploy: () => Promise<any>;
|
||||
}
|
||||
|
||||
export const useOperateStore = create<OperateStore>((set) => ({
|
||||
updateStatus: async () => {
|
||||
updateStatus: async (data) => {
|
||||
const res = await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'updateStatus',
|
||||
status: data.status,
|
||||
appKey: data.appKey,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message.success('操作成功');
|
||||
} else {
|
||||
message.error(res.message || '操作失败');
|
||||
}
|
||||
return res;
|
||||
},
|
||||
detect: async () => {
|
||||
const res = await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'detect',
|
||||
});
|
||||
return res;
|
||||
},
|
||||
download: async () => {
|
||||
const res = await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'download',
|
||||
});
|
||||
return res;
|
||||
},
|
||||
upload: async () => {
|
||||
const res = await query.post({
|
||||
path: 'micro-apps',
|
||||
key: 'upload',
|
||||
});
|
||||
return res;
|
||||
},
|
||||
unload: async () => {
|
||||
const res = await query.post({
|
||||
path: 'micro-apps',
|
||||
key: 'unload',
|
||||
});
|
||||
return res;
|
||||
},
|
||||
deploy: async () => {
|
||||
const res = await query.post({
|
||||
path: 'micro-apps',
|
||||
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 dayjs from 'dayjs';
|
||||
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 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({
|
||||
// plugins: [basicSsl()],
|
||||
plugins: [react()],
|
||||
css: {
|
||||
postcss: {
|
||||
// @ts-ignore
|
||||
plugins: [nesting, tailwindcss, autoprefixer],
|
||||
},
|
||||
},
|
||||
base: isDev ? '/' : './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
@ -32,13 +42,13 @@ export default defineConfig({
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://kevisual.xiongxiao.me',
|
||||
// target: 'http://localhost:9787',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
'/system': {
|
||||
'/system/lib': {
|
||||
target: 'https://kevisual.xiongxiao.me',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/system/, '/system'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user