| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- <template>
- <div class="entity-sections" :class="{ 'is-device': presentation === 'device' }" data-entity-detail-sections>
- <div v-if="view.summary.length && presentation !== 'device'" class="entity-sections__summary">
- <div v-for="item in view.summary" :key="item.label">
- <span>{{ item.label }}</span>
- <!-- 等级作为带色的指标卡(维护者 2026-10-29:不要重复的第二个等级字段)。 -->
- <b v-if="item.tone" :data-tone="item.tone" class="entity-sections__tag">{{ item.value }}</b>
- <b v-else>{{ item.value }}</b>
- </div>
- </div>
- <ol v-if="view.stage.length" class="entity-sections__stage">
- <li v-for="step in view.stage" :key="step.label" :class="'is-' + step.state"><i></i><span>{{ step.label }}</span></li>
- </ol>
- <nav v-if="tabs.length" class="entity-sections__tabs" aria-label="详情分区">
- <button v-for="tab in tabs" :key="tab.key" type="button" role="tab" :aria-selected="tab.key === activeTab" :class="{ 'is-active': tab.key === activeTab }" @click="activeTab = tab.key">{{ tab.label }}</button>
- </nav>
- <section v-if="showOverview" v-for="group in activeSections" :key="group.title" class="entity-sections__group">
- <h3>{{ group.title }}</h3><dl class="entity-sections__grid"><div v-for="item in group.fields" :key="item.label" :class="{ 'is-wide': item.wide }"><dt>{{ item.label }}</dt><dd>{{ item.value }}<small v-if="item.note" class="entity-sections__note">{{ item.note }}</small></dd></div></dl>
- </section>
- <section v-if="showPeople && view.people.length" class="entity-sections__group"><h3>相关人员</h3><ul><li v-for="item in view.people" :key="item.role"><span>{{ item.role }}</span><b>{{ item.name }}</b></li></ul></section>
- <section v-if="showDevices && view.devices.length" class="entity-sections__group"><h3>设备信息</h3><ul><li v-for="item in view.devices" :key="item.code || item.name"><b>{{ item.name }}</b><span>{{ item.code }} · {{ item.status }}</span><small>{{ item.type || item.location }}</small><button v-if="item.code" type="button" class="entity-sections__locate" :data-locate-device="item.code" @click.stop="locateDevice(item.code)">定位 {{ item.code }}</button></li></ul></section>
- <section v-if="showRecords && view.records.length" class="entity-sections__group"><h3>处置记录</h3><ol class="entity-sections__records"><li v-for="(item,index) in view.records" :key="index"><b>{{ item.title }}</b><time>{{ item.time }}</time><span>{{ item.operator }}</span><p>{{ item.description }}</p></li></ol></section>
- <section v-if="showAttachments" class="entity-sections__group"><h3>附件</h3><p v-if="!view.attachments.length">暂无附件</p><ul v-else><li v-for="(item,index) in view.attachments" :key="index"><a v-if="item.url" :href="item.url" target="_blank" rel="noopener">{{ item.name }}</a><span v-else>{{ item.name }}</span></li></ul></section>
- <section v-if="showRelated" class="entity-sections__group"><h3>关联信息</h3><p v-if="!view.devices.length">暂无关联设备数据</p><ul v-else><li v-for="item in view.devices" :key="item.code || item.name"><b>{{ item.name }}</b><span>{{ item.code }} · {{ item.status }}</span><small>{{ item.type || item.location }}</small><button v-if="item.code" type="button" class="entity-sections__locate" :data-locate-device="item.code" @click.stop="locateDevice(item.code)">定位 {{ item.code }}</button></li></ul></section>
- <section v-if="showAlarms" class="entity-sections__group"><h3>关联报警</h3><p v-if="!view.relatedAlarms.length">暂无关联报警</p><ul v-else><li v-for="item in view.relatedAlarms" :key="item.code"><b>{{ item.name }}</b><span>{{ item.code }} · {{ item.status }}</span></li></ul></section>
- <section v-if="showFlow && view.track" class="entity-sections__group"><h3>处置轨迹</h3><WorkOrderTimeline :track="view.track" /></section>
- </div>
- </template>
- <script setup>
- import { computed, ref, watch } from 'vue'
- import { entityDetailView } from '../domain/entityDetail.js'
- import WorkOrderTimeline from './WorkOrderTimeline.vue'
- const props = defineProps({ detail: { type: Object, default: null }, presentation: { type: String, default: 'entity' } })
- const emit = defineEmits(['locate-device'])
- const view = computed(() => entityDetailView(props.detail))
- const activeTab = ref('')
- const tabs = computed(() => view.value.tabs || [])
- watch(() => props.detail && (props.detail.kind + '|' + props.detail.id), () => { activeTab.value = (tabs.value[0] && tabs.value[0].key) || '' }, { immediate: true })
- const showOverview = computed(() => !activeTab.value || ['overview','business'].includes(activeTab.value))
- const activeSections = computed(() => {
- if (activeTab.value === 'business') return view.value.sections.filter((group) => group.title === '业务详情')
- if (activeTab.value === 'overview') return view.value.sections.filter((group) => group.title !== '业务详情')
- return view.value.sections
- })
- const showPeople = computed(() => showOverview.value)
- // 关联设备与定位在**概览页也可见**(维护者 2026-10-29:定位功能不能被藏进二级页签)。
- const showDevices = computed(() => !activeTab.value || activeTab.value === 'overview' || activeTab.value === 'devices')
- const showRecords = computed(() => activeTab.value === 'records')
- const showAttachments = computed(() => activeTab.value === 'attachments')
- const showFlow = computed(() => activeTab.value === 'flow')
- const showRelated = computed(() => activeTab.value === 'related')
- const showAlarms = computed(() => activeTab.value === 'alarms')
- /** 关联设备定位:沿用运行监测「设备台账」的定位方法(由外壳转给 monitoringStore)。 */
- function locateDevice(code) {
- if (!code) return
- emit('locate-device', code)
- }
- </script>
- <style scoped>
- /* 等级/状态徽标:与列表卡片同一套语义色(维护者 2026-10-29:等级那一栏要有颜色)。 */
- .entity-sections__locate{margin-top:4px;padding:0 8px;border:1px solid rgba(1,171,234,.45);border-radius:9px;background:rgba(0,20,72,.5);color:var(--cyan-hi,#35d8ff);font:inherit;font-size:10px;cursor:pointer}
- .entity-sections__locate:hover,.entity-sections__locate:focus-visible{border-color:var(--cyan-hi,#00b8ff);color:var(--text-strong)}
- .entity-sections__tag{display:inline-block;padding:0 7px;border:1px solid rgba(1,171,234,.35);border-radius:9px;font-weight:700}
- .entity-sections__tag[data-tone='lv1']{border-color:rgba(255,59,82,.65);background:rgba(255,59,82,.14);color:#ffc2c9}
- .entity-sections__tag[data-tone='lv2']{border-color:rgba(255,176,32,.65);background:rgba(255,176,32,.14);color:#ffdc91}
- .entity-sections__tag[data-tone='lv3']{border-color:rgba(1,171,234,.65);background:rgba(1,171,234,.14);color:#b7e4ff}
- .entity-sections__tag[data-tone='lv4']{border-color:rgba(53,219,146,.6);background:rgba(53,219,146,.14);color:#b8f2d4}
- .entity-sections{display:grid;gap:8px}.entity-sections__summary{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.entity-sections__summary div,.entity-sections__group{padding:8px;border:1px solid rgba(1,171,234,.18);border-radius:6px;background:rgba(0,20,72,.35)}.entity-sections__summary span,dt,.entity-sections__group li span,.entity-sections__group li small,.entity-sections__group time{display:block;color:var(--muted);font-size:var(--fs-caption)}.entity-sections__stage{display:flex;margin:0;padding:4px 0 8px;list-style:none;overflow-x:auto}.entity-sections__stage li{position:relative;flex:1;min-width:54px;text-align:center;color:var(--muted);font-size:10px}.entity-sections__stage li:before{content:'';position:absolute;top:6px;left:0;width:100%;height:1px;background:rgba(86,128,176,.5)}.entity-sections__stage i{position:relative;z-index:1;display:block;width:13px;height:13px;margin:0 auto 4px;border:1px solid var(--muted);border-radius:50%;background:#061432}.entity-sections__stage .is-done i,.entity-sections__stage .is-active i{border-color:var(--cyan-hi);background:var(--cyan)}.entity-sections__tabs{display:flex;gap:4px;overflow-x:auto;border-bottom:1px solid rgba(1,171,234,.22)}.entity-sections__tabs button{padding:5px 8px;border:0;border-bottom:2px solid transparent;background:none;color:var(--text-2);white-space:nowrap;cursor:pointer}.entity-sections__tabs .is-active{border-bottom-color:var(--cyan-hi);color:var(--text-strong)}.entity-sections__group h3{margin:0 0 6px;color:var(--cyan-hi);font-size:var(--fs-caption)}.entity-sections__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px 10px;margin:0}.entity-sections__grid .is-wide{grid-column:1/-1}.entity-sections dd{margin:1px 0 0;color:var(--text);overflow-wrap:anywhere}.entity-sections__note{display:block;margin-top:1px;color:var(--muted);font-size:10px}.entity-sections__group ul,.entity-sections__records{display:grid;gap:6px;margin:0;padding:0;list-style:none}
- </style>
|