Merge pull request #15 from yumusb/main
feat: Refactor system configuration injection to use a JSON script ta…
This commit is contained in:
7 files changed
+135
-43
No files matched your search
+7
-1
@@ -3,6 +3,7 @@ package internal
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -109,7 +110,12 @@ func setupApi(app *orz.App, components *AppComponents) {
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(ErrorHandler(logger))
|
||||
|
||||
indexTemplate, err := template.New("index").Parse(web.IndexHtml())
|
||||
indexTemplate, err := template.New("index").Funcs(template.FuncMap{
|
||||
"json": func(v interface{}) template.JS {
|
||||
b, _ := json.Marshal(v)
|
||||
return template.JS(b)
|
||||
},
|
||||
}).Parse(web.IndexHtml())
|
||||
if err != nil {
|
||||
logger.Fatal("failed to parse index.html", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ type SystemConfig struct {
|
||||
LogoBase64 string `json:"logoBase64"` // 系统logo(base64编码)
|
||||
ICPCode string `json:"icpCode"` // ICP备案号
|
||||
DefaultView string `json:"defaultView"` // 默认视图 grid | list
|
||||
CustomCSS string `json:"customCSS"` // 自定义 CSS
|
||||
CustomJS string `json:"customJS"` // 自定义 JS
|
||||
}
|
||||
|
||||
// AlertConfig 全局告警配置
|
||||
|
||||
+12
-17
@@ -1,22 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/api/logo"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>{{.SystemNameZh}} | {{.SystemNameEn}}</title>
|
||||
<script type="application/javascript">
|
||||
window.SystemConfig = {
|
||||
SystemNameZh: "{{.SystemNameZh}}",
|
||||
SystemNameEn: "{{.SystemNameEn}}",
|
||||
ICPCode: "{{.ICPCode}}",
|
||||
DefaultView: "{{.DefaultView}}",
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/api/logo" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{.SystemNameZh}} | {{.SystemNameEn}}</title>
|
||||
<script id="app-config" type="application/json">{{json .}}</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -86,6 +86,8 @@ export interface SystemConfig {
|
||||
logoBase64: string; // Logo 的 base64 编码
|
||||
icpCode: string; // ICP 备案号
|
||||
defaultView: string; // 默认视图 grid,list
|
||||
customCSS: string; // 自定义 CSS
|
||||
customJS: string; // 自定义 JS
|
||||
}
|
||||
|
||||
// 获取系统配置(管理后台使用)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
// Default configuration
|
||||
const defaultConfig = {
|
||||
SystemNameZh: "Pika",
|
||||
SystemNameEn: "Monitor",
|
||||
ICPCode: "",
|
||||
DefaultView: "grid",
|
||||
};
|
||||
|
||||
// Initialize SystemConfig from injected JSON
|
||||
try {
|
||||
const el = document.getElementById('app-config');
|
||||
if (el && el.textContent) {
|
||||
const config = JSON.parse(el.textContent);
|
||||
|
||||
// Merge with default config and expose to window
|
||||
window.SystemConfig = {
|
||||
SystemNameZh: config.systemNameZh || defaultConfig.SystemNameZh,
|
||||
SystemNameEn: config.systemNameEn || defaultConfig.SystemNameEn,
|
||||
ICPCode: config.icpCode || defaultConfig.ICPCode,
|
||||
DefaultView: config.defaultView || defaultConfig.DefaultView,
|
||||
};
|
||||
|
||||
// Inject Custom CSS
|
||||
if (config.customCSS) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = config.customCSS;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Inject Custom JS
|
||||
if (config.customJS) {
|
||||
const injectJS = () => {
|
||||
const script = document.createElement('script');
|
||||
script.innerHTML = config.customJS;
|
||||
document.body.appendChild(script);
|
||||
};
|
||||
|
||||
if (document.body) {
|
||||
injectJS();
|
||||
} else {
|
||||
window.addEventListener('DOMContentLoaded', injectJS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback if no config element found
|
||||
window.SystemConfig = defaultConfig;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse app config:', e);
|
||||
window.SystemConfig = defaultConfig;
|
||||
}
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
import {StrictMode} from 'react'
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
|
||||
import './init'
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
@@ -16,7 +17,7 @@ const queryClient = new QueryClient({
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App/>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -1,21 +1,21 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {App, Button, Card, Form, Input, Radio, Space, Spin, Upload} from 'antd';
|
||||
import {Upload as UploadIcon, Grid3x3, List} from 'lucide-react';
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import type {SystemConfig} from '@/api/property.ts';
|
||||
import {getSystemConfig, saveSystemConfig} from '@/api/property.ts';
|
||||
import {getErrorMessage} from '@/lib/utils.ts';
|
||||
import type {RcFile} from 'antd/es/upload/interface';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { App, Button, Card, Form, Input, Radio, Space, Spin, Upload } from 'antd';
|
||||
import { Upload as UploadIcon, Grid3x3, List } from 'lucide-react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { SystemConfig } from '@/api/property.ts';
|
||||
import { getSystemConfig, saveSystemConfig } from '@/api/property.ts';
|
||||
import { getErrorMessage } from '@/lib/utils.ts';
|
||||
import type { RcFile } from 'antd/es/upload/interface';
|
||||
|
||||
const SystemConfigComponent = () => {
|
||||
const [form] = Form.useForm();
|
||||
const {message: messageApi} = App.useApp();
|
||||
const { message: messageApi } = App.useApp();
|
||||
const queryClient = useQueryClient();
|
||||
const [logoPreview, setLogoPreview] = useState<string>('');
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
// 获取系统配置
|
||||
const {data: config, isLoading} = useQuery({
|
||||
const { data: config, isLoading } = useQuery({
|
||||
queryKey: ['systemConfig'],
|
||||
queryFn: getSystemConfig,
|
||||
});
|
||||
@@ -25,7 +25,7 @@ const SystemConfigComponent = () => {
|
||||
mutationFn: saveSystemConfig,
|
||||
onSuccess: () => {
|
||||
messageApi.success('保存成功');
|
||||
queryClient.invalidateQueries({queryKey: ['systemConfig']});
|
||||
queryClient.invalidateQueries({ queryKey: ['systemConfig'] });
|
||||
// 刷新页面以应用新的系统配置
|
||||
window.location.reload();
|
||||
},
|
||||
@@ -42,6 +42,8 @@ const SystemConfigComponent = () => {
|
||||
systemNameZh: config.systemNameZh,
|
||||
icpCode: config.icpCode,
|
||||
defaultView: config.defaultView ?? true, // 默认为 grid 视图
|
||||
customCSS: config.customCSS,
|
||||
customJS: config.customJS,
|
||||
});
|
||||
if (config.logoBase64) {
|
||||
setLogoPreview(config.logoBase64);
|
||||
@@ -99,6 +101,8 @@ const SystemConfigComponent = () => {
|
||||
logoBase64: logoPreview,
|
||||
icpCode: values.icpCode || '',
|
||||
defaultView: values.defaultView ?? true,
|
||||
customCSS: values.customCSS || '',
|
||||
customJS: values.customJS || '',
|
||||
} as SystemConfig);
|
||||
} catch (error) {
|
||||
// 表单验证失败
|
||||
@@ -113,6 +117,8 @@ const SystemConfigComponent = () => {
|
||||
systemNameZh: config.systemNameZh,
|
||||
icpCode: config.icpCode,
|
||||
defaultView: config.defaultView ?? true,
|
||||
customCSS: config.customCSS,
|
||||
customJS: config.customJS,
|
||||
});
|
||||
setLogoPreview(config.logoBase64 || '');
|
||||
}
|
||||
@@ -153,22 +159,22 @@ const SystemConfigComponent = () => {
|
||||
label="系统英文名称"
|
||||
name="systemNameEn"
|
||||
rules={[
|
||||
{required: true, message: '请输入系统英文名称'},
|
||||
{max: 50, message: '系统名称不能超过 50 个字符'},
|
||||
{ required: true, message: '请输入系统英文名称' },
|
||||
{ max: 50, message: '系统名称不能超过 50 个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:Pika Monitor"/>
|
||||
<Input placeholder="例如:Pika Monitor" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="系统中文名称"
|
||||
name="systemNameZh"
|
||||
rules={[
|
||||
{required: true, message: '请输入系统中文名称'},
|
||||
{max: 50, message: '系统名称不能超过 50 个字符'},
|
||||
{ required: true, message: '请输入系统中文名称' },
|
||||
{ max: 50, message: '系统名称不能超过 50 个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:皮卡监控"/>
|
||||
<Input placeholder="例如:皮卡监控" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
@@ -176,11 +182,11 @@ const SystemConfigComponent = () => {
|
||||
label="ICP 备案号"
|
||||
name="icpCode"
|
||||
rules={[
|
||||
{max: 50, message: 'ICP 备案号不能超过 50 个字符'},
|
||||
{ max: 50, message: 'ICP 备案号不能超过 50 个字符' },
|
||||
]}
|
||||
tooltip="ICP 备案号将显示在公共页面底部,例如:京ICP备12345678号"
|
||||
>
|
||||
<Input placeholder="例如:京ICP备12345678号"/>
|
||||
<Input placeholder="例如:京ICP备12345678号" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -215,7 +221,7 @@ const SystemConfigComponent = () => {
|
||||
beforeUpload={beforeUpload}
|
||||
disabled={uploading}
|
||||
>
|
||||
<Button icon={<UploadIcon size={16}/>} loading={uploading}>
|
||||
<Button icon={<UploadIcon size={16} />} loading={uploading}>
|
||||
{uploading ? '处理中...' : '上传 Logo'}
|
||||
</Button>
|
||||
</Upload>
|
||||
@@ -223,13 +229,41 @@ const SystemConfigComponent = () => {
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="自定义代码"
|
||||
type="inner"
|
||||
className="mb-4"
|
||||
>
|
||||
<Form.Item
|
||||
label="自定义 CSS"
|
||||
name="customCSS"
|
||||
tooltip="输入自定义 CSS 代码,将注入到页面 <style> 标签中"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="例如:body { background-color: #f0f0f0; }"
|
||||
rows={6}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="自定义 JS"
|
||||
name="customJS"
|
||||
tooltip="输入自定义 JavaScript 代码,将注入到页面 <script> 标签中"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="例如:console.log('Hello World');"
|
||||
rows={6}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="预览效果"
|
||||
type="inner"
|
||||
className="mb-4"
|
||||
>
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({getFieldValue}) => {
|
||||
{({ getFieldValue }) => {
|
||||
const systemNameEn = getFieldValue('systemNameEn') || '';
|
||||
const systemNameZh = getFieldValue('systemNameZh') || '';
|
||||
|
||||
|
||||
Reference in new issue
Block a user