Procházet zdrojové kódy

feat(daily-report): 支持异常提示跳转非生产时效统计

- 非生产时效异常项支持点击跳转日报汇总
- 同时存在钻井和修井数据时提供业务类型选择
- 跳转时携带时间范围及异常类型查询参数
- 汇总页面支持同步路由时间和统计页签
Zimo před 1 měsícem
rodič
revize
a1d52de450

+ 35 - 0
src/views/pms/iotrydailyreport/summary.vue

@@ -10,6 +10,8 @@ defineOptions({
   name: 'IotRyDailyReportSummary'
 })
 
+const route = useRoute()
+const pageRouteName = route.name
 const TOP_DEPT_ID = 158
 const deptId = resolveDailyReportDeptId(useUserStore().getUser.deptId, TOP_DEPT_ID)
 
@@ -42,6 +44,32 @@ const activeTab = ref<'日报统计' | '非生产时效'>('日报统计')
 const deptName = ref('瑞鹰国际钻井')
 const refreshKey = ref(0)
 
+const getRouteCreateTime = () => {
+  const createTime = route.query.createTime
+
+  if (Array.isArray(createTime)) {
+    const values = createTime.filter((item): item is string => typeof item === 'string')
+    return values.length === 2 ? values : undefined
+  }
+
+  if (typeof createTime === 'string') {
+    const values = createTime.split(',').filter(Boolean)
+    return values.length === 2 ? values : undefined
+  }
+
+  return undefined
+}
+
+const syncQueryFromRoute = () => {
+  if (route.name !== pageRouteName) return
+
+  const createTime = getRouteCreateTime()
+  if (createTime) query.value.createTime = createTime
+
+  activeTab.value = route.query.activeTab === '非生产时效' ? '非生产时效' : '日报统计'
+  handleQuery()
+}
+
 const handleDeptNodeClick = (node: any) => {
   deptName.value = node.name
   handleQuery()
@@ -57,6 +85,13 @@ const resetQuery = () => {
   deptName.value = '瑞鹰国际钻井'
   handleQuery()
 }
+
+onMounted(syncQueryFromRoute)
+
+watch(
+  () => [route.query.createTime, route.query.activeTab],
+  syncQueryFromRoute
+)
 </script>
 
 <template>

+ 35 - 0
src/views/pms/iotrydailyreport/xsummary.vue

@@ -10,6 +10,8 @@ defineOptions({
   name: 'IotRyXjDailyReportSummary'
 })
 
+const route = useRoute()
+const pageRouteName = route.name
 const TOP_DEPT_ID = 158
 const deptId = resolveDailyReportDeptId(useUserStore().getUser.deptId, TOP_DEPT_ID)
 
@@ -42,6 +44,32 @@ const activeTab = ref<'日报统计' | '非生产时效'>('日报统计')
 const deptName = ref('瑞鹰国际修井')
 const refreshKey = ref(0)
 
+const getRouteCreateTime = () => {
+  const createTime = route.query.createTime
+
+  if (Array.isArray(createTime)) {
+    const values = createTime.filter((item): item is string => typeof item === 'string')
+    return values.length === 2 ? values : undefined
+  }
+
+  if (typeof createTime === 'string') {
+    const values = createTime.split(',').filter(Boolean)
+    return values.length === 2 ? values : undefined
+  }
+
+  return undefined
+}
+
+const syncQueryFromRoute = () => {
+  if (route.name !== pageRouteName) return
+
+  const createTime = getRouteCreateTime()
+  if (createTime) query.value.createTime = createTime
+
+  activeTab.value = route.query.activeTab === '非生产时效' ? '非生产时效' : '日报统计'
+  handleQuery()
+}
+
 const handleDeptNodeClick = (node: any) => {
   deptName.value = node.name
   handleQuery()
@@ -57,6 +85,13 @@ const resetQuery = () => {
   deptName.value = '瑞鹰国际修井'
   handleQuery()
 }
+
+onMounted(syncQueryFromRoute)
+
+watch(
+  () => [route.query.createTime, route.query.activeTab],
+  syncQueryFromRoute
+)
 </script>
 
 <template>

+ 94 - 2
src/views/pms/stat/rykb/exception-prompt.vue

@@ -10,11 +10,20 @@ type TimeRange = 'day' | 'month' | 'quarter' | 'year'
 type NptCountItem = {
   nptName: string
   teamCount: number
+  classificationPair: {
+    zj: number
+    xj: number
+  }
 }
 
+type ProjectClassification = 'zj' | 'xj'
+
+const router = useRouter()
 const activeTimeRange = ref<TimeRange>('day')
 const loading = ref(false)
 const productionList = ref<NptCountItem[]>([])
+const classificationDialogVisible = ref(false)
+const pendingProductionItem = ref<NptCountItem | null>(null)
 
 const timeOptions: Array<{ label: string; value: TimeRange }> = [
   { label: '本日', value: 'day' },
@@ -34,6 +43,48 @@ function getCreateTime(type: TimeRange) {
   return [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]
 }
 
+function hasClassification(item: NptCountItem, classification: ProjectClassification) {
+  return item.classificationPair[classification] > 0
+}
+
+function navigateToSummary(item: NptCountItem, classification: ProjectClassification) {
+  classificationDialogVisible.value = false
+  pendingProductionItem.value = null
+
+  router.push({
+    name:
+      classification === 'zj' ? 'IotRyDailyReportSummary' : 'IotRyXjDailyReportSummary',
+    query: {
+      activeTab: '非生产时效',
+      createTime: getCreateTime(activeTimeRange.value),
+      nptName: item.nptName
+    }
+  })
+}
+
+function handleProductionClick(item: NptCountItem) {
+  const hasZj = hasClassification(item, 'zj')
+  const hasXj = hasClassification(item, 'xj')
+
+  if (hasZj && hasXj) {
+    pendingProductionItem.value = item
+    classificationDialogVisible.value = true
+    return
+  }
+
+  if (hasZj) {
+    navigateToSummary(item, 'zj')
+  } else if (hasXj) {
+    navigateToSummary(item, 'xj')
+  }
+}
+
+function handleClassificationSelect(classification: ProjectClassification) {
+  if (!pendingProductionItem.value) return
+
+  navigateToSummary(pendingProductionItem.value, classification)
+}
+
 async function loadData() {
   loading.value = true
   try {
@@ -44,7 +95,11 @@ async function loadData() {
     productionList.value = Array.isArray(res)
       ? res.map((item) => ({
           nptName: item.nptName || '-',
-          teamCount: Number(item.teamCount || 0)
+          teamCount: Number(item.teamCount || 0),
+          classificationPair: {
+            zj: Number(item.classificationPair?.zj || 0),
+            xj: Number(item.classificationPair?.xj || 0)
+          }
         }))
       : []
   } catch (error) {
@@ -92,7 +147,12 @@ onMounted(() => {
           <div
             v-for="(item, index) in productionTopList"
             :key="item.nptName"
-            class="production-item">
+            class="production-item"
+            :class="{
+              'production-item--clickable':
+                hasClassification(item, 'zj') || hasClassification(item, 'xj')
+            }"
+            @click="handleProductionClick(item)">
             <span class="production-item__rank">{{ index + 1 }}</span>
             <span class="production-item__name">{{ item.nptName }}</span>
             <span class="production-item__count">
@@ -118,6 +178,21 @@ onMounted(() => {
         </div>
       </section>
     </div>
+
+    <el-dialog
+      v-model="classificationDialogVisible"
+      title="选择业务类型"
+      width="360px"
+      append-to-body>
+      <div class="classification-dialog__content">
+        “{{ pendingProductionItem?.nptName }}”同时包含钻井和修井数据,请选择要查看的类型。
+      </div>
+      <template #footer>
+        <el-button @click="classificationDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleClassificationSelect('zj')">查看钻井</el-button>
+        <el-button type="primary" @click="handleClassificationSelect('xj')">查看修井</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -232,6 +307,23 @@ onMounted(() => {
   gap: calc(6px * var(--kb-scale, 1));
 }
 
+.production-item--clickable {
+  cursor: pointer;
+  transition:
+    border-color 0.2s,
+    transform 0.2s;
+
+  &:hover {
+    border-color: rgb(45 124 248 / 38%);
+    transform: translateY(calc(-1px * var(--kb-scale, 1)));
+  }
+}
+
+.classification-dialog__content {
+  line-height: 1.7;
+  color: var(--el-text-color-regular);
+}
+
 .production-item__rank {
   display: inline-flex;
   width: calc(18px * var(--kb-scale, 1));