重构客户端登录页并更新品牌图标
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@qiming-ai/qimingclaw",
|
"name": "@qiming-ai/qimingclaw",
|
||||||
"version": "0.9.7",
|
"version": "0.9.7",
|
||||||
"description": "启明数字员工",
|
"description": "陇电数字员工",
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"author": "Qiming Team",
|
"author": "Qiming Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.qiming.qimingclaw",
|
"appId": "com.qiming.qimingclaw",
|
||||||
"productName": "启明数字员工",
|
"productName": "陇电数字员工",
|
||||||
"afterSign": "scripts/build/after-sign.js",
|
"afterSign": "scripts/build/after-sign.js",
|
||||||
"electronLanguages": [
|
"electronLanguages": [
|
||||||
"en",
|
"en",
|
||||||
@@ -297,8 +297,8 @@
|
|||||||
"hardenedRuntime": true,
|
"hardenedRuntime": true,
|
||||||
"extendInfo": {
|
"extendInfo": {
|
||||||
"CFBundleIdentifier": "com.qiming.agent",
|
"CFBundleIdentifier": "com.qiming.agent",
|
||||||
"CFBundleName": "启明数字员工",
|
"CFBundleName": "陇电数字员工",
|
||||||
"CFBundleDisplayName": "启明数字员工",
|
"CFBundleDisplayName": "陇电数字员工",
|
||||||
"NSDesktopFolderUsageDescription": "需要访问桌面文件夹以支持文件操作",
|
"NSDesktopFolderUsageDescription": "需要访问桌面文件夹以支持文件操作",
|
||||||
"NSDocumentsFolderUsageDescription": "需要访问文档文件夹以支持文件操作",
|
"NSDocumentsFolderUsageDescription": "需要访问文档文件夹以支持文件操作",
|
||||||
"NSDownloadsFolderUsageDescription": "需要访问下载文件夹以支持文件操作"
|
"NSDownloadsFolderUsageDescription": "需要访问下载文件夹以支持文件操作"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 672 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 67 KiB |
@@ -39,6 +39,7 @@ import {
|
|||||||
syncConfigToServer,
|
syncConfigToServer,
|
||||||
normalizeServerHost,
|
normalizeServerHost,
|
||||||
loginAndRegister,
|
loginAndRegister,
|
||||||
|
isLoggedIn as checkIsLoggedIn,
|
||||||
} from "./services/core/auth";
|
} from "./services/core/auth";
|
||||||
import {
|
import {
|
||||||
APP_DISPLAY_NAME,
|
APP_DISPLAY_NAME,
|
||||||
@@ -49,6 +50,7 @@ import type { QuickInitConfig } from "@shared/types/quickInit";
|
|||||||
import type { UpdateState } from "@shared/types/updateTypes";
|
import type { UpdateState } from "@shared/types/updateTypes";
|
||||||
import { t, getCurrentLang } from "./services/core/i18n";
|
import { t, getCurrentLang } from "./services/core/i18n";
|
||||||
import SetupWizard from "./components/setup/SetupWizard";
|
import SetupWizard from "./components/setup/SetupWizard";
|
||||||
|
import LoginPage from "./components/setup/LoginPage";
|
||||||
import SetupDependencies from "./components/setup/SetupDependencies";
|
import SetupDependencies from "./components/setup/SetupDependencies";
|
||||||
import ClientPage from "./components/pages/ClientPage";
|
import ClientPage from "./components/pages/ClientPage";
|
||||||
import SettingsPage from "./components/pages/SettingsPage";
|
import SettingsPage from "./components/pages/SettingsPage";
|
||||||
@@ -187,6 +189,9 @@ function App() {
|
|||||||
// 初始化向导状态
|
// 初始化向导状态
|
||||||
// ============================================
|
// ============================================
|
||||||
const [isSetupComplete, setIsSetupComplete] = useState<boolean | null>(null);
|
const [isSetupComplete, setIsSetupComplete] = useState<boolean | null>(null);
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState<boolean | null>(null);
|
||||||
|
const [loginStep1Config, setLoginStep1Config] =
|
||||||
|
useState<Step1Config>(DEFAULT_STEP1_CONFIG);
|
||||||
const setupJustCompleted = useRef(false);
|
const setupJustCompleted = useRef(false);
|
||||||
// 内存变量:标记服务是否由登录流程启动(不持久化)
|
// 内存变量:标记服务是否由登录流程启动(不持久化)
|
||||||
const loginStartedRef = useRef(false);
|
const loginStartedRef = useRef(false);
|
||||||
@@ -392,6 +397,10 @@ function App() {
|
|||||||
const checkSetup = async () => {
|
const checkSetup = async () => {
|
||||||
try {
|
try {
|
||||||
const completed = await setupService.isSetupCompleted();
|
const completed = await setupService.isSetupCompleted();
|
||||||
|
const step1 = await setupService.getStep1Config();
|
||||||
|
setLoginStep1Config(step1);
|
||||||
|
const loggedIn = await checkIsLoggedIn();
|
||||||
|
setIsLoggedIn(loggedIn);
|
||||||
|
|
||||||
// 每次启动优先读取 quick init 配置
|
// 每次启动优先读取 quick init 配置
|
||||||
if (completed) {
|
if (completed) {
|
||||||
@@ -411,6 +420,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to check setup status:", error);
|
log.error("Failed to check setup status:", error);
|
||||||
setIsSetupComplete(true);
|
setIsSetupComplete(true);
|
||||||
|
setIsLoggedIn(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkSetup();
|
checkSetup();
|
||||||
@@ -445,6 +455,8 @@ function App() {
|
|||||||
// ============================================
|
// ============================================
|
||||||
const handleAuthChange = useCallback(async () => {
|
const handleAuthChange = useCallback(async () => {
|
||||||
const user = await authService.getAuthUser();
|
const user = await authService.getAuthUser();
|
||||||
|
const loggedIn = await checkIsLoggedIn();
|
||||||
|
setIsLoggedIn(loggedIn);
|
||||||
if (user) {
|
if (user) {
|
||||||
setUsername(
|
setUsername(
|
||||||
user.displayName || user.username || t("Claw.App.defaultUsername"),
|
user.displayName || user.username || t("Claw.App.defaultUsername"),
|
||||||
@@ -459,6 +471,29 @@ function App() {
|
|||||||
loginStartedRef.current = true;
|
loginStartedRef.current = true;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const ensureDefaultLoginConfig = useCallback(async () => {
|
||||||
|
const currentConfig = await setupService.getStep1Config();
|
||||||
|
const nextConfig: Step1Config = {
|
||||||
|
...DEFAULT_STEP1_CONFIG,
|
||||||
|
...currentConfig,
|
||||||
|
serverHost: currentConfig.serverHost || DEFAULT_STEP1_CONFIG.serverHost,
|
||||||
|
workspaceDir:
|
||||||
|
currentConfig.workspaceDir || DEFAULT_STEP1_CONFIG.workspaceDir,
|
||||||
|
};
|
||||||
|
await setupService.saveStep1Config(nextConfig);
|
||||||
|
setLoginStep1Config(nextConfig);
|
||||||
|
return nextConfig;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFullscreenLoginSuccess = useCallback(async () => {
|
||||||
|
await setupService.completeStep2();
|
||||||
|
await setupService.completeSetup();
|
||||||
|
setupJustCompleted.current = true;
|
||||||
|
setIsSetupComplete(true);
|
||||||
|
await handleAuthChange();
|
||||||
|
setAuthRefreshTrigger((v) => v + 1);
|
||||||
|
}, [handleAuthChange]);
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 主界面下必需依赖检查:仅当存在「未安装」或「错误」时进入依赖安装
|
// 主界面下必需依赖检查:仅当存在「未安装」或「错误」时进入依赖安装
|
||||||
// 版本以当前真实安装为准,outdated 不触发(用户可在依赖 Tab 手动升级)
|
// 版本以当前真实安装为准,outdated 不触发(用户可在依赖 Tab 手动升级)
|
||||||
@@ -1106,7 +1141,8 @@ function App() {
|
|||||||
// ============================================
|
// ============================================
|
||||||
if (
|
if (
|
||||||
isSetupComplete === null ||
|
isSetupComplete === null ||
|
||||||
(isSetupComplete && needsRequiredDepsReinstall === null)
|
isLoggedIn === null ||
|
||||||
|
(isLoggedIn && isSetupComplete && needsRequiredDepsReinstall === null)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<I18nContext.Provider value={i18nContextValue}>
|
<I18nContext.Provider value={i18nContextValue}>
|
||||||
@@ -1120,6 +1156,20 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return (
|
||||||
|
<I18nContext.Provider value={i18nContextValue}>
|
||||||
|
<ConfigProvider theme={currentTheme}>
|
||||||
|
<LoginPage
|
||||||
|
step1Config={loginStep1Config}
|
||||||
|
onLoginStart={ensureDefaultLoginConfig}
|
||||||
|
onLoginSuccess={handleFullscreenLoginSuccess}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</I18nContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 渲染:初始化向导
|
// 渲染:初始化向导
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(360px, 36.1vw) 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f4f4f1;
|
||||||
|
color: #171412;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandPanel {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #1a1714;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandContent {
|
||||||
|
width: 280px;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandIcon {
|
||||||
|
position: relative;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: linear-gradient(135deg, #00a960 0%, #007d73 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandIconHead {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
left: 32px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandIconBody {
|
||||||
|
position: absolute;
|
||||||
|
top: 38px;
|
||||||
|
left: 24px;
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-radius: 14px 14px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandTitle {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 29px;
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandSubtitle {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
text-indent: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandSlogan {
|
||||||
|
margin-top: 24px;
|
||||||
|
color: rgba(255, 255, 255, 0.74);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandCopyright {
|
||||||
|
margin-top: 24px;
|
||||||
|
color: rgba(255, 255, 255, 0.38);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginPanel {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
width: 420px;
|
||||||
|
height: 636px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #ededeb;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 18px 40px rgba(26, 23, 20, 0.08);
|
||||||
|
padding: 46px 48px 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
color: #171412;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 20px 0 0;
|
||||||
|
color: #8c8883;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #171412;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.input:global(.ant-input-affix-wrapper) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) {
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 13px;
|
||||||
|
border: 1px solid #d7d3ce;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fdfdfb;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) :global(.ant-input) {
|
||||||
|
height: 46px;
|
||||||
|
color: #171412;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) :global(.ant-input-prefix) {
|
||||||
|
margin-inline-end: 13px;
|
||||||
|
color: #8c8883;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) :global(.ant-input-suffix) {
|
||||||
|
color: #8c8883;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) :global(.ant-input-clear-icon) {
|
||||||
|
color: #c8c5bf;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper) :global(.ant-input::placeholder),
|
||||||
|
.input:global(.ant-input-affix-wrapper)
|
||||||
|
:global(.ant-input-password input::placeholder) {
|
||||||
|
color: #b7b3ad;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:global(.ant-input-affix-wrapper-focused),
|
||||||
|
.input:global(.ant-input-affix-wrapper:hover) {
|
||||||
|
border-color: #00846d;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
height: 0;
|
||||||
|
margin-top: -16px;
|
||||||
|
color: #dc2626;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginButton {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
margin-top: 2px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(90deg, #00a455 0%, #007d73 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
box-shadow: 0 9px 16px rgba(0, 160, 88, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginButton:hover,
|
||||||
|
.loginButton:focus {
|
||||||
|
background: linear-gradient(90deg, #00ae5d 0%, #00887c 100%) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginButton:active {
|
||||||
|
background: linear-gradient(90deg, #00964e 0%, #006e66 100%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
width: 100%;
|
||||||
|
margin: 24px 0 0;
|
||||||
|
color: #006f68;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hintIcon {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border: 1.5px solid currentColor;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hintArrow {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
margin-top: 25px;
|
||||||
|
background: #e7e1dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 25px;
|
||||||
|
color: #aaa5a0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.page {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandPanel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginPanel {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 720px), (max-width: 520px) {
|
||||||
|
.loginPanel {
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: min(420px, calc(100vw - 48px));
|
||||||
|
height: auto;
|
||||||
|
min-height: 636px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { Button, Input, message } from "antd";
|
||||||
|
import {
|
||||||
|
GlobalOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import type { Step1Config } from "../../services/core/setup";
|
||||||
|
import {
|
||||||
|
getAuthErrorMessage,
|
||||||
|
loginAndRegister,
|
||||||
|
} from "../../services/core/auth";
|
||||||
|
import { t } from "../../services/core/i18n";
|
||||||
|
import styles from "./LoginPage.module.css";
|
||||||
|
|
||||||
|
interface LoginPageProps {
|
||||||
|
step1Config: Step1Config;
|
||||||
|
initialUsername?: string;
|
||||||
|
onLoginStart?: () => Promise<void>;
|
||||||
|
onLoginSuccess: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoginPage({
|
||||||
|
step1Config,
|
||||||
|
initialUsername = "",
|
||||||
|
onLoginStart,
|
||||||
|
onLoginSuccess,
|
||||||
|
}: LoginPageProps) {
|
||||||
|
const [domain, setDomain] = useState(step1Config.serverHost || "");
|
||||||
|
const [username, setUsername] = useState(initialUsername);
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [retryCooldown, setRetryCooldown] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDomain(step1Config.serverHost || "");
|
||||||
|
}, [step1Config.serverHost]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUsername(initialUsername);
|
||||||
|
}, [initialUsername]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (retryCooldown <= 0) return;
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
setRetryCooldown((prev) => Math.max(0, prev - 1));
|
||||||
|
}, 1000);
|
||||||
|
return () => window.clearInterval(timer);
|
||||||
|
}, [retryCooldown]);
|
||||||
|
|
||||||
|
const loginButtonText = useMemo(() => {
|
||||||
|
if (retryCooldown > 0) {
|
||||||
|
return t("Claw.Setup.login.pleaseWait", { seconds: retryCooldown });
|
||||||
|
}
|
||||||
|
return t("Claw.Setup.login.loginButton");
|
||||||
|
}, [retryCooldown]);
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
const normalizedDomain = domain.trim();
|
||||||
|
const normalizedUsername = username.trim();
|
||||||
|
if (!normalizedUsername || !password) {
|
||||||
|
message.warning(t("Claw.Setup.login.accountAndCodeRequired"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!normalizedDomain) {
|
||||||
|
message.warning(t("Claw.Setup.login.domainRequired"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
await onLoginStart?.();
|
||||||
|
await loginAndRegister(normalizedUsername, password, {
|
||||||
|
suppressToast: true,
|
||||||
|
domain: normalizedDomain,
|
||||||
|
});
|
||||||
|
await onLoginSuccess();
|
||||||
|
} catch (loginError: any) {
|
||||||
|
const errorMessage = getAuthErrorMessage(loginError);
|
||||||
|
message.error(errorMessage);
|
||||||
|
setError(errorMessage);
|
||||||
|
setPassword("");
|
||||||
|
setRetryCooldown(3);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<aside className={styles.brandPanel}>
|
||||||
|
<div className={styles.brandContent}>
|
||||||
|
<div className={styles.brandIcon} aria-hidden="true">
|
||||||
|
<span className={styles.brandIconHead} />
|
||||||
|
<span className={styles.brandIconBody} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.brandTitle}>陇电数字员工</div>
|
||||||
|
<div className={styles.brandSubtitle}>DIGITAL EMPLOYEE</div>
|
||||||
|
<div className={styles.brandSlogan}>智能协同,高效工作</div>
|
||||||
|
<div className={styles.brandCopyright}>© 2026 陇电数字员工</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main className={styles.loginPanel}>
|
||||||
|
<section className={styles.card}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h1 className={styles.title}>欢迎登录</h1>
|
||||||
|
<p className={styles.subtitle}>请输入您的认证信息以继续</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.form}>
|
||||||
|
<label className={styles.field}>
|
||||||
|
<span className={styles.label}>认证地址</span>
|
||||||
|
<Input
|
||||||
|
className={styles.input}
|
||||||
|
prefix={<GlobalOutlined />}
|
||||||
|
value={domain}
|
||||||
|
onChange={(event) => setDomain(event.target.value)}
|
||||||
|
placeholder="请输入认证地址"
|
||||||
|
autoComplete="off"
|
||||||
|
allowClear
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className={styles.field}>
|
||||||
|
<span className={styles.label}>用户名</span>
|
||||||
|
<Input
|
||||||
|
className={styles.input}
|
||||||
|
prefix={<UserOutlined />}
|
||||||
|
value={username}
|
||||||
|
onChange={(event) => setUsername(event.target.value)}
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
autoComplete="username"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className={styles.field}>
|
||||||
|
<span className={styles.label}>密码</span>
|
||||||
|
<Input.Password
|
||||||
|
className={styles.input}
|
||||||
|
prefix={<LockOutlined />}
|
||||||
|
value={password}
|
||||||
|
onChange={(event) => setPassword(event.target.value)}
|
||||||
|
placeholder="请输入密码"
|
||||||
|
autoComplete="current-password"
|
||||||
|
onPressEnter={handleLogin}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className={styles.error}>{error}</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={styles.loginButton}
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
loading={loading}
|
||||||
|
disabled={retryCooldown > 0}
|
||||||
|
onClick={handleLogin}
|
||||||
|
>
|
||||||
|
{loginButtonText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.hint}>
|
||||||
|
<span className={styles.hintIcon} />
|
||||||
|
<span>统一权限登录</span>
|
||||||
|
<span className={styles.hintArrow}>›</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.divider} />
|
||||||
|
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<span>需要帮助?</span>
|
||||||
|
<span>v2.0.1</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginPage;
|
||||||
@@ -33,10 +33,7 @@ import {
|
|||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
RobotOutlined,
|
|
||||||
FolderOpenOutlined,
|
FolderOpenOutlined,
|
||||||
LockOutlined,
|
|
||||||
GlobalOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
setupService,
|
setupService,
|
||||||
@@ -46,9 +43,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
loginAndRegister,
|
loginAndRegister,
|
||||||
normalizeServerHost,
|
normalizeServerHost,
|
||||||
isLoggedIn as checkIsLoggedIn,
|
|
||||||
getCurrentAuth,
|
getCurrentAuth,
|
||||||
getAuthErrorMessage,
|
|
||||||
logout,
|
logout,
|
||||||
} from "../../services/core/auth";
|
} from "../../services/core/auth";
|
||||||
import { AUTH_KEYS } from "@shared/constants";
|
import { AUTH_KEYS } from "@shared/constants";
|
||||||
@@ -56,6 +51,7 @@ import type { QuickInitConfig } from "@shared/types/quickInit";
|
|||||||
import SetupDependencies, {
|
import SetupDependencies, {
|
||||||
type MockDependenciesApi,
|
type MockDependenciesApi,
|
||||||
} from "./SetupDependencies";
|
} from "./SetupDependencies";
|
||||||
|
import LoginPage from "./LoginPage";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -94,19 +90,15 @@ function SetupWizard({
|
|||||||
const [dependenciesReady, setDependenciesReady] = useState<boolean | null>(
|
const [dependenciesReady, setDependenciesReady] = useState<boolean | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(2);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [step1Config, setStep1Config] =
|
const [step1Config, setStep1Config] =
|
||||||
useState<Step1Config>(DEFAULT_STEP1_CONFIG);
|
useState<Step1Config>(DEFAULT_STEP1_CONFIG);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [completed, setCompleted] = useState(false);
|
const [completed, setCompleted] = useState(false);
|
||||||
const [domain, setDomain] = useState("");
|
const [domain, setDomain] = useState("");
|
||||||
const [loginLoading, setLoginLoading] = useState(false);
|
|
||||||
const [loginError, setLoginError] = useState("");
|
|
||||||
const [retryCooldown, setRetryCooldown] = useState(0);
|
|
||||||
const [isAlreadyLoggedIn, setIsAlreadyLoggedIn] = useState(false);
|
const [isAlreadyLoggedIn, setIsAlreadyLoggedIn] = useState(false);
|
||||||
const [checkingAuth, setCheckingAuth] = useState(false);
|
const [checkingAuth, setCheckingAuth] = useState(true);
|
||||||
const [quickIniting, setQuickIniting] = useState(false);
|
const [quickIniting, setQuickIniting] = useState(false);
|
||||||
const quickInitAttempted = useRef(false);
|
const quickInitAttempted = useRef(false);
|
||||||
|
|
||||||
@@ -117,14 +109,14 @@ function SetupWizard({
|
|||||||
if (skipDependencyCheck) {
|
if (skipDependencyCheck) {
|
||||||
setDependenciesReady(true);
|
setDependenciesReady(true);
|
||||||
const state = await setupService.getSetupState();
|
const state = await setupService.getSetupState();
|
||||||
if (state.completed) {
|
|
||||||
setCompleted(true);
|
|
||||||
onCompleteRef.current();
|
|
||||||
} else {
|
|
||||||
setCurrentStep(state.step1Completed ? 2 : 1);
|
|
||||||
}
|
|
||||||
const config = await setupService.getStep1Config();
|
const config = await setupService.getStep1Config();
|
||||||
setStep1Config(config);
|
setStep1Config(config);
|
||||||
|
if (state.completed) {
|
||||||
|
setCompleted(true);
|
||||||
|
onComplete();
|
||||||
|
} else {
|
||||||
|
setCurrentStep(2);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -173,7 +165,7 @@ function SetupWizard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCurrentStep(state.step1Completed ? 2 : 1);
|
setCurrentStep(2);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 有依赖缺失,进入安装流程
|
// 有依赖缺失,进入安装流程
|
||||||
@@ -203,6 +195,7 @@ function SetupWizard({
|
|||||||
try {
|
try {
|
||||||
// 1. 保存 step1 配置
|
// 1. 保存 step1 配置
|
||||||
const step1: Step1Config = {
|
const step1: Step1Config = {
|
||||||
|
...DEFAULT_STEP1_CONFIG,
|
||||||
serverHost: normalizeServerHost(config.serverHost),
|
serverHost: normalizeServerHost(config.serverHost),
|
||||||
agentPort: config.agentPort,
|
agentPort: config.agentPort,
|
||||||
fileServerPort: config.fileServerPort,
|
fileServerPort: config.fileServerPort,
|
||||||
@@ -247,9 +240,7 @@ function SetupWizard({
|
|||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
setQuickIniting(false);
|
setQuickIniting(false);
|
||||||
// step1 可能已保存,从 step1 或 step2 继续
|
setCurrentStep(2);
|
||||||
const state = await setupService.getSetupState();
|
|
||||||
setCurrentStep(state.step1Completed ? 2 : 1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onComplete],
|
[onComplete],
|
||||||
@@ -272,9 +263,23 @@ function SetupWizard({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentStep(1);
|
setCurrentStep(2);
|
||||||
}, [performQuickInit]);
|
}, [performQuickInit]);
|
||||||
|
|
||||||
|
const ensureDefaultStep1Config = useCallback(async () => {
|
||||||
|
const currentConfig = await setupService.getStep1Config();
|
||||||
|
const nextConfig: Step1Config = {
|
||||||
|
...DEFAULT_STEP1_CONFIG,
|
||||||
|
...currentConfig,
|
||||||
|
serverHost: currentConfig.serverHost || DEFAULT_STEP1_CONFIG.serverHost,
|
||||||
|
workspaceDir:
|
||||||
|
currentConfig.workspaceDir || DEFAULT_STEP1_CONFIG.workspaceDir,
|
||||||
|
};
|
||||||
|
await setupService.saveStep1Config(nextConfig);
|
||||||
|
setStep1Config(nextConfig);
|
||||||
|
return nextConfig;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Check login status when entering step 2
|
// Check login status when entering step 2
|
||||||
const checkLoginStatus = useCallback(async () => {
|
const checkLoginStatus = useCallback(async () => {
|
||||||
setCheckingAuth(true);
|
setCheckingAuth(true);
|
||||||
@@ -300,15 +305,6 @@ function SetupWizard({
|
|||||||
}
|
}
|
||||||
}, [currentStep, checkLoginStatus]);
|
}, [currentStep, checkLoginStatus]);
|
||||||
|
|
||||||
// Retry cooldown timer
|
|
||||||
useEffect(() => {
|
|
||||||
if (retryCooldown <= 0) return;
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setRetryCooldown((prev) => Math.max(0, prev - 1));
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, [retryCooldown]);
|
|
||||||
|
|
||||||
const handleStep1Submit = async () => {
|
const handleStep1Submit = async () => {
|
||||||
if (!step1Config.fileServerPort) {
|
if (!step1Config.fileServerPort) {
|
||||||
message.warning(t("Claw.Setup.basicConfig.fileServerPortRequired"));
|
message.warning(t("Claw.Setup.basicConfig.fileServerPortRequired"));
|
||||||
@@ -327,49 +323,13 @@ function SetupWizard({
|
|||||||
await setupService.saveStep1Config(step1Config);
|
await setupService.saveStep1Config(step1Config);
|
||||||
setCurrentStep(2);
|
setCurrentStep(2);
|
||||||
message.success(t("Claw.Setup.basicConfig.saved"));
|
message.success(t("Claw.Setup.basicConfig.saved"));
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error(t("Claw.Setup.basicConfig.saveFailed"));
|
message.error(t("Claw.Setup.basicConfig.saveFailed"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = async () => {
|
|
||||||
if (!username || !password) {
|
|
||||||
message.warning(t("Claw.Setup.login.accountAndCodeRequired"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginDomain = domain || step1Config.serverHost;
|
|
||||||
if (!loginDomain) {
|
|
||||||
message.warning(t("Claw.Setup.login.domainRequired"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoginLoading(true);
|
|
||||||
setLoginError("");
|
|
||||||
try {
|
|
||||||
await loginAndRegister(username, password, {
|
|
||||||
suppressToast: true,
|
|
||||||
domain: loginDomain,
|
|
||||||
});
|
|
||||||
setLoginError("");
|
|
||||||
await setupService.completeStep2();
|
|
||||||
await setupService.completeSetup();
|
|
||||||
setCompleted(true);
|
|
||||||
message.success(t("Claw.Setup.login.success"));
|
|
||||||
setTimeout(() => onComplete(), 2000);
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = getAuthErrorMessage(error);
|
|
||||||
message.error(errorMessage);
|
|
||||||
setLoginError(errorMessage);
|
|
||||||
setPassword("");
|
|
||||||
setRetryCooldown(3);
|
|
||||||
} finally {
|
|
||||||
setLoginLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContinueLoggedIn = async () => {
|
const handleContinueLoggedIn = async () => {
|
||||||
try {
|
try {
|
||||||
await setupService.completeStep2();
|
await setupService.completeStep2();
|
||||||
@@ -381,6 +341,15 @@ function SetupWizard({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLoginSuccess = async () => {
|
||||||
|
await ensureDefaultStep1Config();
|
||||||
|
await setupService.completeStep2();
|
||||||
|
await setupService.completeSetup();
|
||||||
|
setCompleted(true);
|
||||||
|
message.success(t("Claw.Setup.login.success"));
|
||||||
|
setTimeout(() => onComplete(), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await logout();
|
await logout();
|
||||||
@@ -551,85 +520,11 @@ function SetupWizard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form layout="vertical">
|
<LoginPage
|
||||||
{loginError && (
|
step1Config={step1Config}
|
||||||
<Alert
|
initialUsername={username}
|
||||||
message={t("Claw.Setup.login.failed")}
|
onLoginSuccess={handleLoginSuccess}
|
||||||
description={
|
/>
|
||||||
<Text
|
|
||||||
copyable={{ text: loginError }}
|
|
||||||
style={{
|
|
||||||
fontSize: 12,
|
|
||||||
color: "var(--color-text-secondary)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loginError}
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
type="error"
|
|
||||||
showIcon
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form.Item label={t("Claw.Setup.login.domain")} required>
|
|
||||||
<Input
|
|
||||||
prefix={<GlobalOutlined />}
|
|
||||||
value={domain || step1Config.serverHost}
|
|
||||||
onChange={(e) => setDomain(e.target.value)}
|
|
||||||
placeholder={t("Claw.Setup.login.domainPlaceholder")}
|
|
||||||
autoComplete="off"
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label={t("Claw.Setup.login.account")} required>
|
|
||||||
<Input
|
|
||||||
prefix={<UserOutlined />}
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
placeholder={t("Claw.Setup.login.accountPlaceholder")}
|
|
||||||
autoComplete="username"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label={t("Claw.Setup.login.code")} required>
|
|
||||||
<Input.Password
|
|
||||||
prefix={<LockOutlined />}
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
placeholder={t("Claw.Setup.login.codePlaceholder")}
|
|
||||||
autoComplete="current-password"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space style={{ width: "100%" }} direction="vertical">
|
|
||||||
<Space>
|
|
||||||
<Button onClick={() => setCurrentStep(1)}>
|
|
||||||
{t("Claw.Setup.login.prevStep")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handleLogin}
|
|
||||||
loading={loginLoading}
|
|
||||||
disabled={retryCooldown > 0}
|
|
||||||
>
|
|
||||||
{retryCooldown > 0
|
|
||||||
? t("Claw.Setup.login.pleaseWait", {
|
|
||||||
seconds: retryCooldown,
|
|
||||||
})
|
|
||||||
: t("Claw.Setup.login.loginButton")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<Text
|
|
||||||
style={{ fontSize: 11, color: "var(--color-text-tertiary)" }}
|
|
||||||
>
|
|
||||||
{t("Claw.Setup.login.supportMultipleAccountTypes")}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -709,6 +604,22 @@ function SetupWizard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
dependenciesReady &&
|
||||||
|
currentStep === 2 &&
|
||||||
|
!completed &&
|
||||||
|
!checkingAuth &&
|
||||||
|
!isAlreadyLoggedIn
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<LoginPage
|
||||||
|
step1Config={step1Config}
|
||||||
|
initialUsername={username}
|
||||||
|
onLoginSuccess={handleLoginSuccess}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 阶段 2-4: 配置步骤
|
// 阶段 2-4: 配置步骤
|
||||||
return (
|
return (
|
||||||
<div style={styles.container}>
|
<div style={styles.container}>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/png" href="/icon.png" />
|
<link rel="icon" type="image/png" href="/icon.png" />
|
||||||
<!-- 与 @shared/constants APP_DISPLAY_NAME 保持一致 -->
|
<!-- 与 @shared/constants APP_DISPLAY_NAME 保持一致 -->
|
||||||
<title>启明</title>
|
<title>陇电数字员工</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import {
|
|||||||
describe("Constants", () => {
|
describe("Constants", () => {
|
||||||
describe("App Identity", () => {
|
describe("App Identity", () => {
|
||||||
it("should have consistent app name", () => {
|
it("should have consistent app name", () => {
|
||||||
expect(APP_DISPLAY_NAME).toBe("启明数字员工");
|
expect(APP_DISPLAY_NAME).toBe("陇电数字员工");
|
||||||
expect(APP_NAME_IDENTIFIER).toBe("qimingclaw");
|
expect(APP_NAME_IDENTIFIER).toBe("qimingclaw");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { AgentEngineType } from "@shared/types/electron";
|
|||||||
// ==================== 应用名称 ====================
|
// ==================== 应用名称 ====================
|
||||||
|
|
||||||
/** 应用对外显示名称(窗口标题、关于、安装包名称等),与 package.json build.productName 保持一致 */
|
/** 应用对外显示名称(窗口标题、关于、安装包名称等),与 package.json build.productName 保持一致 */
|
||||||
export const APP_DISPLAY_NAME = "启明数字员工";
|
export const APP_DISPLAY_NAME = "陇电数字员工";
|
||||||
|
|
||||||
/** 应用技术标识(进程名、目录名等,小写字母),与 appId 等保持一致 */
|
/** 应用技术标识(进程名、目录名等,小写字母),与 appId 等保持一致 */
|
||||||
export const APP_NAME_IDENTIFIER = "qimingclaw";
|
export const APP_NAME_IDENTIFIER = "qimingclaw";
|
||||||
|
|||||||