v36 第 36 批:时间控件改「日期 + 时/分下拉」(修掉 v35 真没生效的可见性 bug)+ 详情页去掉补照片
用户原话:
①「观影模块,开始时间和结束时间是空白的,无法选择,可以调整为,前面是日期,
后面是时和分,只记录到时和分就行了。时和分,是下拉的。」
②「观影详情页面,不需要设置补充照片的功能,都放在编辑页面就行了。」
① 时间控件:**v35 的修复根本没生效**(我的锅),而且探针连绿两轮没抓到
真因:upgradeRange 里只给范围**组**加了 `.is-native-range`,
**没给两个端点 `.dt-input` 加 `.is-native`** ⇒ CSS
`html.js .dt-input:not(.is-semi) .dt-native { visibility: hidden }`(特异性 0,3,0)
照样命中 ⇒ 控件 **visibility:hidden**:**占着 48px 高度、完全不可见、点不到**
(用户看到的"一片空白")。
🔴 探针为什么没抓到:它用 `offsetParent !== null` 判"可见" ——
而 **visibility:hidden 的元素 offsetParent 仍然非 null**,于是连绿两轮。
⇒ 判可见改成 computed visibility + display + getBoundingClientRect 有宽高
(_fb_b34_dtrange.py 与 _fb_geom_interact.py 都已改,后者直接用 is_visible())。
修法(比 v35 更笨但更稳):**不造新控件、不加 class 猜 CSS**,直接
① 端点加 `.is-native`(压掉藏原生那条规则);
② 删掉"秒"那一档(连它前面的冒号)—— 用户"只记录到时和分";
③ **inline 强制 display/visibility**(双保险;v35 就是被历史 CSS 规则翻掉的)。
CSS 侧关键几条加 `!important`,并删掉 v35 遗留的 `.dt-merged` / `.dt-time-input`
规则(免得后人误以为还有"收起来的下拉")。
形态 = 「日期框 + 时下拉 + 分下拉」,提交契约仍是 *_date/_hour/_minute
(秒栏不再有,服务端 read_dt_input 对缺失 *_second 按 00)。
② 详情页移除"补一张照片"表单(form.photo-add),留一句"要加照片点上方编辑"。
⚠️ 服务端 `movies/photo_add` 路由**保留**(老书签/直接 POST 仍可用),只是不再有入口。
验证(全部真机 + 反向验证):
· _fb_b34_dtrange.py **37/0**:四个表单页断言「日期框可见 + **时**下拉可见 +
**分**下拉可见 + **没有"秒"** + 只改时分也能提交」;
· _fb_b34_dtrange_falsify.py **5/0**:注入"去掉 .is-native"(= v35 的真错)
→ **12 条断言变红** ⇒ 证明新判据真的能抓到那个 bug;
⚠️ 而"只撤 CSS !important"或"只撤 inline 强制"都 0 条变红(被别的兜底救回来)——
做证伪必须挑"真正决定行为的那一层";
· _fb_b35_movie_media.py **16/0**(⑥ 改为"详情页没有补照片入口");
· e2e **1503/0**(详情页入口断言反过来)、几何交互 **96/0**(+4 条时分下拉);
· 各批真机/反向全绿、部署静态断言空跑通过;
· 线上 app.js / show.php 的 SHA256 与本地一致(v36 已上线)。
This commit is contained in:
+16
-27
@@ -599,14 +599,19 @@ textarea.input { min-height: 76px; resize: vertical; }
|
||||
⚠️ 用 visibility:hidden 而不是 display:none —— 保留元素占位高度,
|
||||
否则升级完成的那一瞬表单会"跳"一下(高度从 0 变 40px)。 */
|
||||
html.js .dt-input:not(.is-semi) .dt-native { visibility: hidden; }
|
||||
/* 🔴 v35:native 升级态 —— 不再挂 Semi,原生控件**就是要显示**。
|
||||
这条必须写在上面那条**之后**(同选择器强度下后者胜),
|
||||
否则"未 .is-semi → 藏原生"会把我们露出来的控件又藏回去。 */
|
||||
html.js .dt-input.is-native .dt-native { visibility: visible; }
|
||||
html.js .dt-input.is-native::after { display: none; } /* 去掉占位骨架 */
|
||||
html.js .dt-range.is-native-range .dt-input,
|
||||
html.js .dt-range.is-native-range > .dt-range-label { visibility: visible; }
|
||||
html.js .dt-range.is-native-range::before { display: none; } /* 去掉范围占位骨架 */
|
||||
/* 🔴🔴 v36:`.is-native` 态下,原生控件**必须可见**。
|
||||
用 `!important` 是**故意**的:v35 那次就是被
|
||||
`html.js .dt-input:not(.is-semi) .dt-native{visibility:hidden}`
|
||||
(特异性 0,3,0,比我那条覆盖规则更强/更早)又藏回了 hidden ——
|
||||
用户看到"一片空白、点不到",而探针因为用错判据(offsetParent)还"全绿"两轮。
|
||||
⇒ 这条用 `!important` 一次性钉死,别再让任何历史规则把它藏起来。 */
|
||||
html.js .dt-input.is-native .dt-native { visibility: visible !important; }
|
||||
html.js .dt-input.is-native::after { display: none !important; }
|
||||
html.js .dt-range.is-native-range .dt-input { visibility: visible !important; }
|
||||
html.js .dt-range.is-native-range > .dt-range-label { visibility: visible !important; }
|
||||
html.js .dt-range.is-native-range::before { display: none !important; }
|
||||
html.js .dt-range.is-native-range > .dt-input,
|
||||
html.js .dt-range.is-native-range > .field { display: flex !important; }
|
||||
/* ⚠️ v24 第 21 批 第 7 条:两端现在各自被 `.field` 包着,所以选择器要跟着下沉一层。
|
||||
写成 `> .dt-input` 的话这条**永远不会命中** → 未升级时原生控件照旧可见,
|
||||
与下面那块"占位骨架"叠在一起,会看到两套控件重影。 */
|
||||
@@ -738,27 +743,11 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
}
|
||||
.dt-input .dt-colon { color: var(--muted); font-weight: 600; }
|
||||
|
||||
/* 🔴 v35:v35 起时间不再是「时:分:秒」三个下拉,而是**一个**原生
|
||||
`<input type="time" step="1">`(24 小时制、精确到秒、点了就选、无需确认按钮)。
|
||||
三个下拉仍在 DOM 里(提交契约不变),但视觉上收起来。 */
|
||||
.dt-input .dt-merged { display: none !important; }
|
||||
.dt-input .dt-time-input {
|
||||
flex: 0 0 auto;
|
||||
padding: 9px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
|
||||
background: var(--surface); color: var(--ink); font: inherit;
|
||||
font-variant-numeric: tabular-nums; cursor: pointer;
|
||||
accent-color: var(--brand);
|
||||
}
|
||||
.dt-input .dt-time-input::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .65; }
|
||||
/* 范围里两端各占一行时,"日期 + 时间"两个框排在一起 */
|
||||
/* 🔴 v36:时间不再有"合成的时间框"(v35 试过 `<input type="time">`,已弃用)。
|
||||
`.dt-merged` / `.dt-time-input` 的规则一并删掉 —— 免得将来有人误以为
|
||||
还有"被收起来的下拉"。用户要的形态就是**日期 + 时/分下拉**直接显示。 */
|
||||
.dt-input.is-native-range { gap: 8px; }
|
||||
.dt-range.is-native-range { display: flex; flex-direction: column; gap: 10px; }
|
||||
/* 🔴 v35:`.dt-range.is-semi-range > .dt-input { display:none }` 是 **Semi 时代**
|
||||
"两端让位给那个一体化范围控件"的规则。v35 起两端**就是要显示**的,
|
||||
所以 native-range 必须把它覆盖回来 —— 否则端点被 display:none,用户又看不到。 */
|
||||
.dt-range.is-native-range > .dt-input,
|
||||
.dt-range.is-native-range > .field { display: flex; }
|
||||
.dt-range.is-native-range > .field { flex-direction: column; gap: 8px; }
|
||||
|
||||
/* 「以哪个生日为准」的两块:被选中的那块描一圈,一眼看出填的是哪边 */
|
||||
.bd-source { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
|
||||
|
||||
+50
-93
@@ -1406,64 +1406,47 @@
|
||||
这里只是把两端的日期框**露出来**、并把原来的 时/分/秒 三个下拉
|
||||
合成**一个**原生时间框(三个下拉仍留在 DOM 里,只是不单独显示)。 */
|
||||
|
||||
/* 把某个端点原来的「日期 + 三个下拉」变成「日期框 + 一个时间框」。
|
||||
值照旧写回那三个下拉(POST 契约),只是视觉上收成一个控件。 */
|
||||
function mergeTimeInputs(box) {
|
||||
|
||||
/* 🔴🔴 v36:**不合成任何"时间框",直接把原生的「日期 + 时/分下拉」露出来**。
|
||||
用户原话(第 2 次反馈,2026-10-09):
|
||||
「观影模块,开始时间和结束时间是空白的,无法选择,可以调整为,
|
||||
前面是日期,后面是时和分,只记录到时和分就行了。时和分,是下拉的。」
|
||||
|
||||
⚠️ v35 那一版为什么用户看到的是**一片空白**(我的锅):
|
||||
当时只给范围**组**加了 `.is-native-range`,**没给两个端点 `.dt-input`
|
||||
加 `.is-native`** —— 于是 CSS 里
|
||||
`html.js .dt-input:not(.is-semi) .dt-native { visibility: hidden; }`
|
||||
照样命中 ⇒ 控件 `visibility:hidden`(**占着 48px 高度、但完全不可见、点不到**)。
|
||||
🔴 更糟的是探针当时用 `offsetParent !== null` 判"可见" ——
|
||||
对 `visibility:hidden` 的元素它**仍然返回 true**,于是连续两轮都"通过"。
|
||||
⇒ 判可见**必须看 computed visibility**(_fb_b34_dtrange.py 已改)。
|
||||
|
||||
⇒ 本版走最笨但最稳的路:**不造新控件**,直接把原有的 日期 + 时/分 下拉显示出来。 */
|
||||
function revealEndpoint(box) {
|
||||
var native = box.querySelector('.dt-native');
|
||||
if (!native) { return; }
|
||||
var dateEl = native.querySelector('.dt-date');
|
||||
var hourEl = native.querySelector('.dt-hour');
|
||||
var minEl = native.querySelector('.dt-minute');
|
||||
var secEl = native.querySelector('.dt-second');
|
||||
if (!dateEl || !hourEl || !minEl) { return; }
|
||||
var timeEl = document.createElement('input');
|
||||
timeEl.type = 'time';
|
||||
timeEl.className = 'dt-time-input';
|
||||
timeEl.step = secEl ? '1' : '60'; // 要秒的槽位精确到秒
|
||||
timeEl.setAttribute('aria-label', '时间(24 小时制)');
|
||||
function syncFromSelects() {
|
||||
timeEl.value = (hourEl.value || '00') + ':' + (minEl.value || '00')
|
||||
+ (secEl ? ':' + (secEl.value || '00') : '');
|
||||
// ① 端点加 `.is-native`(压掉"未 .is-semi 就藏原生"那条规则)
|
||||
box.classList.add('is-native');
|
||||
// ② 去掉"秒"那一档(用户只要到分);紧跟它的那个冒号一起收掉
|
||||
var secEl = native.querySelector('.dt-second');
|
||||
if (secEl) {
|
||||
var prev = secEl.previousElementSibling;
|
||||
if (prev && prev.classList && prev.classList.contains('dt-colon')) { prev.remove(); }
|
||||
secEl.remove();
|
||||
}
|
||||
syncFromSelects();
|
||||
timeEl.addEventListener('change', function () {
|
||||
var m = (timeEl.value || '').match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
|
||||
if (!m) { return; }
|
||||
hourEl.value = pad(+m[1]);
|
||||
minEl.value = pad(+m[2]);
|
||||
if (secEl) { secEl.value = pad(+(m[3] || 0)); }
|
||||
});
|
||||
// 供"程序化改值时让时间框跟上"(如日程页点日历空白处预填)
|
||||
box.__fbSyncTime = syncFromSelects;
|
||||
// 插在日期框后面;三个下拉只留下标(提交用),视觉隐藏
|
||||
dateEl.parentNode.insertBefore(timeEl, dateEl.nextSibling);
|
||||
if (secEl) { secEl.classList.add('dt-merged'); }
|
||||
minEl.classList.add('dt-merged');
|
||||
hourEl.classList.add('dt-merged');
|
||||
// 原来把 时:分:秒 串起来的分隔符也一起收掉
|
||||
var nat = box.querySelector('.dt-native');
|
||||
Array.prototype.forEach.call(nat.querySelectorAll('.dt-colon, .dt-time'), function (n) {
|
||||
n.classList.add('dt-merged');
|
||||
});
|
||||
// ③ 显示(inline 覆盖,避免再被任何 CSS 规则藏住 —— 这是 v35 的教训)
|
||||
native.style.display = 'contents';
|
||||
native.style.visibility = 'visible';
|
||||
box.style.display = '';
|
||||
box.style.visibility = 'visible';
|
||||
box.classList.add('has-value');
|
||||
}
|
||||
|
||||
mergeTimeInputs(a);
|
||||
mergeTimeInputs(b);
|
||||
revealEndpoint(a);
|
||||
revealEndpoint(b);
|
||||
|
||||
// 两端各自**露出来**(不再藏)—— v35 的形态就是"两个 日期+时间 对"
|
||||
a.style.display = '';
|
||||
b.style.display = '';
|
||||
var na = a.querySelector('.dt-native'), nb = b.querySelector('.dt-native');
|
||||
if (na) { na.style.display = 'contents'; }
|
||||
if (nb) { nb.style.display = 'contents'; }
|
||||
a.classList.add('has-value');
|
||||
if (readDT(b).trim() !== ' ') { b.classList.add('has-value'); }
|
||||
|
||||
// 仅供 fbSetDT / fbUpgradeRange 的既有调用点使用(不再挂 Semi)
|
||||
group.__fbRemount = function () {
|
||||
if (a.__fbSyncTime) { a.__fbSyncTime(); }
|
||||
if (b.__fbSyncTime) { b.__fbSyncTime(); }
|
||||
};
|
||||
// 供 fbSetDT / fbUpgradeRange 的既有调用点使用(不再挂 Semi,无需同步)
|
||||
group.__fbRemount = function () {};
|
||||
|
||||
/* ⚠️ 原来这里会隐藏"端点最外层的包装"(因为 Semi 范围控件顶替了两端)。
|
||||
v35 起两端**就是要显示**的,所以不再隐藏任何东西 ——
|
||||
@@ -1485,51 +1468,25 @@
|
||||
var native = box.querySelector('.dt-native');
|
||||
if (!native) { return; }
|
||||
|
||||
/* 🔴 v35:单点时间控件同样**不再用 Semi**(图省事不再区分两条路径)——
|
||||
Semi 的 `dateTime` 面板同样没有可选的时间列(见 upgradeRange 里的实测)。
|
||||
这里只把原来的 时/分/秒 三个下拉合成**一个原生时间框**,
|
||||
日期框照旧是原生 `type="date"`,两者都**直接显示**。
|
||||
契约不变(*_date/_hour/_minute/_second 仍在 DOM 里提交)。 */
|
||||
if (!box.__fbMerged) {
|
||||
box.__fbMerged = true;
|
||||
var dateEl = native.querySelector('.dt-date');
|
||||
var hourEl = native.querySelector('.dt-hour');
|
||||
var minEl = native.querySelector('.dt-minute');
|
||||
var secEl = native.querySelector('.dt-second');
|
||||
if (dateEl && hourEl && minEl) {
|
||||
var timeEl = document.createElement('input');
|
||||
timeEl.type = 'time';
|
||||
timeEl.className = 'dt-time-input';
|
||||
timeEl.step = secEl ? '1' : '60';
|
||||
timeEl.setAttribute('aria-label', '时间(24 小时制)');
|
||||
function syncFromSelects() {
|
||||
timeEl.value = (hourEl.value || '00') + ':' + (minEl.value || '00')
|
||||
+ (secEl ? ':' + (secEl.value || '00') : '');
|
||||
}
|
||||
syncFromSelects();
|
||||
timeEl.addEventListener('change', function () {
|
||||
var m = (timeEl.value || '').match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
|
||||
if (!m) { return; }
|
||||
hourEl.value = pad(+m[1]);
|
||||
minEl.value = pad(+m[2]);
|
||||
if (secEl) { secEl.value = pad(+(m[3] || 0)); }
|
||||
});
|
||||
box.__fbSyncTime = syncFromSelects;
|
||||
dateEl.parentNode.insertBefore(timeEl, dateEl.nextSibling);
|
||||
if (secEl) { secEl.classList.add('dt-merged'); }
|
||||
minEl.classList.add('dt-merged');
|
||||
hourEl.classList.add('dt-merged');
|
||||
Array.prototype.forEach.call(native.querySelectorAll('.dt-colon, .dt-time'), function (n) {
|
||||
n.classList.add('dt-merged');
|
||||
});
|
||||
/* 🔴 v36:单点控件也**不合成时间框**了 —— 与范围那两端同一套做法
|
||||
(见上面 revealEndpoint 的说明)。直接把原生的「日期 + 时/分下拉」显示出来。
|
||||
⚠️ 秒那一档按用户要求去掉("只记录到时和分就行了")。 */
|
||||
if (!box.__fbRevealed) {
|
||||
box.__fbRevealed = true;
|
||||
var secEl1 = native.querySelector('.dt-second');
|
||||
if (secEl1) {
|
||||
var prev1 = secEl1.previousElementSibling;
|
||||
if (prev1 && prev1.classList && prev1.classList.contains('dt-colon')) { prev1.remove(); }
|
||||
secEl1.remove();
|
||||
}
|
||||
}
|
||||
// 直接露出原生(日期 + 时间)
|
||||
native.style.display = 'contents';
|
||||
// 端点加 .is-native(压掉"未 .is-semi 就藏原生"的规则)+ 直接显示
|
||||
box.classList.add('is-native');
|
||||
box.classList.add('has-value');
|
||||
// 程序化改值时让时间框跟上
|
||||
box.__fbRemount = function () { if (box.__fbSyncTime) { box.__fbSyncTime(); } };
|
||||
native.style.display = 'contents';
|
||||
native.style.visibility = 'visible';
|
||||
box.style.visibility = 'visible';
|
||||
box.__fbRemount = function () {};
|
||||
guardRequired(box);
|
||||
box.setAttribute('data-semi-done', '1');
|
||||
}
|
||||
|
||||
@@ -75,12 +75,11 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
||||
<?php if (!empty($errors['title'])): ?><span class="err"><?= e($errors['title']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* 用户 2026-10-04 第 7 条:起止是一个**范围**,应该由日期插件的
|
||||
范围选择方式一次选完,而不是并列两个控件(不好看、也多一步操作)。
|
||||
服务端仍然收 starts_at_* / ends_at_* 两组字段(契约不变),
|
||||
只是升级后由**一个** Semi dateTimeRange 统一填两端(见 app.js)。 */ ?>
|
||||
<?php /* 用户 2026-10-04 第 7 条:起止是一个**范围**。用户 2026-10-09 第 2 次:
|
||||
「前面是日期,后面是时和分,只记录到时和分就行了。时和分,是下拉的。」
|
||||
⇒ 形态 = 日期框 + 时/分两个下拉(**没有秒**,见 app.js upgradeRange 的说明)。 */ ?>
|
||||
<div class="field full dt-range">
|
||||
<label <?= $isEdit ? '' : 'for="starts_at_date"' ?>>开始 / 结束时间(精确到秒)</label>
|
||||
<label <?= $isEdit ? '' : 'for="starts_at_date"' ?>>开始 / 结束时间(时:分)</label>
|
||||
<?php $dtName = 'starts_at'; $dtValue = $dtStart; $dtId = 'starts_at_date';
|
||||
$dtRange = 'start';
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
|
||||
@@ -282,21 +282,15 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php /* 🔴 v36:详情页**不再提供"补照片"**(用户 2026-10-09 第 2 条:
|
||||
「观影详情页面,不需要设置补充照片的功能,都放在编辑页面就行了。」)
|
||||
⇒ 加照片统一走「编辑」按钮(编辑页的"观影照片"块可多选/可多次拖)。
|
||||
这里留一个入口链接,免得到详情页想加照片时找不到路。 */ ?>
|
||||
<?php if ($canWrite): ?>
|
||||
<div class="detail-section">
|
||||
<form method="post" enctype="multipart/form-data" action="<?= e(url('movies/photo_add')) ?>" class="photo-add">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<label>加照片</label>
|
||||
<?php /* v24 第 21 批 第 7 条:统一成「点击或拖动进行上传」的框。
|
||||
⚠️ 文件框 id 保持 `photo`(原来就叫这个),改名会让既有断言假红。 */
|
||||
$sdName = 'photo'; $sdId = 'photo'; $sdAccept = 'image/*';
|
||||
$sdMultiple = false; $sdRequired = true;
|
||||
require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
<div class="btn-row" style="margin-top:12px">
|
||||
<button type="submit" class="btn btn-ghost btn-sm">上传</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="tip" style="margin:0">
|
||||
要加照片请点上方「编辑」——「观影照片」在编辑页里一起传(可多选)。
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user