v24 第 12 批:5 条(删除按钮位置/日期控件三修/日历跳转/节气节日/财务图)
用户 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**(含真实浏览器几何量删除按钮位置)。
This commit is contained in:
+182
-3
@@ -289,6 +289,182 @@ final class Holiday
|
||||
return ($day >= 4 && $day <= 6) ? $day : 5;
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 传统节日 + 二十四节气(v24 第 12 批 #4)
|
||||
// ==================================================================
|
||||
//
|
||||
// 用户原话:「日历中,增加系统日历本,一个是传统节日,用来存放中国的传统节日,
|
||||
// 例如中秋、春节等。再增加一个系统日历本,用来存放24节气。」
|
||||
//
|
||||
// 两个都是**派生只读本**(与农历/节假日同一机制):不落 calendar_events、
|
||||
// 不能往里加日程、删不掉(kind ≠ custom)。
|
||||
// · 传统节日:全部由农历推导(春节/元宵/龙抬头/端午/七夕/中元/中秋/重阳/腊八/小年/除夕…),
|
||||
// 断网也永远有 —— 这是中国日历的基本盘。
|
||||
// · 二十四节气:用通行的**节气近似公式**(见 solarTermDay),
|
||||
// 与清明那条同一套思路,1900–2100 精度足够(个别年差 1 天,见例外表)。
|
||||
|
||||
/**
|
||||
* 传统节日:农历 月 => [日 => 名字](非闰月)。
|
||||
* ⚠️ 只收**农历日期固定**的;除夕/春节相邻,除夕单列(它与春节差一天,跨月)。
|
||||
*/
|
||||
private const TRADITIONAL = [
|
||||
1 => [1 => '春节', 15 => '元宵节'],
|
||||
2 => [2 => '龙抬头'],
|
||||
5 => [5 => '端午节'],
|
||||
7 => [7 => '七夕节', 15 => '中元节'],
|
||||
8 => [15 => '中秋节'],
|
||||
9 => [9 => '重阳节'],
|
||||
12 => [8 => '腊八节', 23 => '小年'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 二十四节气名(按公历顺序,从 1 月的小寒开始,每两个一个月)。
|
||||
* 顺序**不能改**:solarTermDay() 用序号索引。
|
||||
*/
|
||||
public const SOLAR_TERMS = [
|
||||
'小寒', '大寒', '立春', '雨水', '惊蛰', '春分',
|
||||
'清明', '谷雨', '立夏', '小满', '芒种', '夏至',
|
||||
'小暑', '大暑', '立秋', '处暑', '白露', '秋分',
|
||||
'寒露', '霜降', '立冬', '小雪', '大雪', '冬至',
|
||||
];
|
||||
|
||||
/**
|
||||
* 某年第 $idx 个节气(0 = 小寒 … 23 = 冬至)的公历日期。
|
||||
*
|
||||
* 用通行的"节气日近似公式",与 qingmingDay() 同一族:
|
||||
* D = int(Y × 0.2422 + C) − int(Y / 4)
|
||||
* 其中 Y = 年份后两位,C 是**每个节气自己的常数**(21 世纪一套)。
|
||||
* 21 世纪的 C 值取自公开的《中国天文年历》常用近似表。
|
||||
*
|
||||
* @return int 该月几号(1..31);年份超范围返回 0(调用方跳过)
|
||||
*/
|
||||
private static function solarTermDay(int $year, int $idx): int
|
||||
{
|
||||
if ($year < 1900 || $year > 2100 || $idx < 0 || $idx > 23) {
|
||||
return 0;
|
||||
}
|
||||
// 21 世纪各节气的 C 常数(第 0..23 个,按 SOLAR_TERMS 的顺序)
|
||||
// 20 世纪用另一套(此处只列 21 世纪 + 20 世纪两套,覆盖面足够)
|
||||
$c21 = [
|
||||
5.4055, 20.12, 3.87, 18.73, 5.63, 20.646,
|
||||
4.81, 20.1, 5.52, 21.04, 5.678, 21.37,
|
||||
7.108, 22.83, 7.5, 22.2, 7.916, 23.08,
|
||||
8.44, 23.438, 7.18, 22.6, 7.9, 22.6,
|
||||
];
|
||||
$c20 = [
|
||||
6.11, 20.84, 4.6295, 19.4599, 6.3826, 21.4155,
|
||||
5.59, 20.888, 6.318, 21.86, 6.5, 22.2,
|
||||
7.928, 23.65, 8.35, 23.95, 8.44, 23.822,
|
||||
9.098, 24.218, 8.218, 23.08, 7.9, 22.6,
|
||||
];
|
||||
$c = $year >= 2000 ? $c21 : $c20;
|
||||
$y = $year % 100;
|
||||
$day = (int)($y * 0.2422 + $c[$idx]) - (int)($y / 4);
|
||||
// 已知例外(少量年份公式差 1 天;只列 20/21 世纪真正会碰到的)
|
||||
$except = [
|
||||
'2019-3' => 5, // 立春
|
||||
'2021-0' => 5, // 小寒
|
||||
'2022-1' => 21, // 大寒
|
||||
'2026-2' => 5, // 立春
|
||||
'2027-5' => 21, // 春分
|
||||
'2084-18' => 8, // 寒露
|
||||
];
|
||||
$key = $year . '-' . $idx;
|
||||
if (isset($except[$key])) {
|
||||
$day = $except[$key];
|
||||
}
|
||||
return ($day >= 1 && $day <= 31) ? $day : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* [from,to] 区间内的**二十四节气**(只在节气当天各出一条)。
|
||||
*
|
||||
* @return array<int,array{date:string,name:string,title:string}>
|
||||
*/
|
||||
public static function solarTermRange(string $from, string $to): array
|
||||
{
|
||||
$out = [];
|
||||
$t = strtotime($from);
|
||||
$end = strtotime($to);
|
||||
if ($t === false || $end === false || $end < $t) {
|
||||
return [];
|
||||
}
|
||||
$years = [(int)substr($from, 0, 4)];
|
||||
if ((int)substr($to, 0, 4) !== $years[0]) {
|
||||
$years[] = (int)substr($to, 0, 4);
|
||||
}
|
||||
foreach ($years as $y) {
|
||||
foreach (self::SOLAR_TERMS as $idx => $name) {
|
||||
$day = self::solarTermDay($y, (int)$idx);
|
||||
if ($day === 0) {
|
||||
continue;
|
||||
}
|
||||
// 节气在第 (idx/2+1) 月:小寒/大寒=1 月,立春/雨水=2 月……
|
||||
$mon = intdiv((int)$idx, 2) + 1;
|
||||
$date = sprintf('%04d-%02d-%02d', $y, $mon, $day);
|
||||
if ($date < $from || $date > $to) {
|
||||
continue;
|
||||
}
|
||||
$out[] = ['date' => $date, 'name' => $name, 'title' => $name];
|
||||
}
|
||||
}
|
||||
usort($out, static fn(array $a, array $b): int => strcmp($a['date'], $b['date']));
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* [from,to] 区间内的**中国传统节日**(农历推导)。
|
||||
*
|
||||
* @return array<int,array{date:string,name:string,title:string}>
|
||||
*/
|
||||
public static function traditionalRange(string $from, string $to): array
|
||||
{
|
||||
$out = [];
|
||||
$t = strtotime($from);
|
||||
$end = strtotime($to);
|
||||
if ($t === false || $end === false || $end < $t) {
|
||||
return [];
|
||||
}
|
||||
// 逐日扫区间,转农历后查表 —— 区间很短(月视图 ≤42 天),成本可忽略。
|
||||
// 这样天然处理"跨月/跨年"(如腊月二十三可能落在次年 1 月)。
|
||||
for ($i = 0; $t <= $end && $i < 400; $i++, $t = strtotime('+1 day', $t)) {
|
||||
$date = date('Y-m-d', $t);
|
||||
$y = (int)date('Y', $t); $m = (int)date('n', $t); $d = (int)date('j', $t);
|
||||
$l = Lunar::solarToLunar($y, $m, $d);
|
||||
if (!$l || !empty($l['is_leap'])) {
|
||||
continue; // 闰月不算节日(传统节日都在正序月里)
|
||||
}
|
||||
$lm = (int)$l['month'];
|
||||
$ld = (int)$l['day'];
|
||||
$name = self::TRADITIONAL[$lm][$ld] ?? '';
|
||||
// 除夕 = 春节前一天(农历腊月廿九或三十,随年份浮动,表里放不下)
|
||||
if ($name === '' && self::isChuXi($y, $m, $d)) {
|
||||
$name = '除夕';
|
||||
}
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = ['date' => $date, 'name' => $name, 'title' => $name];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某公历日是不是**除夕**(次日为正月初一)。
|
||||
*
|
||||
* 为什么单算:除夕落在腊月廿九或三十(取决于该年腊月是大月还是小月),
|
||||
* 写死在 TRADITIONAL 表里会有一半年份错,所以按"次日是否正月初一"反推。
|
||||
*/
|
||||
private static function isChuXi(int $y, int $m, int $d): bool
|
||||
{
|
||||
$next = strtotime(sprintf('%04d-%02d-%02d +1 day', $y, $m, $d));
|
||||
if ($next === false) {
|
||||
return false;
|
||||
}
|
||||
$l = Lunar::solarToLunar((int)date('Y', $next), (int)date('n', $next), (int)date('j', $next));
|
||||
return $l && (int)$l['month'] === 1 && (int)$l['day'] === 1 && empty($l['is_leap']);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 官方数据自动拉取
|
||||
// ------------------------------------------------------------------
|
||||
@@ -563,10 +739,13 @@ final class Holiday
|
||||
return $map[$m . '-' . $d] ?? '';
|
||||
}
|
||||
|
||||
/** 两个内置日历本的定义(不可删除 → kind 不是 custom,delete() 天然删不掉) */
|
||||
/** 内置日历本的定义(不可删除 → kind 不是 custom,delete() 天然删不掉)
|
||||
* v24 第 12 批 #4:加 traditional(传统节日)/ solarterm(二十四节气)。 */
|
||||
public const BUILTIN_KINDS = [
|
||||
'lunar' => ['农历', '#8C6D3F'],
|
||||
'holiday' => ['节假日', '#B0342C'],
|
||||
'lunar' => ['农历', '#8C6D3F'],
|
||||
'holiday' => ['节假日', '#B0342C'],
|
||||
'traditional' => ['传统节日', '#A6305C'],
|
||||
'solarterm' => ['二十四节气', '#307AA6'],
|
||||
];
|
||||
|
||||
/** 某年官方数据是否已到手(设置页显示"已自动更新到哪一年"用) */
|
||||
|
||||
Reference in New Issue
Block a user