路线地图体验修正 + 节点表单裁剪改按钮触发(批 11)
地图四项调整(用户反馈): - 位置移到封面之下、时间轴之前 —— 行程一长不用拉到底才看到地图; - 圆形标记放在该段行程线的中点(起终点都有才取中点,缺一头放 有的一头),不再是起点位置;anchor 改 center 居中骑在线上; - 只有地名、没坐标的地点(偏僻地方下拉选不到点位):浏览器端调 AMap.Geocoder 按地名查坐标,查到补进路线全量重画(drawRoute 幂等), 查不到保持缺省不硬造位置;data-route 新增 sname/ename 地名; - 提示文案同步更新。 裁剪交互(用户反馈): - 选图后不再自动弹出裁剪框;每张能裁的缩略图旁放「✂ 裁剪」按钮 (左上角,与右上角 × 对称),点了才弹框,只处理这一张; - 「逐张裁剪」批量入口保留:确定后仍自动弹下一张(cropChain 标志 区分两种流程);删除 addedCroppable 自动触发逻辑。 已验证:本地全链 11 步全绿(渲染检查 339 项、真实浏览器交互更新为 按钮触发流程 75 项);远端 e2e 340 项 0 失败(新增 9 项断言); 部署静态断言/双 PHP 渲染/内容断言全过后提交。
This commit is contained in:
@@ -817,6 +817,15 @@ input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; padding
|
|||||||
}
|
}
|
||||||
.photo-del:hover { background: var(--danger); }
|
.photo-del:hover { background: var(--danger); }
|
||||||
|
|
||||||
|
/* 批 11:表单缩略图旁的「✂ 裁剪」按钮 —— 上传后不再自动弹裁剪框,点它才弹 */
|
||||||
|
.photo-card > .photo-crop-btn {
|
||||||
|
position: absolute; top: 6px; left: 6px;
|
||||||
|
border: 0; border-radius: 6px; padding: 3px 9px;
|
||||||
|
font-size: 12px; line-height: 1.4; cursor: pointer;
|
||||||
|
background: rgba(20, 24, 34, .62); color: #fff;
|
||||||
|
}
|
||||||
|
.photo-card > .photo-crop-btn:hover { background: rgba(20, 24, 34, .85); }
|
||||||
|
|
||||||
/* 详情页上的小照片墙 */
|
/* 详情页上的小照片墙 */
|
||||||
.photo-more {
|
.photo-more {
|
||||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
|
|||||||
+159
-123
@@ -30,6 +30,165 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/* ── 路线地图(批 10 引入,批 11 调整位置与标记逻辑)──────────────
|
||||||
|
位置放在封面之下、时间轴之前:行程一长,拉到底才看到地图的体验很差。
|
||||||
|
按时间顺序把执行节点的起终点连成路线:节点内实线、节点之间虚线;
|
||||||
|
圆形标记放在**这段行程线的中点**(缺一头就放有的一头)——
|
||||||
|
有图片附件显示图片,没有显示节点类型 emoji,左上角带序号,
|
||||||
|
点一下打开该节点详情。
|
||||||
|
只有地名、没坐标的地点(偏僻地方下拉选不到点位):浏览器端调
|
||||||
|
高德地理编码(AMap.Geocoder)按地名查坐标,查到就补进路线重画;
|
||||||
|
查不到就保持缺省,不硬造位置。AMap 2.0 已由 layout.php 全局加载。 */
|
||||||
|
$routeNodes = [];
|
||||||
|
$nodeFirstImage = $nodeFirstImage ?? [];
|
||||||
|
foreach (($realityNodes ?? []) as $rn) {
|
||||||
|
$s = json_decode_field($rn['start_location'] ?? null);
|
||||||
|
$e = json_decode_field($rn['end_location'] ?? null);
|
||||||
|
$hasS = !empty($s['lng']) && !empty($s['lat']);
|
||||||
|
$hasE = !empty($e['lng']) && !empty($e['lat']);
|
||||||
|
$sname = trim((string)($s['name'] ?? ''));
|
||||||
|
$ename = trim((string)($e['name'] ?? ''));
|
||||||
|
if (!$hasS && !$hasE && $sname === '' && $ename === '') {
|
||||||
|
continue; // 坐标和地名一个都没有的节点不上地图
|
||||||
|
}
|
||||||
|
$imgId = (int)($nodeFirstImage[(int)$rn['id']] ?? 0);
|
||||||
|
$routeNodes[] = [
|
||||||
|
'id' => (int)$rn['id'],
|
||||||
|
'title' => (string)($rn['title'] ?: '(无标题节点)'),
|
||||||
|
'icon' => (string)($rn['node_type_icon'] ?: '📍'),
|
||||||
|
'img' => $imgId > 0 ? media_att($imgId) : '',
|
||||||
|
'url' => url('nodes/show', ['id' => (int)$rn['id']]),
|
||||||
|
'sname' => $sname, // 出发地名:没有坐标时交给高德地理编码兜底
|
||||||
|
'ename' => $ename, // 到达地名:同上
|
||||||
|
'start' => $hasS ? ['lng' => (float)$s['lng'], 'lat' => (float)$s['lat']] : null,
|
||||||
|
'end' => $hasE ? ['lng' => (float)$e['lng'], 'lat' => (float)$e['lat']] : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php if ($routeNodes !== []): ?>
|
||||||
|
<section class="route-map-section">
|
||||||
|
<h2 class="section-title" id="route-map">🗺️ 行程路线</h2>
|
||||||
|
<div class="route-map" id="route-map-canvas"
|
||||||
|
data-route="<?= e(json_encode($routeNodes, JSON_UNESCAPED_UNICODE)) ?>"></div>
|
||||||
|
<p class="muted route-map-hint">
|
||||||
|
按时间顺序连出的执行节点路线(节点间虚线衔接),圆标在每段行程的中间,点它可打开对应节点。
|
||||||
|
<span class="route-legend-end">红点</span> = 到达地。只有地名没坐标的地点会自动向高德查询定位,太偏僻的地名可能查不到。
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var el = document.getElementById('route-map-canvas');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
/* AMap 脚本加载失败(罕见)时给一句人话,不白屏 */
|
||||||
|
if (typeof AMap === 'undefined' || window.__AMAP_ERROR) {
|
||||||
|
el.innerHTML = '<p class="route-map-fallback">地图脚本没有加载成功,暂时无法显示路线。</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var route;
|
||||||
|
try {
|
||||||
|
route = JSON.parse(el.getAttribute('data-route') || '[]');
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!route.length) return;
|
||||||
|
|
||||||
|
var map = new AMap.Map(el, { viewMode: '2D', zoom: 5 });
|
||||||
|
var overlays = []; // 当前画上去的所有覆盖物;地理编码补点后整体重画
|
||||||
|
|
||||||
|
/* 全量重画:先清掉旧覆盖物再按当前 route 数据画。
|
||||||
|
地理编码是异步的,一个点查回来就重画一次,数据是幂等的。 */
|
||||||
|
function drawRoute() {
|
||||||
|
overlays.forEach(function (o) { map.remove(o); });
|
||||||
|
overlays = [];
|
||||||
|
var inPath = []; // 节点内:出发地 → 到达地(实线)
|
||||||
|
var hopPath = []; // 节点之间:上一站到达地 → 下一站出发地(虚线)
|
||||||
|
var prevEnd = null;
|
||||||
|
|
||||||
|
route.forEach(function (n, i) {
|
||||||
|
var s = n.start, en = n.end;
|
||||||
|
|
||||||
|
if (s && en) inPath.push([s.lng, s.lat], [en.lng, en.lat]);
|
||||||
|
if (s && prevEnd) hopPath.push([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]);
|
||||||
|
|
||||||
|
/* 圆形标记放这段行程线的中点(起终点都有才取中点),缺一头就放有的一头 */
|
||||||
|
var pos = (s && en)
|
||||||
|
? [(s.lng + en.lng) / 2, (s.lat + en.lat) / 2]
|
||||||
|
: (s || en);
|
||||||
|
if (pos) {
|
||||||
|
var html = '<div class="route-marker" title="' + n.title.replace(/"/g, '"') + '">'
|
||||||
|
+ (n.img
|
||||||
|
? '<img src="' + n.img + '" alt="">'
|
||||||
|
: '<span class="route-marker-emoji">' + n.icon + '</span>')
|
||||||
|
+ '<span class="route-marker-num">' + (i + 1) + '</span>'
|
||||||
|
+ '</div>';
|
||||||
|
var marker = new AMap.Marker({
|
||||||
|
position: pos,
|
||||||
|
content: html,
|
||||||
|
anchor: 'center',
|
||||||
|
map: map,
|
||||||
|
});
|
||||||
|
marker.on('click', function () { window.location.href = n.url; });
|
||||||
|
overlays.push(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 到达地小圆点(和出发地不是同一点时才画) */
|
||||||
|
if (en && (!s || en.lng !== s.lng || en.lat !== s.lat)) {
|
||||||
|
var dot = new AMap.Marker({
|
||||||
|
position: [en.lng, en.lat],
|
||||||
|
content: '<div class="route-end-dot"></div>',
|
||||||
|
anchor: 'center',
|
||||||
|
map: map,
|
||||||
|
});
|
||||||
|
dot.on('click', function () { window.location.href = n.url; });
|
||||||
|
overlays.push(dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevEnd = en || s;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inPath.length > 1) {
|
||||||
|
overlays.push(new AMap.Polyline({
|
||||||
|
path: inPath, strokeColor: '#3B82F6', strokeWeight: 5,
|
||||||
|
strokeOpacity: 0.9, lineJoin: 'round', showDir: true, map: map,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (hopPath.length) {
|
||||||
|
overlays.push(new AMap.Polyline({
|
||||||
|
path: hopPath, strokeColor: '#94A3B8', strokeWeight: 3,
|
||||||
|
strokeStyle: 'dashed', strokeOpacity: 0.8, map: map,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (overlays.length) map.setFitView(null, false, [50, 50, 50, 50]);
|
||||||
|
}
|
||||||
|
drawRoute();
|
||||||
|
|
||||||
|
/* 只有地名、没有坐标的地点:向高德地理编码查坐标(插件按需加载)。
|
||||||
|
查到就补进数据并重画;查不到(地名太偏/写错)保持缺省,不硬造位置。 */
|
||||||
|
function geocode(name, cb) {
|
||||||
|
if (!name) return;
|
||||||
|
AMap.plugin('AMap.Geocoder', function () {
|
||||||
|
try {
|
||||||
|
new AMap.Geocoder({}).getLocation(name, function (status, result) {
|
||||||
|
if (status === 'complete' && result && result.geocodes && result.geocodes.length
|
||||||
|
&& result.geocodes[0].location) {
|
||||||
|
var loc = result.geocodes[0].location;
|
||||||
|
cb({ lng: Number(loc.getLng()), lat: Number(loc.getLat()) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) { /* 地理编码服务不可用时保持原状 */ }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
route.forEach(function (n) {
|
||||||
|
if (!n.start && n.sname) geocode(n.sname, function (p) { n.start = p; drawRoute(); });
|
||||||
|
if (!n.end && n.ename) geocode(n.ename, function (p) { n.end = p; drawRoute(); });
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="timeline-grid">
|
<div class="timeline-grid">
|
||||||
<section class="timeline-col">
|
<section class="timeline-col">
|
||||||
<h2>📋 计划 (<?= count($planNodes) ?>)</h2>
|
<h2>📋 计划 (<?= count($planNodes) ?>)</h2>
|
||||||
@@ -54,129 +213,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
|
||||||
/* ── 路线地图(批 10)──────────────────────────────
|
|
||||||
按时间顺序把执行节点的起终点连成路线:节点内实线、节点之间虚线;
|
|
||||||
每个节点在起点(没有起点就放终点)放一个圆形标记 ——
|
|
||||||
有图片附件显示图片,没有显示节点类型 emoji,左上角带序号,
|
|
||||||
点一下打开该节点详情。AMap 2.0 已由 layout.php 全局加载。 */
|
|
||||||
$routeNodes = [];
|
|
||||||
$nodeFirstImage = $nodeFirstImage ?? [];
|
|
||||||
foreach (($realityNodes ?? []) as $rn) {
|
|
||||||
$s = json_decode_field($rn['start_location'] ?? null);
|
|
||||||
$e = json_decode_field($rn['end_location'] ?? null);
|
|
||||||
$hasS = !empty($s['lng']) && !empty($s['lat']);
|
|
||||||
$hasE = !empty($e['lng']) && !empty($e['lat']);
|
|
||||||
if (!$hasS && !$hasE) {
|
|
||||||
continue; // 起终点一个坐标都没有的节点不上地图
|
|
||||||
}
|
|
||||||
$imgId = (int)($nodeFirstImage[(int)$rn['id']] ?? 0);
|
|
||||||
$routeNodes[] = [
|
|
||||||
'id' => (int)$rn['id'],
|
|
||||||
'title' => (string)($rn['title'] ?: '(无标题节点)'),
|
|
||||||
'icon' => (string)($rn['node_type_icon'] ?: '📍'),
|
|
||||||
'img' => $imgId > 0 ? media_att($imgId) : '',
|
|
||||||
'url' => url('nodes/show', ['id' => (int)$rn['id']]),
|
|
||||||
'start' => $hasS ? ['lng' => (float)$s['lng'], 'lat' => (float)$s['lat']] : null,
|
|
||||||
'end' => $hasE ? ['lng' => (float)$e['lng'], 'lat' => (float)$e['lat']] : null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php if ($routeNodes !== []): ?>
|
|
||||||
<section class="route-map-section">
|
|
||||||
<h2 class="section-title" id="route-map">🗺️ 行程路线</h2>
|
|
||||||
<div class="route-map" id="route-map-canvas"
|
|
||||||
data-route="<?= e(json_encode($routeNodes, JSON_UNESCAPED_UNICODE)) ?>"></div>
|
|
||||||
<p class="muted route-map-hint">
|
|
||||||
按时间顺序连出的执行节点路线(节点间虚线衔接),点标记可打开对应节点。
|
|
||||||
线上圆标 = 节点出发地,<span class="route-legend-end">红点</span> = 到达地。
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
var el = document.getElementById('route-map-canvas');
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
/* AMap 脚本加载失败(罕见)时给一句人话,不白屏 */
|
|
||||||
if (typeof AMap === 'undefined' || window.__AMAP_ERROR) {
|
|
||||||
el.innerHTML = '<p class="route-map-fallback">地图脚本没有加载成功,暂时无法显示路线。</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var route;
|
|
||||||
try {
|
|
||||||
route = JSON.parse(el.getAttribute('data-route') || '[]');
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!route.length) return;
|
|
||||||
|
|
||||||
var map = new AMap.Map(el, { viewMode: '2D', zoom: 5 });
|
|
||||||
var inNodePath = []; // 节点内:出发地 → 到达地(实线)
|
|
||||||
var hopPath = []; // 节点之间:上一站到达地 → 下一站出发地(虚线)
|
|
||||||
var prevEnd = null;
|
|
||||||
|
|
||||||
route.forEach(function (n, i) {
|
|
||||||
var s = n.start, en = n.end;
|
|
||||||
|
|
||||||
/* 节点内实线 */
|
|
||||||
if (s && en) {
|
|
||||||
inNodePath.push([s.lng, s.lat], [en.lng, en.lat]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 节点间虚线(衔接上一站的到达地) */
|
|
||||||
if (s && prevEnd) {
|
|
||||||
hopPath.push([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 圆形标记:优先放在出发地,只有到达地就放到达地 */
|
|
||||||
var pos = s || en;
|
|
||||||
if (pos) {
|
|
||||||
var html = '<div class="route-marker" title="' + n.title.replace(/"/g, '"') + '">'
|
|
||||||
+ (n.img
|
|
||||||
? '<img src="' + n.img + '" alt="">'
|
|
||||||
: '<span class="route-marker-emoji">' + n.icon + '</span>')
|
|
||||||
+ '<span class="route-marker-num">' + (i + 1) + '</span>'
|
|
||||||
+ '</div>';
|
|
||||||
var marker = new AMap.Marker({
|
|
||||||
position: [pos.lng, pos.lat],
|
|
||||||
content: html,
|
|
||||||
anchor: 'bottom-center',
|
|
||||||
map: map,
|
|
||||||
});
|
|
||||||
marker.on('click', function () { window.location.href = n.url; });
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 到达地小圆点(和出发地不是同一点时才画) */
|
|
||||||
if (en && (!s || en.lng !== s.lng || en.lat !== s.lat)) {
|
|
||||||
var dot = new AMap.Marker({
|
|
||||||
position: [en.lng, en.lat],
|
|
||||||
content: '<div class="route-end-dot"></div>',
|
|
||||||
anchor: 'center',
|
|
||||||
map: map,
|
|
||||||
});
|
|
||||||
dot.on('click', function () { window.location.href = n.url; });
|
|
||||||
}
|
|
||||||
|
|
||||||
prevEnd = en || s;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (inNodePath.length > 1) {
|
|
||||||
map.add(new AMap.Polyline({
|
|
||||||
path: inNodePath, strokeColor: '#3B82F6', strokeWeight: 5,
|
|
||||||
strokeOpacity: 0.9, lineJoin: 'round', showDir: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (hopPath.length) {
|
|
||||||
map.add(new AMap.Polyline({
|
|
||||||
path: hopPath, strokeColor: '#94A3B8', strokeWeight: 3,
|
|
||||||
strokeStyle: 'dashed', strokeOpacity: 0.8,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
map.setFitView(null, false, [50, 50, 50, 50]);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$commentTargetType = \App\Comments::TARGET_ACTIVITY;
|
$commentTargetType = \App\Comments::TARGET_ACTIVITY;
|
||||||
|
|||||||
+22
-12
@@ -615,6 +615,7 @@ $tripField = [
|
|||||||
var cropper = null;
|
var cropper = null;
|
||||||
var cropUid = null;
|
var cropUid = null;
|
||||||
var cropStopped = false; // 用户点了「取消」就结束本次逐张裁剪
|
var cropStopped = false; // 用户点了「取消」就结束本次逐张裁剪
|
||||||
|
var cropChain = false; // true = 「逐张裁剪」的连裁流程(确定后自动下一张)
|
||||||
|
|
||||||
function extOf(name) {
|
function extOf(name) {
|
||||||
var i = String(name || '').lastIndexOf('.');
|
var i = String(name || '').lastIndexOf('.');
|
||||||
@@ -711,6 +712,10 @@ $tripField = [
|
|||||||
? '<button type="button" class="photo-del" data-idx="' + idx + '" title="移除">×</button>'
|
? '<button type="button" class="photo-del" data-idx="' + idx + '" title="移除">×</button>'
|
||||||
: '')
|
: '')
|
||||||
+ (it.cropped ? '<span class="photo-badge">已裁剪</span>' : '')
|
+ (it.cropped ? '<span class="photo-badge">已裁剪</span>' : '')
|
||||||
|
+ (it.croppable && modal
|
||||||
|
? '<button type="button" class="photo-crop-btn" data-crop="' + idx
|
||||||
|
+ '" title="裁剪这张图">✂ 裁剪</button>'
|
||||||
|
: '')
|
||||||
+ '<div class="photo-cap">'
|
+ '<div class="photo-cap">'
|
||||||
+ '<span class="photo-cap-name" title="' + esc(it.name) + '">' + esc(it.name) + '</span>'
|
+ '<span class="photo-cap-name" title="' + esc(it.name) + '">' + esc(it.name) + '</span>'
|
||||||
+ '<span class="photo-cap-sub">' + esc(fmtSize(bytesOf(it))) + '</span>'
|
+ '<span class="photo-cap-sub">' + esc(fmtSize(bytesOf(it))) + '</span>'
|
||||||
@@ -757,7 +762,6 @@ $tripField = [
|
|||||||
if (!arr.length) return;
|
if (!arr.length) return;
|
||||||
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
var addedCroppable = false;
|
|
||||||
arr.forEach(function (f) {
|
arr.forEach(function (f) {
|
||||||
if (picked.length >= MAX_FILES) {
|
if (picked.length >= MAX_FILES) {
|
||||||
errors.push(f.name + ':一次最多 ' + MAX_FILES + ' 个');
|
errors.push(f.name + ':一次最多 ' + MAX_FILES + ' 个');
|
||||||
@@ -779,7 +783,6 @@ $tripField = [
|
|||||||
var image = isImageFile(f);
|
var image = isImageFile(f);
|
||||||
var url = image ? URL.createObjectURL(f) : '';
|
var url = image ? URL.createObjectURL(f) : '';
|
||||||
var croppable = image && canvasSafe(f);
|
var croppable = image && canvasSafe(f);
|
||||||
if (croppable) addedCroppable = true;
|
|
||||||
picked.push({
|
picked.push({
|
||||||
uid: ++seq,
|
uid: ++seq,
|
||||||
file: f,
|
file: f,
|
||||||
@@ -799,11 +802,8 @@ $tripField = [
|
|||||||
render();
|
render();
|
||||||
if (errors.length) { setStatus(errors.join(';'), true); return; }
|
if (errors.length) { setStatus(errors.join(';'), true); return; }
|
||||||
|
|
||||||
// 新选进来的图片里有能裁的 → 自动开始逐张裁剪;不想裁就一路点「确定」或直接「取消」
|
/* 批 11:选图后**不再自动弹出**裁剪框 —— 每张缩略图旁有「✂ 裁剪」按钮,
|
||||||
if (addedCroppable && modal && typeof window.Cropper !== 'undefined') {
|
想裁哪张点哪张;想批量处理就用上面的「逐张裁剪」。 */
|
||||||
cropStopped = false;
|
|
||||||
advanceCrop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAt(idx) {
|
function removeAt(idx) {
|
||||||
@@ -816,7 +816,15 @@ $tripField = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
preview.addEventListener('click', function (ev) {
|
preview.addEventListener('click', function (ev) {
|
||||||
var btn = ev.target.closest ? ev.target.closest('button[data-idx]') : null;
|
if (!ev.target.closest) return;
|
||||||
|
/* 「✂ 裁剪」:只打开这一张的裁剪框,确定/取消都不自动跳下一张 */
|
||||||
|
var cropBtn = ev.target.closest('button[data-crop]');
|
||||||
|
if (cropBtn) {
|
||||||
|
var ci = parseInt(cropBtn.getAttribute('data-crop'), 10);
|
||||||
|
if (picked[ci]) openCrop(picked[ci].uid, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var btn = ev.target.closest('button[data-idx]');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
removeAt(parseInt(btn.getAttribute('data-idx'), 10));
|
removeAt(parseInt(btn.getAttribute('data-idx'), 10));
|
||||||
});
|
});
|
||||||
@@ -856,13 +864,13 @@ $tripField = [
|
|||||||
if (cropStopped) return;
|
if (cropStopped) return;
|
||||||
for (var i = 0; i < picked.length; i++) {
|
for (var i = 0; i < picked.length; i++) {
|
||||||
if (picked[i].croppable && !picked[i].reviewed) {
|
if (picked[i].croppable && !picked[i].reviewed) {
|
||||||
openCrop(picked[i].uid);
|
openCrop(picked[i].uid, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCrop(uid) {
|
function openCrop(uid, chained) {
|
||||||
var it = find(uid);
|
var it = find(uid);
|
||||||
if (!it || !it.croppable) return;
|
if (!it || !it.croppable) return;
|
||||||
if (typeof window.Cropper === 'undefined') {
|
if (typeof window.Cropper === 'undefined') {
|
||||||
@@ -871,8 +879,10 @@ $tripField = [
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cropUid = uid;
|
cropUid = uid;
|
||||||
|
cropChain = !!chained;
|
||||||
var pending = picked.filter(function (x) { return x.croppable && !x.reviewed; }).length;
|
var pending = picked.filter(function (x) { return x.croppable && !x.reviewed; }).length;
|
||||||
cropTitle.textContent = '裁剪:' + it.name + (pending > 1 ? '(还有 ' + (pending - 1) + ' 张待处理)' : '');
|
cropTitle.textContent = '裁剪:' + it.name
|
||||||
|
+ (chained && pending > 1 ? '(还有 ' + (pending - 1) + ' 张待处理)' : '');
|
||||||
|
|
||||||
// 每次都换一个新的 img 节点,避免 Cropper 复用旧实例时状态残留
|
// 每次都换一个新的 img 节点,避免 Cropper 复用旧实例时状态残留
|
||||||
var fresh = document.createElement('img');
|
var fresh = document.createElement('img');
|
||||||
@@ -986,7 +996,7 @@ $tripField = [
|
|||||||
it.reviewed = true;
|
it.reviewed = true;
|
||||||
closeCrop();
|
closeCrop();
|
||||||
render();
|
render();
|
||||||
advanceCrop(); // 还有没处理过的图就继续弹下一张
|
if (cropChain) advanceCrop(); // 只有「逐张裁剪」流程才自动弹下一张
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cropAll) {
|
if (cropAll) {
|
||||||
|
|||||||
Reference in New Issue
Block a user