| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- <!--
- 事件列表 + 预警/报警分段切换(票 13)。
- 这是一个**可复用**的面板内容组件:当前预警面板(本票挂载)与票 19 的
- 历史分析 / 预警效率 / 预警分析三块面板都要求「预警/报警切换」,切换是共享状态
- (`eventStore.mode`),所以本组件只消费 store 的 `view`,不做任何字段改名、
- 不自己取数、不碰后端字段(t05 静态守卫盯着)。
- 三条硬纪律在这里的落点(措辞与色键都来自 domain/eventList.js,组件不重写):
- · **列表字段与状态标签随切换同步变化**:`view.switchView` 是唯一一份 ——
- 表头(预警编号/报警编号)与状态标签名(预警状态/报警状态)从同一个对象取,
- 切换后整张表一起换,不会出现字段与标签对不上;
- · **等级 = 色键 + 文字**:每行等级是 StatusTag(tone + 'N级' 文字),
- 颜色不是唯一线索;
- · **只读**:没有任何写操作入口;事件只带设备编码,不产生地图标记。
- `data-*` 是走查断言面:`data-event-switch` 是分段按钮,
- `data-event-row` 是列表行,`data-event-level` 是等级标签,
- `data-event-status` 是状态文本,`data-event-mode` 是当前切换。
- -->
- <template>
- <div class="event-list-pane" :data-event-mode="view.switchView.key">
- <!-- 预警/报警分段切换(共享状态:eventStore.mode,四块面板共用一份) -->
- <div class="event-list-pane__switch" role="group" aria-label="预警/报警切换">
- <button
- v-for="option in switchOptions"
- :key="option.key"
- type="button"
- class="event-list-pane__switch-btn"
- :class="{ 'is-active': option.key === view.switchView.key }"
- :aria-pressed="option.key === view.switchView.key"
- :data-event-switch="option.key"
- @click="setMode(option.key)"
- >
- {{ option.label }}
- </button>
- </div>
- <!--
- 票 67:全局筛选「当前范围」在本面板的落点 —— 当前事件端点实测不收片区入参,
- 所以整张表不随片区变化。措辞来自 domain/filterCaliber.js 的单一来源,
- 与 PanelBlocks 的面板口径说明共用一个断言钩子 `data-panel-gap`。
- 放在状态文案之前:读者先知道"这个条件在这里不起作用",再看数。
- -->
- <p v-if="view.gap" class="event-list-pane__note" data-panel-gap>{{ view.gap }}</p>
- <!-- 分区自己的状态(pending / empty / failed / unavailable 各说各的话) -->
- <!-- 还没取过数:说"正在取数",不说"没有数据" -->
- <p v-if="pending" class="event-list-pane__placeholder" data-event-placeholder>{{ view.placeholder }}</p>
- <!-- 有内容:按行渲染;只读,无任何操作按钮 -->
- <template v-else-if="view.hasContent">
- <table class="event-list-pane__table" data-event-table>
- <!-- 2026-10-13 维护者反馈「右侧太长被裁切」:长编号/长名称撑爆 auto 布局,
- 右边列整列看不见。改 fixed 布局 + 每格省略号 + title 全值兜底,
- 列宽合计恒等于表宽;完整值在悬停 title 与行点开的事件详情卡里。 -->
- <colgroup>
- <col class="col-level" />
- <col class="col-name" />
- <col class="col-code" />
- <col class="col-status" />
- <col class="col-time" />
- <col class="col-value" />
- <col class="col-device" />
- </colgroup>
- <thead>
- <tr>
- <th>等级</th>
- <th>{{ view.switchView.nameLabel }}</th>
- <th>{{ view.switchView.codeLabel }}</th>
- <th>{{ view.switchView.statusLabel }}</th>
- <th>时间</th>
- <th>量值</th>
- <th>设备</th>
-
- </tr>
- </thead>
- <tbody>
- <tr
- v-for="row in view.rows"
- :key="row.key"
- class="event-list-pane__row"
- :data-event-row="row.key"
- :data-event-level-color="row.level.tone"
- tabindex="0"
- @click="eventStore.openEvent(row)"
- @keydown.enter="eventStore.openEvent(row)"
- >
- <td data-event-level>
- <StatusTag :tone="row.level.tone" :label="row.level.label" />
- </td>
- <td class="event-list-pane__name" data-event-name :title="row.name">{{ row.name }}</td>
- <td class="event-list-pane__mono" data-event-code :title="row.code">{{ row.code }}</td>
- <td data-event-status :title="row.statusText + (row.workOrderStateName ? ' · 工单' + row.workOrderStateName : '')">{{ row.statusText }}<span v-if="row.workOrderStateName" data-event-work-order> · 工单{{ row.workOrderStateName }}</span></td>
- <td class="event-list-pane__time" data-event-time :title="row.eventTime">{{ row.eventTimeText || row.eventTime }}</td>
- <td class="event-list-pane__value" data-event-value :title="row.quantity">{{ row.quantity }}</td>
- <td :title="row.deviceName || row.monitoringPointName || ''">
- <span v-if="row.deviceName" data-event-device>{{ row.deviceName }}</span>
- <span v-else-if="row.monitoringPointName" data-event-device>{{ row.monitoringPointName }}</span>
- <span v-else data-event-device class="is-muted">—</span>
- </td>
- </tr>
- </tbody>
- </table>
- <!--
- 票 61:服务端页码分页(共享 PagerNav,规范条文 2/3/4/9 + 跳页节)。
- 翻页带 page/pageSize 发请求(页码状态在 eventStore,组件只转发目标页);
- 一页时整块不渲染(控件自己按 pageCount>1 收口);
- 计数行「第 X/Y 页 · 共 N 条」由控件单源给,不暴露 page/pageSize 参数名(条文 10)。
- -->
- <div v-if="view.pagination && view.pagination.pageCount > 1" class="event-list-pane__pager" data-event-pager>
- <PagerNav
- :page="view.pagination.page"
- :page-count="view.pagination.pageCount"
- :total="view.pagination.total"
- :list-name="view.switchView.title"
- @page="(target) => eventStore.setCurrentPage(target)"
- />
- </div>
- <p v-if="view.updatedAt" class="event-list-pane__note" data-event-updated>
- 数据时间 {{ view.updatedAt }}
- </p>
- </template>
- <!-- 没内容:如实说(空/失败/不可用各有措辞,来自 domain) -->
- <p v-else class="event-list-pane__placeholder" data-event-placeholder>{{ view.placeholder }}</p>
- </div>
- </template>
- <script setup>
- import { computed } from 'vue'
- import { eventListSwitchOptions } from '../domain/eventList.js'
- import { sectionPending } from '../domain/panelBlocks.js'
- import { useEventStore } from '../stores/eventStore.js'
- import PagerNav from './PagerNav.vue'
- import StatusTag from './StatusTag.vue'
- const eventStore = useEventStore()
- const view = computed(() => eventStore.view)
- const switchOptions = eventListSwitchOptions()
- /** 还没取过数(界面说"正在取数",不说"没有数据")。 */
- const pending = computed(() => sectionPending(view.value.section))
- function setMode(next) {
- eventStore.setMode(next)
- }
- </script>
- <style scoped>
- .event-list-pane,
- .event-list-pane *,
- .event-list-pane *::before,
- .event-list-pane *::after {
- box-sizing: border-box;
- }
- .event-list-pane {
- display: grid;
- gap: calc(4 * var(--u, 1px));
- min-height: 0;
- }
- /* 分段切换:两个等宽的胶囊按钮,选中项高亮(与全局筛选条同一套视觉语言) */
- .event-list-pane__switch {
- display: inline-flex;
- gap: calc(3 * var(--u, 1px));
- }
- .event-list-pane__switch-btn {
- padding: calc(1 * var(--u, 1px)) calc(10 * var(--u, 1px));
- border: 1px solid rgba(1, 171, 234, 0.32);
- border-radius: calc(8 * var(--u, 1px));
- background: rgba(0, 20, 72, 0.6);
- color: var(--text-2);
- font: inherit;
- font-size: var(--fs-caption, 10.5px);
- letter-spacing: 0.04em;
- cursor: pointer;
- }
- .event-list-pane__switch-btn:hover,
- .event-list-pane__switch-btn:focus-visible {
- border-color: rgba(1, 171, 234, 0.6);
- color: var(--text-strong);
- }
- .event-list-pane__switch-btn.is-active {
- border-color: rgba(0, 184, 255, 0.85);
- background: linear-gradient(180deg, rgba(1, 171, 234, 0.85), rgba(0, 82, 168, 0.85));
- color: var(--text-strong);
- }
- .event-list-pane__table {
- width: 100%;
- /* 固定布局:列宽由 colgroup 说了算,长内容不再撑爆表格把右列顶出可视区。 */
- table-layout: fixed;
- border-collapse: collapse;
- font-size: var(--fs-caption, 10.5px);
- }
- /* 列宽(合计 100%):等级/状态/时间/量值/设备是短列,名称与编号吃掉弹性宽度。
- 时间列给足宽度完整显示「MM-DD HH:MM」;编号列本就长串必截断,收窄让给时间;
- 量值/设备列保证右侧完整可见(维护者反馈的裁切点)。 */
- .col-level {
- width: 9%;
- }
- .col-name {
- width: 17%;
- }
- .col-code {
- width: 12%;
- }
- .col-status {
- width: 14%;
- }
- .col-time {
- width: 21%;
- }
- .col-value {
- width: 13%;
- }
- .col-device {
- width: 14%;
- }
- .event-list-pane__table td {
- /* 数据格收口:超宽省略号,绝不外溢撑破表格(完整值在 title 与详情卡)。 */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .event-list-pane__table th {
- padding: calc(2 * var(--u, 1px)) calc(3 * var(--u, 1px));
- border-bottom: 1px solid rgba(1, 171, 234, 0.35);
- color: var(--muted, #5680b0);
- font-weight: 700;
- letter-spacing: 0.04em;
- text-align: left;
- /* 表头不截断:允许换行完整显示(「预警编号」不被压成「预警…」)。 */
- white-space: normal;
- }
- .event-list-pane__table td {
- padding: calc(2 * var(--u, 1px)) calc(3 * var(--u, 1px));
- border-bottom: 1px dashed rgba(1, 171, 234, 0.14);
- color: var(--text-2, #88b7e8);
- vertical-align: top;
- }
- .event-list-pane__name {
- color: var(--text, #d7ecff);
- }
- .event-list-pane__mono,
- .event-list-pane__time,
- .event-list-pane__value {
- font-family: var(--num, monospace);
- letter-spacing: 0.02em;
- }
- .event-list-pane__placeholder,
- .event-list-pane__note,
- .is-muted {
- margin: 0;
- color: var(--muted, #5680b0);
- font-size: var(--fs-caption, 10.5px);
- letter-spacing: 0.04em;
- }
- /* 窄栏采用事件卡片排版,保留全部字段与读取接缝。 */
- .event-list-pane__table,
- .event-list-pane__table tbody {
- display: block;
- width: 100%;
- }
- .event-list-pane__table colgroup { display: none; }
- .event-list-pane__table thead {
- position: absolute;
- width: 1px;
- height: 1px;
- overflow: hidden;
- clip-path: inset(50%);
- }
- .event-list-pane__row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
- gap: 5px 8px;
- margin-bottom: 8px;
- padding: 8px;
- border: 1px solid var(--panel-line);
- border-radius: 4px;
- background: var(--inset);
- cursor: pointer;
- transition: border-color .15s, background-color .15s;
- }
- .event-list-pane__row:hover,
- .event-list-pane__row:focus-visible {
- outline: none;
- border-color: var(--cyan-hi);
- background: rgba(0, 82, 168, .5);
- box-shadow: inset 3px 0 0 var(--cyan-hi);
- }
- .event-list-pane__table .event-list-pane__row td {
- min-width: 0;
- padding: 0;
- border: 0;
- overflow: visible;
- white-space: normal;
- overflow-wrap: anywhere;
- }
- .event-list-pane__row td:nth-child(2),
- .event-list-pane__row td:nth-child(3),
- .event-list-pane__row td:nth-child(7) { grid-column: 1 / -1; }
- .event-list-pane__row td:nth-child(2) { font-size: var(--fs-body); }
- .event-list-pane__row td::before {
- display: block;
- color: var(--muted);
- font-size: var(--fs-caption);
- }
- .event-list-pane__row td:nth-child(3)::before { content: '编号'; }
- .event-list-pane__row td:nth-child(4)::before { content: '状态 / 工单'; }
- .event-list-pane__row td:nth-child(5)::before { content: '时间'; }
- .event-list-pane__row td:nth-child(6)::before { content: '量值'; }
- .event-list-pane__row td:nth-child(7)::before { content: '设备'; }
- </style>
|