news 2026/9/13 8:01:46

WeKan 列表(Lists)机制详解:看板列的作用域、归档删除与共享/个人宽度系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WeKan 列表(Lists)机制详解:看板列的作用域、归档删除与共享/个人宽度系统

WeKan 列表(Lists)机制详解:看板列的作用域、归档删除与共享/个人宽度系统

【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan

本文以 WeKan 官方功能文档 Lists 为主体,完整覆盖列表(List)作为看板列的核心概念、跨泳道共享规则、添加/归档/删除操作流程,以及列表宽度系统的共享模式与个人模式;并结合 models/lists.js、models/lib/listWidth.js、client/components/lists/list.js 等源码,说明宽度解析链路、权限控制与软删除实现,帮助你在实际运维和二次开发中准确理解 WeKan 列表的数据模型与渲染规则。

列表是什么:看板的列,且全看板共享

列表(Lists)就是看板的列(columns),卡片随着工作推进在列表之间移动。WeKan 中列表最重要的作用域规则是:列表属于整个看板,而不是某一条泳道

具体含义:

  • 同一个列表会出现在看板的每一条泳道(swimlane)中——所有泳道共享同一组列;
  • 一张卡片同时属于一个列表和一个泳道(泳道是它的水平带状区域);
  • WeKan **没有“每个泳道各自一套列表”**的功能。历史上曾实验过让每条泳道拥有独立列表的方案(issue #4049),但由于该方案会在每条泳道重复列、并导致卡片意外移动,最终被回退;
  • 兜底修复机制:如果某个列表因异常数据被绑定到了单条泳道(从而从其他泳道中消失),看板打开时的修复逻辑会把它重新共享到所有泳道,相关说明见 Repairs。

从源码结构看,这一“全看板共享”特性对应列表文档中的swimlaneId字段:在 models/lists.js 的 schema 中,swimlaneId是可选字段(defaultValue: ''),空值即表示该列表为看板级列表。同文件中cards(swimlaneId)等 helper(见 models/lists.js)展示了列表在给定泳道上下文中如何筛选卡片:不传泳道时返回整个列表的卡片,传入非首泳道时只返回该泳道的卡片,孤儿卡片(指向已删除泳道)统一浮现在第一条泳道。

添加、归档、恢复与删除列表

官方文档给出的四个操作:

  • 添加(Add):在看板侧边的列表输入区(list composer)新建列表;
  • 归档(Archive):把列表隐藏而不删除,归档后的列表可以恢复;
  • 恢复(Restore):把归档的列表重新显示出来;
  • 删除(Delete):永久删除列表。删除不可撤销——WeKan 有意设计了更多点击步骤来防止误删。

官方提示(Tip):日常应使用“归档”卡片/列表以便日后恢复。如果想快速删除大量卡片,可以把它们拖到一个新建的列表里,然后删除该列表。删除不可撤销——多余的操作步骤是刻意设计的。早期版本存在一个容易被误点的删除按钮,导致用户误删重要列表,后来被专门修复。

源码层面的对应实现(models/lists.js):

async archive() { // 模板列表(board template)归档时会级联归档其中所有卡片 if (this.isTemplateList()) { for (const card of await this.cards()) { await card.archive(); } } return await Lists.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); }, async restore() { // 模板列表恢复时级联恢复所有卡片 if (this.isTemplateList()) { for (const card of await this.allCards()) { await card.restore(); } } return await Lists.updateAsync(this._id, { $set: { archived: false } }); },

归档状态由两个字段承载:archived(布尔值,默认false,见 models/lists.js)和archivedAt(最近一次归档时间)。值得注意的区分是:archived是“可见的搁置状态”,有独立的归档界面;而“删除”则走软删除路径——schema 中的deletedAt/deletedBy/deleteBatchId字段(见 models/lists.js)标记被删除的列表,deleteBatchId把列表和随之删除的卡片归为一组,以便恢复时精确还原这一整组对象,这正是 WeKan 撤销/恢复能力(#1023)在列表上的落地方式。

列表宽度:两种调整方式

每个列表只有一个宽度值。官方文档给出两种修改途径:

  1. 拖拽:拖动列表右边缘的调宽手柄(resize handle);
  2. 输入:打开列表菜单 →Set width,输入以像素为单位的宽度。

关于宽度数值,文档原文写的是最小 270 px、默认 272 px。需要注意,当前仓库源码中的实际取值已更新(源码注释引用了 issue #6465/#6409 的改动),以源码为准:

常量位置当前值说明
DEFAULT_LIST_WIDTHmodels/lib/listWidth.js220 px所有未自定义列表的渲染默认宽度(由 272 收窄到 220,让屏幕能放下更多列)
MIN_LIST_WIDTHmodels/lib/listWidth.js200 px拖拽手柄与校验的最小宽度(随默认值一起下调)
schema 校验范围models/lists.js100–1000 pxlists.width超出范围会触发widthOutOfRange,schema 默认值同为 220

models/lib/listWidth.js是宽度逻辑的单一事实来源(single source of truth)。它存在的背景是 issue #5659:此前默认宽度分散在客户端列表组件、列表头、schema 和用户模型多处,取值互相矛盾(272/270 混用),导致同一看板上不同访问路径解析出不同宽度,公开看板的匿名访客表现最明显。该模块刻意写成不依赖 Meteor 的纯函数,可以在纯 Node 环境下单元测试(对应 tests/listWidthDefaults.test.cjs)。

宽度解析的核心函数resolveListWidth(models/lib/listWidth.js)定义了清晰的回退顺序:

function resolveListWidth(options) { const { fixedEnabled = false, fixedWidth = null, sharedWidth = null, personalMode = false, personalWidth = null, } = options || {}; if (fixedEnabled) { return normalizeListWidth(fixedWidth); // 1) 固定宽度模式:所有列表同一宽度 } const shared = normalizeListWidth(sharedWidth); if (!personalMode) { return shared; // 2) 共享模式:直接用 lists.width } return normalizeListWidth(personalWidth, shared); // 3) 个人模式:个人值 → 共享值 → 默认值 }

客户端入口在 client/components/lists/list.js 的effectiveListWidth(list):它按当前查看者收集“固定宽度、共享宽度list.width、个人模式开关、个人宽度”四项输入,然后交给resolveListWidth决定最终像素值——没有任何自定义时,永远返回DEFAULT_LIST_WIDTH,这保证了公开看板上所有列表默认同宽。

拖拽调宽的实现位于同一文件的initializeListResize(client/components/lists/list.js):手柄是.js-list-resize-handle元素,拖拽过程中实时设置--list-width/width等 CSS 变量,松手时以Math.max(minWidth, startWidth + deltaX)钳制最小值后调用saveListWidth持久化;该逻辑同时绑定了鼠标事件和原生touchstart/touchmove/touchend{ passive: false }),支持移动端拖拽调宽。折叠(collapsed)状态的列表按设计不渲染调宽手柄。

共享宽度 vs 个人宽度(#6409)

一个看板级设置控制“改宽度影响谁”。在看板侧边栏的“Show at all boards page”设置区,切换Personal list widths开关(对应模板 client/components/sidebar/sidebar.jade,i18n 文案见 imports/i18n/data/en.i18n.json 的personal-list-width键):

  • 关闭(默认)— 共享(Shared)
    • 宽度存储在列表文档本身的lists.width字段上,看板上所有人看到同一布局;
    • 只有具备写权限的成员才能修改宽度(只读/仅评论成员看不到调宽手柄)。源码中的判定函数canResizeList(client/components/lists/list.js)在共享模式下最终返回Utils.canModifyBoard()
    • 宽度随看板一起导出/导入迁移。
  • 开启 — 个人(Personal)
    • 每个用户保存自己的宽度(登录用户存在用户 profile,未登录访客存在浏览器 localStorage,键名为wekan-list-widths,见 client/components/lists/list.js);
    • 个人宽度未设置时,回退到共享宽度,再回退到默认值;
    • 个人模式下任何查看者都可以拖拽调宽(canResizeList直接返回true),因为改动只影响自己的视图。

该开关在看板文档上的字段是boards.allowsPersonalListWidth(models/boards.js),切换事件在 client/components/sidebar/sidebar.js 中处理;REST API 也暴露了这个字段(public/api/wekan.yml)。

共享模式下宽度的服务端写入走Meteor.call('applyListWidth', ...),由服务端再次校验看板成员身份(见 client/components/lists/list.js 与 server/models/users.js 中applyListWidth的实现)。

源码补充:宽度系统中还存在的两种“同宽”模式

从源码结构看,除了文档描述的共享/个人两种模式,当前代码还实现了两种“所有列表统一同宽”的模式,它们都位于effectiveListWidth的解析链路中,且优先级高于个人宽度

  • 查看者个人的固定宽度(#5729,isFixedListWidth):按查看者、按看板开启,让该查看者的所有列表渲染成同一宽度,并持久化到 profile 或 localStorage(wekan-fixed-list-width*键);
  • 看板级的固定宽度(#6680,boards.sameWidthForAllLists/sameWidthForAllListsValue,见 models/boards.js):由管理员在顶部栏设置,对所有查看者生效,覆盖任何成员的个人偏好;另有listWidthResizeLocked(models/boards.js)可在开启期间锁定所有人的拖拽调宽。

对应的端到端验证在 tests/playwright/specs/38-fixed-list-width.e2e.js。

自动宽度(Auto-width)

不指定固定宽度,也可以让列表按内容自适应(auto-width):打开列表菜单 →Set widthAuto list width开关。自动宽度作用于看板的所有列表,并且与固定宽度遵循同样的作用域规则

  • 共享模式下:它是看板级设置(字段boards.autoWidth,models/boards.js),由有写权限的成员修改,所有人看到同样的结果;
  • 个人模式下:它是按用户的(存储在各用户的profile.autoWidthBoards)。

判定逻辑见 client/components/lists/list.js:

function effectiveAutoWidth(boardId) { if (isPersonalListWidth(boardId)) { const user = ReactiveCache.getCurrentUser(); return !!(user && user.isAutoWidth(boardId)); // 个人模式:读个人 profile } const board = ReactiveCache.getBoard(boardId); return !!(board && board.autoWidth); // 共享模式:读看板字段 }

自动宽度开启时,固定宽度的输入框和拖拽手柄都会被隐藏(canResizeListeffectiveAutoWidth为真时直接返回false),避免两种宽度来源互相覆盖。

需要说明的历史变更:此前每个列表上“最小宽度 / 最大宽度”的像素选项已被移除——列表现在只有一个明确的宽度(固定或自动),能可靠地在刷新后持久保留。这一简化正是 #6409 引入的“单一宽度模型”,文件头部的注释(client/components/lists/list.js)对此有明确记录。

列表的数据模型速览

结合 models/lists.js 的 SimpleSchema,列表文档的核心字段:

字段类型/约束说明
titleString(必填)列表标题
boardIdString(必填)所属看板
swimlaneIdString,默认''所属泳道;空表示看板级列表
archived/archivedAtBoolean / Date归档状态与最近归档时间
deletedAt/deletedBy/deleteBatchIdDate / String / String软删除标记,deleteBatchId关联同批删除的卡片
sortNumber列表在看板中的排序
starredBoolean,默认false星标后置顶
wipLimit.{value,enabled,soft}对象WIP 限制(默认值 1、关闭、硬限制),详见 WIP Limits
colorString,可选命名列表色板色或自定义#rrggbb十六进制(#5514),校验逻辑见 models/lists.js
typeString,默认'list''list''template-list'(看板模板列表)
widthNumber,默认 220,范围 100–1000共享宽度,见上文
syncSource.{type,url,projectKey,...}对象,可选外部跟踪器(Jira/GitHub/GitLab/Gitea)同步来源元信息,由 server/listSync.js 的定时任务维护;凭据不存这里,而在服务端私有集合 models/listSyncCredentials.js
createdAt/updatedAt/modifiedAtDate时间戳,updatedAt在插入/更新/上插时自动刷新

相关文档

  • WIP Limits
  • Swimlanes
  • Archive and Delete
  • Repairs

【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:00:35

滑动窗口最大值问题:单调队列解法与工程实践

1. 问题背景与核心挑战 滑动窗口最大值问题(LeetCode 239题)是算法面试中的经典高频题目,考察对数据结构和滑动窗口技巧的综合运用能力。题目要求:给定一个整数数组nums和一个固定大小的窗口k,窗口从数组最左端滑动到最…

作者头像 李华
网站建设 2026/9/13 8:00:19

AI编程演进:从提示词到上下文工程的技术突破

1. 从提示词到上下文:AI程序员的技术演进图谱三年前,我们还在为ChatGPT设计"请用Python写一个冒泡排序"这样的基础提示词。去年,行业开始讨论如何通过上下文窗口注入代码规范、API文档和项目背景。而当我最近看到Claude-3轻松处理百…

作者头像 李华
网站建设 2026/9/13 7:59:22

数据中台架构设计与实施指南

1. 数据中台的本质与核心价值数据中台是企业数字化转型过程中形成的统一数据能力平台,它既不是单纯的技术架构,也不是简单的数据仓库升级版。我在2016年参与某零售集团数据中台建设时,最初团队对数据中台的理解就存在严重偏差——技术部门把它…

作者头像 李华
网站建设 2026/9/13 7:58:30

Python函数进阶:参数、装饰器与函数式编程详解

1. Python函数进阶概述在Python编程中,函数是最基础也是最重要的构建模块之一。第六章"函数进阶"将带领大家超越基础函数的定义和调用,深入探索Python函数的高级特性和实用技巧。作为有五年Python开发经验的工程师,我发现很多初学者…

作者头像 李华