refactor(desktop): simplify all-projects dashboard
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
- Implementation screenshot: `test-results/app-home.png`
|
- Implementation screenshot: `test-results/app-home.png`
|
||||||
- Viewport: 420 × 820 CSS px at device scale factor 1; implementation image is 420 × 820 px.
|
- Viewport: 420 × 820 CSS px at device scale factor 1; implementation image is 420 × 820 px.
|
||||||
- Source image: 144 × 343 px and only shows the compact icon-navigation language. It does not define the requested all-projects dashboard.
|
- Source image: 144 × 343 px and only shows the compact icon-navigation language. It does not define the requested all-projects dashboard.
|
||||||
- State: signed-in light theme, “工作台 / 全部项目” selected, one pending task, one completed task, one latest file.
|
- State: signed-in light theme, “工作台 / 全部项目” selected, one pending task and one latest file. The content header is “全部项目” with its creation action aligned on the right.
|
||||||
- Primary interactions tested: default all-projects homepage, task editor modal, explore view, project picker, project-only right navigation, document panel, AI panel, and profile dropdown. Console errors: none.
|
- Primary interactions tested: default all-projects homepage, task editor modal, explore view, project picker, project-only right navigation, document panel, AI panel, and profile dropdown. Console errors: none.
|
||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
@@ -24,11 +24,11 @@ No actionable P0, P1, or P2 findings.
|
|||||||
- Spacing and layout rhythm: the all-projects page fills the former rail width after navigation is hidden. At 420 px, the header, tabs, statistics, actions, task lists, and latest files remain within the viewport with no horizontal overflow.
|
- Spacing and layout rhythm: the all-projects page fills the former rail width after navigation is hidden. At 420 px, the header, tabs, statistics, actions, task lists, and latest files remain within the viewport with no horizontal overflow.
|
||||||
- Colors and visual tokens: workbench/explore selection uses the existing blue token, while statistic colors distinguish projects, pending work, files, and plan tasks without overpowering the task list.
|
- Colors and visual tokens: workbench/explore selection uses the existing blue token, while statistic colors distinguish projects, pending work, files, and plan tasks without overpowering the task list.
|
||||||
- Image and icon fidelity: the existing product mark is retained and all controls use the installed Arco icon set; no placeholder or hand-drawn image assets were added.
|
- Image and icon fidelity: the existing product mark is retained and all controls use the installed Arco icon set; no placeholder or hand-drawn image assets were added.
|
||||||
- Copy and content: “工作台”, “探索”, and “全部项目” make scope explicit; the project-only menu contains 概况、计划、资料、AI、计划任务 and no longer exposes 收集箱.
|
- Copy and content: “工作台”, “探索”, and “全部项目” make scope explicit; its homepage shows only pending tasks and latest files. The project-only menu contains 概况、计划、资料、AI、计划任务 and no longer exposes 收集箱.
|
||||||
|
|
||||||
## Comparison history
|
## Comparison history
|
||||||
|
|
||||||
- Current pass: changed the all-projects scope from a flat merged task list to a dedicated dashboard and hid the right rail for that scope. Re-captured `test-results/app-home.png` and verified the requested dashboard sections and task/edit interactions.
|
- Current pass: removed the completed-task section and upload action from the all-projects homepage, then removed the content-level “工作台” label and moved 创建项目 beside 全部项目. Re-captured `test-results/app-home.png` and verified that only pending tasks and latest files remain.
|
||||||
|
|
||||||
## Follow-up polish
|
## Follow-up polish
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ No actionable P0, P1, or P2 findings.
|
|||||||
|
|
||||||
- [x] Replace the project strip with workbench, explore, and current-project controls.
|
- [x] Replace the project strip with workbench, explore, and current-project controls.
|
||||||
- [x] Make all-projects the default selection.
|
- [x] Make all-projects the default selection.
|
||||||
- [x] Add all-project statistics, pending tasks, project creation, targeted file upload, and latest files.
|
- [x] Add all-project statistics, pending tasks, project creation, and latest files.
|
||||||
- [x] Hide right navigation for all-projects and explore views.
|
- [x] Hide right navigation for all-projects and explore views.
|
||||||
- [x] Remove 收集箱 and rename 周期计划 to 计划任务.
|
- [x] Remove 收集箱 and rename 周期计划 to 计划任务.
|
||||||
- [x] Verify key interaction paths and responsive composition.
|
- [x] Verify key interaction paths and responsive composition.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ if (home.width !== 420) failures.push(`expected 420px shell, got ${home.width}`)
|
|||||||
if (home.selectedAll !== '全部项目') failures.push(`default project scope must be 全部项目, got ${home.selectedAll}`)
|
if (home.selectedAll !== '全部项目') failures.push(`default project scope must be 全部项目, got ${home.selectedAll}`)
|
||||||
if (home.navCount !== 0) failures.push(`all-projects view must hide the right rail, got ${home.navCount}`)
|
if (home.navCount !== 0) failures.push(`all-projects view must hide the right rail, got ${home.navCount}`)
|
||||||
if (home.statCount !== 4) failures.push(`expected four summary cards, got ${home.statCount}`)
|
if (home.statCount !== 4) failures.push(`expected four summary cards, got ${home.statCount}`)
|
||||||
if (home.pending !== 1 || home.completed !== 1) failures.push(`task sections incorrect: ${JSON.stringify(home)}`)
|
if (home.pending !== 1 || home.completed !== 0) failures.push(`all-project task sections incorrect: ${JSON.stringify(home)}`)
|
||||||
if (taskModal !== 1) failures.push('task editor did not open')
|
if (taskModal !== 1) failures.push('task editor did not open')
|
||||||
if (exploreProjects !== 1) failures.push(`expected one project in explore, got ${exploreProjects}`)
|
if (exploreProjects !== 1) failures.push(`expected one project in explore, got ${exploreProjects}`)
|
||||||
if (documents !== 1) failures.push(`expected one document, got ${documents}`)
|
if (documents !== 1) failures.push(`expected one document, got ${documents}`)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Alert, Avatar, Button, Dropdown, Empty, Form, Input, Menu, Message, Modal, Select, Spin, Switch, Tag, Typography } from '@arco-design/web-react'
|
import { Alert, Avatar, Button, Dropdown, Empty, Form, Input, Menu, Modal, Select, Spin, Switch, Tag, Typography } from '@arco-design/web-react'
|
||||||
import {
|
import {
|
||||||
IconApps, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
IconApps, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
||||||
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRight, IconRobot,
|
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRight, IconRobot,
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ApiError, createProject, fetchProjectWorkspace, fetchProjects, getCurrentUser,
|
ApiError, createProject, fetchProjectWorkspace, fetchProjects, getCurrentUser,
|
||||||
listAIExperts, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
listAIExperts, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
||||||
uploadDocument,
|
|
||||||
type ApiSession, type CurrentUser, type Expert, type Project, type Workspace, type WorkspaceTask,
|
type ApiSession, type CurrentUser, type Expert, type Project, type Workspace, type WorkspaceTask,
|
||||||
} from './api'
|
} from './api'
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ export function App() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{error ? <Alert className="app-alert" type="error" content={error} closable onClose={() => setError('')} /> : null}
|
{error ? <Alert className="app-alert" type="error" content={error} closable onClose={() => setError('')} /> : null}
|
||||||
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel projects={projects} onOpen={(id) => { setProjectId(id); setWorkspaceMode('workbench') }} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} onUpload={async (project, file) => { await uploadDocument(session, project, file); await loadWorkspaces() }} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel projects={projects} onOpen={(id) => { setProjectId(id); setWorkspaceMode('workbench') }} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
||||||
|
|
||||||
{projectId && workspaceMode === 'workbench' ? <aside className="channel-rail" aria-label="项目子菜单">
|
{projectId && workspaceMode === 'workbench' ? <aside className="channel-rail" aria-label="项目子菜单">
|
||||||
{navItems.map((item) => <button key={item.id} title={item.label} className={view === item.id ? 'active' : ''} onClick={() => { setView(item.id); if (item.id === 'ai') void loadAI() }}>{item.icon}<span>{item.label}</span></button>)}
|
{navItems.map((item) => <button key={item.id} title={item.label} className={view === item.id ? 'active' : ''} onClick={() => { setView(item.id); if (item.id === 'ai') void loadAI() }}>{item.icon}<span>{item.label}</span></button>)}
|
||||||
@@ -149,17 +148,17 @@ function Content({ view, title, workspaces, tasks, onEditTask, onLoadAI, experts
|
|||||||
return <CronPanel title={title} workspaces={workspaces} />
|
return <CronPanel title={title} workspaces={workspaces} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaskBoard({ title, subtitle, tasks, onEditTask }: { title: string; subtitle: string; tasks: TaskWithProject[]; onEditTask: (task: TaskWithProject) => void }) {
|
function TaskBoard({ title, subtitle, tasks, onEditTask, showCompleted = true }: { title: string; subtitle: string; tasks: TaskWithProject[]; onEditTask: (task: TaskWithProject) => void; showCompleted?: boolean }) {
|
||||||
const [tag, setTag] = useState('全部')
|
const [tag, setTag] = useState('全部')
|
||||||
const tags = [...new Set(tasks.map((task) => task.tag).filter(Boolean))]
|
const tags = [...new Set(tasks.map((task) => task.tag).filter(Boolean))]
|
||||||
const visible = tag === '全部' ? tasks : tasks.filter((task) => task.tag === tag)
|
const visible = tag === '全部' ? tasks : tasks.filter((task) => task.tag === tag)
|
||||||
const pending = visible.filter((task) => !task.completed)
|
const pending = visible.filter((task) => !task.completed)
|
||||||
const completed = visible.filter((task) => task.completed)
|
const completed = visible.filter((task) => task.completed)
|
||||||
return <section className="content-page task-page">
|
return <section className="content-page task-page">
|
||||||
<div className="content-heading"><div><Text type="secondary">{subtitle}</Text><Title heading={4}>{title}</Title></div><Tag color="arcoblue">{pending.length} 项待办</Tag></div>
|
<div className="content-heading"><div>{subtitle ? <Text type="secondary">{subtitle}</Text> : null}<Title heading={4}>{title}</Title></div><Tag color="arcoblue">{pending.length} 项待办</Tag></div>
|
||||||
<div className="tag-filter"><button className={tag === '全部' ? 'active' : ''} onClick={() => setTag('全部')}>全部</button>{tags.map((item) => <button key={item} className={tag === item ? 'active' : ''} onClick={() => setTag(item)}>{item}</button>)}</div>
|
<div className="tag-filter"><button className={tag === '全部' ? 'active' : ''} onClick={() => setTag('全部')}>全部</button>{tags.map((item) => <button key={item} className={tag === item ? 'active' : ''} onClick={() => setTag(item)}>{item}</button>)}</div>
|
||||||
<section className="task-section"><div className="section-label"><strong>待办任务</strong><span>{pending.length}</span></div><div className="task-list">{pending.map((task) => <TaskRow key={task.id} task={task} onClick={() => onEditTask(task)} />)}{!pending.length && <Empty description="当前没有待办任务" />}</div></section>
|
<section className="task-section"><div className="section-label"><strong>待办任务</strong><span>{pending.length}</span></div><div className="task-list">{pending.map((task) => <TaskRow key={task.id} task={task} onClick={() => onEditTask(task)} />)}{!pending.length && <Empty description="当前没有待办任务" />}</div></section>
|
||||||
<section className="task-section completed-section"><div className="section-label"><strong>已完成</strong><span>{completed.length}</span></div><div className="task-list">{completed.map((task) => <TaskRow key={task.id} task={task} onClick={() => onEditTask(task)} />)}{!completed.length && <Empty description="尚未完成任何任务" />}</div></section>
|
{showCompleted ? <section className="task-section completed-section"><div className="section-label"><strong>已完成</strong><span>{completed.length}</span></div><div className="task-list">{completed.map((task) => <TaskRow key={task.id} task={task} onClick={() => onEditTask(task)} />)}{!completed.length && <Empty description="尚未完成任何任务" />}</div></section> : null}
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,18 +167,14 @@ function TaskRow({ task, onClick }: { task: TaskWithProject; onClick: () => void
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function AllProjectsHome({ projects, workspaces, tasks, onEditTask, onCreateProject, onUpload }: { projects: Project[]; workspaces: Workspace[]; tasks: TaskWithProject[]; onEditTask: (task: TaskWithProject) => void; onCreateProject: () => void; onUpload: (projectId: string, file: File) => Promise<void> }) {
|
function AllProjectsHome({ projects, workspaces, tasks, onEditTask, onCreateProject }: { projects: Project[]; workspaces: Workspace[]; tasks: TaskWithProject[]; onEditTask: (task: TaskWithProject) => void; onCreateProject: () => void }) {
|
||||||
const fileInput = useRef<HTMLInputElement>(null)
|
|
||||||
const [uploadProject, setUploadProject] = useState(projects[0]?.id ?? '')
|
|
||||||
const [uploading, setUploading] = useState(false)
|
|
||||||
useEffect(() => { if (!projects.some((project) => project.id === uploadProject)) setUploadProject(projects[0]?.id ?? '') }, [projects, uploadProject])
|
|
||||||
const pending = tasks.filter((task) => !task.completed)
|
const pending = tasks.filter((task) => !task.completed)
|
||||||
const latestFiles = workspaces.flatMap((workspace) => workspace.documents.map((document) => ({ ...document, projectName: workspace.project.name }))).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, 6)
|
const latestFiles = workspaces.flatMap((workspace) => workspace.documents.map((document) => ({ ...document, projectName: workspace.project.name }))).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, 6)
|
||||||
const statItems = [
|
const statItems = [
|
||||||
{ label: '项目', value: projects.length, color: 'blue' }, { label: '待办', value: pending.length, color: 'orange' },
|
{ label: '项目', value: projects.length, color: 'blue' }, { label: '待办', value: pending.length, color: 'orange' },
|
||||||
{ label: '文件', value: latestFiles.length, color: 'green' }, { label: '计划任务', value: workspaces.reduce((total, workspace) => total + workspace.cronPlans.length, 0), color: 'purple' },
|
{ label: '文件', value: latestFiles.length, color: 'green' }, { label: '计划任务', value: workspaces.reduce((total, workspace) => total + workspace.cronPlans.length, 0), color: 'purple' },
|
||||||
]
|
]
|
||||||
return <section className="content-page all-projects-home"><div className="content-heading"><div><Text type="secondary">工作台</Text><Title heading={4}>全部项目</Title></div></div><div className="stat-grid">{statItems.map((item) => <div className={`stat-card ${item.color}`} key={item.label}><strong>{item.value}</strong><span>{item.label}</span></div>)}</div><div className="quick-workspace-actions"><Button type="primary" icon={<IconPlus />} onClick={onCreateProject}>创建项目</Button><Button icon={<IconFile />} disabled={!uploadProject} loading={uploading} onClick={() => fileInput.current?.click()}>上传文件</Button><Select value={uploadProject || undefined} placeholder="选择上传项目" onChange={setUploadProject}>{projects.map((project) => <Select.Option key={project.id} value={project.id}>{project.name}</Select.Option>)}</Select><input ref={fileInput} className="hidden-file" type="file" onChange={async (event) => { const file = event.target.files?.[0]; if (!file || !uploadProject) return; setUploading(true); try { await onUpload(uploadProject, file); Message.success('文件已上传') } catch (error) { Message.error(errorMessage(error)) } finally { event.target.value = ''; setUploading(false) } }} /></div><TaskBoard title="待办任务" subtitle="全部项目" tasks={tasks} onEditTask={onEditTask} /><section className="latest-files"><div className="section-label"><strong>最新文件</strong><span>{latestFiles.length}</span></div><div className="detail-list">{latestFiles.map((file) => <article className="detail-row" key={file.id}><IconFile /><div><strong>{file.name}</strong><p>{file.extension || file.mimeType || file.kind} · {formatDate(file.updatedAt)}</p></div><Tag>{file.projectName}</Tag></article>)}{!latestFiles.length && <Empty description="暂无文件,上传一份项目资料开始吧" />}</div></section></section>
|
return <section className="content-page all-projects-home"><div className="content-heading"><div><Title heading={4}>全部项目</Title></div><Button type="primary" icon={<IconPlus />} onClick={onCreateProject}>创建项目</Button></div><div className="stat-grid">{statItems.map((item) => <div className={`stat-card ${item.color}`} key={item.label}><strong>{item.value}</strong><span>{item.label}</span></div>)}</div><TaskBoard title="待办任务" subtitle="" tasks={tasks} onEditTask={onEditTask} showCompleted={false} /><section className="latest-files"><div className="section-label"><strong>最新文件</strong><span>{latestFiles.length}</span></div><div className="detail-list">{latestFiles.map((file) => <article className="detail-row" key={file.id}><IconFile /><div><strong>{file.name}</strong><p>{file.extension || file.mimeType || file.kind} · {formatDate(file.updatedAt)}</p></div><Tag>{file.projectName}</Tag></article>)}{!latestFiles.length && <Empty description="暂无文件" />}</div></section></section>
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExplorePanel({ projects, onOpen }: { projects: Project[]; onOpen: (id: string) => void }) {
|
function ExplorePanel({ projects, onOpen }: { projects: Project[]; onOpen: (id: string) => void }) {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ button { font: inherit; }
|
|||||||
.expert-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
|
.expert-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||||
.expert-chips span { display: inline-flex; align-items: center; gap: 4px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font-size: 11px; }.expert-chips i { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 50%; font-style: normal; }
|
.expert-chips span { display: inline-flex; align-items: center; gap: 4px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font-size: 11px; }.expert-chips i { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 50%; font-style: normal; }
|
||||||
.stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 13px; }.stat-card { display: grid; gap: 3px; padding: 11px; border: 1px solid var(--line); border-radius: 11px; background: var(--surface); }.stat-card strong { font-size: 22px; line-height: 1; }.stat-card span { color: var(--muted); font-size: 11px; }.stat-card.blue strong { color: #165dff; }.stat-card.orange strong { color: #ff7d00; }.stat-card.green strong { color: #00b42a; }.stat-card.purple strong { color: #722ed1; }
|
.stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 13px; }.stat-card { display: grid; gap: 3px; padding: 11px; border: 1px solid var(--line); border-radius: 11px; background: var(--surface); }.stat-card strong { font-size: 22px; line-height: 1; }.stat-card span { color: var(--muted); font-size: 11px; }.stat-card.blue strong { color: #165dff; }.stat-card.orange strong { color: #ff7d00; }.stat-card.green strong { color: #00b42a; }.stat-card.purple strong { color: #722ed1; }
|
||||||
.quick-workspace-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 18px; }.quick-workspace-actions .arco-select { grid-column: 1 / -1; width: 100%; }.hidden-file { display: none; }.all-projects-home .task-page { margin-top: 4px; }.latest-files { padding: 13px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }.project-card-grid { display: grid; gap: 9px; }.project-explore-card { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 10px; width: 100%; padding: 13px; border: 1px solid var(--line); border-radius: 11px; color: var(--text); background: var(--surface); text-align: left; cursor: pointer; }.project-explore-card:hover { border-color: #9fc9ff; background: #f7faff; }.project-explore-card > i { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: #fff; font-style: normal; font-weight: 700; font-size: 11px; }.project-explore-card span { display: grid; min-width: 0; gap: 3px; }.project-explore-card strong, .project-explore-card small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.project-explore-card strong { font-size: 13px; }.project-explore-card small { color: var(--muted); font-size: 11px; }
|
.quick-workspace-actions { display: flex; gap: 8px; margin-bottom: 18px; }.all-projects-home .task-page { margin-top: 4px; }.latest-files { padding: 13px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }.project-card-grid { display: grid; gap: 9px; }.project-explore-card { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 10px; width: 100%; padding: 13px; border: 1px solid var(--line); border-radius: 11px; color: var(--text); background: var(--surface); text-align: left; cursor: pointer; }.project-explore-card:hover { border-color: #9fc9ff; background: #f7faff; }.project-explore-card > i { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: #fff; font-style: normal; font-weight: 700; font-size: 11px; }.project-explore-card span { display: grid; min-width: 0; gap: 3px; }.project-explore-card strong, .project-explore-card small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.project-explore-card strong { font-size: 13px; }.project-explore-card small { color: var(--muted); font-size: 11px; }
|
||||||
|
|
||||||
.login-page { display: grid; min-height: 100vh; place-items: center; padding: 20px; background: #edf2f9; }.login-card { display: grid; width: min(100%, 360px); gap: 12px; padding: 28px; border: 1px solid #e5e6eb; border-radius: 16px; background: #fff; box-shadow: 0 18px 48px rgba(31, 35, 41, .12); }.login-card > img { width: 34px; }.login-card .arco-typography { margin: 0; }.login-card label { display: grid; gap: 5px; color: #4e5969; font-size: 12px; }
|
.login-page { display: grid; min-height: 100vh; place-items: center; padding: 20px; background: #edf2f9; }.login-card { display: grid; width: min(100%, 360px); gap: 12px; padding: 28px; border: 1px solid #e5e6eb; border-radius: 16px; background: #fff; box-shadow: 0 18px 48px rgba(31, 35, 41, .12); }.login-card > img { width: 34px; }.login-card .arco-typography { margin: 0; }.login-card label { display: grid; gap: 5px; color: #4e5969; font-size: 12px; }
|
||||||
.theme-dark .project-strip, .theme-dark .app-header, .theme-dark .task-section, .theme-dark .detail-row, .theme-dark .tag-filter button { color: var(--text); background: var(--surface); }.theme-dark .project-pill { color: var(--muted); }.theme-dark .project-pill.active, .theme-dark .channel-rail button.active { color: #8ac7ff; background: #1d4d77; }.theme-dark .task-row:hover { background: #293847; border-color: #477da7; }.theme-dark .section-label span { color: #8ac7ff; background: #1d4d77; }
|
.theme-dark .project-strip, .theme-dark .app-header, .theme-dark .task-section, .theme-dark .detail-row, .theme-dark .tag-filter button { color: var(--text); background: var(--surface); }.theme-dark .project-pill { color: var(--muted); }.theme-dark .project-pill.active, .theme-dark .channel-rail button.active { color: #8ac7ff; background: #1d4d77; }.theme-dark .task-row:hover { background: #293847; border-color: #477da7; }.theme-dark .section-label span { color: #8ac7ff; background: #1d4d77; }
|
||||||
|
|||||||
Reference in New Issue
Block a user