Merge pull request #15 from yumusb/main

feat: Refactor system configuration injection to use a JSON script ta…
This commit is contained in:
dushixiang authored and GitHub committed 2025-12-30 21:19:42 +08:00
commit c347ba815a
7 files changed
+135 -43

No files matched your search

+7 -1
View File
@@ -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))
}
+2
View File
@@ -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
View File
@@ -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>
+2
View File
@@ -86,6 +86,8 @@ export interface SystemConfig {
logoBase64: string; // Logo 的 base64 编码
icpCode: string; // ICP 备案号
defaultView: string; // 默认视图 grid,list
customCSS: string; // 自定义 CSS
customJS: string; // 自定义 JS
}
// 获取系统配置(管理后台使用)
+52
View File
@@ -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
View File
@@ -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>,
)
+55 -21
View File
@@ -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') || '';