画廊添加
This commit is contained in:
+5
-5
@@ -1,12 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Adminive Demo</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Adminive Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+51
-40
@@ -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<string, unknown>;
|
||||
export default function App() {
|
||||
const [schema, set_schema] = useState<Amis_Schema>();
|
||||
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<Api_Envelope<Amis_Schema>>('/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 (
|
||||
<main className="app-shell">
|
||||
<header className="app-header">
|
||||
<div>
|
||||
<h1>Adminive</h1>
|
||||
<p>组件画廊、字段控件、布局组合、表格操作、状态刷新和配置页面均由后端 JSON 描述驱动。</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => void load_schema()} disabled={loading}>重新读取后端描述</button>
|
||||
</header>
|
||||
<section className="amis-region">
|
||||
{loading && <div className="state-message">正在加载后端描述……</div>}
|
||||
{!loading && error && <div className="state-message error">{error}</div>}
|
||||
{!loading && !error && amis_view}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
const [schema, set_schema] = useState<Amis_Schema>();
|
||||
const [loading, set_loading] = useState(true);
|
||||
const [error, set_error] = useState('');
|
||||
const amis_container = useRef<HTMLDivElement>(null);
|
||||
const load_schema = useCallback(async () => {
|
||||
set_loading(true);
|
||||
set_error('');
|
||||
try {
|
||||
const response = await axios.get<Api_Envelope<Amis_Schema>>('/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 (
|
||||
<main className="app-shell">
|
||||
<header className="app-header">
|
||||
<div>
|
||||
<h1>Adminive</h1>
|
||||
<p>组件画廊、字段控件、布局组合、表格操作、状态刷新和配置页面均由后端 JSON 描述驱动。</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => void load_schema()} disabled={loading}>重新读取后端描述</button>
|
||||
</header>
|
||||
<section className="amis-region">
|
||||
{loading && <div className="state-message">正在加载后端描述……</div>}
|
||||
{!loading && error && <div className="state-message error">{error}</div>}
|
||||
{!loading && !error && <div ref={amis_container} className="amis-runtime" />}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(<App />);
|
||||
|
||||
+116
-12
@@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user