v38 第 38 批:观影试点 Ant Design 日期控件 + 弹框内联排版(对齐)+ 去提示 + 默认带自己
用户原话(四条):
①「我终于找到了一个我比较认可的日期选择插件
https://ant-design.antgroup.com/components/date-picker-cn 现在观影这个模块进行
测试吧,测试那个有开始、结束时间的。等这个模块测试完没问题了,再更新到其他的模块。」
②「记录一场电影,这个弹框因为标题和下面的输入框都是两行的,所以导致弹框非常长。
能不能排排版,让前面的标题后面加个冒号,后面的输入框直接跟在它后面,这样就变成一行。
包括开始、结束时间、影院、同行的人、简介等等,能把它排成一行的都排成一行,
但是一定要注意对齐的问题,如果不对齐的话可能不好看。」
③「里面的一些提示,比如片名可以从豆瓣自动补充,还有刚新建的影院会自动选中,
观影照片可以放这里且可多选,这些都把它去掉吧,节省一下上下的空间。」
④「另外一个同行的人,这里每个人添加的时候,应该默认带上自己,也就是自己不用选择
就应该已经选上了。」
① Ant Design RangePicker —— **只在观影试点**(用户明确"试完再推广")
· 资源 `assets/vendor/antd/{antd.min.js, dayjs.min.js, reset.css}`(unpkg 下载、随包上线);
layout.php 加 `fb-antd` 清单;`_datetime.php` 加 `$dtPlugin = 'antd'` 开关 +
`data-dt-antd="1"` 标记;app.js 加 `bootAntd()` / `mountAntd()`(按需加载)。
· 🔴 UMD 的硬依赖:antd 从**全局**取 React / ReactDOM / dayjs。缺 React 的报错是
`Cannot read properties of undefined (reading 'createContext')` —— 表现是"控件完全
不出现",不是样式问题。项目本来就有一套 React 18.3.1(Semi 用的),直接复用同一份。
· 🔴 实测出的关键行为(独立探针 `_antd_probe`,15/0):面板里有 **42 个日期格 +
2 个内联时间列(84 项)+ 「确定」按钮** —— 正是之前 Semi 缺的那两样;
且 **`onChange` 只在两端都齐了才触发**,单端改动走 `onCalendarChange` ⇒
回写必须两个都挂,否则"只填了开始时间就提交"会丢值。
· ⚠️ 原生那对下拉**全部保留**(值载体 + 拉不动时的兜底),挂上 antd 后用 clip 视觉收起
(不用 display:none —— 保持可聚焦)。
· 部署断言钉住试点边界:日程/任务**不该**出现 `$dtPlugin = 'antd'`。
② 弹框排版:标签内联 + 控件左缘对齐
· `.mv-row` = 标签(固定 62px、右对齐、**冒号由 CSS ::after 给**):控件(占满剩余)。
· 并排对:简介+备注、海报+观影照片 → `.mv-pair`(grid 1fr 1fr 保证两列等宽);窄屏回单列。
· 🔴 真机量出**两个真对齐缺陷**(不是猜的):
1) 时间行的控件左缘比别的行**偏 8px** —— `.dt-cols` 是 `gap: 10px 18px`(列距 18),
而普通 `.mv-row` 只有 `gap:10px` ⇒ 改 `.mv-row.dt-range.dt-cols { column-gap: 10px }`;
2) 海报/观影照片两行歪了 **130px** —— `_stage_drop.php` 在最前面插的 `.sr-only`
`<input>`(绝对定位 1px)**占掉了一整份 flex:1 1 auto**,把上传框推出去
⇒ 这两块改纵向 `.mv-media`(标签一行、内容从 72px 起,与主列对齐)。
⇒ 现在**所有字段控件左缘都是 444**;A/B 实测**弹框 1036px → 817px(矮 219px)**。
③ 删掉三句冗余提示(豆瓣补充 / 新建影院自动选中 / 观影照片),简介 rows 6→3。
④ 同行的人默认带上自己:新增时预置 `[我]`;编辑以库值为准;`old()` 存在时尊重用户上次勾选。
🔴 顺带修的真 bug:**antd 面板与弹框抢 Esc** —— 两个处理器都挂 document 上,
按一次 Esc 把日期面板和整个弹框一起关掉。已改成"antd 面板开着时弹框不吃 Esc"。
验证(全部真机 + 反向验证):
· _fb_b38_movie_form.py **33/0**(antd 挂上/面板有时间列与确定/值写回隐藏 *_date/
标签同行与左缘对齐/三句提示消失/默认带自己/试点边界);
· _fb_b38_narrow.py **6/0**(宽屏并排、窄屏回单列、无横向溢出);
· _fb_b38_falsify.py **8/0**(排版退回上下两行、antd 只挂 onChange、不预置自己 → 各自必须红);
· e2e **1503/0**、日期控件真机 **53/0** + 反向 **7/0**、v37 两套 **27/0 + 5/0**、
观影照片 **16/0** + **8/0**、b30 三件套、b18 选人 **10/0**、几何交互 **109/0**、
DAV 三套、部署静态断言空跑 —— 全绿;
· 线上 app.js / app.css / antd.min.js 的 SHA256 与本地一致。
⚠️ 顺带修了探针/夹具的三处**测试侧**问题(都不是页面 bug):
· e2e 里 6 条 jsdom 用例(测选人弹框"暂存区机制")前提是"一个都没选",
默认带自己后全假红 ⇒ 在**测试自己的 HTML 副本**上清掉 `data-pp-selected`;
· `_fb_b18_picker` 真机探针同理,改成**点 chip 上的 ×**(组件把已选存在闭包里,
改属性没用 —— 试过两种写法都是白试);
· `_fb_b34_dtrange` 的"标签文案"断言写死了「开始 / 结束」,
而 v38 把观影的标签简化成了「时间」⇒ 改成"有非空标签即可";
另补一条 antd 分支(挂 antd 后原生被裁成 1px,`1px > 0` 会让旧判据**假通过**)。
· 顺带:夹具名册只有 1 人时会误报"无法验证多选",已把实际候选数打出来。
This commit is contained in:
+74
-32
@@ -28,7 +28,22 @@ $dtEnd = $dtFromOld('ends_at', $m['ends_at'] ?? null);
|
||||
// 优先级:老输入 > ?cinema=(刚新建的)> 记录里的值。
|
||||
$selCinema = (int)old('cinema_id', (int)($_GET['cinema'] ?? 0) ?: ($m['cinema_id'] ?? 0));
|
||||
$curAttendees = array_map('intval', array_column($m['attendees'] ?? [], 'id'));
|
||||
$selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
||||
/* 🔴 v38(用户 2026-10-10):「同行的人,这里每个人添加的时候,应该默认带上自己,
|
||||
也就是自己不用选择就应该已经选上了。」
|
||||
⇒ **新增**时把"我"预置进已选列表;**编辑**时以库里的值为准
|
||||
(用户可能就是自己改了,不能每次打开都把"我"塞回去)。
|
||||
⚠️ `old()` 存在(= 刚才这次提交校验失败重画)时,尊重用户上一次的勾选 ——
|
||||
否则他取消掉"自己"、填错别处再提交,会发现"自己"又回来了。 */
|
||||
$meId = (int)\App\Auth::personId();
|
||||
$hasOld = array_key_exists('attendees', (array)($_SESSION['_old'] ?? []));
|
||||
if ($hasOld) {
|
||||
$selAttendees = array_map('intval', (array)old('attendees', []));
|
||||
} elseif ($isEdit) {
|
||||
$selAttendees = $curAttendees;
|
||||
} else {
|
||||
// 新增:默认带上自己(名册里查得到才加 —— 免得给一个不存在的 id)
|
||||
$selAttendees = $meId > 0 ? [$meId] : [];
|
||||
}
|
||||
?>
|
||||
<?php /* 🔴 v37:本模板现在有两种用法 ——
|
||||
① **整页**(`movies/edit` 的编辑页;以及 `movies/new` 的直接访问 / 校验失败重画);
|
||||
@@ -87,9 +102,19 @@ $mvOpen = $mvModal && !$mvModalOnly;
|
||||
<?php if ($mvModal): ?><div class="cal-modal-body"><?php endif; ?>
|
||||
|
||||
<div class="<?= $mvModal ? 'mv-form-inner' : 'card card-pad' ?>">
|
||||
<div class="form-grid">
|
||||
<div class="field full">
|
||||
<label for="title">片名 *</label>
|
||||
<?php /* 🔴🔴 v38(用户 2026-10-10):**标签内联 + 固定宽度对齐**。
|
||||
用户原话:「记录一场电影,这个弹框因为标题和下面的输入框都是两行的,
|
||||
所以导致弹框非常长。能不能排排版,让前面的标题后面加个冒号,后面的输入框
|
||||
直接跟在它后面,这样就变成一行。包括开始、结束时间、影院、同行的人、简介等等,
|
||||
能把它排成一行的都排成一行,但是一定要注意对齐的问题,如果不对齐的话可能不好看。」
|
||||
⇒ 换成 `.mv-grid`:每行 "标签(固定 62px,右对齐) : 控件(占满剩余)"。
|
||||
固定宽度是"对齐"的关键 —— 标签长短不一(片名/时间/影院/同行的人)时,
|
||||
控件左缘仍然在**同一条竖线**上。
|
||||
⚠️ 不用 `.form-grid`(它是 2 列 grid,本来就是"标题在上、控件在下"):
|
||||
那一套是给普通页面表单用的,别为了弹框把它改掉。 */ ?>
|
||||
<div class="mv-grid">
|
||||
<div class="mv-row">
|
||||
<label class="mv-lb" for="title">片名<i>*</i></label>
|
||||
<div class="db-title-row">
|
||||
<input class="input" id="title" name="title" value="<?= e(old('title', $m['title'] ?? '')) ?>"
|
||||
required autocomplete="off">
|
||||
@@ -105,21 +130,24 @@ $mvOpen = $mvModal && !$mvModalOnly;
|
||||
<img id="dbPosterImg" alt="海报预览">
|
||||
<span class="tip">海报已从豆瓣取回,保存后生效。</span>
|
||||
</div>
|
||||
<span class="tip">片名可从豆瓣自动补充:输入后从下拉里选,或点「自动获取」。</span>
|
||||
<?php if (!empty($errors['title'])): ?><span class="err"><?= e($errors['title']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* 用户 2026-10-04 第 7 条:起止是一个**范围**。用户 2026-10-09 第 2 次:
|
||||
「前面是日期,后面是时和分,只记录到时和分就行了。时和分,是下拉的。」
|
||||
⇒ 形态 = 日期框 + 时/分两个下拉(**没有秒**,见 app.js upgradeRange 的说明)。 */ ?>
|
||||
<div class="field full dt-range">
|
||||
<span class="dt-range-label">开始 / 结束时间(时:分)</span>
|
||||
<div class="mv-row dt-range">
|
||||
<span class="mv-lb dt-range-label">时间</span>
|
||||
<?php /* v37:秒去掉、"散场时间可不填。"那句也删了(用户要求省空间)。
|
||||
v38:`$dtPlugin = 'antd'` ⇒ app.js 把这对端点升级成
|
||||
**Ant Design 的 RangePicker**(用户 2026-10-10 点名要的插件,
|
||||
先在观影模块试点)。原生下拉**照旧在**,拉不动时就是它们。 */ ?>
|
||||
<?php $dtName = 'starts_at'; $dtValue = $dtStart; $dtId = 'starts_at_date';
|
||||
$dtRange = 'start';
|
||||
$dtRange = 'start'; $dtPlugin = 'antd';
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
<?php if (!empty($errors['starts_at'])): ?><span class="err"><?= e($errors['starts_at']) ?></span><?php endif; ?>
|
||||
<?php $dtName = 'ends_at'; $dtValue = $dtEnd; $dtId = '';
|
||||
$dtRange = 'end';
|
||||
$dtRange = 'end'; $dtPlugin = 'antd';
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
<?php if (!empty($errors['ends_at'])): ?><span class="err"><?= e($errors['ends_at']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
@@ -133,8 +161,8 @@ $mvOpen = $mvModal && !$mvModalOnly;
|
||||
→ 弹框里放"地方/名称/颜色"三个字段(影院管理页那张表单的核心),
|
||||
提交时带 back=movies/new,服务端存完回到本页并自动选中新影院。
|
||||
③ 下拉/弹框的样式与全站统一(宣纸底色、品牌色描边)。 */ ?>
|
||||
<div class="field full">
|
||||
<label for="cinema_id">影院</label>
|
||||
<div class="mv-row">
|
||||
<label class="mv-lb" for="cinema_id">影院</label>
|
||||
<div class="cinema-select-row">
|
||||
<div class="cs" id="cinemaPicker" data-open="0">
|
||||
<button type="button" class="cs-btn" id="csBtn" aria-haspopup="listbox" aria-expanded="false">
|
||||
@@ -160,11 +188,10 @@ $mvOpen = $mvModal && !$mvModalOnly;
|
||||
</div>
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="cinemaNewBtn">新建影院</button>
|
||||
</div>
|
||||
<span class="tip">刚新建的影院会自动选中;下拉里的色点就是它在日历与列表里的颜色。</span>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label>同行的人</label>
|
||||
<div class="mv-row">
|
||||
<span class="mv-lb">同行的人</span>
|
||||
<?php /* v24 第 11 条:换成全局人员选择器(templates/_person_picker.php)。
|
||||
原来这套是观影页私有的 .att-*,它的「过滤」其实一直是坏的 ——
|
||||
.att-item 自己写了 display:flex,压过了浏览器的 [hidden]{display:none},
|
||||
@@ -185,15 +212,20 @@ $mvOpen = $mvModal && !$mvModalOnly;
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="info">简介</label>
|
||||
<textarea class="input" id="info" name="info" rows="6"
|
||||
placeholder="剧情简介;点「自动获取」会把导演/主演/年份/类型等主要信息一起写进来"><?= e(old('info', $m['info'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
<?php /* 🔴 v38 用户:「能把它排成一行的都排成一行」——
|
||||
简介与备注都是多行文本、又都不长,并排成一对最省高度
|
||||
(各自仍带自己的内联标签,左右两列**各自内部对齐**)。 */ ?>
|
||||
<div class="mv-pair">
|
||||
<div class="mv-row">
|
||||
<label class="mv-lb" for="info">简介</label>
|
||||
<textarea class="input" id="info" name="info" rows="3"
|
||||
placeholder="剧情简介"><?= e(old('info', $m['info'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="note">备注</label>
|
||||
<textarea class="input" id="note" name="note" rows="2"><?= e(old('note', $m['note'] ?? '')) ?></textarea>
|
||||
<div class="mv-row">
|
||||
<label class="mv-lb" for="note">备注</label>
|
||||
<textarea class="input" id="note" name="note" rows="3"><?= e(old('note', $m['note'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
@@ -217,7 +249,16 @@ $mHasTicket = $isEdit && !empty($m['ticket_key']);
|
||||
$mPhotos = $isEdit ? (array)($m['photos'] ?? []) : [];
|
||||
$mDelForms = []; // 收集起来,在表单**外面**统一渲染
|
||||
?>
|
||||
<div class="form-grid" style="margin-top:4px">
|
||||
<?php /* 🔴 v38 排版:海报 / 观影照片这两块不用 `.mv-row`。
|
||||
原因(真机量出来的):它们的控件区里有**多个**东西 ——
|
||||
· `_stage_drop.php` 会吐一个 `.sr-only` 的 `<input>`(绝对定位、1px)
|
||||
排在 `.upfield` **前面**;
|
||||
· 于是"谁才是控件"就说不清了 —— 按 `.mv-row` 的 flex 排,那个 1px 的
|
||||
隐框会占掉一份 `flex:1 1 auto`,把后面的上传框**推到 130px 之外**
|
||||
(实测左缘 443 vs 隔壁 573,一眼就是歪的)。
|
||||
⇒ 这两块改成纵向:**标签一行、内容另起一行**,内容从**标签列右侧**开始
|
||||
(`margin-left` 对齐那 72px),这样既不会歪,也不会被无关元素挤走。 */ ?>
|
||||
<div class="mv-grid mv-pair mv-grid-media" style="margin-top:10px">
|
||||
<?php /* ---- 观影照片(v35:不再分类,合并成一块)----
|
||||
用户原话:「除了海报之外,票根、现场照片等等都不分类别,都只分一个类,
|
||||
放在一块。…只有海报是单独的…其他的都放在一个模块就行,
|
||||
@@ -228,8 +269,9 @@ $mDelForms = []; // 收集起来,在表单**外面**统一渲染
|
||||
⚠️ **旧的 ticket_key 仍然要显示**(历史数据不能丢):编辑页把已有票根
|
||||
列进下面这块的网格里(它没有 `event_photos` 行 id,所以只能"看"、
|
||||
不能用照片的删除按钮删;要换就直接上传新的)。 */ ?>
|
||||
<div class="field">
|
||||
<label for="sd-poster">海报</label>
|
||||
<div class="mv-media">
|
||||
<span class="mv-lb">海报</span>
|
||||
<div class="mv-media-body">
|
||||
<?php if ($mHasPoster): ?>
|
||||
<?php $mDelForms['poster'] = 'movies/media_del'; ?>
|
||||
<div class="mv-slot">
|
||||
@@ -249,13 +291,14 @@ $mDelForms = []; // 收集起来,在表单**外面**统一渲染
|
||||
$sdMultiple = false;
|
||||
$sdMsg = $mHasPoster ? '点击或拖动进行上传(换一张海报)' : '点击或拖动进行上传';
|
||||
require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
<span class="tip"><?= $mHasPoster ? '选/拖一张新的会自动替换旧的那张。' : '还没有海报。' ?></span>
|
||||
</div><?php /* /.mv-media-body */ ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 观影照片:票根 + 合影 + 其它相关图片,**全部放这一块** ---- */ ?>
|
||||
<div class="field full">
|
||||
<label for="sd-photo">观影照片</label>
|
||||
<div class="mv-media">
|
||||
<span class="mv-lb">观影照片</span>
|
||||
<div class="mv-media-body">
|
||||
<?php if ($mHasTicket || $mPhotos): ?>
|
||||
<div class="photo-grid">
|
||||
<?php if ($mHasTicket): /* 历史数据:旧票根(不在 event_photos 里,只展示) */ ?>
|
||||
@@ -289,10 +332,9 @@ $mDelForms = []; // 收集起来,在表单**外面**统一渲染
|
||||
<?php $sdName = 'photo[]'; $sdSlot = 'photo'; $sdAccept = 'image/*';
|
||||
$sdMsg = '点击或拖动进行上传';
|
||||
require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
<span class="tip"><?= $isEdit ? '观影照片都可以放这里(可多选);删掉的在保存后生效。'
|
||||
: '观影照片都可以放这里(可多选);之后也能在详情页里逐张补传。' ?></span>
|
||||
</div><?php /* /.field.full(观影照片) */ ?>
|
||||
</div><?php /* /.form-grid */ ?>
|
||||
</div><?php /* /.mv-media-body */ ?>
|
||||
</div><?php /* /.mv-media */ ?>
|
||||
</div><?php /* /.mv-grid */ ?>
|
||||
</div><?php /* /.mv-form-inner */ ?>
|
||||
<?php /* 🔴🔴 下面这一段**严格按分支配对**,别再把 </div> 与 <?php if ?> 混着写 ——
|
||||
v37 前两版就是在这里把 `.mv-form-inner` / `.cal-modal-body` 关错了层数,
|
||||
|
||||
@@ -125,10 +125,20 @@ if ($canWrite) {
|
||||
if (btn) { btn.addEventListener('click', open); }
|
||||
if (x) { x.addEventListener('click', close); }
|
||||
if (c1) { c1.addEventListener('click', close); }
|
||||
/* 点遮罩关、面板内不关;Esc 关 —— 与站内其它浮层同一套行为。 */
|
||||
/* 点遮罩关、面板内不关;Esc 关 —— 与站内其它浮层同一套行为。
|
||||
🔴🔴 v38:**antd 的日期面板自己也要吃 Esc**(按一次收日期面板)。
|
||||
两个处理器都挂在 document 上,一次 Esc 会把"日期面板"和"弹框"**一起**关掉 ——
|
||||
用户想关日期面板,结果整个弹框没了(探针当场抓到的)。
|
||||
⇒ 判据加一条:**antd 面板开着时不吃 Esc**,让 antd 先收它自己的面板。 */
|
||||
modal.addEventListener('click', function (e) { if (e.target === modal) { close(); } });
|
||||
function antdPanelOpen() {
|
||||
var d = document.querySelector('.ant-picker-dropdown');
|
||||
return !!(d && !d.classList.contains('ant-picker-dropdown-hidden'));
|
||||
}
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && !modal.hidden) { close(); }
|
||||
if (e.key !== 'Escape' || modal.hidden) { return; }
|
||||
if (antdPanelOpen()) { return; } // 让 antd 先关它自己的面板
|
||||
close();
|
||||
});
|
||||
<?php if (!empty($openModal)): ?>open();<?php endif; ?>
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user