接入可配置文档编辑与智能取数服务
This commit is contained in:
@@ -185,6 +185,12 @@ eco-market:
|
|||||||
mcp:
|
mcp:
|
||||||
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
||||||
|
|
||||||
|
intelligent-data:
|
||||||
|
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
|
||||||
|
|
||||||
|
document-editor:
|
||||||
|
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
|
||||||
|
|
||||||
knife4j:
|
knife4j:
|
||||||
# 开启增强配置
|
# 开启增强配置
|
||||||
enable: true
|
enable: true
|
||||||
|
|||||||
@@ -185,6 +185,12 @@ eco-market:
|
|||||||
mcp:
|
mcp:
|
||||||
proxy-base-url: ${MCP_PROXY_URL:http://localhost:18020}
|
proxy-base-url: ${MCP_PROXY_URL:http://localhost:18020}
|
||||||
|
|
||||||
|
intelligent-data:
|
||||||
|
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
|
||||||
|
|
||||||
|
document-editor:
|
||||||
|
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
|
||||||
|
|
||||||
knife4j:
|
knife4j:
|
||||||
# 开启增强配置
|
# 开启增强配置
|
||||||
enable: true
|
enable: true
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ eco-market:
|
|||||||
mcp:
|
mcp:
|
||||||
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
||||||
|
|
||||||
|
intelligent-data:
|
||||||
|
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
|
||||||
|
|
||||||
|
document-editor:
|
||||||
|
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
|
||||||
|
|
||||||
knife4j:
|
knife4j:
|
||||||
# 开启增强配置
|
# 开启增强配置
|
||||||
enable: true
|
enable: true
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ eco-market:
|
|||||||
mcp:
|
mcp:
|
||||||
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
|
||||||
|
|
||||||
|
intelligent-data:
|
||||||
|
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
|
||||||
|
|
||||||
|
document-editor:
|
||||||
|
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
|
||||||
|
|
||||||
knife4j:
|
knife4j:
|
||||||
# 开启增强配置
|
# 开启增强配置
|
||||||
enable: true
|
enable: true
|
||||||
|
|||||||
@@ -179,6 +179,12 @@ public class TenantConfigDto implements Serializable {
|
|||||||
@Schema(description = "模型API根地址")
|
@Schema(description = "模型API根地址")
|
||||||
private String baseModelApiUrl;
|
private String baseModelApiUrl;
|
||||||
|
|
||||||
|
@Schema(description = "智能取数服务地址")
|
||||||
|
private String intelligentDataServiceUrl;
|
||||||
|
|
||||||
|
@Schema(description = "文档编辑服务地址")
|
||||||
|
private String documentEditorServiceUrl;
|
||||||
|
|
||||||
public enum AuthTypeEnum {
|
public enum AuthTypeEnum {
|
||||||
PHONE(1, "手机"),
|
PHONE(1, "手机"),
|
||||||
CAS(2, "CAS"),
|
CAS(2, "CAS"),
|
||||||
|
|||||||
@@ -72,6 +72,30 @@ public class TenantConfigServiceImpl extends ServiceImpl<TenantConfigMapper, Ten
|
|||||||
.required(true)
|
.required(true)
|
||||||
.sort(1)
|
.sort(1)
|
||||||
.build());
|
.build());
|
||||||
|
tenantConfigDefaultList.add(TenantConfig.builder()
|
||||||
|
.name("intelligentDataServiceUrl")
|
||||||
|
.description("智能取数服务地址")
|
||||||
|
.value("http://192.168.2.106:3000/test-console")
|
||||||
|
.notice("客户端点击智能取数时加载的 HTTP 或 HTTPS 服务地址")
|
||||||
|
.placeholder("请输入智能取数服务地址")
|
||||||
|
.category(TenantConfig.ConfigCategory.BaseConfig)
|
||||||
|
.inputType(TenantConfig.InputType.Input)
|
||||||
|
.dataType(TenantConfig.DataType.String)
|
||||||
|
.required(true)
|
||||||
|
.sort(2)
|
||||||
|
.build());
|
||||||
|
tenantConfigDefaultList.add(TenantConfig.builder()
|
||||||
|
.name("documentEditorServiceUrl")
|
||||||
|
.description("文档编辑服务地址")
|
||||||
|
.value("")
|
||||||
|
.notice("客户端点击文档编辑时加载的 HTTP 或 HTTPS 服务地址")
|
||||||
|
.placeholder("请输入文档编辑服务地址")
|
||||||
|
.category(TenantConfig.ConfigCategory.BaseConfig)
|
||||||
|
.inputType(TenantConfig.InputType.Input)
|
||||||
|
.dataType(TenantConfig.DataType.String)
|
||||||
|
.required(false)
|
||||||
|
.sort(3)
|
||||||
|
.build());
|
||||||
tenantConfigDefaultList.add(TenantConfig.builder()
|
tenantConfigDefaultList.add(TenantConfig.builder()
|
||||||
.name("siteDescription")
|
.name("siteDescription")
|
||||||
.description("站点介绍")
|
.description("站点介绍")
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ public class TenantController {
|
|||||||
@Value("${model-api-proxy.base-api-url:}")
|
@Value("${model-api-proxy.base-api-url:}")
|
||||||
private String baseApiUrl;
|
private String baseApiUrl;
|
||||||
|
|
||||||
|
@Value("${intelligent-data.service-url:http://192.168.2.106:3000/test-console}")
|
||||||
|
private String intelligentDataServiceUrl;
|
||||||
|
|
||||||
|
@Value("${document-editor.service-url:}")
|
||||||
|
private String documentEditorServiceUrl;
|
||||||
|
|
||||||
@Operation(summary = "租户配置信息查询接口")
|
@Operation(summary = "租户配置信息查询接口")
|
||||||
@RequestMapping(path = "/config", method = RequestMethod.GET)
|
@RequestMapping(path = "/config", method = RequestMethod.GET)
|
||||||
public ReqResult<TenantConfigDto> getConfig() {
|
public ReqResult<TenantConfigDto> getConfig() {
|
||||||
@@ -69,6 +75,12 @@ public class TenantController {
|
|||||||
tenantConfigDto.setFaviconUrl(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getFaviconUrl(), true));
|
tenantConfigDto.setFaviconUrl(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getFaviconUrl(), true));
|
||||||
tenantConfigDto.setSquareBanner(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getSquareBanner(), true));
|
tenantConfigDto.setSquareBanner(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getSquareBanner(), true));
|
||||||
tenantConfigDto.setMaxFileSize(maxFileSize);
|
tenantConfigDto.setMaxFileSize(maxFileSize);
|
||||||
|
if (StringUtils.isBlank(tenantConfigDto.getIntelligentDataServiceUrl())) {
|
||||||
|
tenantConfigDto.setIntelligentDataServiceUrl(intelligentDataServiceUrl);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(tenantConfigDto.getDocumentEditorServiceUrl())) {
|
||||||
|
tenantConfigDto.setDocumentEditorServiceUrl(documentEditorServiceUrl);
|
||||||
|
}
|
||||||
if (StringUtils.isBlank(tenantConfigDto.getTemplateConfig()) || tenantConfigDto.getTemplateConfig().equals("{}")) {
|
if (StringUtils.isBlank(tenantConfigDto.getTemplateConfig()) || tenantConfigDto.getTemplateConfig().equals("{}")) {
|
||||||
tenantConfigDto.setTemplateConfig("{\"primaryColor\":\"#5147ff\",\"backgroundId\":\"bg-variant-1\",\"antdTheme\":\"light\",\"layoutStyle\":\"light\",\"navigationStyle\":\"style1\",\"timestamp\":1757425328082}");
|
tenantConfigDto.setTemplateConfig("{\"primaryColor\":\"#5147ff\",\"backgroundId\":\"bg-variant-1\",\"antdTheme\":\"light\",\"layoutStyle\":\"light\",\"navigationStyle\":\"style1\",\"timestamp\":1757425328082}");
|
||||||
}
|
}
|
||||||
@@ -108,4 +120,4 @@ public class TenantController {
|
|||||||
I18nUtil.replaceSystemMessage(tenantConfigDto);
|
I18nUtil.replaceSystemMessage(tenantConfigDto);
|
||||||
return ReqResult.success(tenantConfigDto);
|
return ReqResult.success(tenantConfigDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,24 @@ export default function BaseFormItem({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
const serviceUrlFieldNames = [
|
||||||
|
'intelligentDataServiceUrl',
|
||||||
|
'documentEditorServiceUrl',
|
||||||
|
];
|
||||||
|
const fieldRules: Rule[] | undefined = serviceUrlFieldNames.includes(
|
||||||
|
props.name,
|
||||||
|
)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
required: props.name === 'intelligentDataServiceUrl',
|
||||||
|
message: `请输入${props.description}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'url',
|
||||||
|
message: '请输入有效的 HTTP 或 HTTPS 地址',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: rules[currentTab];
|
||||||
// 处理 Select 类型的初始值:-1 表示未选择,应转换为 undefined
|
// 处理 Select 类型的初始值:-1 表示未选择,应转换为 undefined
|
||||||
const getInitialValue = () => {
|
const getInitialValue = () => {
|
||||||
if (['Select'].includes(props.inputType) && Number(props.value) === -1) {
|
if (['Select'].includes(props.inputType) && Number(props.value) === -1) {
|
||||||
@@ -181,7 +199,7 @@ export default function BaseFormItem({
|
|||||||
key={props.name}
|
key={props.name}
|
||||||
name={props.name}
|
name={props.name}
|
||||||
initialValue={getInitialValue()}
|
initialValue={getInitialValue()}
|
||||||
rules={rules[currentTab]}
|
rules={fieldRules}
|
||||||
{...(props.inputType === 'MultiInput'
|
{...(props.inputType === 'MultiInput'
|
||||||
? {
|
? {
|
||||||
label: <MultiInputLabel />,
|
label: <MultiInputLabel />,
|
||||||
|
|||||||
@@ -369,6 +369,10 @@ export interface TenantConfigDto {
|
|||||||
siteName?: string;
|
siteName?: string;
|
||||||
/** 站点描述 */
|
/** 站点描述 */
|
||||||
siteDescription?: string;
|
siteDescription?: string;
|
||||||
|
/** 智能取数服务地址 */
|
||||||
|
intelligentDataServiceUrl?: string;
|
||||||
|
/** 文档编辑服务地址 */
|
||||||
|
documentEditorServiceUrl?: string;
|
||||||
/** 站点LOGO(为空使用默认值) */
|
/** 站点LOGO(为空使用默认值) */
|
||||||
siteLogo?: string;
|
siteLogo?: string;
|
||||||
/** 登录页banner */
|
/** 登录页banner */
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ import SkillsMarketplacePage from "./components/pages/SkillsMarketplacePage";
|
|||||||
import AgentMarketplacePage from "./components/pages/AgentMarketplacePage";
|
import AgentMarketplacePage from "./components/pages/AgentMarketplacePage";
|
||||||
import McpMarketplacePage from "./components/pages/McpMarketplacePage";
|
import McpMarketplacePage from "./components/pages/McpMarketplacePage";
|
||||||
import WorkflowMarketplacePage from "./components/pages/WorkflowMarketplacePage";
|
import WorkflowMarketplacePage from "./components/pages/WorkflowMarketplacePage";
|
||||||
|
import DataFetchPage from "./components/pages/DataFetchPage";
|
||||||
|
import DocumentEditorPage from "./components/pages/DocumentEditorPage";
|
||||||
import MCPSettings from "./components/settings/MCPSettings";
|
import MCPSettings from "./components/settings/MCPSettings";
|
||||||
import AppSidebar, {
|
import AppSidebar, {
|
||||||
NavKey,
|
NavKey,
|
||||||
@@ -106,6 +108,8 @@ type TabKey =
|
|||||||
| "legacySessions"
|
| "legacySessions"
|
||||||
| "automation"
|
| "automation"
|
||||||
| "knowledge"
|
| "knowledge"
|
||||||
|
| "document"
|
||||||
|
| "dataFetch"
|
||||||
| "skills"
|
| "skills"
|
||||||
| "agents"
|
| "agents"
|
||||||
| "mcpLibrary"
|
| "mcpLibrary"
|
||||||
@@ -1080,6 +1084,16 @@ function App() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === "document") {
|
||||||
|
setActiveTab("document");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "dataFetch") {
|
||||||
|
setActiveTab("dataFetch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (key === "toolIntegration" || key === "systemSettings") {
|
if (key === "toolIntegration" || key === "systemSettings") {
|
||||||
setActiveTab("settings");
|
setActiveTab("settings");
|
||||||
return;
|
return;
|
||||||
@@ -1337,7 +1351,9 @@ function App() {
|
|||||||
{/* 主内容区:flex 子撑满,便于日志等页占满高度 */}
|
{/* 主内容区:flex 子撑满,便于日志等页占满高度 */}
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
webviewActions
|
webviewActions ||
|
||||||
|
activeTab === "document" ||
|
||||||
|
activeTab === "dataFetch"
|
||||||
? "app-content app-content-fullwidth"
|
? "app-content app-content-fullwidth"
|
||||||
: "app-content"
|
: "app-content"
|
||||||
}
|
}
|
||||||
@@ -1385,6 +1401,8 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
{activeTab === "automation" && <AutomationPage />}
|
{activeTab === "automation" && <AutomationPage />}
|
||||||
{activeTab === "knowledge" && <KnowledgePage />}
|
{activeTab === "knowledge" && <KnowledgePage />}
|
||||||
|
{activeTab === "document" && <DocumentEditorPage />}
|
||||||
|
{activeTab === "dataFetch" && <DataFetchPage />}
|
||||||
{activeTab === "skills" && <SkillsMarketplacePage />}
|
{activeTab === "skills" && <SkillsMarketplacePage />}
|
||||||
{activeTab === "agents" && (
|
{activeTab === "agents" && (
|
||||||
<AgentMarketplacePage onStartChat={handleStartAgentChat} />
|
<AgentMarketplacePage onStartChat={handleStartAgentChat} />
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { Alert, Button, Spin } from "antd";
|
||||||
|
import { ReloadOutlined } from "@ant-design/icons";
|
||||||
|
import styles from "../../styles/components/ConfiguredServicePage.module.css";
|
||||||
|
|
||||||
|
interface ConfiguredServicePageProps {
|
||||||
|
title: string;
|
||||||
|
loadServiceUrl: () => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfiguredServicePage({
|
||||||
|
title,
|
||||||
|
loadServiceUrl,
|
||||||
|
}: ConfiguredServicePageProps) {
|
||||||
|
const webviewRef = useRef<HTMLElement | null>(null);
|
||||||
|
const [serviceUrl, setServiceUrl] = useState<string | null>(null);
|
||||||
|
const [loadingConfig, setLoadingConfig] = useState(true);
|
||||||
|
const [loadingPage, setLoadingPage] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const loadService = useCallback(async () => {
|
||||||
|
setLoadingConfig(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
setServiceUrl(await loadServiceUrl());
|
||||||
|
} catch (cause) {
|
||||||
|
setServiceUrl(null);
|
||||||
|
setError(
|
||||||
|
cause instanceof Error ? cause.message : `读取${title}服务配置失败`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoadingConfig(false);
|
||||||
|
}
|
||||||
|
}, [loadServiceUrl, title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadService();
|
||||||
|
}, [loadService]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = webviewRef.current as any;
|
||||||
|
if (!element || !serviceUrl) return;
|
||||||
|
|
||||||
|
const handleStart = () => {
|
||||||
|
setLoadingPage(true);
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
const handleStop = () => setLoadingPage(false);
|
||||||
|
const handleFailure = (event: any) => {
|
||||||
|
if (event.errorCode !== -3) {
|
||||||
|
setLoadingPage(false);
|
||||||
|
setError(
|
||||||
|
`${title}页面加载失败:${event.errorDescription || "网络连接失败"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener("did-start-loading", handleStart);
|
||||||
|
element.addEventListener("did-stop-loading", handleStop);
|
||||||
|
element.addEventListener("did-fail-load", handleFailure);
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener("did-start-loading", handleStart);
|
||||||
|
element.removeEventListener("did-stop-loading", handleStop);
|
||||||
|
element.removeEventListener("did-fail-load", handleFailure);
|
||||||
|
};
|
||||||
|
}, [serviceUrl, title]);
|
||||||
|
|
||||||
|
const handleReload = useCallback(() => {
|
||||||
|
const element = webviewRef.current as any;
|
||||||
|
if (element?.reload) {
|
||||||
|
element.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void loadService();
|
||||||
|
}, [loadService]);
|
||||||
|
|
||||||
|
if (loadingConfig) {
|
||||||
|
return (
|
||||||
|
<div className={styles.state}>
|
||||||
|
<Spin tip={`正在加载${title}`} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serviceUrl) {
|
||||||
|
return (
|
||||||
|
<div className={styles.state}>
|
||||||
|
<Alert
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
message={error || `${title}服务地址未配置`}
|
||||||
|
action={
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={loadService}
|
||||||
|
>
|
||||||
|
重试
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.toolbar}>
|
||||||
|
<span className={styles.title}>{title}</span>
|
||||||
|
<span className={styles.url} title={serviceUrl}>
|
||||||
|
{serviceUrl}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
loading={loadingPage}
|
||||||
|
onClick={handleReload}
|
||||||
|
title={`刷新${title}页面`}
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
className={styles.alert}
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
closable
|
||||||
|
message={error}
|
||||||
|
onClose={() => setError(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<webview
|
||||||
|
key={serviceUrl}
|
||||||
|
ref={webviewRef as any}
|
||||||
|
src={serviceUrl}
|
||||||
|
allowpopups={"true" as any}
|
||||||
|
className={styles.webview}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfiguredServicePage;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { fetchIntelligentDataServiceUrl } from "../../services/core/servicePages";
|
||||||
|
import ConfiguredServicePage from "./ConfiguredServicePage";
|
||||||
|
|
||||||
|
function DataFetchPage() {
|
||||||
|
return (
|
||||||
|
<ConfiguredServicePage
|
||||||
|
title="智能取数"
|
||||||
|
loadServiceUrl={fetchIntelligentDataServiceUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataFetchPage;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { fetchDocumentEditorServiceUrl } from "../../services/core/servicePages";
|
||||||
|
import ConfiguredServicePage from "./ConfiguredServicePage";
|
||||||
|
|
||||||
|
function DocumentEditorPage() {
|
||||||
|
return (
|
||||||
|
<ConfiguredServicePage
|
||||||
|
title="文档编辑"
|
||||||
|
loadServiceUrl={fetchDocumentEditorServiceUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocumentEditorPage;
|
||||||
@@ -2,7 +2,7 @@ import { message } from "antd";
|
|||||||
import {
|
import {
|
||||||
AUTH_KEYS,
|
AUTH_KEYS,
|
||||||
DEFAULT_API_TIMEOUT,
|
DEFAULT_API_TIMEOUT,
|
||||||
DEFAULT_SERVER_HOST,
|
STORAGE_KEYS,
|
||||||
} from "@shared/constants";
|
} from "@shared/constants";
|
||||||
import { getDomainTokenKey } from "@shared/utils/domain";
|
import { getDomainTokenKey } from "@shared/utils/domain";
|
||||||
import { normalizeServerHost } from "./auth";
|
import { normalizeServerHost } from "./auth";
|
||||||
@@ -179,11 +179,15 @@ export interface AutomationTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getBaseUrl(): Promise<string> {
|
async function getBaseUrl(): Promise<string> {
|
||||||
const config = (await window.electronAPI?.settings.get(
|
const [userInfo, config] = (await Promise.all([
|
||||||
AUTH_KEYS.STEP1_CONFIG,
|
window.electronAPI?.settings.get(AUTH_KEYS.USER_INFO),
|
||||||
)) as { serverHost?: string } | null;
|
window.electronAPI?.settings.get(STORAGE_KEYS.STEP1_CONFIG),
|
||||||
|
])) as [{ currentDomain?: string } | null, { serverHost?: string } | null];
|
||||||
return normalizeServerHost(config?.serverHost || DEFAULT_SERVER_HOST);
|
const loginServerHost = userInfo?.currentDomain || config?.serverHost || "";
|
||||||
|
if (!loginServerHost) {
|
||||||
|
throw new Error("未找到登录站点地址,请重新登录");
|
||||||
|
}
|
||||||
|
return normalizeServerHost(loginServerHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAuthToken(domain: string): Promise<string | null> {
|
async function getAuthToken(domain: string): Promise<string | null> {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import {
|
||||||
|
AUTH_KEYS,
|
||||||
|
DEFAULT_API_TIMEOUT,
|
||||||
|
STORAGE_KEYS,
|
||||||
|
} from "@shared/constants";
|
||||||
|
import { getDomainTokenKey } from "@shared/utils/domain";
|
||||||
|
import { normalizeServerHost } from "./auth";
|
||||||
|
|
||||||
|
const SUCCESS_CODE = "0000";
|
||||||
|
const INTELLIGENT_DATA_FALLBACK_URL = "http://192.168.2.106:3000/test-console";
|
||||||
|
|
||||||
|
interface TenantConfigResponse {
|
||||||
|
code: string;
|
||||||
|
message?: string;
|
||||||
|
data?: {
|
||||||
|
intelligentDataServiceUrl?: string;
|
||||||
|
documentEditorServiceUrl?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLoginServerHost(): Promise<string> {
|
||||||
|
const [userInfo, config] = (await Promise.all([
|
||||||
|
window.electronAPI?.settings.get(AUTH_KEYS.USER_INFO),
|
||||||
|
window.electronAPI?.settings.get(STORAGE_KEYS.STEP1_CONFIG),
|
||||||
|
])) as [{ currentDomain?: string } | null, { serverHost?: string } | null];
|
||||||
|
const loginServerHost = userInfo?.currentDomain || config?.serverHost || "";
|
||||||
|
if (!loginServerHost) {
|
||||||
|
throw new Error("未找到登录站点地址,请重新登录");
|
||||||
|
}
|
||||||
|
return normalizeServerHost(loginServerHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getToken(serverHost: string): Promise<string | null> {
|
||||||
|
const oneShotToken = (await window.electronAPI?.settings.get(
|
||||||
|
AUTH_KEYS.AUTH_TOKEN,
|
||||||
|
)) as string | null;
|
||||||
|
if (oneShotToken) return oneShotToken;
|
||||||
|
return (await window.electronAPI?.settings.get(
|
||||||
|
getDomainTokenKey(serverHost),
|
||||||
|
)) as string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeServiceUrl(value: string, serviceName: string): string {
|
||||||
|
const candidate = value.trim();
|
||||||
|
if (!candidate) {
|
||||||
|
throw new Error(`${serviceName}服务地址未配置,请在系统设置中配置`);
|
||||||
|
}
|
||||||
|
const url = new URL(candidate);
|
||||||
|
if (!/^https?:$/.test(url.protocol)) {
|
||||||
|
throw new Error(`${serviceName}服务地址必须使用 HTTP 或 HTTPS`);
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTenantConfig(): Promise<TenantConfigResponse["data"]> {
|
||||||
|
const serverHost = await getLoginServerHost();
|
||||||
|
const token = await getToken(serverHost);
|
||||||
|
const response = await fetch(`${serverHost}/api/tenant/config`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
cache: "no-store",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(DEFAULT_API_TIMEOUT),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`读取服务配置失败(HTTP ${response.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = (await response.json()) as TenantConfigResponse;
|
||||||
|
if (result.code !== SUCCESS_CODE) {
|
||||||
|
throw new Error(result.message || "读取服务配置失败");
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchIntelligentDataServiceUrl(): Promise<string> {
|
||||||
|
const config = await fetchTenantConfig();
|
||||||
|
return normalizeServiceUrl(
|
||||||
|
config?.intelligentDataServiceUrl || INTELLIGENT_DATA_FALLBACK_URL,
|
||||||
|
"智能取数",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchDocumentEditorServiceUrl(): Promise<string> {
|
||||||
|
const config = await fetchTenantConfig();
|
||||||
|
return normalizeServiceUrl(
|
||||||
|
config?.documentEditorServiceUrl || "",
|
||||||
|
"文档编辑",
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-bg-container);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 7px 16px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-bg-section-header);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
font-size: 12px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: 8px 12px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
border: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 240px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user