修复: 前端UX一致性(Dashboard状态映射/状态徽章全局/Knowledge下一条与失败反馈/Ideas筛选与错误条/ConfirmDialog安全/快捷菜单状态机/分页越界) + 后端校验(queue/count-list一致/软删拒改/父聚合/promote CAS/MCP状态机收口/update白名单剔除id/created_at) + 销账
This commit is contained in:
@@ -42,6 +42,9 @@ function onKey(e: KeyboardEvent) {
|
||||
// Enter 确认:危险操作(删除)仍走 confirm 分支,父级二次语义由 dangerLabel 区分;
|
||||
// 避免在 input/textarea 内回车误触发(SVG/按钮 mask 场景无输入控件,守卫保留)。
|
||||
const target = e.target as HTMLElement
|
||||
// LW-1(安全):焦点在按钮上按 Enter 直接 return —— 否则「焦点在取消按钮按 Enter」
|
||||
// 会先触发 cancel 按钮 click(emit false)再被此处 emit true 覆盖,点取消=执行删除。
|
||||
if (target && target.tagName === 'BUTTON') return
|
||||
if (target && (target.tagName === 'TEXTAREA' || (target.tagName === 'INPUT' && (target as HTMLInputElement).type !== 'submit' && (target as HTMLInputElement).type !== 'button'))) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,16 +58,16 @@ function getProjectTaskCount(projectId: string): number {
|
||||
return store.projectTaskCounts[projectId] ?? 0
|
||||
}
|
||||
|
||||
// 阶段 label key:projectStageInfo 返回的 stage 值(coding/testing/release/planning)
|
||||
// 阶段 label key:projectStageInfo 返回的 stage 值(coding/testing/release/planning/paused/cancelled)
|
||||
// 直接拼接 i18n key dashboard.stage.<stage>,与 constants/project.ts PROJECT_STAGE_INFO 单一来源。
|
||||
// 面板名为"活跃项目",只显示 active 状态(DB 实际默认值,ProjectStatus union 历史遗留未含
|
||||
// 'active',此处断言绕过;DB 实际无 planning/in_progress 等值产生,详见功能决策记录)。
|
||||
// 面板名为"活跃项目",按真实生命周期过滤:排除已完结/已取消(后端状态机无 'active',原
|
||||
// filter status==='active' 恒空,见走查 DBP-1)。
|
||||
//
|
||||
// 问题3:按「最近活跃」排序(last_active_at 优先,回退 updated_at),反映业务活跃
|
||||
// (任务/灵感/状态推进事件)而非元信息修改时间。取前 6 条(completed 归档项目不混入)。
|
||||
const displayProjects = computed(() =>
|
||||
store.projects
|
||||
.filter(p => (p.status as string) === 'active')
|
||||
.filter(p => p.status !== 'completed' && p.status !== 'cancelled')
|
||||
.sort((a, b) =>
|
||||
(b.last_active_at ?? b.updated_at).localeCompare(a.last_active_at ?? a.updated_at)
|
||||
)
|
||||
@@ -114,6 +114,8 @@ const displayProjects = computed(() =>
|
||||
.dot-release { background: var(--df-success); }
|
||||
.dot-planning { background: var(--df-info); }
|
||||
.dot-cancelled { background: var(--df-text-dim); }
|
||||
/* WK-1:paused 置灰(对齐 cancelled dim 档,临时暂停不误标为已取消) */
|
||||
.dot-paused { background: var(--df-text-dim); }
|
||||
|
||||
.project-name {
|
||||
font-size: 12px;
|
||||
@@ -135,6 +137,7 @@ const displayProjects = computed(() =>
|
||||
.chip-release { background: rgba(61,219,160,0.12); color: var(--df-success); }
|
||||
.chip-planning { background: rgba(94,175,240,0.12); color: var(--df-info); }
|
||||
.chip-cancelled { background: rgba(255,255,255,0.06); color: var(--df-text-dim); }
|
||||
.chip-paused { background: rgba(255,255,255,0.06); color: var(--df-text-dim); }
|
||||
|
||||
.project-bar-wrap { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
|
||||
.project-bar {
|
||||
@@ -154,6 +157,7 @@ const displayProjects = computed(() =>
|
||||
.fill-release { background: var(--df-success); }
|
||||
.fill-planning { background: var(--df-info); }
|
||||
.fill-cancelled { background: var(--df-text-dim); }
|
||||
.fill-paused { background: var(--df-text-dim); }
|
||||
|
||||
.project-pct {
|
||||
font-family: var(--df-font-mono);
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
</button>
|
||||
<ul v-show="statusMenuOpen" class="status-menu">
|
||||
<li
|
||||
v-for="s in statusOptions"
|
||||
v-for="s in menuStatusOptions"
|
||||
:key="s.value"
|
||||
class="status-menu-item"
|
||||
:class="{ active: s.value === idea.status }"
|
||||
@@ -391,6 +391,9 @@ watch(() => props.idea.description, () => void nextTick(measureDesc))
|
||||
// P1-④ 状态切换 select → badge 点击:点击当前 badge 展开/收起下拉菜单,
|
||||
// 选中项后 emit('status-change') 交父组件确认+落库(沿用既有契约,不破坏 statusOptions 来源)。
|
||||
const statusMenuOpen = ref(false)
|
||||
// WK-5:状态下拉排除 promoted(立项走独立 promoteToProject 流程,列表状态菜单同排除,
|
||||
// 见 Ideas.vue onStatusChange 兜底);statusOptions 仍含 promoted 供 label 映射(已转立项的灵感 badge 正常显示)。
|
||||
const menuStatusOptions = computed(() => props.statusOptions.filter(s => s.value !== 'promoted'))
|
||||
function closeStatusMenu() {
|
||||
statusMenuOpen.value = false
|
||||
}
|
||||
|
||||
@@ -106,10 +106,12 @@ const resizing = ref(false)
|
||||
// maximized=全宽(flex:1填充) | sidebar=固定宽
|
||||
const panelStyle = computed(() => {
|
||||
if (aiStore.state.maximized) return { flex: '1', minWidth: '0' }
|
||||
// LW-11:内联 width 用 min(panelWidth, 100vw) —— 小屏 media query 的 width:100vw 会被内联样式
|
||||
// 更高优先级覆盖致失效;min() 让窄视口下宽度钳制到 100vw,media query 的 absolute 定位/层级仍生效
|
||||
return {
|
||||
width: panelWidth.value + 'px',
|
||||
minWidth: panelWidth.value + 'px',
|
||||
maxWidth: panelWidth.value + 'px',
|
||||
width: `min(${panelWidth.value}px, 100vw)`,
|
||||
minWidth: `min(${panelWidth.value}px, 100vw)`,
|
||||
maxWidth: `min(${panelWidth.value}px, 100vw)`,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -92,15 +92,16 @@ const appSettings = useAppSettingsStore()
|
||||
// 展开 192px(文字+图标) / 收缩 56px(仅图标,hover tooltip 显示文字)
|
||||
// 优先级:用户手动设置 > 小窗口自动收缩(≤768px)
|
||||
const COLLAPSE_THRESHOLD = 768
|
||||
const userCollapsed = ref(false) // 用户手动设置的收缩状态
|
||||
// LW-10:useSetting 响应式读(loadAll 异步填充缓存后 watch 自动同步刷新)——
|
||||
// 原 onMounted 用 appSettings.get 同步读早于父级 loadAll 完成,重启后偏好丢失
|
||||
const userCollapsed = appSettings.useSetting('df-sidebar-collapsed', false)
|
||||
const isNarrowWindow = ref(false) // 小窗口自动收缩标记
|
||||
|
||||
const collapsed = computed(() => userCollapsed.value || isNarrowWindow.value)
|
||||
|
||||
// 持久化用户手动收缩状态
|
||||
// 持久化用户手动收缩状态(useSetting 写 ref 自动 debounce 落库,无需显式 appSettings.set)
|
||||
function toggleCollapsed() {
|
||||
userCollapsed.value = !userCollapsed.value
|
||||
void appSettings.set('df-sidebar-collapsed', userCollapsed.value)
|
||||
}
|
||||
|
||||
// 监听窗口宽度:小窗口自动收缩(但不改变用户偏好)
|
||||
@@ -118,8 +119,7 @@ function handleCollapseKeydown(e: KeyboardEvent) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 恢复用户偏好(默认展开)
|
||||
userCollapsed.value = appSettings.get('df-sidebar-collapsed', false)
|
||||
// LW-10:收缩偏好由 useSetting 响应式恢复(loadAll 填充缓存后 watch 自动同步),移除 onMounted 同步读
|
||||
// 检查当前窗口宽度
|
||||
checkWindowWidth()
|
||||
window.addEventListener('resize', checkWindowWidth)
|
||||
|
||||
@@ -136,6 +136,8 @@ watch(() => props.view, () => nextTick(measureDesc))
|
||||
.stage-coding { background: var(--df-accent-soft); color: var(--df-accent); }
|
||||
.stage-testing { background: rgba(255,217,61,0.15); color: var(--df-warning); }
|
||||
.stage-release { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
/* WK-1:暂停/取消置灰(PROJECT_STATUS_BADGE_CLASS 映射) */
|
||||
.stage-dim { background: rgba(158,158,158,0.15); color: #9e9e9e; }
|
||||
|
||||
.card-desc-wrap {
|
||||
position: relative;
|
||||
|
||||
Reference in New Issue
Block a user