v25 第 25 批(9 条):名册分组不刷页 / 纪念日多人 / 首页等高 / 移出不回流 /
名册点击看详情拖动才排序 / 任务复选框与状态 / 财务标签多选 / 微信读书同步与统计 /
导航线上数据与图标
用户本轮 9 条需求(原话见 templates/ 与 app/ 内的逐条注释):
1. 名册:列表里给某人分组改为 AJAX 就地挪卡片(不再整页刷新)。
2. 纪念日:关联人改**多选**(复用 _person_picker,字段 person_ids[]);
自建纪念日的按钮与其它模块统一(改=蓝 .btn、删=黄 .btn-warn);
列表新增「关联人」列并给人名前加头像。
3. 首页:日程/任务两块**完全等高**(align-items:stretch + 固定总高 + flex 子项
min-height:0);修「0 件待办」——数字与右侧列表同源(原来各算各的)。
4. 🔴 名册里被移出的人过一阵又回来:根因是 PersonRoster::seedFromPersons()
**每次 Schema::ensure() 都无条件重灌**(线上实测每跑一次 +1 行)。
修:给它加自守卫(只有在 persons 还留着 group_id/sort_order 的**老库**才跑)。
5. 名册:点击=看详情、**只有拖 ⠿ 手柄**才是排序(原来整行既能点又能拖,语义混乱)。
6. 任务:所有任务前加复选框,任何位置点勾即完成;
状态语义照业界整理为 To Do / Doing / Done 并写出说明(用户要求参考别人的做法)。
7. 财务:类别标签改**多选非互斥**(再点一次取消该类别),上下两图 + 图例 + 表格
全部按同一个集合联动;高亮条刻意不给「合计」(多选下各类金额不能相加)。
8. 微信读书:新增**懒同步**(访问时顺手做,6h TTL,只补空字段不覆盖用户填的)、
封面图**拉下来存本站存储**(原来只存外链 → 看不到)、接官方 /readdata/detail 统计。
9. 导航:线上数据只搬了一半(5 板块 29 站 → 7 板块 67 站 + 67 图标)、
展示页头像不显示 / 搜索按钮错位 / 点登录 404。三个真根因:
· 相对地址在 /nav/ 下全 404 → 新增 Nav::abs();
· 搜索容器丢了 id="search"(CSS 是 #search button.submit 绝对定位);
· Nav::iconKey() 与 SimpleNav 的 icon_key() **算法不一致**(少一次 md5 归一化)
→ 搬来的图标文件名对不上;且 uploads/ 整目录 Require all denied。
新增两个**免登录公开出口** nav_icon.php / nav_avatar.php。
顺带修掉一个「数据损坏级」的真 bug(做第 4 条线上反证时发现的,之前一直没人注意):
app/Region.php 三处用中文标点当 trim/ltrim 的 charlist,而 PHP 的 charlist 是
**按字节**匹配的 —— 「。」= E3 80 **82** → 字节 82 进了可剥集合 →
「层」= E5 B1 **82** 被切掉尾字节 → 非法 UTF-8 → backfillAddress 的 UPDATE 报
`1366 Incorrect string value` → **地址拆分升级从上线起一直在失败被跳过**(每请求报一次)。
改成字符级 preg_replace('/…/u');线上实测 ensure() 现在无报错、待拆行数真的在下降。
验证战绩:
- e2e 1289/0(新增 Region 字节级用例等)
- 真机探针:选人 19/0 · 首页/导航 24/0 · 导航模块 34/0 · b18 暂存+确定 10/0 ·
第23批 52/0
- 桌面几何 428/0 · 移动端几何 680/0 · 交互 92/0 · 窄屏 24/0
- 老库升级 153/0(含 anniversary_people 迁移 + 「再跑 ensure() 名册行数不变」)
- 部署静态断言 180 OK / 0 FAIL · 反向验证 3/3 + Region 三变体各被两条断言同时抓住
- 线上 26 个文件与本地**逐字节一致**;展示页 200、图标 66/66 200、登录 200、
越权/不存在均 404、签发短语后 ensure() 不再报错。
This commit is contained in:
+180
-14
@@ -225,6 +225,21 @@ a:hover { text-decoration: underline; }
|
||||
/* ⚠️ 上面**只写 .card + .card**:.section 自己有 margin-top:28px,
|
||||
若在这里再写 `.card + .section { margin-top:18px }`,特异性 0,2,0 会压过
|
||||
`.section` 的 0,1,0,反而把原本 28px 的间距改小 —— 修 A 不能顺手弄坏 B。 */
|
||||
/* 🔴🔴 v24 第 25 批 用户第 3 条:**并排的两块不该再吃这条"相邻间距"**。
|
||||
用户原话:「日程和任务看板,两个模块,现在有点错位,一个高,一个低,
|
||||
应该完全一样才对。」
|
||||
|
||||
根因(实测,不是猜):`.home-duo` 里左右两块**都是 `.card`**、且在 DOM 里互为
|
||||
紧邻兄弟 → `.card + .card` 命中**第二块** → 给它加了 `margin-top:18px`。
|
||||
grid 的 `align-items: stretch` 让两块**高度**相等(那个数是对的),
|
||||
但第二块被整体推下去 18px —— 于是"一样高、却一高一低"。
|
||||
(我上一版只调了 `.home-duo` 的 align-items 与高度,没动这条,
|
||||
真机探针才把 18px 这个偏移量量出来 —— 光看代码是看不出来的。)
|
||||
|
||||
⇒ 并排容器里的 `.card` 一律不吃相邻间距(间距由 `gap` 统一给)。
|
||||
用 `:first-child` 之外的兄弟选择器把它们点掉,且**特异性要比 0,2,0 高**。 */
|
||||
.home-duo > .card { margin-top: 0; }
|
||||
.home-duo > .card + .card { margin-top: 0; }
|
||||
|
||||
/* 首页头图 */
|
||||
.hero {
|
||||
@@ -1243,14 +1258,21 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
border-radius: 6px; font-size: 14px;
|
||||
color: var(--muted); background: rgba(255, 255, 255, .82);
|
||||
border: 1px solid var(--line-2);
|
||||
/* ⚠️ 手柄现在是**能抓的**(不再 pointer-events:none):
|
||||
鼠标可以拖整行,但**触摸只能抓这个手柄** —— 否则手指在行上一划
|
||||
就被浏览器当成滚页面,永远拖不起来。touch-action:none 就是这句话。 */
|
||||
/* 🔴 v24 第 25 批 用户第 5 条:手柄是**拖动名册的唯一入口**。
|
||||
用户原话:「点击列表中的人,是直接查看这个人的信息,拖动的时候才是调整数序。」
|
||||
→ 整行不再参与拖动,**只有按在这个 ⠿ 上才算**(鼠标与触摸同一判据)。
|
||||
所以它必须一眼看得出来"这里是能抓的":
|
||||
· `cursor: grab`(抓着时 JS 加 .is-dragged 的卡片会变 grabbing,见下)
|
||||
· `touch-action: none` —— 不写它,手指在柄上一划会被浏览器当成滚页面,永远拖不起来
|
||||
· 悬停加一点底色,别让它看着像一个装饰性的字符 */
|
||||
touch-action: none; cursor: grab;
|
||||
}
|
||||
.drag-handle:hover { background: var(--brand-soft, rgba(168,74,40,.10)); color: var(--brand); }
|
||||
.drag-handle-inline { color: var(--muted); }
|
||||
/* 列表里每行要留出左侧拖柄的位置(否则⠿会压在头像上) */
|
||||
.roster.is-droppable .person { padding-left: 18px; }
|
||||
/* 正被拖的那张卡:手柄变抓握态(与 cursor:grab 配成一对) */
|
||||
.person.is-dragged .drag-handle { cursor: grabbing; }
|
||||
|
||||
/* 空栏平时收起来(七个预置分组可能只用到三个,摊开会显得名册破破烂烂)——
|
||||
但**拖动时必须露出来**,否则"想拖到一个现在没人的分组里"就没地方放。 */
|
||||
@@ -1904,6 +1926,24 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
}
|
||||
.ann-date { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||
.ann-title { flex: 1 1 auto; color: var(--ink); min-width: 0; }
|
||||
/* ---- 纪念日的「关联人」列(v24 第 25 批 用户第 2 条)----
|
||||
用户原话:「再加一列,关联人。列表中,人的前面,都加头像。」
|
||||
|
||||
⚠️ 列里画的是全站统一的 person_chip(`.viewer-tag`,自带头像 + 底色),
|
||||
所以这里只需要处理**排布**,不要另造一套人名样式(会与其它模块不一致)。
|
||||
⚠️ 关联多个人时要能换行 —— `.viewer-tag` 是 `inline-flex`,
|
||||
不写 flex-wrap 的话 5 个人会把表格撑宽(手机上直接横向溢出)。
|
||||
⚠️ 「改/删」两个按钮要排成一行:`.res-inline` 那个 form 是 inline 的,
|
||||
但两个按钮之间得靠 gap 分开,否则会贴在一起、看着像一个双色按钮。 */
|
||||
.ann-people { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; max-width: 260px; }
|
||||
.ann-acts { display: flex; gap: 6px; align-items: center; }
|
||||
.ann-acts .btn { flex: 0 0 auto; }
|
||||
@media (max-width: 767px) {
|
||||
/* 窄屏:关联人那一列不再限宽(表格本来就会横向滚动),
|
||||
但两个按钮要保持不换行 —— 换行后「删除」会掉到「修改」下面,
|
||||
在表格行里看着像两张卡片。 */
|
||||
.ann-people { max-width: none; }
|
||||
}
|
||||
.chk-inline { display: flex; align-items: center; gap: 6px; font-size: 14px; }
|
||||
/* ---- 任务设置(v24 第 9、10 条):看板/分类的增删改列表 ---- */
|
||||
.kb-boards { margin-bottom: 12px; }
|
||||
@@ -2609,6 +2649,13 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
}
|
||||
.kb-card.is-dragging { opacity: .45; }
|
||||
.kb-card.is-overdue { border-color: rgba(192, 83, 63, .4); }
|
||||
/* 🔴 v24 第 25 批 用户第 6 条:卡片加了「完成」复选框之后,它必须与标题**同一行**。
|
||||
作者样式是 `flex-direction: column` —— 不处理的话复选框会独占一行、
|
||||
标题被挤到第二行,一张卡凭空高一截(用户要的是"任务前面带一个复选框",
|
||||
不是"任务上面带一个")。做法:把标题包成一行(.kb-top),
|
||||
复选框 + 标题一起横排,剩下的 meta/foot 仍然竖着叠。 */
|
||||
.kb-top { display: flex; align-items: flex-start; gap: 8px; }
|
||||
.kb-top .kb-title { flex: 1 1 auto; min-width: 0; }
|
||||
.kb-title { font-size: 14px; font-weight: 500; color: var(--ink); }
|
||||
.kb-title:hover { color: var(--brand); }
|
||||
.kb-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 12px; color: var(--muted); }
|
||||
@@ -2647,11 +2694,9 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
|
||||
}
|
||||
.sub-row.is-done .sub-title { color: var(--muted); text-decoration: line-through; }
|
||||
.sub-check {
|
||||
background: none; border: 0; cursor: pointer; font-size: 17px; line-height: 1;
|
||||
padding: 0 2px; color: var(--brand);
|
||||
}
|
||||
.sub-check.is-static { cursor: default; color: var(--muted); }
|
||||
/* ⚠️ `.sub-check`(旧的 ☐/☑ 字符按钮)已在 v24 第 25 批 用户第 6 条**删掉**:
|
||||
子任务改用全站统一的 `.tk-check`(见上面那一段)。留一份没人用的样式
|
||||
只会让下一个人以为还有第二条画法。 */
|
||||
.sub-title { flex: 1 1 auto; min-width: 0; font-size: 14px; }
|
||||
.cmt-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.cmt-row { border-top: 1px solid var(--line-2); padding-top: 10px; }
|
||||
@@ -4118,13 +4163,32 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b
|
||||
/* ---- ② 日程 / 任务 左右各半 ----
|
||||
⚠️ 高度**固定**(用户原话「为了好看高度需要固定」),滚动条**藏起来**
|
||||
(「鼠标放上去之后向下可以滚动,暂时不要出现滚动条太丑了」)。
|
||||
藏滚动条要三套写法(Firefox / IE-Edge / WebKit 各一套),少一套就在某个浏览器上露出来。 */
|
||||
.home-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
|
||||
.hd-pane { display: flex; flex-direction: column; }
|
||||
.hd-pane-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
||||
藏滚动条要三套写法(Firefox / IE-Edge / WebKit 各一套),少一套就在某个浏览器上露出来。
|
||||
|
||||
🔴🔴 v24 第 25 批 用户第 3 条修的真 bug:「日程和任务看板,两个模块,
|
||||
现在有点错位,一个高,一个低,应该完全一样才对。」
|
||||
|
||||
根因:这两块原来是 `align-items: start` + 各自内容撑高 ——
|
||||
左边日程的**刻度行数**由"今天有几件事"决定(掐头去尾之后 3~9 行不等),
|
||||
右边的任务条数也各不相同 ⇒ 两张卡片高度天然不等,一眼就是"一高一低"。
|
||||
|
||||
修法(三步,缺一不可):
|
||||
① `align-items: stretch` —— 让两栏拉成同一行高(grid 的默认行为,
|
||||
但上一版显式写成了 `start`,那等于主动放弃对齐);
|
||||
② `.hd-pane` 给一个**固定总高**并 `display:flex; flex-direction:column`
|
||||
(它本来就有 flex 列),head 不收缩、body 吃满剩余;
|
||||
③ `.hd-pane-body` 用 `flex: 1 1 auto` + `min-height: 0`
|
||||
取代原来的固定 `height: 360px`。
|
||||
⚠️ `min-height: 0` 不能省:flex 子项的默认 `min-height:auto`
|
||||
会让它**不肯缩到内容以下**,于是内容一多就把卡片顶高、
|
||||
对齐又失效(这是"固定高度 + 内部滚动"这套写法的经典坑)。
|
||||
这样两块的**外框高度恒等**,与各自内容多少无关 —— 对齐不再依赖数据。 */
|
||||
.home-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; }
|
||||
.hd-pane { display: flex; flex-direction: column; height: 440px; min-height: 0; }
|
||||
.hd-pane-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex: 0 0 auto; }
|
||||
.hd-pane-head .cinema-h2 { margin: 0; flex: 1 1 auto; }
|
||||
.hd-pane-body {
|
||||
height: 360px; overflow-y: auto; overscroll-behavior: contain;
|
||||
flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* 旧 Edge / IE */
|
||||
}
|
||||
@@ -4172,10 +4236,60 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b
|
||||
}
|
||||
.tk-due { flex: 0 0 auto; font-size: 11.5px; color: var(--faint); }
|
||||
|
||||
/* ---- 任务的「完成」复选框(v24 第 25 批 用户第 6 条)----
|
||||
用户原话:「所有的任务,前面都带一个复选框,在任何地方,点这个复选框,
|
||||
这个任务就完成了。」
|
||||
|
||||
⚠️ 它是 `<button>` 不是 `<input type=checkbox>`(见 _task_check.php 的说明):
|
||||
勾没勾**由服务端状态渲染的 class** 说了算,不是浏览器自己管 checked。
|
||||
⚠️ 尺寸写死 18px 且 `flex: 0 0 auto`:卡片标题是长文本,
|
||||
不写死的话它会被 flex 压扁(同一行里"文字优先")。
|
||||
⚠️ 勾选态用**伪元素画对勾**而不是塞一个 ✓ 字符:
|
||||
字符在不同字体里基线不同,18px 的方块里会歪。 */
|
||||
.tk-check {
|
||||
flex: 0 0 auto; width: 18px; height: 18px; padding: 0; margin: 0;
|
||||
border: 1.5px solid var(--line); border-radius: 5px; background: #fff;
|
||||
cursor: pointer; position: relative; transition: border-color .15s, background .15s;
|
||||
}
|
||||
.tk-check:hover { border-color: var(--brand); }
|
||||
.tk-check::after {
|
||||
content: ""; position: absolute; left: 5px; top: 1.5px;
|
||||
width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg) scale(0); transition: transform .13s ease;
|
||||
}
|
||||
.tk-check.is-done { background: var(--ok, #5E9E4E); border-color: var(--ok, #5E9E4E); }
|
||||
.tk-check.is-done::after { transform: rotate(45deg) scale(1); }
|
||||
/* 只读账号:一颗不可点的静态方块 */
|
||||
.tk-check.is-static { cursor: default; }
|
||||
.tk-check.is-static:hover { border-color: var(--line); }
|
||||
/* 已完成的那张卡:标题压灰划掉(一眼看出"这件做完了") */
|
||||
.kb-card.is-done .kb-title { color: var(--muted); text-decoration: line-through; }
|
||||
/* ⚠️ 首页/详情那种"一行文字 + 一颗复选框"的场合:
|
||||
`.dash-row` 是 `align-items: baseline`,而复选框是 18px 的方块 ——
|
||||
按基线对齐会让它**沉到文字下面半截**(基线是文字的底边,方块没有基线)。
|
||||
所以带 .tk-check 的行改成 `center`,文字与方块的**中线**对齐,
|
||||
这才是"复选框在文字前面"该有的样子。
|
||||
(`.sub-row` 本来就是 center,不用管它。) */
|
||||
.dash-row:has(.tk-check) { align-items: center; }
|
||||
/* 详情页标题前那颗:标题是 22px 大字,复选框要跟着走中线才不悬空 */
|
||||
.page-head h1:has(> .tk-check) { display: inline-flex; align-items: center; gap: 10px; }
|
||||
|
||||
/* 看板页顶上的「三个状态到底什么意思」(用户第 6 条直接问出来的那句话) */
|
||||
.tk-legend {
|
||||
display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center;
|
||||
margin: 0 0 12px; padding: 9px 12px;
|
||||
background: var(--surface-2, rgba(0, 0, 0, .02));
|
||||
border: 1px solid var(--line); border-radius: var(--radius-s);
|
||||
}
|
||||
.tk-legend .tk-lg-item { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
|
||||
.tk-legend .tk-lg-hint { color: var(--muted); }
|
||||
|
||||
/* ---- 窄屏:并排改成上下(各占一半宽度在手机上没法读)---- */
|
||||
@media (max-width: 760px) {
|
||||
.home-duo { grid-template-columns: 1fr; }
|
||||
.hd-pane-body { height: 300px; }
|
||||
/* ⚠️ 窄屏仍要保持**固定高度**(用户要的是"高度固定、内部滚动"),
|
||||
只是比桌面矮一点。不写成 auto:那样内容一多整页就长得没法用。 */
|
||||
.hd-pane { height: 320px; }
|
||||
.hb-weather { margin-left: 0; padding-left: 0; border-left: 0; padding-top: 10px; border-top: 1px solid var(--line-2); width: 100%; }
|
||||
}
|
||||
|
||||
@@ -4235,3 +4349,55 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b
|
||||
.wr-q i { font-style: normal; color: var(--ink); }
|
||||
.wr-q em { font-style: normal; color: var(--brand-ink); }
|
||||
.wr-q em::before { content: '💭 '; }
|
||||
|
||||
/* ---- 微信读书:封面(v24 第 25 批 用户第 8 条「封面图看不到」)----
|
||||
⚠️ 尺寸必须写死(52×74 ≈ 2:3,与 bookcover 槽位的裁剪比例一致):
|
||||
不写死的话,官方封面各尺寸不一,一行里高低参差(这正是"看起来乱"的原因)。
|
||||
⚠️ `object-fit: cover` + 固定高宽:封面比例不一时居中裁,不拉伸变形。 */
|
||||
.wr-cv {
|
||||
flex: 0 0 auto; width: 52px; height: 74px; border-radius: 6px;
|
||||
object-fit: cover; background: var(--line-2);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .10);
|
||||
}
|
||||
.wr-cv-ph {
|
||||
display: flex; align-items: center; justify-content: center; font-size: 22px;
|
||||
color: var(--faint); box-shadow: none; border: 1px dashed var(--line);
|
||||
}
|
||||
/* 外链封面(统计块里的「读得最多的书」,直接来自官方 CDN,本站不下载) */
|
||||
.wr-top-cv {
|
||||
flex: 0 0 auto; width: 34px; height: 48px; border-radius: 5px;
|
||||
object-fit: cover; background: var(--line-2);
|
||||
}
|
||||
.wr-top-ph { display: flex; align-items: center; justify-content: center; color: var(--faint); }
|
||||
|
||||
/* ---- 自动同步(用户第 8 条)---- */
|
||||
.wr-sync { display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 13px; }
|
||||
.wr-sync-item { color: var(--muted); }
|
||||
.wr-sync-item b { color: var(--ink); font-weight: 600; }
|
||||
|
||||
/* ---- 阅读统计(用户第 8 条)----
|
||||
一排数字卡。⚠️ `flex: 0 1 auto` + `min-width` 而不是等宽:
|
||||
指标个数(时长/天数/日均/读过/读完/笔记)随官方返回而变,
|
||||
写死等宽会在某个指标缺失时留一块空。 */
|
||||
.wr-stats { display: flex; flex-wrap: wrap; gap: 10px; margin: 2px 0 4px; }
|
||||
.wr-stat {
|
||||
flex: 0 1 auto; min-width: 108px; padding: 10px 14px;
|
||||
background: var(--line-2); border-radius: var(--radius-s);
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
}
|
||||
.wr-stat b { font-size: 17px; color: var(--ink); font-weight: 600; }
|
||||
.wr-stat span { font-size: 12px; color: var(--muted); }
|
||||
.wr-sub { margin: 18px 0 8px; font-size: 14px; font-weight: 600; color: var(--ink); }
|
||||
/* 读得最多的书 */
|
||||
.wr-top { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.wr-top-item { display: flex; align-items: center; gap: 10px; }
|
||||
.wr-top-b { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
||||
.wr-top-t { font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.wr-top-time { flex: 0 0 auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||
/* 偏好分类:名称 + 条 + 数值 */
|
||||
.wr-cats { display: flex; flex-direction: column; gap: 6px; }
|
||||
.wr-cat { display: flex; align-items: center; gap: 10px; font-size: 13px; }
|
||||
.wr-cat-n { flex: 0 0 84px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.wr-cat-bar { flex: 1 1 auto; height: 7px; background: var(--line-2); border-radius: 999px; overflow: hidden; }
|
||||
.wr-cat-bar i { display: block; height: 100%; background: var(--brand); border-radius: 999px; }
|
||||
.wr-cat-v { flex: 0 0 auto; font-size: 12px; }
|
||||
|
||||
+33
-42
@@ -839,33 +839,14 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* 这次按下该不该算"拖动"?
|
||||
* v24 第 25 批 用户第 5 条:拖拽的起手判据只剩一条 —— **必须按在 ⠿ 手柄上**。
|
||||
*
|
||||
* ⚠️ 按钮 / 输入框 / 下拉 / <summary> 一律不算 —— 在那儿按下是"要用它",
|
||||
* 不是"要拖这张卡"。唯一例外是 `.drag-handle`(它是专门用来抓的)。
|
||||
* ⚠️ 大链接 `.person-main` **算**:整行都能拖才顺手(这也是原来的手感),
|
||||
* 靠 MIN_MOVE 门槛区分"点进去看详情"和"拖着走",并且拖过之后要掐掉那次 click。
|
||||
* 原来的 `isControl()`(判断"这一点是不是按钮/输入框/大链接")**已删除**:
|
||||
* 那个函数存在的前提是"整行都能拖",而用户明确要求改成
|
||||
* 「点击列表中的人,是直接查看这个人的信息,拖动的时候才是调整数序」。
|
||||
* 整行不再参与拖动之后,"哪些控件不该触发拖拽"这个问题就不存在了 ——
|
||||
* 留着一个没人调用的函数只会让下一个人以为还有第二条拖拽路径。
|
||||
*/
|
||||
function isControl(node) {
|
||||
var t = node;
|
||||
while (t && t !== roster) {
|
||||
if (t.nodeType !== 1) { t = t.parentNode; continue; }
|
||||
// ⚠️ 判定顺序不能乱:先认"专门用来抓的"和"整行那个大链接",
|
||||
// 再认"点了就有别的作用的控件"。
|
||||
if (t.classList && t.classList.contains('drag-handle')) { return false; }
|
||||
// `.person-main` 虽然是 <a>(点它会进详情页),但整行都能拖才顺手 ——
|
||||
// 靠 MIN_MOVE 区分"点"与"拖",拖过之后掐掉那次 click。
|
||||
if (t.classList && t.classList.contains('person-main')) { return false; }
|
||||
if (t.getAttribute && t.getAttribute('data-no-drag') !== null) { return true; }
|
||||
var tag = t.tagName;
|
||||
if (tag === 'BUTTON' || tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA'
|
||||
|| tag === 'SUMMARY' || tag === 'LABEL' || tag === 'A') {
|
||||
return true;
|
||||
}
|
||||
t = t.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 每栏人数与"空栏"标记跟着 DOM 走 —— 不然拖完之后标题上的「N 人」还是旧的 */
|
||||
function refreshCounts() {
|
||||
@@ -995,6 +976,8 @@
|
||||
|
||||
function beginDrag(card, pt) {
|
||||
dragging = card;
|
||||
// ⚠️ v24 第 25 批:`is-dragging` 从 pointerdown 挪到了这里(见 pointerdown 的说明)。
|
||||
// 只有从 ⠿ 手柄起拖,所以"提前露空栏"不再必要 —— 真开始拖时再露。
|
||||
roster.classList.add('is-dragging');
|
||||
card.classList.add('is-dragged');
|
||||
placeAt(pt);
|
||||
@@ -1020,22 +1003,31 @@
|
||||
if (e.button !== 0 && e.pointerType === 'mouse') { return; }
|
||||
var card = cardFrom(e.target);
|
||||
if (!card) { return; }
|
||||
// 触摸时整行都能拖会让页面没法上下滚 —— 触摸只认那个 ⠿ 手柄
|
||||
/* 🔴🔴 v24 第 25 批 用户第 5 条:**只有从 ⠿ 手柄上按下才算拖动**。
|
||||
*
|
||||
* 用户原话:「现在名册的操作还是很不好,应该是点击列表中的人,是直接查看这个人的信息,
|
||||
* 拖动的时候才是调整数序,现在是混乱的。」
|
||||
*
|
||||
* 改之前的行为(三处叠加,所以"混乱"):
|
||||
* ① 鼠标在整行任何非控件处按下都算"待拖";
|
||||
* ② 而且**按下那一刻**就给 .roster 加 `is-dragging`(本来是为了提前露出空栏当落点);
|
||||
* ③ 于是轻轻点一下卡片,空栏先"冒出来"、页面高度跳一下,视觉上就是"我点了一下,
|
||||
* 页面自己动了一格";抬起时又收回去 —— 用户根本分不清自己是在点还是在拖。
|
||||
* ⇒ 现在:**整行都不再触发拖拽**;只有那枚 ⠿(.drag-handle,专门用来抓的)
|
||||
* 按下才进入待拖状态。点击整行 = 沿 .person-main 链接进详情页(原样保留)。
|
||||
*
|
||||
* ⚠️ 鼠标与触摸**同一判据**(不再按 pointerType 分叉):
|
||||
* 以前触摸只认手柄、鼠标认整行 —— 两种设备手感不同,用户换台机器就"又变了"。
|
||||
* 统一成"都只认手柄"之后,语义只有一条:**拖这个 ⠿**。
|
||||
* ⚠️ `is-dragging` 也挪到 beginDrag() 里加(不再在 pointerdown 加):
|
||||
* 既然只有手柄能起拖,就没有"提前露空栏"的必要了 ——
|
||||
* 手柄本身很小,用户按下它就是明确要拖,那一刻再露空栏不影响瞄准。 */
|
||||
var fromHandle = !!(e.target.closest && e.target.closest('.drag-handle'));
|
||||
if (e.pointerType !== 'mouse' && !fromHandle) { return; }
|
||||
if (!fromHandle && isControl(e.target)) { return; }
|
||||
if (!fromHandle) { return; }
|
||||
pending = { card: card, id: e.pointerId, type: e.pointerType };
|
||||
startPt = { x: e.clientX, y: e.clientY };
|
||||
lastPt = { x: e.clientX, y: e.clientY };
|
||||
/* 🔴 空栏必须在**按下的这一刻**就露出来,不能等"真的开始拖"才露。
|
||||
露出来会让页面变高、下面的分组整体下移 ——
|
||||
如果等到拖动开始才露,用户瞄准的那个位置在手指一动之后就跑了
|
||||
(症状:想拖到「未分类」,松手却落进了别的栏,或者干脆没动静)。
|
||||
按下时就露,布局在**瞄准之前**已经稳定,之后全程不变。
|
||||
⚠️ 只点一下不拖的情况:松手时会收回去(见下面的 pointerup);
|
||||
而"点一下"多半是点这一行看详情,页面本来就要跳走,看不到这一下。 */
|
||||
roster.classList.add('is-dragging');
|
||||
if (fromHandle) { e.preventDefault(); } // 别顺手选中文字
|
||||
e.preventDefault(); // 别顺手选中文字
|
||||
});
|
||||
|
||||
document.addEventListener('pointermove', function (e) {
|
||||
@@ -1054,16 +1046,15 @@
|
||||
document.addEventListener('pointerup', function (e) {
|
||||
if (dragging) { endDrag(); }
|
||||
else if (pending && e.pointerId === pending.id) {
|
||||
// 在手柄上按下、但位移没到门槛(= 没真拖)→ 清掉待拖态,什么都不做。
|
||||
// ⚠️ v24 第 25 批:这里**不再**需要 `is-dragging` 的收回 ——
|
||||
// 那个类现在只在 beginDrag() 里加(见 pointerdown 的说明)。
|
||||
pending = null;
|
||||
roster.classList.remove('is-dragging'); // 只是点了一下:把露出来的空栏收回去
|
||||
}
|
||||
});
|
||||
document.addEventListener('pointercancel', function () {
|
||||
if (dragging) { endDrag(); }
|
||||
else if (pending) {
|
||||
pending = null;
|
||||
roster.classList.remove('is-dragging');
|
||||
}
|
||||
else if (pending) { pending = null; }
|
||||
});
|
||||
|
||||
// 拖完掐掉那一次 click(捕获阶段就拦,别等它冒泡到链接上)
|
||||
|
||||
Reference in New Issue
Block a user