Files
TripLog/templates/nodes/show.php
T
laoyang 02f40b6d6f 路线地图/节点卡片/详情按出行方式显示不同图标(批 19)
出行方式(飞机/火车/游船/自驾/地铁…)原本在路线地图标记、节点卡片交通行、
节点详情「出行方式」行一律显示节点类型图标(赶路节点恒为 🚗),易让人误解。

- TravelModeController 新增 MODE_ICONS(mode_type → emoji 映射)
- ActivityController::show() 两路查询 LEFT JOIN travel_modes 取 mode_type,注入 modeIcons
- 路线地图标记 / 卡片交通行 / 详情出行方式行:优先用出行方式图标,
  未选出行方式时回退节点类型图标
2026-09-24 20:08:17 +08:00

232 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 节点详情:基础信息 + 参与人 + 花销 + 讨论
* 一个节点一页,把挂在这个节点上的所有记录集中起来
*/
use App\Attachments;
use App\Comments;
$statusLabels = [
'planned' => '待执行',
'ongoing' => '进行中',
'completed' => '已完成',
'cancelled' => '已取消',
];
$st = (string)($node['status'] ?? 'planned');
if (!isset($statusLabels[$st])) $st = 'planned';
$sLoc = json_decode_field($node['start_location'] ?? null);
$eLoc = json_decode_field($node['end_location'] ?? null);
/**
* 交通只记一个号码(航班号 / 车次 / 班次)——
* 航班号本身就隐含航空公司,座位号对多人同行又没法记,所以都不收。
*/
$tripFieldLabels = [
'plane' => '航班号',
'train' => '车次',
'bus' => '班次',
'subway' => '线路',
'ship' => '航次',
'taxi' => '备注',
];
$tripLabel = $tripFieldLabels[(string)($node['travel_mode_type'] ?? '')] ?? '车次 / 班次';
$transportParts = [];
if (!empty($node['trip_number'])) {
$transportParts[] = $tripLabel . ' ' . $node['trip_number'];
}
$durationText = '';
if (!empty($node['duration_minutes'])) {
$mins = (int)$node['duration_minutes'];
$durationText = $mins >= 60 ? intdiv($mins, 60) . ' 小时 ' . ($mins % 60) . ' 分' : $mins . ' 分钟';
}
?>
<div class="page-header">
<div>
<h1><?= e(($node['node_type_icon'] ?? '') . ' ' . ($node['title'] ?: '(无标题节点)')) ?></h1>
<p class="muted">
<a href="<?= url('activities/show', ['id' => $node['activity_id']]) ?>"><?= e($node['activity_title']) ?></a>
· <?= $node['node_category'] === 'plan' ? '计划节点' : '执行节点' ?>
· <span class="status-tag status-<?= e($st) ?>"><?= e($statusLabels[$st]) ?></span>
<?php if ($node['node_category'] === 'reality' && !empty($node['original_plan_node_id'])): ?>
· <span class="tag-default">由计划节点生成</span>
<?php endif; ?>
</p>
</div>
<div class="actions" style="display:flex; gap:8px; flex-wrap:wrap;">
<a href="<?= url('nodes/edit', ['id' => $node['id']]) ?>" class="btn">编辑节点</a>
<a href="<?= url('attachments/node', ['id' => $node['id']]) ?>" class="btn">📎 附件 (<?= (int)$attachmentCount ?>)</a>
<a href="<?= url('activities/show', ['id' => $node['activity_id']]) ?>" class="btn">返回活动</a>
<form method="post" action="<?= url('nodes/destroy') ?>" style="display:inline;"
onsubmit="return confirm('确定删除这个节点?它的附件、参与记录、花销和讨论都会一并删除。');">
<input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="id" value="<?= (int)$node['id'] ?>">
<button type="submit" class="btn btn-danger">删除节点</button>
</form>
</div>
</div>
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">时间</span>
<span class="detail-value">
<?= date('Y-m-d H:i', strtotime((string)$node['start_time'])) ?>
→
<?= date('Y-m-d H:i', strtotime((string)$node['end_time'])) ?>
<?php if ($durationText !== ''): ?>
<span class="muted">(<?= e($durationText) ?>)</span>
<?php endif; ?>
</span>
</div>
<div class="detail-item">
<span class="detail-label">类型</span>
<span class="detail-value"><?= e($node['node_type_name'] ?? '未分类') ?></span>
</div>
<?php if (!empty($node['travel_mode_label'])): ?>
<div class="detail-item">
<span class="detail-label">出行方式</span>
<?php
/* 批 19:行前加一个出行方式图标(飞机/火车/游船…),更直观 */
$modeIcons = $modeIcons ?? [];
$tIcon = $modeIcons[(string)($node['travel_mode_type'] ?? '')] ?? '';
?>
<span class="detail-value"><?= e($tIcon) ?> <?= e($node['travel_mode_label']) ?></span>
</div>
<?php endif; ?>
<?php if (!empty($transportParts)): ?>
<div class="detail-item">
<span class="detail-label">交通</span>
<span class="detail-value"><?= e(implode(' · ', $transportParts)) ?></span>
</div>
<?php endif; ?>
<?php if (!empty($sLoc['name']) || !empty($sLoc['address'])): ?>
<div class="detail-item">
<span class="detail-label">起点</span>
<span class="detail-value">
📍 <?= e($sLoc['name'] ?? '') ?>
<?php if (!empty($sLoc['address'])): ?>
<span class="muted"><?= e($sLoc['address']) ?></span>
<?php endif; ?>
<?php /* 存下来的坐标就是为了一键导航:手机端点开会直接唤起高德 App */ ?>
<?php $sNav = amap_nav_url($sLoc); ?>
<?php if ($sNav !== ''): ?>
<a class="loc-nav-link loc-nav-inline" href="<?= e($sNav) ?>"
target="_blank" rel="noopener">🧭 导航</a>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<?php if (!empty($eLoc['name']) || !empty($eLoc['address'])): ?>
<div class="detail-item">
<span class="detail-label">终点</span>
<span class="detail-value">
📍 <?= e($eLoc['name'] ?? '') ?>
<?php if (!empty($eLoc['address'])): ?>
<span class="muted"><?= e($eLoc['address']) ?></span>
<?php endif; ?>
<?php $eNav = amap_nav_url($eLoc); ?>
<?php if ($eNav !== ''): ?>
<a class="loc-nav-link loc-nav-inline" href="<?= e($eNav) ?>"
target="_blank" rel="noopener">🧭 导航</a>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<?php if (!empty($node['description'])): ?>
<div class="detail-item detail-item-wide">
<span class="detail-label">描述</span>
<span class="detail-value"><?= nl2br(e($node['description'])) ?></span>
</div>
<?php endif; ?>
<?php if ($st === 'cancelled' && !empty($node['cancel_reason'])): ?>
<div class="detail-item detail-item-wide">
<span class="detail-label">取消原因</span>
<span class="detail-value node-cancel-reason"><?= e($node['cancel_reason']) ?></span>
</div>
<?php endif; ?>
</div>
<?php
/* 附件墙(批 8 起统一展示):不再把图片和其它附件分开列 ——
图片给缩略图,视频 / PDF / 其它给类型图标;全部走 media/show 代理,
能预览的(图片 / 视频 / PDF)点击进灯箱直接看,
预览不了的(Word / ZIP 等)灯箱里给下载按钮(见 _lightbox.php) */
$showAtts = array_slice(array_values($attachments ?? []), 0, 12);
$attIcon = static function (string $mime): string {
if (strpos($mime, 'video/') === 0) return '🎬';
if ($mime === 'application/pdf') return '📄';
return '📎';
};
?>
<?php if (!empty($attachments)): ?>
<h2 class="section-title" id="photos">照片与附件(<?= count($attachments) ?>)</h2>
<?php if (!empty($showAtts)): ?>
<div class="photo-grid photo-grid-sm">
<?php foreach ($showAtts as $attRow):
$attId = (int)$attRow['id'];
$attName = (string)($attRow['file_name'] ?? '');
$attMime = (string)($attRow['mime_type'] ?? '');
$isImg = Attachments::isImageRow($attRow);
if ($isImg) {
$kind = 'image';
} elseif (strpos($attMime, 'video/') === 0) {
$kind = 'video';
} elseif ($attMime === 'application/pdf') {
$kind = 'pdf';
} else {
$kind = 'file';
}
?>
<?php if ($isImg): ?>
<?php /* js-photo:图片点击在当前页灯箱里看大图 */ ?>
<a class="photo-card js-photo" href="<?= e(media_att($attId)) ?>"
data-cap="<?= e($attName) ?>" title="<?= e($attName) ?>">
<img src="<?= e(media_att($attId)) ?>" alt="<?= e($attName) ?>" loading="lazy">
</a>
<?php else: ?>
<?php /* js-media:非图片走四态灯箱(视频/PDF 可预览,其它给下载)。
视频卡放一个静音迷你 <video preload="metadata">:浏览器只拉视频头部
(media/show 支持 Range)就显示第一帧画面,当缩略图用;
加载不出来时 video 透明,露出下面的类型图标(老视频无需任何处理) */ ?>
<a class="photo-card photo-card-icon js-media" href="<?= e(media_att($attId)) ?>"
data-kind="<?= $kind ?>" data-cap="<?= e($attName) ?>"
data-dl="<?= e(media_att($attId, true)) ?>" title="<?= e($attName) ?>">
<span class="attachment-icon"><?= $attIcon($attMime) ?></span>
<?php if ($kind === 'video'): ?>
<video class="video-thumb-mini" src="<?= e(media_att($attId)) ?>"
muted preload="metadata" playsinline tabindex="-1"></video>
<?php endif; ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
<?php if (count($attachments) > count($showAtts)): ?>
<a class="photo-more" href="<?= url('attachments/node', ['id' => (int)$node['id']]) ?>#photos">
+<?= count($attachments) - count($showAtts) ?><span>个</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="photo-bar">
<a class="btn btn-sm" href="<?= url('attachments/node', ['id' => (int)$node['id']]) ?>">
📎 管理附件(<?= count($attachments) ?>)
</a>
</div>
<?php endif; ?>
<?php require __DIR__ . '/_members.php'; ?>
<?php require __DIR__ . '/_expenses.php'; ?>
<?php
$commentTargetType = Comments::TARGET_NODE;
$commentTargetId = (int)$node['id'];
require dirname(__DIR__) . '/comments/_block.php';
?>
<?php /* 照片灯箱:当前页弹窗看大图,左右箭头切换 */ ?>
<?php require dirname(__DIR__) . '/_lightbox.php'; ?>