diff --git a/app/web/components/skills/style.scss b/app/web/components/skills/style.scss
index 6de28ff..8741ed3 100644
--- a/app/web/components/skills/style.scss
+++ b/app/web/components/skills/style.scss
@@ -64,7 +64,12 @@
align-items: center;
justify-content: center;
flex: 0 0 auto;
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(99, 102, 241, 0.12) 100%);
+ background:
+ linear-gradient(
+ 135deg,
+ rgba(59, 130, 246, 0.08) 0%,
+ rgba(99, 102, 241, 0.12) 100%
+ );
border: 1px solid rgba(219, 234, 254, 0.5);
.is-package-icon {
color: #EC4899;
diff --git a/app/web/pages/skills/detail/components/SkillInstallPanel.tsx b/app/web/pages/skills/detail/components/SkillInstallPanel.tsx
index 5c2b111..b7bfa17 100644
--- a/app/web/pages/skills/detail/components/SkillInstallPanel.tsx
+++ b/app/web/pages/skills/detail/components/SkillInstallPanel.tsx
@@ -40,23 +40,6 @@ interface SkillInstallPanelProps {
contributor?: string;
}
-const renderInlineCommand = (command: string, copyMessage: string, compact = false) => (
-
-
-
- {command || '暂无可复制命令'}
-
-
}
- onClick={() => copyToClipboard(command, copyMessage)}
- disabled={!command}
- />
-
-
-);
-
const renderTerminalCommand = (command: string, copyMessage: string) => (
@@ -97,10 +80,7 @@ export const SkillInstallPanel: React.FC
= ({
}) => (
@@ -205,16 +175,24 @@ export const SkillInstallPanel: React.FC
= ({
手动下载
-
- {renderInlineCommand(downloadCommand, '下载命令已复制到剪贴板', true)}
+
+
+ }
+ onClick={() => copyToClipboard(manualDownloadUrl, '下载链接已复制到剪贴板')}
+ disabled={!manualDownloadUrl}
+ />
+
+ {renderTerminalCommand(downloadCommand, '下载命令已复制到剪贴板')}
diff --git a/app/web/pages/skills/detail/style.scss b/app/web/pages/skills/detail/style.scss
index 54ef490..c849db9 100644
--- a/app/web/pages/skills/detail/style.scss
+++ b/app/web/pages/skills/detail/style.scss
@@ -584,17 +584,6 @@
align-items: center;
gap: 6px;
}
- .install-soon-badge {
- display: inline-flex;
- align-items: center;
- padding: 2px 6px;
- font-size: 9px;
- font-weight: 700;
- letter-spacing: 0.05em;
- color: #C2410C;
- background: #FED7AA;
- border-radius: 4px;
- }
.install-option-card {
border: 1px solid #DBE5F0;
border-radius: 8px;
@@ -685,17 +674,6 @@
flex-direction: column;
gap: 12px;
}
- .human-command-card {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .human-command-title {
- color: #566166;
- font-size: 12px;
- font-weight: 600;
- line-height: 16px;
- }
.download-panel,
.related-panel,
.meta-panel {
@@ -703,7 +681,6 @@
}
.download-btn {
height: 28px;
- margin-bottom: 12px;
padding: 0 12px;
border-radius: 4px;
font-size: 12px;
@@ -715,6 +692,7 @@
align-items: center;
justify-content: center;
gap: 8px;
+ flex: 1;
&:hover,
&:focus {
background: #1B2438;
@@ -727,6 +705,15 @@
color: #FFF;
}
}
+ .download-btn-row {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 12px;
+ .is-download-copy {
+ flex: 0 0 auto;
+ }
+ }
.install-panel,
.download-panel,
.related-panel,
diff --git a/app/web/pages/skills/detail/utils/skillDetailUtils.ts b/app/web/pages/skills/detail/utils/skillDetailUtils.ts
index 8029ef7..050f4e2 100644
--- a/app/web/pages/skills/detail/utils/skillDetailUtils.ts
+++ b/app/web/pages/skills/detail/utils/skillDetailUtils.ts
@@ -152,5 +152,8 @@ export const formatCompactDate = (value?: string) => {
if (!value) return '-';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '-';
- return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
+ const pad = (n: number) => String(n).padStart(2, '0');
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
+ date.getHours()
+ )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};
diff --git a/app/web/pages/skills/index.tsx b/app/web/pages/skills/index.tsx
index 200ec48..7169a59 100644
--- a/app/web/pages/skills/index.tsx
+++ b/app/web/pages/skills/index.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
CopyOutlined,
DeleteOutlined,
@@ -24,6 +24,7 @@ import {
Typography,
Upload,
} from 'antd';
+import debounce from 'lodash/debounce';
import { API } from '@/api';
import { SkillCard } from '@/components/skills/SkillCard';
@@ -66,7 +67,10 @@ const SkillsMarket: React.FC = ({ history }) => {
const [importForm] = Form.useForm();
const [editForm] = Form.useForm();
const [query, setQuery] = useState(INITIAL_QUERY);
+ const [keywordInput, setKeywordInput] = useState('');
const [selectedSlugs, setSelectedSlugs] = useState>(new Set());
+ const queryRef = useRef(query);
+ queryRef.current = query;
const fetchSkills = useCallback(async (nextQuery) => {
setLoading(true);
@@ -90,14 +94,29 @@ const SkillsMarket: React.FC = ({ history }) => {
useEffect(() => {
fetchSkills(query);
- }, [fetchSkills, query]);
+ }, []);
+
+ useEffect(() => {
+ return () => {
+ debouncedFetchRef.current.cancel();
+ };
+ }, []);
const updateQueryAndFetch = (patch: Partial) => {
- const next = { ...query, ...patch };
+ const next = { ...queryRef.current, ...patch };
setQuery(next);
setSelectedSlugs(new Set());
+ fetchSkills(next);
};
+ const debouncedFetchRef = useRef(
+ debounce((keyword: string) => {
+ const next = { ...queryRef.current, keyword, pageNum: 1 };
+ setQuery(next);
+ fetchSkills(next);
+ }, 300)
+ );
+
const handleSelect = (skill: SkillItem, selected: boolean) => {
setSelectedSlugs((prev) => {
const next = new Set(prev);
@@ -295,12 +314,21 @@ const SkillsMarket: React.FC = ({ history }) => {
}
- onChange={(e) => setQuery({ ...query, keyword: e.target.value })}
- onSearch={(value) => updateQueryAndFetch({ keyword: value, pageNum: 1 })}
+ onChange={(e) => {
+ setKeywordInput(e.target.value);
+ debouncedFetchRef.current(e.target.value);
+ }}
+ onSearch={(value) => {
+ debouncedFetchRef.current.cancel();
+ setKeywordInput(value);
+ const next = { ...queryRef.current, keyword: value, pageNum: 1 };
+ setQuery(next);
+ fetchSkills(next);
+ }}
/>