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 || '暂无可复制命令'} -
-
-
-); - 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)} +
+ +
+ {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); + }} />