feat: connect search and project settings

This commit is contained in:
2026-07-21 17:16:45 +08:00
parent 2275d388a7
commit 000de4bcdb
14 changed files with 698 additions and 141 deletions

View File

@@ -11,15 +11,18 @@ import {
createTask,
fetchProjectWorkspace,
fetchProjects,
updateProject,
updateTask,
uploadSource,
} from '../api/projects'
import type { SearchResultDTO } from '../api/search'
import { LoginPage } from '../pages/login'
import { ProjectActionModals, type CronDraft, type ProjectActionModal, type ProjectDraft, type SourceDraft, type TaskDraft } from '../pages/projects/project-action-modals'
import type { ProjectSettingsUpdate } from '../pages/projects/project-sidebar'
import { ProjectPage } from '../pages/workspace-home'
import type { WorkspaceTaskUpdate } from '../pages/workspace-body'
import type { ChannelKey, Project, ProjectWorkspace, Screen, Theme, WorkbenchView } from '../pages/projects/project-types'
import { useWorkbenchSearch } from './use-workbench-search'
function App() {
const [screen, setScreen] = useState<Screen>('login')
@@ -34,6 +37,7 @@ function App() {
const [loading, setLoading] = useState(false)
const [actionLoading, setActionLoading] = useState(false)
const [activeModal, setActiveModal] = useState<ProjectActionModal>(null)
const workspaceSearch = useWorkbenchSearch(session)
const dark = theme === 'dark'
const activeWorkspace = workspaces.find((workspace) => workspace.project.id === activeProjectID) ?? workspaces[0]
@@ -210,23 +214,35 @@ function App() {
}, '任务已更新')
}
function handleUpdateProject(update: ProjectSettingsUpdate) {
setWorkspaces((current) =>
current.map((workspace) => {
if (workspace.project.id !== update.projectId) return workspace
const nextProject = {
...workspace.project,
name: update.name,
identifier: update.identifier,
icon: update.icon,
background: update.background,
description: update.description,
short: projectIconLabel(update.icon, update.identifier || update.name.slice(0, 2)),
color: projectColor(update.background, workspace.project.color),
}
return { ...workspace, project: nextProject }
}),
)
async function handleUpdateProject(update: ProjectSettingsUpdate) {
const currentSession = requireSession()
await updateProject(currentSession, update.projectId, {
name: update.name,
identifier: update.identifier,
icon: update.icon,
background: update.background,
description: update.description,
})
await loadWorkspaces(currentSession, update.projectId)
Message.success('项目设置已更新')
}
function handleSelectSearchResult(result: SearchResultDTO) {
const owningWorkspace = workspaces.find((workspace) => workspace.project.id === result.projectId)
if (!owningWorkspace) {
Message.warning('该搜索结果所在项目当前不可访问')
return
}
const target = searchResultTarget(result.type)
if (!target) {
Message.warning('该搜索结果暂不支持导航')
return
}
setActiveProjectID(owningWorkspace.project.id)
setActiveView('project')
setActiveChannel(target.channel)
setActiveTaskID(target.openTask ? result.id : null)
if (result.type === 'note') setSelectedItem(result.title)
}
return (
@@ -267,6 +283,13 @@ function App() {
onUpdateWorkspaceTask={handleUpdateWorkspaceTask}
onUpdateProject={handleUpdateProject}
onCreateProjectTag={handleCreateProjectTag}
searchQuery={workspaceSearch.query}
searchLoading={workspaceSearch.loading}
searchSearched={workspaceSearch.searched}
searchResults={workspaceSearch.results}
onSearchQueryChange={workspaceSearch.onQueryChange}
onSearch={() => void workspaceSearch.onSearch()}
onSelectSearchResult={handleSelectSearchResult}
/>
) : (
<Spin loading />
@@ -286,18 +309,11 @@ function App() {
)
}
function projectIconLabel(icon: string, fallback: string) {
const trimmed = icon.trim()
if (!trimmed) return fallback
const chars = Array.from(trimmed)
if (chars.length <= 2) return trimmed
return chars.slice(0, 2).join('').toUpperCase()
}
function projectColor(background: string, fallback: string) {
const trimmed = background.trim()
if (trimmed.startsWith('#') || trimmed.startsWith('rgb') || trimmed.startsWith('hsl')) return trimmed
return fallback
function searchResultTarget(type: string): { channel: ChannelKey; openTask: boolean } | null {
if (type === 'project') return { channel: 'overview', openTask: false }
if (type === 'task') return { channel: 'tasks', openTask: true }
if (type === 'note') return { channel: 'notes', openTask: false }
return null
}
export default App

View File

@@ -0,0 +1,58 @@
import { useRef, useState } from 'react'
import { Message } from '@arco-design/web-react'
import type { ApiSession } from '../api/client'
import { searchWorkspace, type SearchResultDTO } from '../api/search'
export function useWorkbenchSearch(session: ApiSession | null) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchResultDTO[]>([])
const [loading, setLoading] = useState(false)
const [searched, setSearched] = useState(false)
const requestSequence = useRef(0)
function changeQuery(value: string) {
requestSequence.current += 1
setQuery(value)
setLoading(false)
setSearched(false)
}
async function submitSearch() {
const requestID = ++requestSequence.current
const trimmedQuery = query.trim()
if (!trimmedQuery) {
setResults([])
setSearched(false)
Message.warning('请输入搜索关键词')
return
}
if (!session) {
Message.error('未登录或登录已失效')
return
}
setLoading(true)
setSearched(false)
try {
const response = await searchWorkspace(session, trimmedQuery)
if (requestID !== requestSequence.current) return
setResults(response.items)
setSearched(true)
} catch (error) {
if (requestID !== requestSequence.current) return
setResults([])
Message.error(error instanceof Error ? error.message : '搜索失败,请稍后重试')
} finally {
if (requestID === requestSequence.current) setLoading(false)
}
}
return {
query,
results,
loading,
searched,
onQueryChange: changeQuery,
onSearch: submitSearch,
}
}