diff --git a/AGENTS.md b/AGENTS.md
index c2c34d1..8f5d869 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -31,6 +31,8 @@ npm run test:coverage # 测试覆盖率
**重要**: 修改 `src/main/` 目录下的文件后,必须运行 `npm test` 确保测试通过。
+**注意**: 仅修改 `src/renderer/`(前端/UI)或 `src/preload/` 文件时,不需要运行测试,只需确保类型检查通过(`npx tsc --noEmit`)即可。
+
## 2. 项目架构
```
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index 3291699..3cffa59 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -8,20 +8,20 @@ import { useTranslation } from "react-i18next";
import {
Layout,
Typography,
- theme,
ConfigProvider,
App as AntApp,
Space,
} from "antd";
-import { Button, Tooltip, DropdownMenu } from "@lobehub/ui";
-import { SettingOutlined } from "@ant-design/icons";
+
+import { Button, Tooltip, Modal } from "@lobehub/ui";
+
import {
- Github,
Cloud,
CloudUpload,
History,
PanelLeftClose,
PanelLeftOpen,
+ Settings as SettingsIcon,
} from "lucide-react";
import { createStyles } from "antd-style";
@@ -37,7 +37,7 @@ const { Header, Content, Sider } = Layout;
const { Title } = Typography;
// Domain section heights
-const DOMAIN_SECTION_COLLAPSED = 76; // 增加高度,避免按钮覆盖文字
+const DOMAIN_SECTION_COLLAPSED = 76; // 增加高度,避免按钮覆盖文字
const DOMAIN_SECTION_EXPANDED = 240;
const DEFAULT_SIDER_WIDTH = 320;
const MIN_SIDER_WIDTH = 280;
@@ -150,9 +150,6 @@ const useStyles = createStyles(({ token, css }) => ({
const App: React.FC = () => {
const { t } = useTranslation("common");
const { styles } = useStyles();
- const {
- token: { colorBgContainer },
- } = theme.useToken();
const { currentDomain } = useDomainStore();
const {
@@ -221,13 +218,10 @@ const App: React.FC = () => {
-
+
Kintone Manager
-
+
}
@@ -274,7 +268,7 @@ const App: React.FC = () => {
{siderCollapsed && (
-
+
}
@@ -296,29 +290,18 @@ const App: React.FC = () => {
onClick={() => setDeployDialogOpen(true)}
disabled={!currentDomain}
>
- {t("deployFiles")}
+ {t("deployFiles")}
} disabled={!currentDomain}>
- {t("versionHistory")}
+ {t("versionHistory")}
- } />}
- items={[
- {
- key: "settings",
- icon: ,
- label: t("settings"),
- onClick: () => setSettingsOpen(true),
- },
- { type: "divider" },
- {
- key: "github",
- icon: ,
- label: "GitHub",
- onClick: () => window.open("https://github.com", "_blank"),
- },
- ]}
- />
+
+ }
+ onClick={() => setSettingsOpen(true)}
+ />
+
+
@@ -337,12 +320,17 @@ const App: React.FC = () => {
onClose={() => setDeployDialogOpen(false)}
/>
- {/* Settings Panel */}
- {settingsOpen && (
-
- setSettingsOpen(false)} />
-
- )}
+ {/* Settings Modal */}
+ setSettingsOpen(false)}
+ footer={null}
+ width={480}
+ >
+
+
+
diff --git a/src/renderer/src/components/AppList/AppList.tsx b/src/renderer/src/components/AppList/AppList.tsx
index ee14cb1..17942aa 100644
--- a/src/renderer/src/components/AppList/AppList.tsx
+++ b/src/renderer/src/components/AppList/AppList.tsx
@@ -4,13 +4,9 @@
*/
import React from "react";
+import { motion } from "motion/react";
import { useTranslation } from "react-i18next";
-import {
- Input,
- Spin,
- Typography,
- Space,
-} from "antd";
+import { Input, Spin, Typography, Space } from "antd";
import { Button, Tooltip, Empty, Select } from "@lobehub/ui";
import {
RefreshCw,
@@ -65,11 +61,11 @@ const useStyles = createStyles(({ token, css }) => ({
align-items: center;
height: 300px;
`,
- listItem: css`
+ listItemMotion: css`
cursor: pointer;
- transition: background 0.2s;
padding: ${token.paddingSM}px ${token.paddingMD}px !important;
border-bottom: 1px solid ${token.colorBorderSecondary} !important;
+ position: relative;
&:hover {
background: ${token.colorBgTextHover};
@@ -81,13 +77,10 @@ const useStyles = createStyles(({ token, css }) => ({
`,
listItemPinned: css`
background: ${token.colorWarningBg} !important;
- `,
- appInfo: css`
- display: flex;
- align-items: center;
- gap: ${token.paddingSM}px;
- flex: 1;
- min-width: 0;
+
+ &:hover {
+ background: ${token.colorWarningBgHover} !important;
+ }
`,
appName: css`
font-weight: 500;
@@ -114,20 +107,124 @@ const useStyles = createStyles(({ token, css }) => ({
color: ${token.colorTextSecondary};
font-size: 12px;
`,
- pinIcon: css`
+ appInfoWrapper: css`
+ display: flex;
+ align-items: center;
+ gap: ${token.paddingSM}px;
+ flex: 1;
+ min-width: 0;
+ position: relative;
+ `,
+ iconWrapper: css`
+ position: relative;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ `,
+ pinOverlay: css`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 1;
+ `,
+ pinOverlayVisible: css`
+ opacity: 1;
+ `,
+ pinButton: css`
color: ${token.colorTextTertiary};
cursor: pointer;
font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
&:hover {
color: ${token.colorWarning};
}
`,
- pinIconPinned: css`
+ pinButtonPinned: css`
color: ${token.colorWarning};
`,
}));
+interface AppListItemProps {
+ app: AppDetail;
+ isActive: boolean;
+ isPinned: boolean;
+ onItemClick: (app: AppDetail) => void;
+ onPinToggle: (e: React.MouseEvent, appId: string) => void;
+ styles: ReturnType["styles"];
+ t: (key: string) => string;
+}
+
+const AppListItem: React.FC = ({
+ app,
+ isActive,
+ isPinned,
+ onItemClick,
+ onPinToggle,
+ styles,
+ t,
+}) => {
+ const [isHovered, setIsHovered] = React.useState(false);
+
+ // Pin overlay is visible when:
+ // 1. Item is pinned (always show)
+ // 2. Item is hovered (show for unpinned items)
+ const showPinOverlay = isPinned || isHovered;
+
+ return (
+ onItemClick(app)}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ transition={{ type: "spring", stiffness: 500, damping: 30 }}
+ >
+
+
+ {/* Pin overlay - visible on hover for unpinned, always visible for pinned */}
+
onPinToggle(e, app.appId)}
+ >
+
+
+ {isPinned ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* App icon - hidden when pin overlay is visible */}
+ {!showPinOverlay &&
}
+
+
+ {app.name}
+
+
+ ID: {app.appId}
+
+
+
+ );
+};
+
const AppList: React.FC = () => {
const { t } = useTranslation("app");
const { styles } = useStyles();
@@ -260,37 +357,6 @@ const AppList: React.FC = () => {
setAppSortOrder(appSortOrder === "asc" ? "desc" : "asc");
};
- // Render list item
- const renderItem = (app: AppDetail) => {
- const isActive = selectedAppId === app.appId;
- const isPinned = currentPinnedApps.includes(app.appId);
-
- return (
- handleItemClick(app)}
- >
-
-
- handlePinToggle(e, app.appId)}
- >
- {isPinned ? : }
-
-
-
-
- {app.name}
-
-
- ID: {app.appId}
-
-
-
- );
- };
-
if (!currentDomain) {
return (
@@ -371,13 +437,18 @@ const AppList: React.FC = () => {
) : (
- <>
- {displayApps.map((app) => (
-
- {renderItem(app)}
-
- ))}
- >
+ displayApps.map((app) => (
+
+ ))
)}
{/* Footer with info */}
diff --git a/src/renderer/src/components/DomainManager/DomainManager.tsx b/src/renderer/src/components/DomainManager/DomainManager.tsx
index 68af431..0d28593 100644
--- a/src/renderer/src/components/DomainManager/DomainManager.tsx
+++ b/src/renderer/src/components/DomainManager/DomainManager.tsx
@@ -182,7 +182,7 @@ const DomainManager: React.FC = ({
}
- size="small"
+ size={24}
style={{
backgroundColor: getDomainIconColor(currentDomain?.id),
}}
diff --git a/src/renderer/src/components/Settings/Settings.tsx b/src/renderer/src/components/Settings/Settings.tsx
index 01e88c6..071d426 100644
--- a/src/renderer/src/components/Settings/Settings.tsx
+++ b/src/renderer/src/components/Settings/Settings.tsx
@@ -1,18 +1,18 @@
/**
* Settings Component
- * Application settings page with language switcher
+ * Application settings modal with language switcher
*/
import React from "react";
import { useTranslation } from "react-i18next";
import { Typography, Radio, Divider } from "antd";
-import { Button } from "@lobehub/ui";
-import { Globe, XCircle } from "lucide-react";
+import { Globe } from "lucide-react";
+
import { createStyles } from "antd-style";
import { useLocaleStore } from "@renderer/stores/localeStore";
import { LOCALES, type LocaleCode } from "@shared/types/locale";
-const { Title, Text } = Typography;
+const { Title } = Typography;
const useStyles = createStyles(({ token, css }) => ({
container: css`
@@ -67,11 +67,7 @@ const useStyles = createStyles(({ token, css }) => ({
`,
}));
-interface SettingsProps {
- onClose?: () => void;
-}
-
-const Settings: React.FC
= ({ onClose }) => {
+const Settings: React.FC = () => {
const { t } = useTranslation("settings");
const { styles } = useStyles();
const { locale, setLocale } = useLocaleStore();
@@ -86,27 +82,6 @@ const Settings: React.FC = ({ onClose }) => {
return (
- {/* Header with close button */}
-
-
- {t("title")}
-
- {onClose && (
- }
- onClick={onClose}
- />
- )}
-
-
{/* Language Section */}