feat(V0.2): 任务模块界面调整 —— 首屏/项目管理/底部安全距离
Docker Build and Push / build-image (push) Successful in 4m8s
Docker Build and Push / build-image (push) Successful in 4m8s
按 2026-10-24 评审的 5 条调整实现 (需求 T-VW-08~10 / T-PJ-11/12 / T-UI-11): 首屏 (T-VW-08/09): - /task 改为「上=最近项目条(3 个+更多入口), 下=当前项目任务管理」 - 完整项目列表迁至 /task/list, 从「更多项目」进入; /task/:pid 保留供深链 最近项目排序 (T-VW-10): - 卡片长按 300ms 拖动换位, 结果存设备端 prefs.recentOrder - 顺序 = 手动排序 > 最近访问(recentIds) > 列表序, 归档项目不展示 项目管理 (T-PJ-11/12): - 新增 ProjectManage: 成员 / 看板列 / 项目信息 三分区统一入口 - 项目信息含 改名 / 归档切换 / 删除(个人项目隐藏) / 退出(仅创建人) - 项目页头部只留「项目管理 + 新增」, 移除散置的成员/列设置按钮 底部安全距离 (T-UI-11): - 任务详情 / 人员选择 / 拷贝弹层 padding-bottom = 安全区 + 20px - 小屏下弹层 maxHeight + 内部滚动, 底部按钮完整可见 已验证: 97 文件语法 0 错误、相对导入 0 缺失
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { useState } from 'react'
|
||||
import { Dialog, Input, Toast } from 'antd-mobile'
|
||||
import { projectApi } from '../../api/task.js'
|
||||
import { useAuth } from '../../auth/AuthContext.jsx'
|
||||
import ColumnSettings from './ColumnSettings.jsx'
|
||||
import ProjectMembers from './ProjectMembers.jsx'
|
||||
|
||||
// 项目管理 (T-PJ-11/12): 成员 / 看板列 / 项目信息 三个分区的统一入口
|
||||
// onExited: 项目被删除/退出/归档后, 上层应离开该项目(回落个人默认项目)
|
||||
export default function ProjectManage({ project, onClose, onChanged, onExited }) {
|
||||
const { user } = useAuth()
|
||||
const [view, setView] = useState('main') // main | columns | members
|
||||
const [renaming, setRenaming] = useState(false)
|
||||
const [name, setName] = useState(project.name)
|
||||
|
||||
const members = project.members || []
|
||||
const isOwner = user?.id === project.ownerId
|
||||
const isPersonal = !!project.isPersonal
|
||||
|
||||
if (view === 'columns') {
|
||||
return (
|
||||
<ColumnSettings
|
||||
projectId={project.id}
|
||||
columns={project.columns || []}
|
||||
onClose={() => setView('main')}
|
||||
onSaved={() => { setView('main'); onChanged?.() }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (view === 'members') {
|
||||
return (
|
||||
<ProjectMembers
|
||||
project={project}
|
||||
onClose={() => setView('main')}
|
||||
onChanged={onChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
async function saveName() {
|
||||
const t = name.trim()
|
||||
if (!t) { Toast.show({ content: '项目名称不能为空' }); return }
|
||||
try {
|
||||
await projectApi.update(project.id, { name: t })
|
||||
Toast.show({ content: '已改名' })
|
||||
setRenaming(false)
|
||||
onChanged?.()
|
||||
} catch (e) {
|
||||
Toast.show({ content: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleArchive() {
|
||||
const toArchive = !project.archived
|
||||
const ok = await Dialog.confirm({
|
||||
content: toArchive ? '归档该项目?归档后从进行中列表移出,可在归档区找回。' : '取消归档该项目?',
|
||||
confirmText: toArchive ? '归档' : '取消归档',
|
||||
})
|
||||
if (!ok) return
|
||||
try {
|
||||
await projectApi.update(project.id, { archived: toArchive })
|
||||
Toast.show({ content: toArchive ? '已归档' : '已恢复' })
|
||||
if (toArchive) onExited?.()
|
||||
else onChanged?.()
|
||||
} catch (e) {
|
||||
Toast.show({ content: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
const ok = await Dialog.confirm({
|
||||
content: `删除项目「${project.name}」?其中的任务、看板列将一并删除,不可恢复。`,
|
||||
confirmText: '删除',
|
||||
})
|
||||
if (!ok) return
|
||||
try {
|
||||
await projectApi.remove(project.id)
|
||||
Toast.show({ content: '已删除' })
|
||||
onExited?.()
|
||||
} catch (e) {
|
||||
Toast.show({ content: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
async function quit() {
|
||||
const ok = await Dialog.confirm({
|
||||
content: '退出该项目?退出后将不再可见。',
|
||||
confirmText: '退出',
|
||||
})
|
||||
if (!ok) return
|
||||
try {
|
||||
await projectApi.removeMember(project.id, user.id)
|
||||
Toast.show({ content: '已退出' })
|
||||
onExited?.()
|
||||
} catch (e) {
|
||||
Toast.show({ content: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cs-page">
|
||||
<div className="navbar-sub">
|
||||
<span className="back" onClick={onClose}>‹</span>
|
||||
<span className="title">项目管理 · {project.name}</span>
|
||||
</div>
|
||||
|
||||
<div className="cs-body">
|
||||
<div className="set-hint" style={{ paddingTop: 12 }}>
|
||||
项目内所有成员权限均等、全读写;以下配置对全体成员即时生效。
|
||||
</div>
|
||||
|
||||
<div className="section-h" style={{ margin: '6px 14px 8px' }}>成员({members.length})</div>
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={() => setView('members')}>
|
||||
<div className="avatar">{(members[0]?.name || '?').slice(0, 1)}</div>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13.5 }}>
|
||||
{members.slice(0, 3).map((m) => m.name).join('、')}{members.length > 3 ? ` 等 ${members.length} 人` : ''}
|
||||
</span>
|
||||
<span className="link">管理 ›</span>
|
||||
</div>
|
||||
|
||||
<div className="section-h" style={{ margin: '14px 14px 8px' }}>看板列</div>
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={() => setView('columns')}>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13, color: '#999' }}>
|
||||
{(project.columns || []).map((c) => c.name).join(' · ')}
|
||||
</span>
|
||||
<span className="link">列设置 ›</span>
|
||||
</div>
|
||||
|
||||
<div className="section-h" style={{ margin: '14px 14px 8px' }}>项目信息</div>
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={() => { setName(project.name); setRenaming(true) }}>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13.5 }}>项目名称</span>
|
||||
<span style={{ fontSize: 12.5, color: '#999' }}>{project.name}</span>
|
||||
</div>
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={toggleArchive}>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13.5 }}>归档项目</span>
|
||||
<span className="link">{project.archived ? '取消归档' : '归档'}</span>
|
||||
</div>
|
||||
{!isPersonal && (
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={remove}>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13.5, color: '#ff3141' }}>删除项目</span>
|
||||
<span className="link danger">删除</span>
|
||||
</div>
|
||||
)}
|
||||
{isOwner && !isPersonal && (
|
||||
<div className="set-row" style={{ padding: '12px 14px' }} onClick={quit}>
|
||||
<span className="pn" style={{ flex: 1, fontSize: 13.5, color: '#ff3141' }}>退出项目</span>
|
||||
<span className="link danger">退出</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="set-hint">
|
||||
个人默认项目不可删除、不可退出(T-PJ-02);退出项目仅创建人可见(T-PJ-07)。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
visible={renaming}
|
||||
title="项目名称"
|
||||
content={
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Input placeholder="项目名称" value={name} onChange={setName} maxLength={20} />
|
||||
</div>
|
||||
}
|
||||
actions={[
|
||||
{ key: 'c', text: '取消', onClick: () => setRenaming(false) },
|
||||
{ key: 'ok', text: '保存', bold: true, onClick: saveName },
|
||||
]}
|
||||
onClose={() => setRenaming(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user