diff --git a/frontend/index.html b/frontend/index.html
index 1ab7dd7..fe4ffb5 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,12 +1,12 @@
-
-
- Adminive Demo
+
+
+ Adminive Demo
-
-
+
+
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9500ced..b4df87c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,47 +1,58 @@
-import {useCallback, useEffect, useMemo, useState} from 'react';
import axios from 'axios';
-import {render as renderAmis} from 'amis';
+import {useCallback, useEffect, useRef, useState} from 'react';
import {amis_fetcher} from './amis_fetcher';
+import {embed_amis} from './amis_runtime';
import type {Api_Envelope} from './types';
import './App.css';
type Amis_Schema = Record;
export default function App() {
- const [schema, set_schema] = useState();
- const [loading, set_loading] = useState(true);
- const [error, set_error] = useState('');
- const load_schema = useCallback(async () => {
- set_loading(true);
- set_error('');
- try {
- const response = await axios.get>('/admin/amis');
- if(response.data.status !== 0) {
- throw new Error(response.data.msg || '加载后端页面描述失败');
- }
- set_schema(response.data.data);
- } catch(reason) {
- set_error(reason instanceof Error ? reason.message : String(reason));
- } finally {
- set_loading(false);
- }
- }, []);
- useEffect(() => {
- void load_schema();
- }, [load_schema]);
- const amis_view = useMemo(() => schema ? renderAmis(schema as any, {}, {fetcher: amis_fetcher as any}) : null, [schema]);
- return (
-
-
-
- {loading && 正在加载后端描述……
}
- {!loading && error && {error}
}
- {!loading && !error && amis_view}
-
-
- );
+ const [schema, set_schema] = useState();
+ const [loading, set_loading] = useState(true);
+ const [error, set_error] = useState('');
+ const amis_container = useRef(null);
+ const load_schema = useCallback(async () => {
+ set_loading(true);
+ set_error('');
+ try {
+ const response = await axios.get>('/admin/amis');
+ if(response.data.status !== 0) {
+ throw new Error(response.data.msg || '加载后端页面描述失败');
+ }
+ set_schema(response.data.data);
+ } catch(reason) {
+ set_error(reason instanceof Error ? reason.message : String(reason));
+ } finally {
+ set_loading(false);
+ }
+ }, []);
+ useEffect(() => {
+ void load_schema();
+ }, [load_schema]);
+ useEffect(() => {
+ if(!schema || !amis_container.current || loading || error) {
+ return;
+ }
+ try {
+ const scoped = embed_amis(amis_container.current, schema, amis_fetcher);
+ return () => scoped.unmount();
+ } catch(reason) {
+ set_error(reason instanceof Error ? reason.message : String(reason));
+ }
+ }, [schema, loading, error]);
+ return (
+
+
+
+ {loading && 正在加载后端描述……
}
+ {!loading && error && {error}
}
+ {!loading && !error && }
+
+
+ );
}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 2e6f161..4a0c9aa 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,7 +1,4 @@
import ReactDOM from 'react-dom/client';
-import 'amis/lib/themes/cxd.css';
-import 'amis/lib/helper.css';
-import 'amis/sdk/iconfont.css';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render();
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 4830bcc..b41be7d 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,15 +1,124 @@
import react from '@vitejs/plugin-react';
+import {readdirSync, readFileSync, statSync} from 'node:fs';
+import {extname, resolve, sep} from 'node:path';
import {fileURLToPath, URL} from 'node:url';
-import {defineConfig} from 'vite';
-
+import {defineConfig, type Plugin} from 'vite';
const output_dir = fileURLToPath(new URL('../frontend_dist', import.meta.url));
const cache_dir = fileURLToPath(new URL('./node_modules/.vite-adminive', import.meta.url));
-
+const amis_sdk_dir = fileURLToPath(new URL('./node_modules/amis/sdk', import.meta.url));
+const amis_sdk_url_prefix = '/vendor/amis/';
+function content_type(file_name: string) {
+ switch(extname(file_name).toLowerCase()) {
+ case '.css':
+ return 'text/css; charset=utf-8';
+ case '.js':
+ return 'text/javascript; charset=utf-8';
+ case '.json':
+ return 'application/json; charset=utf-8';
+ case '.svg':
+ return 'image/svg+xml';
+ case '.woff':
+ return 'font/woff';
+ case '.woff2':
+ return 'font/woff2';
+ case '.ttf':
+ return 'font/ttf';
+ default:
+ return 'application/octet-stream';
+ }
+}
+function amis_sdk_plugin(): Plugin {
+ return {
+ name: 'adminive-amis-sdk',
+ enforce: 'pre',
+ resolveId(source) {
+ if(source === 'amis' || source.startsWith('amis/')) {
+ this.error(`Do not import ${source} into the Adminive Vite module graph; use the AMIS SDK runtime instead`);
+ }
+ return null;
+ },
+ buildStart() {
+ const emit_directory = (directory: string, prefix: string) => {
+ for(const entry of readdirSync(directory)) {
+ const source_path = resolve(directory, entry);
+ const relative_path = `${prefix}${entry}`;
+ if(statSync(source_path).isDirectory()) {
+ emit_directory(source_path, `${relative_path}/`);
+ continue;
+ }
+ this.emitFile({
+ type: 'asset',
+ fileName: `vendor/amis/${relative_path}`,
+ source: readFileSync(source_path)
+ });
+ }
+ };
+ emit_directory(amis_sdk_dir, '');
+ },
+ transformIndexHtml: {
+ order: 'post',
+ handler() {
+ return [
+ {tag: 'link', attrs: {rel: 'stylesheet', href: `${amis_sdk_url_prefix}sdk.css`}, injectTo: 'head-prepend'},
+ {tag: 'link', attrs: {rel: 'stylesheet', href: `${amis_sdk_url_prefix}helper.css`}, injectTo: 'head-prepend'},
+ {tag: 'link', attrs: {rel: 'stylesheet', href: `${amis_sdk_url_prefix}iconfont.css`}, injectTo: 'head-prepend'},
+ {tag: 'script', attrs: {src: `${amis_sdk_url_prefix}sdk.js`}, injectTo: 'head-prepend'}
+ ];
+ }
+ },
+ configureServer(server) {
+ server.middlewares.use((request, response, next) => {
+ const pathname = new URL(request.url ?? '/', 'http://localhost').pathname;
+ if(!pathname.startsWith(amis_sdk_url_prefix)) {
+ next();
+ return;
+ }
+ const relative_path = decodeURIComponent(pathname.slice(amis_sdk_url_prefix.length));
+ const source_path = resolve(amis_sdk_dir, relative_path);
+ if(source_path !== amis_sdk_dir && !source_path.startsWith(`${amis_sdk_dir}${sep}`)) {
+ next();
+ return;
+ }
+ try {
+ const source = readFileSync(source_path);
+ response.statusCode = 200;
+ response.setHeader('Content-Type', content_type(source_path));
+ response.end(source);
+ } catch {
+ next();
+ }
+ });
+ },
+ configurePreviewServer(server) {
+ server.middlewares.use((request, response, next) => {
+ const pathname = new URL(request.url ?? '/', 'http://localhost').pathname;
+ if(!pathname.startsWith(amis_sdk_url_prefix)) {
+ next();
+ return;
+ }
+ const relative_path = decodeURIComponent(pathname.slice(amis_sdk_url_prefix.length));
+ const source_path = resolve(amis_sdk_dir, relative_path);
+ if(source_path !== amis_sdk_dir && !source_path.startsWith(`${amis_sdk_dir}${sep}`)) {
+ next();
+ return;
+ }
+ try {
+ const source = readFileSync(source_path);
+ response.statusCode = 200;
+ response.setHeader('Content-Type', content_type(source_path));
+ response.end(source);
+ } catch {
+ next();
+ }
+ });
+ }
+ };
+}
export default defineConfig({
cacheDir: cache_dir,
- plugins: [react()],
+ plugins: [react(), amis_sdk_plugin()],
resolve: {
- dedupe: ['mobx', 'mobx-react', 'mobx-react-lite', 'mobx-state-tree', 'react', 'react-dom']
+ dedupe: ['react', 'react-dom']
},
server: {
host: '0.0.0.0',
@@ -23,11 +132,6 @@ export default defineConfig({
},
build: {
outDir: output_dir,
- emptyOutDir: true,
- rollupOptions: {
- output: {
- inlineDynamicImports: true
- }
- }
+ emptyOutDir: true
}
-});
\ No newline at end of file
+});