update
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "_datetime_picker_field.html" import datetime_picker_field %}
|
||||
|
||||
{% block content %}
|
||||
{% include "_mission_ops_tabs.html" %}
|
||||
{% include "_mission_preview_header.html" %}
|
||||
|
||||
<section class="panel mission-control-panel top-gap">
|
||||
<form method="get" action="{{ url_for('web.mission_timeline_preview') }}" class="mission-toolbar-grid">
|
||||
<input type="hidden" name="range_mode" value="{{ timeline_range_mode }}" id="timeline-range-mode">
|
||||
<div class="mission-toolbar-block">
|
||||
<span class="field-label">时间范围</span>
|
||||
<div class="mission-toolbar-inline">
|
||||
{{ datetime_picker_field('开始', 'start', timeline_range_start, field_id='timeline-start') }}
|
||||
{{ datetime_picker_field('结束', 'end', timeline_range_end, field_id='timeline-end') }}
|
||||
<label>
|
||||
<span class="field-label">尺度</span>
|
||||
<select name="scale">
|
||||
{% for option in timeline_scale_options %}
|
||||
<option value="{{ option.value }}" {% if option.value == timeline_scale_mode %}selected{% endif %}>{{ option.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-toolbar-block">
|
||||
<span class="field-label">时间线筛选</span>
|
||||
<div class="mission-filter-grid">
|
||||
<label>
|
||||
<span class="field-label">资产类型</span>
|
||||
<select name="asset_type">
|
||||
<option value="">全部类型</option>
|
||||
{% for option in asset_type_options %}
|
||||
<option value="{{ option }}" {% if option == asset_type_filter %}selected{% endif %}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span class="field-label">地点</span>
|
||||
<select name="location">
|
||||
<option value="">全部地点</option>
|
||||
{% for option in location_options %}
|
||||
<option value="{{ option }}" {% if option == location_filter %}selected{% endif %}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span class="field-label">记录范围</span>
|
||||
<select name="record_scope">
|
||||
{% for option in record_scope_options %}
|
||||
<option value="{{ option.value }}" {% if option.value == record_scope %}selected{% endif %}>{{ option.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button class="primary-button" type="submit">应用</button>
|
||||
<a class="secondary-button" href="{{ reset_url }}">重置</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-toolbar-block mission-toolbar-span-two">
|
||||
<span class="field-label">显示资产</span>
|
||||
{% if timeline_asset_options %}
|
||||
<div class="mission-asset-picker" data-asset-picker>
|
||||
<div class="mission-asset-picker-toolbar">
|
||||
<label class="mission-asset-picker-search">
|
||||
<span class="field-label">按名称搜索</span>
|
||||
<input type="search" placeholder="搜索资产名称" data-asset-picker-search>
|
||||
</label>
|
||||
<div class="mission-asset-picker-actions">
|
||||
<button class="secondary-button" type="button" data-asset-picker-select-all>全选可见</button>
|
||||
<button class="secondary-button" type="button" data-asset-picker-clear>清空选择</button>
|
||||
<span class="small-muted" data-asset-picker-summary></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-asset-picker-grid">
|
||||
{% for option in timeline_asset_options %}
|
||||
<label class="mission-asset-picker-item" data-asset-picker-item data-asset-picker-name="{{ option.label|lower }}">
|
||||
<input type="checkbox" name="asset_ids" value="{{ option.value }}" data-asset-picker-checkbox {% if option.selected %}checked{% endif %}>
|
||||
<span>
|
||||
<strong>{{ option.label }}</strong>
|
||||
<small>{{ option.asset_type }}</small>
|
||||
</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="field-hint">未勾选任何 Asset 时,默认显示当前筛选命中的全部资产。</p>
|
||||
{% else %}
|
||||
<p class="small-muted">当前筛选条件下没有可选资产。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel mission-timeline-panel top-gap">
|
||||
<div class="mission-timeline-toolbar">
|
||||
<div>
|
||||
<p class="eyebrow">Timeline</p>
|
||||
<h2>多资产时间线</h2>
|
||||
</div>
|
||||
<div class="mission-legend">
|
||||
<span class="legend-item"><span class="legend-swatch warning"></span>转移段</span>
|
||||
<span class="legend-item"><span class="legend-swatch primary"></span>持续运行</span>
|
||||
<span class="legend-item"><span class="legend-swatch neutral"></span>停泊 / 地表 / 待命</span>
|
||||
<span class="legend-item"><span class="legend-swatch current"></span>当前活动状态</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-filter-pill-row">
|
||||
<span class="mission-filter-pill"><strong>范围:</strong> {{ timeline_range_display }}</span>
|
||||
<span class="mission-filter-pill"><strong>尺度:</strong> {{ timeline_scale_mode|title }}</span>
|
||||
<span class="mission-filter-pill"><strong>资产:</strong> {% if selected_asset_count %}已选 {{ selected_asset_count }} 个{% else %}显示 {{ timeline_rows|length }} 条{% endif %}</span>
|
||||
<span class="mission-filter-pill"><strong>模式:</strong> {{ record_scope_options|selectattr('value', 'equalto', record_scope)|map(attribute='label')|first }}</span>
|
||||
</div>
|
||||
|
||||
{% if timeline_rows %}
|
||||
<div class="mission-timeline-scroll">
|
||||
<div class="mission-timeline-frame" style="min-width: {{ 320 + (timeline_scale|length * timeline_column_width) }}px;">
|
||||
<div class="mission-timeline-scale">
|
||||
<div class="mission-timeline-corner"></div>
|
||||
<div class="mission-scale-labels" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px;">
|
||||
{% for label in timeline_scale %}
|
||||
<span>{{ label }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-timeline-rows">
|
||||
{% for row in timeline_rows %}
|
||||
<div class="mission-timeline-row">
|
||||
<a class="mission-timeline-label mission-timeline-label-link" href="{{ url_for('web.asset_detail', asset_id=row.asset_id, sim_time=simulation_time_input) }}">
|
||||
<h3>{{ row.name }}</h3>
|
||||
<p class="small-muted mission-timeline-meta">{{ row.asset_type }}</p>
|
||||
<p class="small-muted mission-timeline-status">Current Status: {{ row.current_state }}</p>
|
||||
</a>
|
||||
|
||||
<div class="mission-timeline-track-stack">
|
||||
<div class="mission-timeline-lane mission-timeline-state-lane {% if timeline_scale_mode == 'year' %}mission-timeline-lane-year{% endif %}" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px; --timeline-track-count: {{ row.state_track_count }};">
|
||||
{% for segment in row.segments %}
|
||||
<a class="mission-segment mission-timeline-link {% if segment.compact %}mission-segment-compact {% endif %}{% for tone in segment.tone.split() %}mission-tone-{{ tone }} {% endfor %}" style="left: {{ segment.left_pct }}; width: {{ segment.track_width_pct }}; --timeline-track-index: {{ segment.track }};" title="{{ segment.label }}" data-full-label="{{ segment.label }}" href="{{ segment.url }}">
|
||||
<span class="mission-segment-body" style="width: {{ segment.body_width_pct }};"></span>
|
||||
<span class="mission-segment-text">{{ segment.label }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mission-timeline-lane mission-timeline-event-lane {% if timeline_scale_mode == 'year' %}mission-timeline-lane-year{% endif %}" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px; --timeline-track-count: {{ row.event_track_count }};">
|
||||
{% for event in row.events %}
|
||||
<a class="mission-event-node mission-timeline-link {% if event.state_node %}mission-event-node-state{% endif %}{% if event.label_side == 'left' %} mission-event-node-label-left{% endif %}" style="left: {{ event.left_pct }}; --timeline-track-index: {{ event.track }}; --event-label-width: {{ event.label_width_px }}px;{% if event.connector_height_px %} --state-node-connector-height: {{ event.connector_height_px }};{% endif %}" title="{{ event.label }}" data-full-label="{{ event.full_label }}" href="{{ event.url }}">
|
||||
{% if event.state_node %}
|
||||
<span class="mission-state-node-connector" aria-hidden="true"></span>
|
||||
{% endif %}
|
||||
<span class="mission-event-node-label">{{ event.label }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<section class="empty-state compact-empty">
|
||||
<h2>当前时间范围内没有时间线条目</h2>
|
||||
</section>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const picker = document.querySelector('[data-asset-picker]');
|
||||
if (!picker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchInput = picker.querySelector('[data-asset-picker-search]');
|
||||
const summary = picker.querySelector('[data-asset-picker-summary]');
|
||||
const items = Array.from(picker.querySelectorAll('[data-asset-picker-item]'));
|
||||
const rangeModeField = document.getElementById('timeline-range-mode');
|
||||
const rangeInputs = [document.getElementById('timeline-start'), document.getElementById('timeline-end')].filter(Boolean);
|
||||
|
||||
const checkboxFor = (item) => item.querySelector('[data-asset-picker-checkbox]');
|
||||
|
||||
const updateSummary = () => {
|
||||
const visibleCount = items.filter((item) => !item.hidden).length;
|
||||
const selectedCount = items.filter((item) => checkboxFor(item)?.checked).length;
|
||||
summary.textContent = `已选 ${selectedCount} 个 · 可见 ${visibleCount} 个`;
|
||||
};
|
||||
|
||||
const setRangeModeManual = () => {
|
||||
if (rangeModeField) {
|
||||
rangeModeField.value = 'manual';
|
||||
}
|
||||
};
|
||||
|
||||
const applySearch = () => {
|
||||
const term = (searchInput?.value || '').trim().toLowerCase();
|
||||
for (const item of items) {
|
||||
const assetName = item.dataset.assetPickerName || '';
|
||||
item.hidden = Boolean(term) && !assetName.includes(term);
|
||||
}
|
||||
updateSummary();
|
||||
};
|
||||
|
||||
picker.querySelector('[data-asset-picker-select-all]')?.addEventListener('click', () => {
|
||||
for (const item of items) {
|
||||
const checkbox = checkboxFor(item);
|
||||
if (!item.hidden && checkbox) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
}
|
||||
updateSummary();
|
||||
});
|
||||
|
||||
picker.querySelector('[data-asset-picker-clear]')?.addEventListener('click', () => {
|
||||
for (const item of items) {
|
||||
const checkbox = checkboxFor(item);
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
updateSummary();
|
||||
});
|
||||
|
||||
searchInput?.addEventListener('input', applySearch);
|
||||
for (const item of items) {
|
||||
checkboxFor(item)?.addEventListener('change', updateSummary);
|
||||
}
|
||||
for (const input of rangeInputs) {
|
||||
input.addEventListener('input', setRangeModeManual);
|
||||
input.addEventListener('change', setRangeModeManual);
|
||||
}
|
||||
|
||||
applySearch();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user