Sfoglia il codice sorgente

调整未认领、检测录入列表UI逻辑

yangguanjin 2 giorni fa
parent
commit
ec0917aed6

+ 22 - 12
src/pages/sign/index.vue

@@ -287,6 +287,7 @@ onLoad((options) => {
 })
 
 const orderReportId = ref('')
+const isSigned = ref('0')
 const getPreviewData = async () => {
   // orderId --> templateId + refId --> templateBlob 和 templateData
   /* 
@@ -298,6 +299,18 @@ const getPreviewData = async () => {
     uni.showToast({ title: '必须选择签字文件类型', icon: 'error' })
     return
   }
+  const orderDetail = await taskOrderRequestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, {
+    id: orderId.value,
+  })
+  const signFileList = orderDetail.data.signFileList || []
+  const targetBusinessType = businessTypeMap[routeType.value]
+  const signFile =
+    signFileList.find((row: any) => row.businessType === targetBusinessType)
+  if (signFile == null || signFile.isSignature == '0') {
+    isSigned.value = '0'
+  } else {
+    isSigned.value = '1'
+  }
   switch (routeType.value) {
     case 'FWD':
       const orderReportResp = await getTaskOrderReport({ taskOrderId: orderId.value })
@@ -311,9 +324,6 @@ const getPreviewData = async () => {
       orderReportId.value = orderReport?.id || ''
       break
     case 'JYRS':
-      const orderDetail = await taskOrderRequestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, {
-        id: orderId.value,
-      })
       const notificationformReport = orderDetail.data?.notificationformReport
       if (!notificationformReport) {
         const orderFormRes = await taskOrderRequestFunc(TaskOrderFuncName.GetOrderForm, equipType, {
@@ -321,11 +331,15 @@ const getPreviewData = async () => {
           businessType: 1000,
         })
         const JYRSTemplateId = orderFormRes.data?.templateId || ''
-        const addMajorIssuesRes = await taskOrderRequestFunc(TaskOrderFuncName.AddMajorIssues, equipType, {
-          orderId: orderId.value,
-          businessType: 1000,
-          templateId: JYRSTemplateId,
-        })
+        const addMajorIssuesRes = await taskOrderRequestFunc(
+          TaskOrderFuncName.AddMajorIssues,
+          equipType,
+          {
+            orderId: orderId.value,
+            businessType: 1000,
+            templateId: JYRSTemplateId,
+          },
+        )
         const JYRSReportId = addMajorIssuesRes.data
         templateId.value = JYRSTemplateId
         refId.value = (JYRSReportId as string) || ''
@@ -334,10 +348,6 @@ const getPreviewData = async () => {
         refId.value = notificationformReport?.id || ''
         orderReportId.value = notificationformReport?.id || ''
       }
-      if (!notificationformReport) {
-        uni.showToast({ title: '暂无检验结果告知报表', icon: 'error' })
-        return
-      }
       break
     default:
       uni.showToast({ title: '请选择正确的签字文件类型', icon: 'error' })

+ 4 - 4
src/pages/taskOnlinePage/components/TaskItem.vue

@@ -4,10 +4,10 @@
       <view class="task-top-left">
         <view class="row">
           <text class="task-top-text">
-            {{ item.equipName }}
+            {{ item.boilerModel }}
             <text>
-              <text class="task-top-text">{{ item.productNo }}</text>
+              <text class="task-top-text">{{ item.orderNo }}</text>
             </text>
             <text>主检人:{{ item.mainCheckerUser?.nickname || '-' }}</text>
@@ -17,7 +17,7 @@
     </view>
 
     <!-- 任务项信息 -->
-    <TaskItemInfo :item="taskItemInfo" />
+    <BoilerTaskItemInfo :item="taskItemInfo" />
 
     <!-- 按钮栏 -->
     <view class="task-btn">
@@ -51,7 +51,7 @@
 
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
-import TaskItemInfo from './TaskItemInfo.vue'
+import BoilerTaskItemInfo from './BoilerTaskItemInfo.vue'
 
 interface Props {
   item: any

+ 105 - 0
src/pages/taskOnlinePage/components/BoilerTaskItemInfo.vue

@@ -0,0 +1,105 @@
+<template>
+  <view class="task-center">
+    <view class="row-center">
+      <!-- 检验日期 -->
+      <text class="title">
+        检验日期:
+        <text class="text">{{ item.checkDate }}</text>
+      </text>
+      <!-- 检验性质 -->
+      <text class="title">
+        检验性质:
+        <text class="text">
+          {{ columns.find((col) => col.value === item.checkType)?.label || '-' }}
+        </text>
+      </text>
+      <!-- 主报告状态 -->
+      <text class="title">
+        主报告状态:
+        <text>{{ taskStatusList.find((col) => col.value === item.taskStatus)?.label || '-' }}</text>
+      </text>
+    </view>
+    <view class="row-center">
+      <text class="title">
+        设备注册代码:
+        <text class="text">{{ item.equipCode }}</text>
+      </text>
+    </view>
+    <view class="row-center">
+      <text class="title">
+        使用证编号:
+        <text class="text">{{ item.useRegisterNo }}</text>
+      </text>
+      <!-- <text class="title">费用:<text class="text">{{ item.fee || '-' }}</text></text> -->
+    </view>
+    <view class="row-center">
+      <text class="title">
+        单位名称:
+        <text class="text">{{ item.unitName }}</text>
+      </text>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue'
+import { PressureCheckerMyTaskStatus } from '@/utils/dictMap'
+
+interface Props {
+  item: any
+}
+
+const props = defineProps<Props>()
+
+const columns = [
+  { label: '定期检验', value: 100 },
+  { label: '年度检查', value: 200 },
+  // { label: '超年限检验', value: 300 },
+]
+
+const taskStatusList = [
+  { label: '待认领', value: PressureCheckerMyTaskStatus.WAIT_CONFIRM },
+  { label: '作废', value: PressureCheckerMyTaskStatus.CANCELLATION },
+  { label: '待录入', value: PressureCheckerMyTaskStatus.CONFIRMED },
+  { label: '记录录入', value: PressureCheckerMyTaskStatus.RECORD_INPUT },
+  { label: '记录校核', value: PressureCheckerMyTaskStatus.RECORD_CHECK },
+  { label: '报告编制', value: PressureCheckerMyTaskStatus.REPORT_INPUT },
+  { label: '报告审核', value: PressureCheckerMyTaskStatus.REPORT_AUDIT },
+  { label: '报告审批', value: PressureCheckerMyTaskStatus.REPORT_APPROVE },
+  { label: '报告报告办结', value: PressureCheckerMyTaskStatus.REPORT_END },
+]
+</script>
+
+<style lang="scss" scoped>
+.task-center {
+  display: flex;
+  align-items: center;
+  flex-flow: column nowrap;
+  justify-content: space-between;
+  padding: 8px 12px;
+  background-color: rgb(244, 244, 244);
+  border-radius: 5px;
+}
+
+.row-center {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.title {
+  color: rgb(108, 108, 108);
+  font-size: 12px;
+  line-height: 25px;
+  margin-right: 5px;
+}
+
+.text {
+  color: rgb(51, 51, 51);
+  font-size: 12px;
+  line-height: 25px;
+}
+</style>

+ 178 - 0
src/pages/taskOnlinePage/components/PipeTaskItem.vue

@@ -0,0 +1,178 @@
+<template>
+  <view class="task-cell">
+    <view class="task-top">
+      <view class="task-top-left">
+        <view class="row">
+          <text class="task-top-text">
+            {{ item.orderNo }}
+            <text>主检人:{{ item.mainCheckerUser?.nickname || '-' }}</text>
+          </text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 任务项信息 -->
+    <PipeTaskItemInfo :item="taskItemInfo" />
+
+    <!-- 按钮栏 -->
+    <view class="task-btn">
+      <view class="btn-group">
+        <button
+          v-if="item.mainCheckerUser"
+          class="task-btn-box"
+          @click="handleRouteToEquipmentDetail"
+        >
+          <text class="task-btn-text">检验录入</text>
+        </button>
+        <button v-else class="task-btn-box claim-btn" @click="handleClaim">
+          <text class="task-btn-text">设备认领</text>
+        </button>
+        <button
+          v-if="
+            item.taskStatus == '400' &&
+            item.isClaim &&
+            item.mainCheckerUser &&
+            userId === item.mainCheckerUser?.id
+          "
+          class="task-btn-box cancel-btn"
+          @click="handleClaim"
+        >
+          <text class="task-btn-text">取消认领</text>
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+import PipeTaskItemInfo from './PipeTaskItemInfo.vue'
+
+interface Props {
+  item: any
+  userId: string
+}
+
+const props = defineProps<Props>()
+
+const emit = defineEmits<{
+  claimTask: [id: string, isClaim: boolean]
+}>()
+
+const isClaim = ref(props.item.isClaim)
+
+watch(
+  () => props.item.isClaim,
+  (newVal) => {
+    isClaim.value = newVal
+  },
+)
+
+// 暴露方法给父组件
+defineExpose({
+  setIsClaim: (status: boolean) => {
+    isClaim.value = status
+  },
+})
+
+const taskItemInfo = computed(() => ({
+  ...props.item,
+  checkDate: Array.isArray(props.item.checkDate) ? props.item.checkDate.join('-') : '-',
+  scheduleUserName: props.item?.scheduleUser?.nickname,
+  checkUsers: props.item?.checkUsers?.map((user: any) => user.nickname)?.join(',') || '',
+}))
+
+// 跳转到设备详情页(检验录入)
+const handleRouteToEquipmentDetail = () => {
+  uni.navigateTo({
+    url: `/pages/equipment/detail/equipmentDetail?orderId=${props.item.orderId}&orderItemId=${props.item.id}&equipId=${props.item.equipId}&pageType=INSPECT&useOnline=1&canEdit=true`,
+  })
+}
+
+// 认领/取消认领
+const handleClaim = () => {
+  emit('claimTask', props.item.id, !isClaim.value)
+}
+</script>
+
+<style lang="scss" scoped>
+.task-cell {
+  padding: 12px;
+  margin: 12px;
+  margin-bottom: 12px;
+  background-color: #fff;
+  border-radius: 5px;
+}
+
+.task-top {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: center;
+  padding-bottom: 10px;
+  margin-bottom: 10px;
+  border-bottom: 1px solid rgb(244, 244, 244);
+}
+
+.task-top-left {
+  display: flex;
+  flex: 1;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.row {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.task-top-text {
+  font-size: 17px;
+  font-weight: bold;
+  color: rgb(51, 51, 51);
+}
+
+.task-btn {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+
+.btn-group {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.task-btn-box {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 65px;
+  height: 26px;
+  padding: 0 8px;
+  margin-left: 8px;
+  font-size: 12px;
+  background-color: #2f8eff;
+  border: none;
+  border-radius: 3px;
+}
+
+.task-btn-box.claim-btn {
+  background-color: #67c23a;
+}
+
+.task-btn-box.cancel-btn {
+  background-color: #e6a23c;
+}
+
+.task-btn-text {
+  font-size: 12px;
+  color: rgb(222, 238, 255);
+  text-align: center;
+}
+</style>

+ 82 - 0
src/pages/taskOnlinePage/components/PipeTaskItemInfo.vue

@@ -0,0 +1,82 @@
+<template>
+  <view class="task-center">
+    <view class="row-center">
+      <!-- 检验日期 -->
+      <text class="title">检验日期:<text class="text">{{ item.checkDate }}</text></text>
+      <!-- 检验性质 -->
+      <text class="title">检验性质:<text class="text">{{ columns.find((col) => col.value === item.checkType)?.label || '-' }}</text></text>
+      <text class="title">主报告状态:<text class="text">{{ taskStatusList.find((col) => col.value === item.taskStatus)?.label || '-' }}</text></text>
+    </view>
+    <view class="row-center">
+      <text class="title">工程号:<text class="text">{{ item.projectNo?.split(',')?.join('、') }}</text></text>
+    </view>
+    <view class="row-center">
+      <text class="title">单位名称:<text class="text">{{ item.unitName }}</text></text>
+      <!-- <text class="title">费用:<text class="text">{{ item.fee || '-' }}</text></text> -->
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+
+import { PressureCheckerMyTaskStatus } from '@/utils/dictMap'
+
+interface Props {
+  item: any
+}
+
+const props = defineProps<Props>()
+
+const columns = [
+  { label: '定期检验', value: 100 },
+  { label: '年度检查', value: 200 },
+  // { label: '超年限检验', value: 300 },
+]
+
+const taskStatusList = [
+  { label: '待认领', value: PressureCheckerMyTaskStatus.WAIT_CONFIRM },
+  { label: '作废', value: PressureCheckerMyTaskStatus.CANCELLATION },
+  { label: '待录入', value: PressureCheckerMyTaskStatus.CONFIRMED },
+  { label: '记录录入', value: PressureCheckerMyTaskStatus.RECORD_INPUT },
+  { label: '记录校核', value: PressureCheckerMyTaskStatus.RECORD_CHECK },
+  { label: '报告编制', value: PressureCheckerMyTaskStatus.REPORT_INPUT },
+  { label: '报告审核', value: PressureCheckerMyTaskStatus.REPORT_AUDIT },
+  { label: '报告审批', value: PressureCheckerMyTaskStatus.REPORT_APPROVE },
+  { label: '报告报告办结', value: PressureCheckerMyTaskStatus.REPORT_END },
+]
+
+</script>
+
+<style lang="scss" scoped>
+.task-center {
+  display: flex;
+  align-items: center;
+  flex-flow: column nowrap;
+  justify-content: space-between;
+  padding: 8px 12px;
+  background-color: rgb(244, 244, 244);
+  border-radius: 5px;
+}
+
+.row-center {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.title {
+  color: rgb(108, 108, 108);
+  font-size: 12px;
+  line-height: 25px;
+  margin-right: 5px;
+}
+
+.text {
+  color: rgb(51, 51, 51);
+  font-size: 12px;
+  line-height: 25px;
+}
+</style>

+ 0 - 50
src/pages/taskOnlinePage/components/TaskItemInfo.vue

@@ -1,50 +0,0 @@
-<template>
-  <view class="task-center">
-    <view class="row-center">
-      <text class="title">使用证编号:<text class="text">{{ item.useRegisterNo }}</text></text>
-      <text class="title">设备注册代码:<text class="text">{{ item.equipCode }}</text></text>
-      <text class="title">费用:<text class="text">{{ item.fee || '-' }}</text></text>
-    </view>
-  </view>
-</template>
-
-<script lang="ts" setup>
-interface Props {
-  item: any
-}
-
-defineProps<Props>()
-</script>
-
-<style lang="scss" scoped>
-.task-center {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 8px 12px;
-  background-color: rgb(244, 244, 244);
-  border-radius: 5px;
-}
-
-.row-center {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  justify-content: space-between;
-  align-items: center;
-  width: 100%;
-}
-
-.title {
-  color: rgb(108, 108, 108);
-  font-size: 12px;
-  line-height: 25px;
-  margin-right: 5px;
-}
-
-.text {
-  color: rgb(51, 51, 51);
-  font-size: 12px;
-  line-height: 25px;
-}
-</style>

+ 2 - 8
src/pages/taskOnlinePage/components/query/QueryView.vue

@@ -19,18 +19,11 @@
           :style="{ marginLeft: 0 }"
           @change="handleChange('unitName', $event)"
         />
-        <InputCom
-          ref="productNoRef"
-          title="出厂编号:"
-          type="productNo"
-          :text-style="{ width: '70px' }"
-          :style="{ marginLeft: 0 }"
-          @change="handleChange('productNo', $event)"
-        />
         <CheckDateCom
           ref="checkDateRef"
           type="checkDate"
           :text-style="{ width: '70px' }"
+          title="检验日期:"
           :style="{ marginLeft: 0, marginBottom: 0 }"
           @change="handleChange('checkDate', $event)"
         />
@@ -48,6 +41,7 @@
         <CheckTaskStatusCom
           ref="taskStatusListRef"
           type="taskStatusList"
+          title="主报告状态:"
           :default-value="[400, 500, 510]"
           :text-style="{ width: '90px' }"
           :style="{ marginLeft: 0 }"

+ 59 - 41
src/pages/taskOnlinePage/components/query/CheckDateCom.vue

@@ -1,19 +1,30 @@
 <template>
   <view class="check-date-box" :style="style">
-    <text class="title" :style="textStyle">检验时间:</text>
-    <view class="date-input" @click="handleClick">
-      <text class="date-text">
-        {{ displayText || '请选择日期范围' }}
-      </text>
-    </view>
+    <view class="title" :style="textStyle">{{ title }}</view>
+    <wd-datetime-picker
+      class="date-picker"
+      v-model="dateRange"
+      type="date"
+      :min-date="minDate"
+      :max-date="maxDate"
+      :default-value="defaultValue"
+      @confirm="onConfirm"
+    >
+      <view class="date-input">
+        <text :class="['date-text', { 'date-text--placeholder': !displayText }]">
+          {{ displayText || '请选择日期范围' }}
+        </text>
+      </view>
+    </wd-datetime-picker>
   </view>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
 import dayjs from 'dayjs'
 
 interface Props {
+  title: string
   type: string
   textStyle?: any
   style?: any
@@ -25,43 +36,37 @@ const emit = defineEmits<{
   change: [date: string[], type: string]
 }>()
 
-const rangeStartDate = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'))
-const rangeEndDate = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'))
-const displayText = ref('')
+const minDate = new Date(new Date().getFullYear() - 10, 0, 1).getTime()
+const maxDate = new Date(new Date().getFullYear() + 10, 11, 31).getTime()
+const defaultValue = [Date.now(), Date.now()]
 
-// 暴露方法
-defineExpose({
-  reset: () => {
-    rangeStartDate.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
-    rangeEndDate.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
-    displayText.value = ''
-  },
-  inputContent: displayText.value ? [rangeStartDate.value, rangeEndDate.value] : '',
-})
-
-// 点击选择日期
-const handleClick = () => {
-  const startDatePart = rangeStartDate.value.split(' ')[0]
-  const endDatePart = rangeEndDate.value.split(' ')[0]
+const dateRange = ref<(number | string)[]>([])
 
-  uni.navigateTo({
-    url: `/pages/dateRangePicker/index?startDate=${startDatePart}&endDate=${endDatePart}&type=${props.type}`,
-  })
-}
-
-// 监听日期选择事件
-uni.$on('DateRangeSelected', (data: any) => {
-  if (data.type === props.type) {
-    const startDateTime = `${data.startDate} 00:00:00`
-    const endDateTime = `${data.endDate} 23:59:59`
-
-    rangeStartDate.value = startDateTime
-    rangeEndDate.value = endDateTime
-
-    displayText.value = `${dayjs(data.startDate).format('YYYY-MM-DD')} 至 ${dayjs(data.endDate).format('YYYY-MM-DD')}`
+const displayText = computed(() => {
+  if (!dateRange.value || dateRange.value.length < 2 || !dateRange.value[0] || !dateRange.value[1]) {
+    return ''
+  }
+  const start = dayjs(Number(dateRange.value[0])).format('YYYY-MM-DD')
+  const end = dayjs(Number(dateRange.value[1])).format('YYYY-MM-DD')
+  return `${start} 至 ${end}`
+})
 
+const onConfirm = ({ value }: { value: (number | string)[] }) => {
+  if (value && value.length === 2) {
+    const startDateTime = `${dayjs(Number(value[0])).format('YYYY-MM-DD')} 00:00:00`
+    const endDateTime = `${dayjs(Number(value[1])).format('YYYY-MM-DD')} 23:59:59`
     emit('change', [startDateTime, endDateTime], props.type)
   }
+}
+
+defineExpose({
+  reset: () => {
+    dateRange.value = []
+  },
+  inputContent: computed(() => {
+    if (!dateRange.value || dateRange.value.length < 2) return ''
+    return [dateRange.value[0], dateRange.value[1]]
+  }),
 })
 </script>
 
@@ -74,17 +79,26 @@ uni.$on('DateRangeSelected', (data: any) => {
 }
 
 .title {
-  width: 70px;
+  flex-shrink: 0;
+  width: 60px;
   font-size: 12px;
   color: rgb(51, 51, 51);
   text-align: right;
 }
 
+.date-picker {
+  flex: 1;
+  min-width: 0;
+}
+
 .date-input {
+  box-sizing: border-box;
   display: flex;
   flex: 1;
   align-items: center;
+  min-width: 0;
   height: 30px;
+  min-height: 30px;
   padding: 0 5px;
   border: 1px solid #ccc;
   border-radius: 6px;
@@ -92,6 +106,10 @@ uni.$on('DateRangeSelected', (data: any) => {
 
 .date-text {
   font-size: 12px;
-  color: rgba(136, 136, 136, 1);
+  color: rgb(51, 51, 51);
+
+  &--placeholder {
+    color: rgba(136, 136, 136, 1);
+  }
 }
 </style>

+ 47 - 43
src/pages/taskOnlinePage/components/query/CheckNatureCom.vue

@@ -1,17 +1,23 @@
 <template>
   <view class="check-nature-box">
-    <text class="title" :style="textStyle">检验性质:</text>
-    <view class="nature-input" @click="showPicker">
-      <text class="nature-text">{{ selectorChecked || '请选择' }}</text>
-      <image class="arrow-icon" :src="iconMap.ArrowDown" :class="{ 'arrow-rotate': isOpen }" />
-    </view>
+    <view class="title" :style="textStyle">检验性质:</view>
+    <wd-picker
+      class="nature-picker"
+      v-model="selectCode"
+      :columns="columns"
+      @confirm="onConfirm"
+    >
+      <view class="nature-input">
+        <text :class="['nature-text', { 'nature-text--placeholder': !displayLabel }]">
+          {{ displayLabel || '请选择' }}
+        </text>
+      </view>
+    </wd-picker>
   </view>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
-import iconMap from '@/utils/imagesMap'
-import { getCheckTypeFromText } from '@/utils/dictMap'
+import { ref, computed } from 'vue'
 
 interface Props {
   type: string
@@ -24,36 +30,32 @@ const emit = defineEmits<{
   change: [text: number, type: string]
 }>()
 
-const isOpen = ref(false)
-const selectorChecked = ref('')
 const selectCode = ref<number | undefined>(undefined)
 
-const itemList = ['定期检验', '年度检查', '超年限检验']
+const columns = [
+  { label: '定期检验', value: 100 },
+  { label: '年度检查', value: 200 },
+  // { label: '超年限检验', value: 300 },
+]
+
+const displayLabel = computed(() => {
+  if (selectCode.value == null) return ''
+  const item = columns.find((c) => c.value === selectCode.value)
+  return item ? item.label : ''
+})
+
+const onConfirm = ({ value }: { value: number }) => {
+  if (value) {
+    emit('change', value, props.type)
+  }
+}
 
-// 暴露方法
 defineExpose({
   reset: () => {
-    selectorChecked.value = ''
     selectCode.value = undefined
   },
-  inputContent: selectCode.value || undefined,
+  inputContent: computed(() => selectCode.value || undefined),
 })
-
-// 显示选择器
-const showPicker = () => {
-  uni.showActionSheet({
-    itemList,
-    success: (res) => {
-      const selected = itemList[res.tapIndex]
-      selectorChecked.value = selected
-      const code = getCheckTypeFromText(selected)
-      if (code) {
-        selectCode.value = code
-        emit('change', code, props.type)
-      }
-    },
-  })
-}
 </script>
 
 <style lang="scss" scoped>
@@ -61,23 +63,31 @@ const showPicker = () => {
   display: flex;
   flex-direction: row;
   align-items: center;
-  margin-bottom: 10px !important;
+  margin-bottom: 10px;
 }
 
 .title {
-  width: 70px;
+  flex-shrink: 0;
+  width: 60px;
   font-size: 12px;
   color: rgb(51, 51, 51);
   text-align: right;
 }
 
+.nature-picker {
+  flex: 1;
+  min-width: 0;
+}
+
 .nature-input {
+  box-sizing: border-box;
   display: flex;
   flex: 1;
   flex-direction: row;
   align-items: center;
-  justify-content: space-between;
+  min-width: 0;
   height: 30px;
+  min-height: 30px;
   padding: 0 5px;
   border: 1px solid #ccc;
   border-radius: 6px;
@@ -85,16 +95,10 @@ const showPicker = () => {
 
 .nature-text {
   font-size: 12px;
-  color: rgba(136, 136, 136, 1);
-}
-
-.arrow-icon {
-  width: 15px;
-  height: 15px;
-  transition: transform 0.3s;
-}
+  color: rgb(51, 51, 51);
 
-.arrow-rotate {
-  transform: rotate(180deg);
+  &--placeholder {
+    color: rgba(136, 136, 136, 1);
+  }
 }
 </style>

+ 50 - 75
src/pages/taskOnlinePage/components/query/CheckTaskStatusCom.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="check-task-status-box">
-    <text class="title" :style="textStyle">任务状态:</text>
-    <view class="status-input" @click="showMultiPicker">
+    <text class="title" :style="textStyle">{{ title }}</text>
+    <view class="status-input" @click="openPopup">
       <view class="status-tags">
         <view v-if="selectedItems.length === 0" class="placeholder-tag">
           <text class="placeholder-text">请选择</text>
@@ -11,7 +11,7 @@
             <text class="tag-text">{{ selectedItems[0] }}</text>
           </view>
           <view class="count-tag">
-            <text class="count-text">{{ selectedItems.length - 1 }}</text>
+            <text class="count-text">+{{ selectedItems.length - 1 }}</text>
           </view>
         </template>
         <template v-else>
@@ -20,42 +20,44 @@
           </view>
         </template>
       </view>
-      <image class="arrow-icon" :src="iconMap.ArrowDown" />
     </view>
 
-    <!-- 多选弹窗 -->
-    <view v-if="showPopup" class="popup-mask" @click="closePopup">
-      <view class="popup-content" @click.stop>
-        <view class="popup-header">
-          <text class="popup-title">选择任务状态</text>
-        </view>
-        <view class="popup-options">
-          <view
-            v-for="item in itemList"
-            :key="item.value"
-            class="option-item"
-            :class="{ selected: selectedCodes.includes(item.value) }"
-            @click="toggleOption(item)"
-          >
-            <view class="checkbox" :class="{ checked: selectedCodes.includes(item.value) }"></view>
-            <text class="option-label">{{ item.label }}</text>
-          </view>
-        </view>
-        <view class="popup-footer">
-          <button class="cancel-btn" @click="closePopup">取消</button>
-          <button class="confirm-btn" @click="confirmSelection">确定</button>
+    <wd-popup
+      v-model="popupVisible"
+      position="bottom"
+      :safe-area-inset-bottom="true"
+      closable
+      custom-style="border-radius: 10px 10px 0 0;"
+    >
+      <view class="popup-header">
+        <text class="popup-title">选择{{ title.replace(':', '') }}</text>
+      </view>
+      <view class="popup-options">
+        <view
+          v-for="item in itemList"
+          :key="item.value"
+          class="option-item"
+          :class="{ selected: tempSelectedCodes.includes(item.value) }"
+          @click="toggleOption(item)"
+        >
+          <view class="checkbox" :class="{ checked: tempSelectedCodes.includes(item.value) }"></view>
+          <text class="option-label">{{ item.label }}</text>
         </view>
       </view>
-    </view>
+      <view class="popup-footer">
+        <button class="cancel-btn" @click="cancelSelection">取消</button>
+        <button class="confirm-btn" @click="confirmSelection">确定</button>
+      </view>
+    </wd-popup>
   </view>
 </template>
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue'
-import iconMap from '@/utils/imagesMap'
 import { PressureCheckerMyTaskStatus } from '@/utils/dictMap'
 
 interface Props {
+  title: string
   type: string
   defaultValue?: Array<string | number>
   textStyle?: any
@@ -69,68 +71,68 @@ const emit = defineEmits<{
   change: [selectedCodes: Array<string | number>, type: string]
 }>()
 
-const showPopup = ref(false)
-const selectedCodes = ref<Array<string | number>>(props.defaultValue || [])
+const popupVisible = ref(false)
+const selectedCodes = ref<Array<string | number>>([...props.defaultValue])
 const selectedItems = ref<string[]>([])
+const tempSelectedCodes = ref<Array<string | number>>([])
 
 const itemList = [
+  { label: '待认领', value: PressureCheckerMyTaskStatus.WAIT_CONFIRM },
   { label: '作废', value: PressureCheckerMyTaskStatus.CANCELLATION },
   { label: '待录入', value: PressureCheckerMyTaskStatus.CONFIRMED },
   { label: '记录录入', value: PressureCheckerMyTaskStatus.RECORD_INPUT },
   { label: '记录校核', value: PressureCheckerMyTaskStatus.RECORD_CHECK },
   { label: '报告编制', value: PressureCheckerMyTaskStatus.REPORT_INPUT },
+  { label: '报告审核', value: PressureCheckerMyTaskStatus.REPORT_AUDIT },
+  { label: '报告审批', value: PressureCheckerMyTaskStatus.REPORT_APPROVE },
+  { label: '报告报告办结', value: PressureCheckerMyTaskStatus.REPORT_END },
 ]
 
-// 初始化选中项
 if (props.defaultValue && props.defaultValue.length > 0) {
   selectedItems.value = itemList
     .filter((item) => props.defaultValue?.includes(item.value))
     .map((item) => item.label)
 }
 
-// 判断是否超出宽度(选中1个显示tag,2个及以上显示第一个tag+剩余数量)
 const isOverflow = computed(() => {
   return selectedItems.value.length > 1
 })
 
-// 暴露方法
 defineExpose({
   reset: () => {
-    selectedCodes.value = props.defaultValue || []
+    selectedCodes.value = [...props.defaultValue]
     selectedItems.value = itemList
       .filter((item) => props.defaultValue?.includes(item.value))
       .map((item) => item.label)
   },
-  inputContent: selectedCodes.value,
+  inputContent: computed(() => selectedCodes.value),
 })
 
-// 显示弹窗
-const showMultiPicker = () => {
-  showPopup.value = true
-}
-
-// 关闭弹窗
-const closePopup = () => {
-  showPopup.value = false
+const openPopup = () => {
+  tempSelectedCodes.value = [...selectedCodes.value]
+  popupVisible.value = true
 }
 
-// 切换选项
 const toggleOption = (item: { label: string; value: number }) => {
-  const index = selectedCodes.value.indexOf(item.value)
+  const index = tempSelectedCodes.value.indexOf(item.value)
   if (index > -1) {
-    selectedCodes.value.splice(index, 1)
+    tempSelectedCodes.value.splice(index, 1)
   } else {
-    selectedCodes.value.push(item.value)
+    tempSelectedCodes.value.push(item.value)
   }
 }
 
-// 确认选择
+const cancelSelection = () => {
+  popupVisible.value = false
+}
+
 const confirmSelection = () => {
+  selectedCodes.value = [...tempSelectedCodes.value]
   selectedItems.value = itemList
     .filter((item) => selectedCodes.value.includes(item.value))
     .map((item) => item.label)
   emit('change', selectedCodes.value, props.type)
-  closePopup()
+  popupVisible.value = false
 }
 </script>
 
@@ -154,7 +156,6 @@ const confirmSelection = () => {
   flex: 1;
   flex-direction: row;
   align-items: center;
-  justify-content: space-between;
   height: 30px;
   padding: 0 5px;
   border: 1px solid #ccc;
@@ -212,32 +213,6 @@ const confirmSelection = () => {
   color: rgba(136, 136, 136, 1);
 }
 
-.arrow-icon {
-  width: 15px;
-  height: 15px;
-}
-
-.popup-mask {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 999;
-  display: flex;
-  align-items: flex-end;
-  justify-content: center;
-  background-color: rgba(0, 0, 0, 0.5);
-}
-
-.popup-content {
-  width: 100%;
-  max-width: 500px;
-  overflow: hidden;
-  background-color: #fff;
-  border-radius: 10px 10px 0 0;
-}
-
 .popup-header {
   padding: 15px;
   text-align: center;

+ 243 - 0
src/pages/taskOnlinePage/components/query/PipeQueryView.vue

@@ -0,0 +1,243 @@
+<template>
+  <view class="query-view">
+    <view class="query-content" :style="contentStyle">
+      <view class="query-left">
+        <InputCom
+          ref="orderNoRef"
+          title="任务单号:"
+          type="orderNo"
+          :text-style="{ width: '70px' }"
+          :style="{ marginLeft: 0 }"
+          :default-value="params.orderNo"
+          @change="handleChange('orderNo', $event)"
+        />
+        <InputCom
+          ref="unitNameRef"
+          title="单位名称:"
+          type="unitName"
+          :text-style="{ width: '70px' }"
+          :style="{ marginLeft: 0 }"
+          @change="handleChange('unitName', $event)"
+        />
+        <CheckDateCom
+          ref="checkDateRef"
+          type="checkDate"
+          title="检验日期:"
+          :text-style="{ width: '70px' }"
+          :style="{ marginLeft: 0, marginBottom: 0 }"
+          @change="handleChange('checkDate', $event)"
+        />
+      </view>
+
+      <view class="query-right">
+        <InputCom
+          ref="projectNoRef"
+          title="工程号:"
+          type="projectNo"
+          :text-style="{ width: '90px' }"
+          :style="{ marginLeft: 0 }"
+          @change="handleChange('projectNo', $event)"
+        />
+        <CheckTaskStatusCom
+          ref="taskStatusListRef"
+          type="taskStatusList"
+          title="主报告状态:"
+          :default-value="[400, 500, 510]"
+          :text-style="{ width: '90px' }"
+          :style="{ marginLeft: 0 }"
+          @change="handleChange('taskStatusList', $event)"
+        />
+        <CheckNatureCom
+          ref="checkTypeRef"
+          type="checkType"
+          :text-style="{ width: '90px' }"
+          :style="{ marginLeft: 0, marginBottom: 0 }"
+          @change="handleChange('checkType', $event)"
+        />
+
+        <view class="btn-group">
+          <button class="btn reset-btn" @click="reset">重置</button>
+          <button class="btn query-btn" @click="query">查询</button>
+        </view>
+      </view>
+    </view>
+
+    <view class="query-toggle" @click="toggleFilter">
+      <text class="query-toggle-text">查询</text>
+      <image class="arrow-icon" :src="iconMap.ArrowDown" :class="{ 'arrow-rotate': isOpen }" />
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, computed } from 'vue'
+import iconMap from '@/utils/imagesMap'
+import InputCom from './InputCom.vue'
+import CheckDateCom from './CheckDateCom.vue'
+import CheckNatureCom from './CheckNatureCom.vue'
+import CheckTaskStatusCom from './CheckTaskStatusCom.vue'
+
+interface Props {
+  queryType?: Record<string, any>
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  queryType: () => ({}),
+})
+
+const emit = defineEmits<{
+  queryAction: [params: Record<string, any>]
+}>()
+
+// 默认收起
+const isOpen = ref(false)
+const params = reactive<Record<string, any>>({
+  orderNo: '',
+  projectNo: '',
+  unitName: '',
+  taskStatusList: [],
+  productNo: '',
+  checkType: '',
+  checkDate: [],
+  ...props.queryType,
+})
+
+// 内容区域样式(默认收起)
+const contentStyle = computed(() => ({
+  height: isOpen.value ? '170px' : '0',
+  opacity: isOpen.value ? 1 : 0,
+  overflow: 'hidden',
+  transition: 'all 0.3s',
+}))
+
+// 组件引用
+const orderNoRef = ref<any>(null)
+const unitNameRef = ref<any>(null)
+const productNoRef = ref<any>(null)
+const checkDateRef = ref<any>(null)
+const projectNoRef = ref<any>(null)
+const taskStatusListRef = ref<any>(null)
+const checkTypeRef = ref<any>(null)
+
+// 获取查询参数
+const getQueryParams = () => {
+  return { ...params }
+}
+
+defineExpose({
+  getQueryParams,
+})
+
+// 切换筛选
+const toggleFilter = () => {
+  isOpen.value = !isOpen.value
+}
+
+// 处理变化
+const handleChange = (propName: string, value: any) => {
+  params[propName] = value
+}
+
+// 重置
+const reset = () => {
+  orderNoRef.value?.reset()
+  unitNameRef.value?.reset()
+  productNoRef.value?.reset()
+  checkDateRef.value?.reset()
+  projectNoRef.value?.reset()
+  taskStatusListRef.value?.reset()
+  checkTypeRef.value?.reset()
+
+  Object.keys(params).forEach((key) => {
+    delete params[key]
+  })
+  Object.assign(params, { ...props.queryType })
+
+  emit('queryAction', params)
+}
+
+// 查询
+const query = () => {
+  emit('queryAction', params)
+}
+</script>
+
+<style lang="scss" scoped>
+.query-view {
+  width: 100%;
+  background-color: #fff;
+}
+
+.query-content {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  padding: 0 15px;
+}
+
+.query-left,
+.query-right {
+  flex: 1;
+  padding: 15px 0;
+}
+
+.query-right {
+  margin-left: 10px;
+}
+
+.btn-group {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+
+.btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 30px;
+  padding: 0 15px;
+  margin-left: 10%;
+  font-size: 15px;
+  border: none;
+  border-radius: 3px;
+}
+
+.reset-btn {
+  color: rgb(47, 142, 255);
+  background-color: #fff;
+  border: 1px solid rgb(47, 142, 255);
+}
+
+.query-btn {
+  color: rgb(222, 238, 255);
+  background-color: rgb(47, 142, 255);
+}
+
+.query-toggle {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  height: 60px;
+  padding: 0 20px;
+  border-top: 1px solid rgba(187, 187, 187, 0.8);
+}
+
+.query-toggle-text {
+  font-size: 16px;
+  color: rgb(51, 51, 51);
+}
+
+.arrow-icon {
+  width: 21px;
+  height: 21px;
+  transition: transform 0.3s;
+}
+
+.arrow-rotate {
+  transform: rotate(180deg);
+}
+</style>

+ 40 - 19
src/pages/taskOnlinePage/taskOnline.vue

@@ -18,10 +18,16 @@
     <NavBar title="在线录入" />
 
     <!-- 查询视图 -->
-    <QueryView ref="queryViewRef" :query-type="defaultTypeValue" @query-action="queryAction" />
+    <component
+      :is="queryViewComponent"
+      ref="queryViewRef"
+      :query-type="defaultTypeValue"
+      @query-action="queryAction"
+    />
 
     <!-- 筛选条件栏 -->
     <RadioFilterBar
+      v-if="equipType === EquipmentType.BOILER""
       v-model="rightType"
       :options="rightTypeList"
       type="radio"
@@ -31,7 +37,8 @@
     <!-- 列表 -->
     <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
       <view v-for="item in listData" :key="item.id" class="task-cell">
-        <TaskItem
+        <component
+          :is="taskItemComponent"
           :ref="setItemRef(item.id)"
           :item="item"
           :user-id="userId"
@@ -51,12 +58,15 @@
 <script lang="ts" setup>
 import { ref, reactive, computed, onMounted } from 'vue'
 import { useUserStore } from '@/store/user'
-import QueryView from './components/query/QueryView.vue'
-import TaskItem from './components/TaskItem.vue'
+import BoilerQueryView from './components/query/BoilerQueryView.vue'
+import PipeQueryView from './components/query/PipeQueryView.vue'
 import RadioFilterBar from '@/components/RadioFilterBar/RadioFilterBar.vue'
 import { useConfigStore } from '@/store/config'
 import { TaskOrderFuncName, requestFunc } from '@/api/ApiRouter/taskOrder'
 import NavBar from '@/components/NavBar/NavBar.vue'
+import BoilerTaskItem from './components/BoilerTaskItem.vue'
+import PipeTaskItem from './components/PipeTaskItem.vue'
+import { EquipmentType } from '@/utils/dictMap'
 
 defineOptions({
   name: 'taskOnline',
@@ -71,11 +81,10 @@ const params = reactive({
   pageNo: 1,
   pageSize: 10,
 })
-const rightType = ref<'claim' | 'allot' | 'unclaim' | ''>('unclaim')
+const rightType = ref<'claim' | 'allot' | 'unclaim' | ''>('')
 const rightTypeList = [
   { value: '全部', id: '' },
   { value: '已认领', id: 'claim' },
-  { value: '已分配', id: 'allot' },
   { value: '待认领', id: 'unclaim' },
 ]
 
@@ -85,6 +94,28 @@ const userId = computed(() => userInfo.value?.id || '')
 
 const equipType = useConfigStore().getEquipType()
 
+const taskItemComponent = computed(() => {
+  switch (equipType) {
+    case EquipmentType.BOILER:
+      return BoilerTaskItem
+    case EquipmentType.PIPE:
+      return PipeTaskItem
+    default:
+      return null
+  }
+})
+
+const queryViewComponent = computed(() => {
+  switch (equipType) {
+    case EquipmentType.BOILER:
+      return BoilerQueryView
+    case EquipmentType.PIPE:
+      return PipeQueryView
+    default:
+      return null
+  }
+})
+
 // 默认查询参数
 const defaultTypeValue = computed(() => ({
   orderNo: '',
@@ -118,23 +149,13 @@ const handleRightRefresh = () => {
   const result: Record<string, any> = {}
   switch (rightType.value) {
     case 'claim':
-      result.isClaim = true
-      result.mainCheckerIds = userId.value
-      break
-    case 'allot':
-      result.isClaim = true
-      result.isAllot = true
-      result.checkUserStrIds = userId.value
+      result.isClaim = 'claim'
       break
     case 'unclaim':
-      result.isClaim = false
-      result.planCheckUserIds = userId.value
+      result.isClaim = 'unclaim'
       break
     default:
-      result.mainCheckerIds = userId.value
-      result.planCheckUserIds = userId.value
-      result.checkUserStrIds = userId.value
-      result.managerStrIds = userId.value
+      result.isClaim = null
       break
   }
   return result

+ 4 - 12
src/pages/unClaim/components/TaskItemInfo.vue

@@ -62,7 +62,7 @@ const serviceOrderStatusText = computed(() => {
   if (serviceOrderStatus.value) {
     return serviceOrderStatus.value.isSignature === '1' ? '已签名' : '未签名'
   }
-  return '未添加'
+  return '未签名'
 })
 
 // 检验结果状态
@@ -75,23 +75,15 @@ const resultStatusText = computed(() => {
   if (resultStatus.value) {
     return resultStatus.value.isSignature === '1' ? '已签名' : '未签名'
   }
-  return '未添加'
+  return '未签名'
 })
 
 // PDF 点击事件
 const pdfClickFun = (businessType: number) => {
   if (businessType === 100) {
-    if (serviceOrderStatus.value) {
-      emit('pdfDetail', businessType, serviceOrderStatus.value.signFilePdf || '')
-    } else {
-      uni.showToast({ title: '请先添加服务单/受理单', icon: 'none' })
-    }
+    emit('pdfDetail', businessType, serviceOrderStatus.value?.signFilePdf || '')
   } else if (businessType === 200) {
-    if (resultStatus.value) {
-      emit('pdfDetail', businessType, resultStatus.value.signFilePdf || '')
-    } else {
-      uni.showToast({ title: '请先添加检验结果', icon: 'none' })
-    }
+    emit('pdfDetail', businessType, resultStatus.value?.signFilePdf || '')
   }
 }
 

+ 21 - 25
src/pages/unClaim/unClaimList.vue

@@ -72,6 +72,7 @@
 
 <script lang="ts" setup>
 import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
 import dayjs from 'dayjs'
 import QueryView from './components/query/QueryView.vue'
 import TaskItem from './components/TaskItem.vue'
@@ -229,25 +230,25 @@ const handleBtnTap = async (type: 'FWD' | 'JYRS', taskOrder: any) => {
   }
 
   try {
-    const res = await requestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, {
-      id: taskOrder.id,
-    })
-    if (!res?.data) {
-      uni.showToast({ title: res?.msg || '获取详情失败', icon: 'error' })
-      return
-    }
-    const signFileRespVOList = res.data.signFileRespVOList || []
-    const targetBusinessType = businessTypeMap[type]
-    const currentBusinessType =
-      signFileRespVOList.find((row: any) => row.businessType === targetBusinessType) || {}
+    // const res = await requestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, {
+    //   id: taskOrder.id,
+    // })
+    // if (!res?.data) {
+    //   uni.showToast({ title: res?.msg || '获取详情失败', icon: 'error' })
+    //   return
+    // }
+    // const signFileRespVOList = res.data.signFileRespVOList || []
+    // const targetBusinessType = businessTypeMap[type]
+    // const currentBusinessType =
+    //   signFileRespVOList.find((row: any) => row.businessType === targetBusinessType) || {}
     const { unitContact, unitPhone, receiverEmail } = taskOrder
 
-    if (currentBusinessType.isSignature === '1') {
-      uni.navigateTo({
-        url: `/pages/sign-detail/index?orderId=${taskOrder.id}&type=${type}&unitContact=${unitContact || ''}&unitPhone=${unitPhone || ''}&receiverEmail=${receiverEmail || ''}`,
-      })
-      return
-    }
+    // if (currentBusinessType.isSignature === '1') {
+    //   uni.navigateTo({
+    //     url: `/pages/sign-detail/index?orderId=${taskOrder.id}&type=${type}&unitContact=${unitContact || ''}&unitPhone=${unitPhone || ''}&receiverEmail=${receiverEmail || ''}`,
+    //   })
+    //   return
+    // }
     uni.navigateTo({
       url: `/pages/sign/index?orderId=${taskOrder.id}&type=${type}&unitContact=${unitContact || ''}&unitPhone=${unitPhone || ''}&receiverEmail=${receiverEmail || ''}`,
     })
@@ -294,19 +295,14 @@ const closeUpdateContactPopup = () => {
 const handlePdfDetail = (businessType: number, signFilePdf: string, orderId: string) => {
   const businessTypeTitle = businessType === 200 ? '告知单' : '服务单/受理单'
   uni.navigateTo({
-    url: `/pages/serviceOrderDetail/index?businessType=${businessType}&orderId=${orderId}&signFilePdf=${signFilePdf}&businessTypeTitle=${businessTypeTitle}`,
+    url: `/pages/serviceOrderDetail/index?businessType=${businessType}&orderId=${orderId}&businessTypeTitle=${businessTypeTitle}`,
   })
 }
 
-// 页面显示时刷新
-onMounted(() => {
-  refreshList()
-})
-
 // 页面显示时刷新(使用 onShow 替代 useDidShow)
-const onShow = () => {
+onShow((options) => {
   refreshList()
-}
+})
 
 // 监听页面卸载
 onUnmounted(() => {