节点表单:参与人直接勾选 + 起点终点记录详细地址(批 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:
+129
-1
@@ -44,6 +44,26 @@ $eLoc = [
|
||||
$startTimeVal = old_dt('start_time', $n['start_time'] ?? null);
|
||||
$endTimeVal = old_dt('end_time', $n['end_time'] ?? null);
|
||||
|
||||
/* 参与人在表单里直接勾(不必先保存再回详情页加人)。
|
||||
回填优先:上次提交失败时,用刚勾的而不是库里的。 */
|
||||
$familyUsers = $familyUsers ?? [];
|
||||
$nodeMemberIds = $nodeMemberIds ?? [];
|
||||
$oldMembers = isset($_SESSION['_old']) && is_array($_SESSION['_old'])
|
||||
? (array)($_SESSION['_old']['members'] ?? [])
|
||||
: null;
|
||||
$pickedMembers = static function () use ($oldMembers, $nodeMemberIds): array {
|
||||
$src = $oldMembers ?? $nodeMemberIds;
|
||||
$out = [];
|
||||
foreach ((array)$src as $v) {
|
||||
$v = (int)$v;
|
||||
if ($v > 0) {
|
||||
$out[] = $v;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
};
|
||||
$picked = $pickedMembers();
|
||||
|
||||
/**
|
||||
* 交通信息只留一个输入框:飞机只要航班号(航空公司是隐含的),火车只要车次。
|
||||
* 座位号/登机口这类「一趟车多人同行」时根本没法记,所以不再收集。
|
||||
@@ -185,6 +205,7 @@ $tripField = [
|
||||
target="_blank" rel="noopener">🧭 导航到这里</a>
|
||||
<span class="muted">保存后节点详情页也会带这个链接</span>
|
||||
</div>
|
||||
<div class="loc-detail muted" id="start-location-detail"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -205,6 +226,7 @@ $tripField = [
|
||||
target="_blank" rel="noopener">🧭 导航到这里</a>
|
||||
<span class="muted">保存后节点详情页也会带这个链接</span>
|
||||
</div>
|
||||
<div class="loc-detail muted" id="end-location-detail"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -213,6 +235,30 @@ $tripField = [
|
||||
<textarea name="description" rows="4" placeholder="记录一些细节..."><?= e($oldStr('description', $n['description'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<?php /* 参与人:直接在表单里勾,不必先保存再回详情页加(批 13) */ ?>
|
||||
<fieldset class="fieldset" id="member-field">
|
||||
<legend>参与人(直接勾选)</legend>
|
||||
<?php if ($familyUsers === []): ?>
|
||||
<p class="muted">这个家庭还没有成员。</p>
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="members_present" value="1">
|
||||
<div class="member-pick">
|
||||
<?php foreach ($familyUsers as $u): ?>
|
||||
<?php $uid = (int)$u['id']; ?>
|
||||
<label class="member-pick-item">
|
||||
<input type="checkbox" name="members[]" value="<?= $uid ?>"
|
||||
<?= in_array($uid, $picked, true) ? 'checked' : '' ?>>
|
||||
<?php if (!empty($u['avatar'])): ?>
|
||||
<img class="member-pick-avatar" src="<?= e((string)$u['avatar']) ?>" alt="">
|
||||
<?php endif; ?>
|
||||
<span class="member-pick-name"><?= e((string)$u['name']) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p class="form-hint">勾选后保存即生效;已选的人重新勾选不会清掉之前记的签到 / 心情。</p>
|
||||
<?php endif; ?>
|
||||
</fieldset>
|
||||
|
||||
<?php
|
||||
/* 照片附件:随节点表单一起提交。
|
||||
新建时节点还没有 id,附件挂不上去,所以只提示「保存后一起挂上」;
|
||||
@@ -512,16 +558,98 @@ $tripField = [
|
||||
function selectItem(item) {
|
||||
if (!item) return;
|
||||
input.value = item.name || '';
|
||||
if (addrField) addrField.value = item.address || item.district || '';
|
||||
|
||||
/* 高德联想返回的是「简化地名」(如「青岛北站」),详细地址要另外查:
|
||||
① 有坐标 → 逆地理编码拿这个点的 formattedAddress(最贴合选中的位置);
|
||||
② 没坐标 → 按「区县 + 名称」正查,顺带把坐标也补回来。
|
||||
查不到就退回联想项自带的 district/address,总之不把它留空。 */
|
||||
var fallback = item.address || item.district || '';
|
||||
if (addrField) addrField.value = fallback;
|
||||
|
||||
if (item.location) {
|
||||
if (latField) latField.value = item.location.lat;
|
||||
if (lngField) lngField.value = item.location.lng;
|
||||
reverseDetail(item.location, fallback);
|
||||
} else {
|
||||
var query = [item.district || '', item.name || ''].filter(Boolean).join(' ');
|
||||
if (query) {
|
||||
forwardDetail(query, fallback);
|
||||
} else {
|
||||
showDetail(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
dropdown.style.display = 'none';
|
||||
// 坐标刚变,导航链接要跟着变(有坐标才是真导航,没坐标只是搜索)
|
||||
refreshLocNav(prefix);
|
||||
}
|
||||
|
||||
/* 地理编码插件按需加载(layout.php 只预装了 AutoComplete) */
|
||||
function withGeocoder(cb) {
|
||||
try {
|
||||
if (typeof AMap.Geocoder !== 'undefined') {
|
||||
cb(new AMap.Geocoder({}));
|
||||
return;
|
||||
}
|
||||
AMap.plugin('AMap.Geocoder', function () {
|
||||
cb(typeof AMap.Geocoder !== 'undefined' ? new AMap.Geocoder({}) : null);
|
||||
});
|
||||
} catch (e) {
|
||||
cb(null);
|
||||
}
|
||||
}
|
||||
|
||||
function showDetail(addr) {
|
||||
var box = document.getElementById(prefix + '-location-detail');
|
||||
if (!box) return;
|
||||
box.textContent = addr ? '详细地址:' + addr : '';
|
||||
}
|
||||
|
||||
/** 有坐标:反查这个点的完整地址 */
|
||||
function reverseDetail(loc, fallback) {
|
||||
withGeocoder(function (g) {
|
||||
if (!g) { showDetail(fallback); return; }
|
||||
try {
|
||||
g.getAddress([Number(loc.lng), Number(loc.lat)], function (status, result) {
|
||||
var addr = (status === 'complete' && result && result.regeocode)
|
||||
? (result.regeocode.formattedAddress || '') : '';
|
||||
if (addr && addrField) addrField.value = addr;
|
||||
showDetail(addr || fallback);
|
||||
});
|
||||
} catch (e) {
|
||||
showDetail(fallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 没坐标:按「区县 + 名称」正查,拿到详细地址与坐标 */
|
||||
function forwardDetail(query, fallback) {
|
||||
withGeocoder(function (g) {
|
||||
if (!g) { showDetail(fallback); return; }
|
||||
try {
|
||||
g.getLocation(query, function (status, result) {
|
||||
var addr = '';
|
||||
if (status === 'complete' && result && result.geocodes && result.geocodes.length) {
|
||||
var gc = result.geocodes[0];
|
||||
addr = gc.formattedAddress || '';
|
||||
if (gc.location) {
|
||||
if (latField) latField.value = Number(gc.location.getLat());
|
||||
if (lngField) lngField.value = Number(gc.location.getLng());
|
||||
refreshLocNav(prefix);
|
||||
}
|
||||
}
|
||||
if (addr && addrField) addrField.value = addr;
|
||||
showDetail(addr || fallback);
|
||||
});
|
||||
} catch (e) {
|
||||
showDetail(fallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑已有节点:把库里存下来的详细地址显示出来,让人知道记的是什么
|
||||
showDetail(addrField ? addrField.value : '');
|
||||
|
||||
var searchTimer = null;
|
||||
input.addEventListener('input', function () {
|
||||
var keyword = input.value.trim();
|
||||
|
||||
Reference in New Issue
Block a user