用户 2026-10-05 的 5 条。第 11 批交付后用户追问"确定这些需求都做完了?"—— 没有, 这 5 条是进一步要求。实现过程中查出 4 个真 bug。 #1 详情页删除按钮紧贴「开通账号」 上一版放在页面最下方独立区块(danger-zone),用户要「就放在开通账号按钮下面, 紧挨着,长度大小和开通账号一样,颜色改成红色」。→ 搬进「登录账号」卡片内 (.del-inline + 细分隔线 + .btn-block-lg)。 线上几何实测:同一张卡片、左边缘对齐 255==255、宽度一致 990==990、红色 rgb(192,83,63)。 #2 日期控件三修(两个真 bug + 一次闪烁) · 灰底:Semi **外层** .semi-datepicker-input 自带 rgba(46,50,56,.05)、高 32px; 内层 wrapper 白底 40px → 灰的露在外面。实测「灰底元素」= 1 个 → 修后 0 个。 · 不垂直居中:外层 32px < 内层 40px,实测 gapTop=1 / **gapBottom=17**(文字贴顶) → 修后 gapTop=1 / gapBottom=1。 · 闪烁:实测首 **200ms** 原生输入框可见(要等 3.4MB React 解析)。 用 no-js/js 二态:<html class="no-js"> + head 内联脚本立刻换 js + CSS `html.js .dt-input:not(.is-semi) .dt-native { visibility:hidden }` (配同高骨架 ::after 防布局跳动)。实测「出现过原生可见」= False。 #3 日历跳转保留视图 + 下拉日历 原来写死 view=day(在月视图点跳转会被踢进日视图)。→ 表单带当前 $view(月还带 ym); 选择器从"居中弹窗"改成**按钮下方下拉** .cal-jump-drop。 实测:月视图跳 8/15 仍在月视图且是 8 月;周视图跳 8/15 → 落 8/10 那一周; 日视图跳 8/15 → 显示 8/15。 #4 传统节日 + 二十四节气两个系统本(Schema v18) Holiday::SOLAR_TERMS(24 项 C 常数近似公式 + 例外表)+ traditionalRange (农历推导;除夕用"次日是否正月初一"反推,写死腊月廿九会有一半年份错)。 实测 2026:节气 24 条全对(清明 4/5、冬至 12/22);春节 2/17、除夕 2/16、中秋 9/25。 ⚠️ ENUM 必须显式 ALTER(对已存在的表不生效)——同一坑第二次踩,VERSION 17→18。 #5 财务(一个真 bug + 一个假起点 + 一个真 bug) · chart_line 读 `cents` 而 timeline() 返回 `net` → 键名不匹配,**总资产图每点读成 0** (正是用户说的"图上是 0")。修:兼容 cents ?? net ?? asset。 实测 timeline net=800000(8000 元),线上 polyline 有真实起伏。 · 去掉 timeline 人为插的"前一天=0"假起点(用户:"总资产不应该是 0, 应该是所有账目创建时的金额的总数")。 · ppSetSelected 只用 document.querySelector 按名字找,而财务页有**两个** person_id 组件(「记一笔」+ 修改弹框)→ 永远灌到第一个。加 root 参数、调用方传 panel。 验证:e2e 786/0、交互 92/0、升级 129/0(+3 条 v18 断言)、batch_e 91/0、batch10 46/0、 batch9 38/0、picker_unify 28/0、picker_lazy 11/0、crop 17/0、formbloat 9/0、semi 27/0、 mobile 688/0、drag 5/0、geom 5/5;第 12 批专项:财务 7/0、日历 19/0。 部署静态断言新增 5 组(含反向验证:注入回退 → FAIL=1)。 线上自检 19/19;线上真实账号验收 **30/0**(含真实浏览器几何量删除按钮位置)。
98 lines
6.1 KiB
PHP
98 lines
6.1 KiB
PHP
<?php
|
||
/**
|
||
* 「日期 + 24 小时制时:分:秒」组合输入控件(从 TripLog 的 _datetime.php 移植)。
|
||
*
|
||
* ⚠️ 为什么不用 <input type="datetime-local">:
|
||
* 它的显示格式由**浏览器 UI 语言 / 系统时间格式**决定,<html lang> 与元素 lang
|
||
* 都压不过它。中文环境实测会渲染成「下午 03:30」,用户很容易把上下午填反;
|
||
* 而且它只有时:分,**没有秒**。拆成「原生日期选择器 + 三个下拉」后,
|
||
* 24 小时制与"精确到秒"都由控件本身保证,浏览器改不掉。
|
||
*
|
||
* 日期部分仍是原生 `<input type="date">`(app.js 里的 showPicker 脚本对它同样生效,
|
||
* 点输入框任意位置都能弹出日历 —— 这正是"整个输入框点击都可以弹出"的落点)。
|
||
*
|
||
* 用法(include 前 set 好这些变量;模板里只读它们):
|
||
* $dtName 字段基名,如 'birthday_solar' → 产出 *_date / _hour / _minute / _second
|
||
* $dtValue 可空;'Y-m-d H:i:s' / 'Y-m-d H:i' / 'Y-m-d'。服务端用 read_dt_input($dtName) 取值
|
||
* $dtRequired 日期是否必填(默认 false)
|
||
* $dtId 可选;日期控件的 id(供 <label for>)
|
||
* $dtSeconds 是否要"秒"这一档(默认 true;生日要精确到时分秒)
|
||
*/
|
||
$dtName = (string)($dtName ?? 'time');
|
||
$dtValue = $dtValue ?? null;
|
||
$dtRequired = (bool)($dtRequired ?? false);
|
||
$dtId = (string)($dtId ?? '');
|
||
$dtSeconds = (bool)($dtSeconds ?? true);
|
||
/* 用户 2026-10-04 第 7 条原话:「所有的时间选择,都已更换新插件,但是有个问题,
|
||
如果是选择一个时间范围的情况,就是说,有开始时间,有结束时间,应该用这个日期插件的
|
||
时间范围选择方式,而不是将这个范围拆成两个,这样不好看,也多一步操作。」
|
||
|
||
→ 有起止两端的场景不再并列两个控件,而是**一个范围控件**。
|
||
实现方式是"服务端照旧渲染两个原生控件(表单契约/e2e 都不动),
|
||
由 app.js 在升级时识别成一对、只挂一个 Semi 范围选择器"。所以这里只加标记:
|
||
$dtRange = 'start' | 'end' —— 这对控件属于同一个范围(调用方还要用
|
||
<div class="dt-range"> 把两者包在一起,见 movies/form.php 等)。
|
||
留空 = 普通单点控件(默认,行为完全不变)。 */
|
||
$dtRange = (string)($dtRange ?? '');
|
||
|
||
// ⚠️⚠️ 临时变量**一律带 dt 前缀**,一个都不能漏:这里原来写成
|
||
// `$t = strtotime(...)`,把宿主模板里表示「这条任务 / 这条记录」的 `$t`
|
||
// 覆盖成了 int —— 于是 include 之后凡是读 `$t['id']`、`$t['status']` 的地方
|
||
// 全部拿到 null,子任务列表空、评论不渲染、@高亮消失,而且**不报任何错**
|
||
// (PHP 8 对 "Trying to access array offset on value of type int" 只 Warning)。
|
||
// tasks/show.php 上就是这么被 e2e 抓到的(2026-10-02)。
|
||
$dtTs = (!empty($dtValue) && ($dtTsTmp = strtotime(trim((string)$dtValue)))) ? $dtTsTmp : false;
|
||
$dtDate = $dtTs ? date('Y-m-d', $dtTs) : '';
|
||
$dtHour = $dtTs ? (int)date('G', $dtTs) : 0; // G = 0..23,天然 24 小时制
|
||
$dtMin = $dtTs ? (int)date('i', $dtTs) : 0;
|
||
$dtSec = $dtTs ? (int)date('s', $dtTs) : 0;
|
||
?>
|
||
<?php /* v24 第 9 批 #9:所有日期选择换 Semi Design DatePicker(用户点名,样式与宣纸主题统一)。
|
||
策略是**渐进增强**,原生四字段原样保留:
|
||
· 表单契约(*_date/_hour/_minute/_second → read_dt_input())不变,服务端零改动;
|
||
· Semi 资源(3.4 MB JS)只有页面真出现 .dt-input 才由 app.js 懒加载;
|
||
· 加载成功 → 隐藏 .dt-native、在 .dt-semi 里挂 DatePicker,值回写原字段;
|
||
· 加载失败/断网 → 原生控件原样可见可用,页面不会坏。 */
|
||
$dtCfg = ['name' => $dtName, 'seconds' => $dtSeconds, 'required' => $dtRequired,
|
||
'id' => $dtId, 'range' => $dtRange];
|
||
?>
|
||
<div class="dt-input<?= $dtRange !== '' ? ' dt-part' : '' ?><?= $dtRange === 'start' ? ' is-pending' : '' ?>"
|
||
data-dt="<?= e(json_encode($dtCfg, JSON_UNESCAPED_UNICODE | JSON_HEX_APOS | JSON_HEX_QUOT)) ?>">
|
||
<span class="dt-semi" aria-hidden="true"></span>
|
||
<span class="dt-native">
|
||
<input type="date" class="dt-date" name="<?= e($dtName) ?>_date"
|
||
<?= $dtId !== '' ? 'id="' . e($dtId) . '"' : '' ?>
|
||
value="<?= e($dtDate) ?>"<?= $dtRequired ? ' required' : '' ?>
|
||
aria-label="日期">
|
||
<span class="dt-time">
|
||
<select class="dt-hour" name="<?= e($dtName) ?>_hour" aria-label="时(24 小时制)">
|
||
<?php for ($dtH = 0; $dtH < 24; $dtH++): $hv = sprintf('%02d', $dtH); ?>
|
||
<option value="<?= $hv ?>"<?= $dtH === $dtHour ? ' selected' : '' ?>><?= $hv ?></option>
|
||
<?php endfor; ?>
|
||
</select>
|
||
<span class="dt-colon" aria-hidden="true">:</span>
|
||
<select class="dt-minute" name="<?= e($dtName) ?>_minute" aria-label="分">
|
||
<?php /* ⚠️ 循环变量一律带 dt 前缀:这里曾被写成 $m,把宿主模板里
|
||
表示「观影记录」的 $m 覆盖成了 int —— 编辑页的简介/备注回填
|
||
被静默清空、取消链接变 id=0,还不止一个 Warning(2026-09-30 e2e 抓到)。 */ ?>
|
||
<?php for ($dtM = 0; $dtM < 60; $dtM++): $mv = sprintf('%02d', $dtM); ?>
|
||
<option value="<?= $mv ?>"<?= $dtM === $dtMin ? ' selected' : '' ?>><?= $mv ?></option>
|
||
<?php endfor; ?>
|
||
</select>
|
||
<?php if ($dtSeconds): ?>
|
||
<span class="dt-colon" aria-hidden="true">:</span>
|
||
<select class="dt-second" name="<?= e($dtName) ?>_second" aria-label="秒">
|
||
<?php for ($dtS = 0; $dtS < 60; $dtS++): $sv = sprintf('%02d', $dtS); ?>
|
||
<option value="<?= $sv ?>"<?= $dtS === $dtSec ? ' selected' : '' ?>><?= $sv ?></option>
|
||
<?php endfor; ?>
|
||
</select>
|
||
<?php endif; ?>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<?php /* 用完即清,避免残留变量影响下一次 include */ unset(
|
||
$dtName, $dtValue, $dtRequired, $dtId, $dtSeconds, $dtCfg, $dtRange,
|
||
$dtTs, $dtTsTmp, $dtDate, $dtHour, $dtMin, $dtSec,
|
||
$dtH, $dtM, $dtS, $hv, $mv, $sv
|
||
); ?>
|