|
|
@@ -0,0 +1,346 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
|
|
|
+import { IotStatApi } from '@/api/pms/stat'
|
|
|
+
|
|
|
+dayjs.extend(quarterOfYear)
|
|
|
+
|
|
|
+type TimeRange = 'day' | 'month' | 'quarter' | 'year'
|
|
|
+
|
|
|
+type NptCountItem = {
|
|
|
+ nptName: string
|
|
|
+ displayName: string
|
|
|
+ teamCount: number
|
|
|
+}
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const activeTimeRange = ref<TimeRange>('day')
|
|
|
+const loading = ref(false)
|
|
|
+const productionList = ref<NptCountItem[]>([])
|
|
|
+const deviceExceptionCount = ref(0)
|
|
|
+
|
|
|
+const timeOptions: Array<{ label: string; value: TimeRange }> = [
|
|
|
+ { label: '本日', value: 'day' },
|
|
|
+ { label: '本月', value: 'month' },
|
|
|
+ { label: '本季', value: 'quarter' },
|
|
|
+ { label: '全年', value: 'year' }
|
|
|
+]
|
|
|
+
|
|
|
+const nptNameMap: Record<string, string> = {
|
|
|
+ no: '无工作量',
|
|
|
+ xcdm: '井场待命',
|
|
|
+ wxz: '维修设备',
|
|
|
+ wz: '物资影响',
|
|
|
+ yc: '异常/复杂'
|
|
|
+}
|
|
|
+
|
|
|
+const productionTopList = computed(() => productionList.value.slice(0, 9))
|
|
|
+
|
|
|
+function getNptDisplayName(nptName?: string) {
|
|
|
+ if (!nptName) return '-'
|
|
|
+
|
|
|
+ return nptNameMap[nptName] || nptName
|
|
|
+}
|
|
|
+
|
|
|
+function getCreateTime(type: TimeRange) {
|
|
|
+ const now = dayjs()
|
|
|
+ const start = now.startOf(type === 'day' ? 'day' : type === 'year' ? 'year' : type)
|
|
|
+ const end = now.endOf(type === 'day' ? 'day' : type === 'year' ? 'year' : type)
|
|
|
+
|
|
|
+ return [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]
|
|
|
+}
|
|
|
+
|
|
|
+function handleProductionClick(item: NptCountItem) {
|
|
|
+ router.push({
|
|
|
+ name: 'IotRhDailyReportSummary',
|
|
|
+ query: {
|
|
|
+ activeTab: '非生产时效',
|
|
|
+ nptName: item.nptName,
|
|
|
+ createTime: getCreateTime(activeTimeRange.value)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+async function loadData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await IotStatApi.getRhNptCount({
|
|
|
+ createTime: getCreateTime(activeTimeRange.value)
|
|
|
+ })
|
|
|
+
|
|
|
+ productionList.value = Array.isArray(res)
|
|
|
+ ? res.map((item) => ({
|
|
|
+ nptName: item.nptName || '',
|
|
|
+ displayName: getNptDisplayName(item.nptName),
|
|
|
+ teamCount: Number(item.teamCount || 0)
|
|
|
+ }))
|
|
|
+ : []
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取瑞恒生产异常失败:', error)
|
|
|
+ productionList.value = []
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(activeTimeRange, loadData)
|
|
|
+
|
|
|
+onMounted(loadData)
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="panel flex flex-col">
|
|
|
+ <div class="panel-title flex items-center justify-between">
|
|
|
+ <div class="kb-panel-title-text flex items-center">
|
|
|
+ <div class="icon-decorator">
|
|
|
+ <span></span>
|
|
|
+ <span></span>
|
|
|
+ </div>
|
|
|
+ 异常警示
|
|
|
+ </div>
|
|
|
+ <el-segmented
|
|
|
+ v-model="activeTimeRange"
|
|
|
+ :options="timeOptions"
|
|
|
+ size="small"
|
|
|
+ class="exception-time-switch" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-loading="loading" class="exception-body">
|
|
|
+ <section class="exception-section exception-section--production">
|
|
|
+ <div class="exception-section__title">
|
|
|
+ <span class="exception-section__marker"></span>
|
|
|
+ 生产异常
|
|
|
+ <span class="exception-section__hint">TOP 9</span>
|
|
|
+ </div>
|
|
|
+ <div class="production-grid">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in productionTopList"
|
|
|
+ :key="item.nptName"
|
|
|
+ class="production-item cursor-pointer"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ @click="handleProductionClick(item)"
|
|
|
+ @keyup.enter="handleProductionClick(item)">
|
|
|
+ <span class="production-item__rank">{{ index + 1 }}</span>
|
|
|
+ <span class="production-item__name">{{ item.displayName }}</span>
|
|
|
+ <span class="production-item__count">
|
|
|
+ <span class="production-item__value">{{ item.teamCount }}</span>
|
|
|
+ <span class="production-item__unit">个</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div v-if="!productionTopList.length && !loading" class="exception-empty">
|
|
|
+ 暂无生产异常数据
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="exception-section exception-section--device">
|
|
|
+ <div class="exception-section__title">
|
|
|
+ <span class="exception-section__marker"></span>
|
|
|
+ 设备异常
|
|
|
+ </div>
|
|
|
+ <div class="device-exception-card">
|
|
|
+ <span class="device-exception-card__label">异常数量:</span>
|
|
|
+ <span class="device-exception-card__value">{{ deviceExceptionCount }}</span>
|
|
|
+ <span class="device-exception-card__unit">个</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import url('@/styles/kb.scss');
|
|
|
+
|
|
|
+.exception-time-switch {
|
|
|
+ --el-segmented-item-selected-color: #03409b;
|
|
|
+ --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
|
|
|
+ --el-segmented-bg-color: rgb(31 91 184 / 10%);
|
|
|
+ --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
|
|
|
+
|
|
|
+ min-height: calc(26px * var(--kb-scale, 1));
|
|
|
+ padding: calc(2px * var(--kb-scale, 1));
|
|
|
+ border: 1px solid rgb(31 91 184 / 12%);
|
|
|
+ transform: translateY(calc(-2px * var(--kb-scale, 1)));
|
|
|
+
|
|
|
+ :deep(.el-segmented__item) {
|
|
|
+ min-height: calc(22px * var(--kb-scale, 1));
|
|
|
+ padding: 0 calc(7px * var(--kb-scale, 1));
|
|
|
+ font-size: calc(13px * var(--kb-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: #29527f;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.exception-body {
|
|
|
+ display: flex;
|
|
|
+ min-height: 0;
|
|
|
+ flex: 1;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: calc(7px * var(--kb-scale, 1));
|
|
|
+ padding: calc(10px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1))
|
|
|
+ calc(12px * var(--kb-scale, 1));
|
|
|
+
|
|
|
+ :deep(.el-loading-mask) {
|
|
|
+ background-color: rgb(221 233 251 / 42%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section {
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section--production {
|
|
|
+ display: flex;
|
|
|
+ flex: 1 1 0;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section--device {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ flex: none;
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section__title {
|
|
|
+ display: flex;
|
|
|
+ height: calc(19px * var(--kb-scale, 1));
|
|
|
+ align-items: center;
|
|
|
+ gap: calc(7px * var(--kb-scale, 1));
|
|
|
+ margin-bottom: calc(5px * var(--kb-scale, 1));
|
|
|
+ font-size: calc(14px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: calc(20px * var(--kb-scale, 1));
|
|
|
+ color: #24364f;
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section__marker {
|
|
|
+ width: calc(5px * var(--kb-scale, 1));
|
|
|
+ height: calc(16px * var(--kb-scale, 1));
|
|
|
+ background: linear-gradient(180deg, #2d7cf8 0%, #03409b 100%);
|
|
|
+ border-radius: 999px;
|
|
|
+ box-shadow: 0 0 calc(8px * var(--kb-scale, 1)) rgb(45 124 248 / 28%);
|
|
|
+}
|
|
|
+
|
|
|
+.exception-section__hint {
|
|
|
+ padding-top: calc(1px * var(--kb-scale, 1));
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: calc(12px * var(--kb-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6f85aa;
|
|
|
+}
|
|
|
+
|
|
|
+.production-grid {
|
|
|
+ display: grid;
|
|
|
+ min-height: 0;
|
|
|
+ flex: 1;
|
|
|
+ padding: calc(6px * var(--kb-scale, 1));
|
|
|
+ gap: calc(5px * var(--kb-scale, 1)) calc(7px * var(--kb-scale, 1));
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ grid-template-rows: repeat(3, minmax(0, 1fr));
|
|
|
+}
|
|
|
+
|
|
|
+.production-item {
|
|
|
+ display: grid;
|
|
|
+ height: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 calc(7px * var(--kb-scale, 1));
|
|
|
+ overflow: hidden;
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 36%) 100%);
|
|
|
+ border: 1px solid rgb(255 255 255 / 74%);
|
|
|
+ border-radius: calc(6px * var(--kb-scale, 1));
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 78%),
|
|
|
+ 0 calc(6px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1)) rgb(63 103 171 / 7%);
|
|
|
+ grid-template-columns:
|
|
|
+ calc(18px * var(--kb-scale, 1)) minmax(0, 1fr)
|
|
|
+ calc(46px * var(--kb-scale, 1));
|
|
|
+ gap: calc(6px * var(--kb-scale, 1));
|
|
|
+}
|
|
|
+
|
|
|
+.production-item__rank {
|
|
|
+ display: inline-flex;
|
|
|
+ width: calc(18px * var(--kb-scale, 1));
|
|
|
+ height: calc(18px * var(--kb-scale, 1));
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: calc(11px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f5bb8;
|
|
|
+ background: rgb(31 91 184 / 8%);
|
|
|
+ border: 1px solid rgb(31 91 184 / 10%);
|
|
|
+ border-radius: 999px;
|
|
|
+}
|
|
|
+
|
|
|
+.production-item__name {
|
|
|
+ overflow: hidden;
|
|
|
+ font-size: calc(12px * var(--kb-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: calc(16px * var(--kb-scale, 1));
|
|
|
+ color: #24364f;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.production-item__count {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: calc(2px * var(--kb-scale, 1));
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.production-item__value {
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
+ font-size: calc(19px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1;
|
|
|
+ color: #e43f5f;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.production-item__unit {
|
|
|
+ font-size: calc(12px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ color: #24364f;
|
|
|
+}
|
|
|
+
|
|
|
+.exception-empty {
|
|
|
+ grid-column: 1 / -1;
|
|
|
+ place-self: center;
|
|
|
+ font-size: calc(14px * var(--kb-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6f85aa;
|
|
|
+}
|
|
|
+
|
|
|
+.device-exception-card {
|
|
|
+ display: flex;
|
|
|
+ height: calc(42px * var(--kb-scale, 1));
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 36%) 100%);
|
|
|
+ border: 1px solid rgb(255 255 255 / 74%);
|
|
|
+ border-radius: calc(6px * var(--kb-scale, 1));
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 78%),
|
|
|
+ 0 calc(6px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1)) rgb(63 103 171 / 7%);
|
|
|
+}
|
|
|
+
|
|
|
+.device-exception-card__label,
|
|
|
+.device-exception-card__unit {
|
|
|
+ font-size: calc(15px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ color: #24364f;
|
|
|
+}
|
|
|
+
|
|
|
+.device-exception-card__value {
|
|
|
+ margin: 0 calc(6px * var(--kb-scale, 1));
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
+ font-size: calc(23px * var(--kb-scale, 1));
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1;
|
|
|
+ color: #e43f5f;
|
|
|
+}
|
|
|
+</style>
|