v24 第 9 批:名册九条(分组按钮/加入名册/生日月日/地址四级/千人性能/拖动修复/删除人员/返回名册/Semi DatePicker)

- #131 名册卡片加「分组」按钮(details+radio,显示当前分组,选中即改)
- #132 修复「从已有人里加入名册」无反应
- #133 生日只显示月和日(阳历 MM-DD / 农历中文名),PersonQuery::birthdaysOf 取代 birthdayOf
- #134 地址结构化为 国家/省/市/区+详细:regions 表(3429 行全国区划)+ Region.php +
  ?r=regions 级联接口 + 表单 .addr-grid + 老地址回填(Schema v16)
- #135 千人名册性能:.person content-visibility:auto + 名册页 ob_gzhandler
  (实测 服务端 1.15s/3.8MB → 0.17s/87KB;客户端导航→load 1.35s → 0.37s)
- #136 拖动排序改 Pointer Events(Chrome 下 HTML5 DnD 在 DOM 变动时断)
- #137 个人详情页底部「删除人员」
- #138 编辑资料页加「返回名册」
- #139 所有日期选择换 Semi Design DatePicker(渐进增强:原生四字段保 POST 契约,
  懒加载 vendor semi UMD,值回写原字段;工作经历 type=month 一并升级;宣纸主题对齐)

验证:e2e 785/0、batch9 37/0、upgrade 126/0、picker 28/0、mobile 688/0、interact 97/0、
batch_e 91/0、import 21/0、douban 22/0、drag 5/0、semi-integrate 28/0、semi-probe 17/0、
geom 5/5;部署后线上自检 16/16(含 4 个 semi 资源 200 + regions 路由)。
This commit is contained in:
2026-10-04 08:56:40 +08:00
parent 864e281265
commit 52cc1ecccb
19 changed files with 1629 additions and 154 deletions
+61 -4
View File
@@ -86,11 +86,10 @@ final class ContactImporter
}
// ---- ③ 没命中:新建 ----
$d = [
$d = array_merge([
'name' => $name !== '' ? $name : $phone,
'phone' => $phone,
'email' => (string)($r['email'] ?? ''),
'address' => (string)($r['address'] ?? ''),
'org' => (string)($r['org'] ?? ''),
// 职务 + 职称合成一列(与 PersonJob::joinTitle 同一口径:
// 两个都有时用 " · " 分开,只有一个就只用那个)
@@ -109,7 +108,7 @@ final class ContactImporter
'birthday_lunar' => '',
'birthday_lunar_leap' => 0,
'birthday_id_card' => '',
];
], self::addrFields($r));
try {
$pid = Person::create($familyId, $d);
} catch (\Throwable $ex) {
@@ -172,6 +171,53 @@ final class ContactImporter
*
* @return string[] 这次补上了哪些(中文标签,用来告诉用户"改了什么")
*/
/**
* 把一条导入记录里的地址整理成 persons 的五个字段。
*
* 分两种来源:
* · vCard 的 ADR **本来就是分段的**(街道/城市/省/国家各占一段)→ 直接用,
* 再用 Region::parse() 从街道那一段里认出「区 / 县」;
* · CSV 那种一整串自由文本 → 整串交给 Region::parse() 逐级前缀匹配拆开。
*
* ⚠️ 认不出来的部分**一律留在 address(详细地址)里**,绝不丢:
* 结构化是为了"以后能按省找人",不是为了让地址变短。
*
* @return array{address:string,addr_country:string,addr_province:string,addr_city:string,addr_district:string}
*/
private static function addrFields(array $r): array
{
$country = trim((string)($r['addr_country'] ?? ''));
$province = trim((string)($r['addr_province'] ?? ''));
$city = trim((string)($r['addr_city'] ?? ''));
$district = trim((string)($r['addr_district'] ?? ''));
$detail = trim((string)($r['address'] ?? ''));
if ($province === '' && $city === '' && $detail !== '') {
// 一整串自由文本 → 逐级前缀匹配
$p = Region::parse($detail);
$country = $country !== '' ? $country : (string)($p['country'] ?? '');
$province = (string)($p['province'] ?? '');
$city = (string)($p['city'] ?? '');
$district = (string)($p['district'] ?? '');
$detail = (string)($p['detail'] ?? '');
} elseif ($detail !== '' && $district === '') {
// 已经有省/市(vCard 给的),从剩下的街道里再认一次区县
$p = Region::parse($detail);
if (($p['district'] ?? null) !== null) {
$district = (string)$p['district'];
$detail = (string)($p['detail'] ?? '');
}
}
return [
'address' => $detail,
'addr_country' => $country,
'addr_province' => $province,
'addr_city' => $city,
'addr_district' => $district,
];
}
private static function fillMissing(array $person, array $r, int $meId): array
{
$pid = (int)$person['id'];
@@ -179,9 +225,12 @@ final class ContactImporter
$labels = []; // 给人看的"补了什么"
// ---- ① persons 上的标量列:他空着才补 ----
$addr = self::addrFields($r);
$scalar = [
'email' => ['邮箱', (string)($r['email'] ?? ''), 200],
'address' => ['住址', (string)($r['address'] ?? ''), 200],
// ⚠️ 地址要走 addrFields() 拆过的那份:直接拿 $r['address'] 会把
// vCard 里"省+市+街道"拼成的一整串原样塞进详细地址,等于没结构化。
'address' => ['住址', $addr['address'], 200],
'org' => ['工作单位', (string)($r['org'] ?? ''), 120],
'note' => ['备注', (string)($r['note'] ?? ''), 2000],
];
@@ -200,6 +249,14 @@ final class ContactImporter
$cols['`title` = ?'] = mb_substr($newTitle, 0, 120, 'UTF-8');
$labels[] = '职务/职称';
}
// 地址的四级结构:同样**只补空着的**(他已经在表单里选过省市区就不动)
foreach (['addr_country' => '国家', 'addr_province' => '省',
'addr_city' => '市', 'addr_district' => '区/县'] as $col => $label) {
if (trim((string)($person[$col] ?? '')) === '' && trim($addr[$col]) !== '') {
$cols["`$col` = ?"] = mb_substr(trim($addr[$col]), 0, 60, 'UTF-8');
$labels[] = $label;
}
}
// 生日:他一个生日都没有时才补。有生日就绝不动 —— 生日是强身份信息,错了很难发现。
$hasBd = trim((string)Birthday::solarOf($person)) !== ''
|| trim((string)($person['birthday_lunar'] ?? '')) !== '';