节点表单:参与人直接勾选 + 起点终点记录详细地址(批 13)
参与人(用户反馈:不想等全部填完、保存之后才回详情页加人):
- 节点表单里新增「参与人(直接勾选)」区块:家庭成员按胶囊复选框列出,
保存即生效;编辑时预勾选已有人。
- Members::syncForNode() 只做增删,**不动留下来的那些人的情绪 / 签到 /
角色标签**(重新勾一遍不该把之前记的心情清掉)。
- 安全性:新增走 Members::save(),逐条校验「用户属于该活动所在家庭」,
夹带别家 user_id 进不来;只有请求带 members_present=1(表单确实渲染了
参与人区块)才同步 —— 否则没这个字段的请求会把已有成员清空。
起点/终点记录详细地址(用户反馈:高德联想只返回简化地名):
- 联想项选中后不再是「青岛北站」就完事:
① 有坐标 → 逆地理编码(AMap.Geocoder.getAddress)取该点的
formattedAddress;
② 没坐标 → 按「区县 + 名称」正查,顺带把坐标补回来。
- 详细地址写进 start/end_location 的 address 字段,输入框下方实时显示
「详细地址:…」,节点详情页本来就展示这一项。
- 路线地图补点(只有地名没坐标时)改为**优先用详细地址**查询,
比简化地名定位准。
已验证:本地全链 12 步全绿(渲染检查 350 项、真 AMap 浏览器验证 11 项、
表单交互 75 项);远端 e2e 350 项 0 失败(新增 10 项:勾选落库 / 跨家庭
拒绝 / 取消勾选移出 / 情绪不被清 / 无标记不动参与人);部署静态断言、
双 PHP 渲染、内容断言全过后提交。
This commit is contained in:
@@ -95,8 +95,13 @@ class NodeController
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($data);
|
||||
|
||||
$nodeId = (int)$db->lastInsertId();
|
||||
|
||||
// 节点必须先有 id 才能挂附件,所以附件在插入之后处理
|
||||
$extra = $this->handleNodeUploads((int)$db->lastInsertId());
|
||||
$extra = $this->handleNodeUploads($nodeId);
|
||||
|
||||
// 表单里勾的参与人(members_present=1 才认,避免没渲染这个区块的请求误清)
|
||||
$extra .= $this->syncMembers($nodeId);
|
||||
|
||||
flash('error', '节点已保存' . $extra);
|
||||
redirect('activities/show', ['id' => (int)$activity['id']]);
|
||||
@@ -148,6 +153,9 @@ class NodeController
|
||||
// 再处理这次新选的照片
|
||||
$extra = $this->handleNodeUploads((int)$node['id']);
|
||||
|
||||
// 参与人在表单里直接勾(编辑时重新勾选不会清掉已记的情绪/签到)
|
||||
$extra .= $this->syncMembers((int)$node['id']);
|
||||
|
||||
$msg = '保存成功';
|
||||
if ($removed > 0) {
|
||||
$msg .= ",删除了 {$removed} 个附件";
|
||||
@@ -358,6 +366,18 @@ class NodeController
|
||||
|
||||
$isEdit = $node !== null;
|
||||
|
||||
/* 参与人在表单里直接勾(不必先保存再回详情页加人)。
|
||||
编辑时把已有人带过去预勾选;新建时空着。 */
|
||||
$familyUsers = Members::familyUsers((int)$activity['family_id']);
|
||||
$nodeMemberIds = [];
|
||||
if ($isEdit) {
|
||||
$s = $db->prepare('SELECT user_id FROM node_members WHERE trip_node_id = ?');
|
||||
$s->execute([(int)$node['id']]);
|
||||
foreach ($s->fetchAll() as $row) {
|
||||
$nodeMemberIds[] = (int)$row['user_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'modeLabels' => TravelModeController::MODE_LABELS,
|
||||
'activity' => $activity,
|
||||
@@ -367,6 +387,8 @@ class NodeController
|
||||
'planNodes' => $planNodes,
|
||||
'node' => $node,
|
||||
'isEdit' => $isEdit,
|
||||
'familyUsers' => $familyUsers,
|
||||
'nodeMemberIds' => $nodeMemberIds,
|
||||
// 编辑时把已有照片带进表单:能看、能勾选删除。新建时节点还没 id,
|
||||
// 附件挂不上去,所以只能是空数组(模板里会提示先保存)。
|
||||
'attachments' => $isEdit ? Attachments::forTarget(Attachments::TARGET_NODE, (int)$node['id']) : [],
|
||||
@@ -583,6 +605,36 @@ class NodeController
|
||||
* ⚠️ 必须逐条校验归属:attachments 是「target_type + target_id」的软关联,
|
||||
* 表单里的 id 是用户可以随便改的,不校验就等于给了跨家庭删文件的入口。
|
||||
*/
|
||||
/**
|
||||
* 表单里勾的参与人 → 同步成节点成员(只增删,不动已记的情绪/签到)。
|
||||
*
|
||||
* 只在表单确实渲染了参与人区块时(members_present=1)才同步:
|
||||
* 没有渲染却收到空数组的话,会把节点已有成员全删掉。
|
||||
* 跨家庭的 user_id 由 Members::save() 逐条拦掉。
|
||||
*/
|
||||
private function syncMembers(int $nodeId): string
|
||||
{
|
||||
if (($_POST['members_present'] ?? '') !== '1') {
|
||||
return '';
|
||||
}
|
||||
$picked = $_POST['members'] ?? [];
|
||||
if (!is_array($picked)) {
|
||||
return '';
|
||||
}
|
||||
$res = Members::syncForNode($nodeId, $picked);
|
||||
if ($res['added'] === 0 && $res['removed'] === 0) {
|
||||
return '';
|
||||
}
|
||||
$parts = [];
|
||||
if ($res['added'] > 0) {
|
||||
$parts[] = "加入 {$res['added']} 位参与人";
|
||||
}
|
||||
if ($res['removed'] > 0) {
|
||||
$parts[] = "移出 {$res['removed']} 位参与人";
|
||||
}
|
||||
return ',' . implode(',', $parts);
|
||||
}
|
||||
|
||||
private function deleteCheckedAttachments(int $nodeId): int
|
||||
{
|
||||
$ids = $_POST['delete_attachments'] ?? [];
|
||||
|
||||
Reference in New Issue
Block a user