画廊添加

This commit is contained in:
2026-08-08 13:09:18 +08:00
parent c8b82f640e
commit 989ad91b69
4 changed files with 172 additions and 60 deletions
+5 -5
View File
@@ -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
View File
@@ -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>
);
}
-3
View File
@@ -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
View File
@@ -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
}
});
});