Browse Source

费用录入、结论录入响应式处理

yangguanjin 1 tháng trước cách đây
mục cha
commit
797d34dd01

+ 28 - 16
src/pages/equipment/detail/components/BoilerInspectProject.vue

@@ -256,7 +256,7 @@
                 <view
                   class="conclusion-box"
                   :class="{
-                    'conclusion-error': isConclusionError(getRecordJson(item).reportConclusion),
+                    'conclusion-error': isConclusionError(item.reportConclusion),
                   }"
                   @click="handleUpdateConclusion(item, 'reportConclusion')"
                 >
@@ -644,7 +644,6 @@ const reportGroupedList = computed(() => {
       groupedList.set(item, partReport)
     }
   })
-  console.log('list', groupedList)
 
   return groupedList
 })
@@ -917,7 +916,7 @@ const handleCalcFee = (item: CheckItem) => {
     return
   }
 
-  selectedCheckItem.value = { ...item, orderItemId: props.orderItemId }
+  selectedCheckItem.value = item
   showCalcPopup.value = true
 }
 
@@ -926,19 +925,25 @@ const hideCalcPopup = () => {
   selectedCheckItem.value = null
 }
 
-const handleUpdateRenderItem = (calcData: any) => {
-  const updatedList = props.reportList.map((x) => {
-    if (x.id === calcData.id) {
-      return {
-        ...x,
-        fee: calcData.fee,
-        feeCalculateJson: calcData.feeCalculateJson,
-      }
-    }
-    return x
-  })
-  emit('updateReportList', updatedList)
-  initSelected()
+const handleUpdateRenderItem = async (calcData: any) => {
+  await updateBoilerTaskOrderItemReportConclusion(calcData)
+  const report = props.reportList.find(item => item.id === calcData.id)
+  const feeKey = 'fee'
+  report[feeKey] = calcData[feeKey]
+  props.refreshDetail()
+  showCalcPopup.value = false
+  // const updatedList = props.reportList.map((x) => {
+  //   if (x.id === calcData.id) {
+  //     return {
+  //       ...x,
+  //       fee: calcData.fee,
+  //       feeCalculateJson: calcData.feeCalculateJson,
+  //     }
+  //   }
+  //   return x
+  // })
+  // emit('updateReportList', updatedList)
+  // initSelected()
 }
 
 const handleExChangeItemChecker = (item: CheckItem) => {
@@ -1006,6 +1011,13 @@ const hideConclusionPopup = () => {
 
 const handleUpdateConclusionConfirm = async (conclusionData: any) => {
   await updateBoilerTaskOrderItemReportConclusion(conclusionData)
+  // 先前端缓存结果,让响应更快,从而可以不引入Loading动画表达中间过渡
+  for (const key in conclusionData) {
+    if (!Object.hasOwn(conclusionData, key)) continue;
+    const element = conclusionData[key];
+    currentCheckItem.value[key] = element
+  }
+  props.refreshDetail()
   showConclusionPopup.value = false
 }
 

+ 27 - 14
src/pages/equipment/detail/components/PipeInspectProject.vue

@@ -29,7 +29,7 @@
               <view
                 class="conclusion-box"
                 :class="{
-                  'conclusion-error': isConclusionError(getRecordJson(item).reportConclusion),
+                  'conclusion-error': isConclusionError(item.reportConclusion),
                 }"
                 @click="handleUpdateConclusion(item, 'reportConclusion')"
               >
@@ -656,19 +656,25 @@ const hideCalcPopup = () => {
   selectedCheckItem.value = null
 }
 
-const handleUpdateRenderItem = (calcData: any) => {
-  const updatedList = props.reportList.map((x) => {
-    if (x.id === calcData.id) {
-      return {
-        ...x,
-        fee: calcData.fee,
-        feeCalculateJson: calcData.feeCalculateJson,
-      }
-    }
-    return x
-  })
-  emit('updateReportList', updatedList)
-  initSelected()
+const handleUpdateRenderItem = async (calcData: any) => {
+  await updatePipeTaskOrderItemReportConclusion(calcData)
+  const report = props.reportList.find(item => item.id === calcData.id)
+  const feeKey = 'fee'
+  report[feeKey] = calcData[feeKey]
+  props.refreshDetail()
+  showCalcPopup.value = false
+  // const updatedList = props.reportList.map((x) => {
+  //   if (x.id === calcData.id) {
+  //     return {
+  //       ...x,
+  //       fee: calcData.fee,
+  //       feeCalculateJson: calcData.feeCalculateJson,
+  //     }
+  //   }
+  //   return x
+  // })
+  // emit('updateReportList', updatedList)
+  // initSelected()
 }
 
 const handleExChangeItemChecker = (item: CheckItem) => {
@@ -736,6 +742,13 @@ const hideConclusionPopup = () => {
 
 const handleUpdateConclusionConfirm = async (conclusionData: any) => {
   await updatePipeTaskOrderItemReportConclusion(conclusionData)
+    // 先前端缓存结果,让响应更快,从而可以不引入Loading动画表达中间过渡
+  for (const key in conclusionData) {
+    if (!Object.hasOwn(conclusionData, key)) continue;
+    const element = conclusionData[key];
+    currentCheckItem.value[key] = element
+  }
+  props.refreshDetail()
   showConclusionPopup.value = false
 }
 

+ 3 - 7
src/pages/equipment/detail/components/inspectProject/component/UpdateConclusionPopup.vue

@@ -80,14 +80,10 @@ onMounted(() => {
 watch(
   () => props.checkItem,
   (newItem) => {
-    if (newItem && newItem.recordJson) {
+    if (newItem) {
       try {
-        const recordJson =
-          typeof newItem.recordJson === 'string'
-            ? JSON.parse(newItem.recordJson)
-            : newItem.recordJson
-        inputValue.value = recordJson[props.fieldKey] || ''
-        selectedConclusion.value = recordJson[props.fieldKey] || ''
+        inputValue.value = newItem[props.fieldKey] || ''
+        selectedConclusion.value = newItem[props.fieldKey] || ''
       } catch {
         inputValue.value = ''
         selectedConclusion.value = ''

+ 103 - 508
src/pages/equipment/detail/components/inspectProject/component/calcCheckItemPopup.vue

@@ -1,117 +1,50 @@
 <template>
-  <view class="popup-overlay" @click="handleClose">
-    <view class="popup-container" :style="{ width: popupWidth, height: containerHeight }" @click.stop>
-      <view class="head-title">
-        <text class="title-text">费用录入</text>
-      </view>
-
-      <scroll-view class="scroll-content" scroll-y>
-        <view v-if="enterDataList.length" class="form-section">
-          <text class="section-title">参数录入</text>
-          <scroll-view class="input-list" scroll-y>
-            <view v-for="item in enterDataList" :key="item.code" class="input-row">
-              <text class="input-label">{{ item.name }}:</text>
-              <input
-                v-model="feeCalculateJson[item.code]"
-                class="input-field"
-                :placeholder="'请输入'"
-                :type="item.valueType === 'number' ? 'digit' : 'text'"
-                @blur="handleInputBlur"
-              />
-              <text v-if="item.description" class="input-desc">{{ item.description }}</text>
-            </view>
-          </scroll-view>
-        </view>
-
-        <view v-if="outputDataList.length" class="form-section">
-          <text class="section-title">自动计算</text>
-          <scroll-view class="input-list" scroll-y>
-            <view v-for="item in outputDataList" :key="item.code" class="input-row">
-              <text class="input-label">{{ item.name }}:</text>
-              <view class="output-value">
-                <text class="output-text">{{ formatOutputValue(item.inputText) }}</text>
-                <view v-if="isCalculating" class="loading-indicator"></view>
-              </view>
-            </view>
-          </scroll-view>
-        </view>
-
-        <view class="fee-summary">
-          <view class="fee-item">
-            <text class="fee-label">历史收费:</text>
-            <text class="fee-value">无</text>
-          </view>
-          <view class="fee-item">
-            <text class="fee-label">实际收费</text>
-            <input
-              v-model="feeCalculateJson.totalCost"
-              class="fee-input"
-              placeholder="请输入"
-              type="digit"
-            />
-            <view v-if="isCalculating" class="loading-indicator"></view>
-          </view>
-        </view>
+  <wd-popup
+    v-model="showPopup"
+    position="center"
+    closable
+    custom-style="border-radius: 8px; width: 70%; max-width: 300px;"
+    @close="handleClose"
+  >
+    <view class="popup-header">
+      <text class="popup-title">费用计算</text>
+    </view>
 
-        <view v-if="!enterDataList.length && !outputDataList.length" class="empty-state">
-          <text>暂无费用数据</text>
-        </view>
-      </scroll-view>
+    <view class="popup-body">
+      <view class="form-row">
+        <text class="form-label">历史收费:</text>
+        <text class="form-value">{{ historyFee }}</text>
+      </view>
+      <view class="form-row">
+        <text class="form-label">实际费用:</text>
+        <input
+          v-model="actualFee"
+          class="form-input"
+          placeholder="请输入"
+          type="digit"
+        />
+      </view>
+    </view>
 
-      <view class="bottom-actions">
-        <button class="cancel-btn" @click="handleClose">
-          <text>取消</text>
-        </button>
-        <button class="confirm-btn" @click="handleConfirm">
-          <text>确定</text>
-          <view v-if="isSubmitting" class="loading-indicator"></view>
-        </button>
+    <view class="popup-footer">
+      <view class="footer-btn cancel-btn" @click="handleClose">
+        <text class="footer-btn-text">取消</text>
+      </view>
+      <view class="footer-btn confirm-btn" @click="handleConfirm">
+        <text class="footer-btn-text confirm-text">确定</text>
       </view>
     </view>
-  </view>
+  </wd-popup>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, onMounted, watch } from 'vue'
-import {
-  getPressureReportTemplateInfo,
-  queryCheckItemCalcPreFillField,
-  updateCheckerReportItemFee
-} from '@/api/task'
-
-const isOnline = ref(true)
-
-const getOnlineStatus = () => {
-  try {
-    const networkType = uni.getSystemInfoSync?.()?.platform || ''
-    return networkType !== 'devtools'
-  } catch {
-    return true
-  }
-}
-
-onMounted(() => {
-  isOnline.value = getOnlineStatus()
-})
+import { ref, onMounted } from 'vue'
+import { updateCheckerReportItemFee } from '@/api/task'
 
 interface CheckItem {
   id: string
-  feeCalculateJson: string
-  feeCalculateFormJson: string
   fee: number
-  reportName: string
-  templateId?: string
-  equipId?: string
-  [key: string]: any
-}
-
-interface FormField {
-  code: string
-  name: string
-  type: string
-  defaultValue?: string
-  valueType?: string
-  description?: string
+  feeCalculateJson?: string
   [key: string]: any
 }
 
@@ -119,473 +52,135 @@ interface Props {
   checkItem: CheckItem
 }
 
-interface Emits {
-  (e: 'hide'): void
-  (e: 'updateRenderItem', data: any): void
-}
-
 const props = defineProps<Props>()
-const emit = defineEmits<Emits>()
-
-const enterDataList = ref<FormField[]>([])
-const outputDataList = ref<FormField[]>([])
-const feeCalculateJson = ref<Record<string, any>>({})
-const blob = ref('')
-const bindingPathSchema = ref('')
-const isCalculating = ref(false)
-const isSubmitting = ref(false)
+const emit = defineEmits<{
+  hide: []
+  updateRenderItem: [data: any]
+}>()
 
-let calculateTimer: any = null
-
-const popupWidth = ref('80vw')
-const containerHeight = computed(() => {
-  if (!enterDataList.value.length && !outputDataList.value.length) {
-    return '240px'
-  }
-  return '57vh'
-})
+const showPopup = ref(false)
+const actualFee = ref('')
+const historyFee = ref('0')
 
 onMounted(() => {
-  initData()
-})
-
-const initData = async () => {
-  try {
-    if (!props.checkItem) return
-
-    const checkItem = props.checkItem
-
-    if (isOnline.value && checkItem.templateId) {
-      await loadOnlineTemplate(checkItem)
-    } else {
-      await loadLocalTemplate(checkItem)
-    }
-
-    parseFormData(checkItem)
-  } catch (error) {
-    console.error('初始化费用数据失败:', error)
-    uni.showToast({
-      title: '初始化费用数据失败',
-      icon: 'none'
-    })
-  }
-}
-
-const loadOnlineTemplate = async (checkItem: CheckItem) => {
-  try {
-    const templateInfoResult = await getPressureReportTemplateInfo({
-      id: checkItem.templateId
-    })
-
-    bindingPathSchema.value = templateInfoResult?.data?.bindingPathSchema || ''
-
-    const preFillResult = await queryCheckItemCalcPreFillField({
-      templateId: checkItem.templateId,
-      equipId: checkItem.equipId
-    })
-
-    if (preFillResult?.data) {
-      checkItem.feeCalculateFormJson = JSON.stringify(preFillResult.data)
-    }
-  } catch (error) {
-    console.error('加载在线模板失败:', error)
-  }
-}
-
-const loadLocalTemplate = async (checkItem: CheckItem) => {
-  try {
-    const localStorageKey = `template_${checkItem.templateId}`
-    const templateInfo = uni.getStorageSync(localStorageKey)
-
-    if (templateInfo) {
-      bindingPathSchema.value = templateInfo.bindingPathSchema || ''
-
-      if (templateInfo.formulaTemplateUrl) {
-        const fileData = uni.getStorageSync(`file_${templateInfo.formulaTemplateUrl}`)
-        if (fileData) {
-          blob.value = fileData
-        }
-      }
-    }
-  } catch (error) {
-    console.error('加载本地模板失败:', error)
-  }
-}
-
-const parseFormData = (checkItem: CheckItem) => {
-  try {
-    const formData: FormField[] = checkItem.feeCalculateFormJson
-      ? JSON.parse(checkItem.feeCalculateFormJson)
-      : []
-
-    const savedCalcJson: Record<string, any> = checkItem.feeCalculateJson
-      ? JSON.parse(checkItem.feeCalculateJson)
-      : {}
-
-    const enterFields = formData
-      .filter(field => field.type === '0')
-      .map(field => {
-        const val = savedCalcJson[field.code] || field.defaultValue || ''
-        const inputText = isNaN(parseFloat(val)) ? val : parseFloat(val)
-        return {
-          ...field,
-          inputText
-        }
-      })
-
-    const outputFields = formData
-      .filter(field => field.type === '1')
-      .map(field => ({
-        ...field,
-        inputText: savedCalcJson[field.code]
-      }))
-
-    enterDataList.value = enterFields
-    outputDataList.value = outputFields
-
-    const defaultData: Record<string, any> = {}
-    formData.forEach(item => {
-      defaultData[item.code] = item.inputText || ''
-    })
-
-    enterFields.forEach(item => {
-      defaultData[item.code] = item.inputText || ''
-    })
-
-    Object.assign(defaultData, savedCalcJson)
-
-    feeCalculateJson.value = defaultData
-  } catch (error) {
-    console.error('解析表单数据失败:', error)
-  }
-}
-
-const handleInputBlur = () => {
-  if (calculateTimer) {
-    clearTimeout(calculateTimer)
-  }
-
-  calculateTimer = setTimeout(() => {
-    performCalculation()
-    if (calculateTimer) {
-      clearTimeout(calculateTimer)
-    }
-  }, 300)
-}
-
-const performCalculation = () => {
-  isCalculating.value = true
-
-  try {
-    const formData = props.checkItem?.feeCalculateFormJson
-      ? JSON.parse(props.checkItem.feeCalculateFormJson)
-      : []
-
-    const inputValues: Record<string, any> = {}
-    formData.forEach((field: FormField) => {
-      const val = feeCalculateJson.value[field.code]
-      if (field.valueType === 'number') {
-        inputValues[field.code] = isNaN(parseFloat(val)) ? 0 : parseFloat(val)
-      } else {
-        inputValues[field.code] = val
-      }
-    })
-
-    let totalCost = 0
-    const outputValues: Record<string, any> = {}
-
-    outputDataList.value.forEach(item => {
-      const calcResult = calculateFieldValue(item.code, inputValues)
-      outputValues[item.code] = calcResult
-      if (item.code === 'totalCost') {
-        totalCost = calcResult
-      }
-    })
-
-    Object.assign(feeCalculateJson.value, outputValues)
-    feeCalculateJson.value.totalCost = totalCost
-
-    outputDataList.value = outputDataList.value.map(item => ({
-      ...item,
-      inputText: outputValues[item.code]
-    }))
-  } catch (error) {
-    console.error('计算费用失败:', error)
-  } finally {
-    setTimeout(() => {
-      isCalculating.value = false
-    }, 500)
-  }
-}
-
-const calculateFieldValue = (code: string, inputs: Record<string, any>): number => {
-  const formula = getFormulaForCode(code)
-  if (!formula) return 0
-
-  try {
-    const inputKeys = Object.keys(inputs)
-    let evalString = formula
-
-    inputKeys.forEach(key => {
-      const regex = new RegExp(`\\b${key}\\b`, 'g')
-      evalString = evalString.replace(regex, `(${inputs[key]})`)
-    })
-
-    const result = eval(evalString)
-    return isNaN(result) ? 0 : Number(result.toFixed(2))
-  } catch (error) {
-    console.error('计算字段失败:', code, error)
-    return 0
+  if (props.checkItem) {
+    const fee = typeof props.checkItem.fee === 'number' && !isNaN(props.checkItem.fee)
+      ? props.checkItem.fee
+      : 0
+    historyFee.value = fee.toFixed(2)
+    actualFee.value = '0.00'
   }
-}
-
-const getFormulaForCode = (code: string): string => {
-  const formulas: Record<string, string> = {
-    totalCost: 'baseFee + additionalFee',
-  }
-  return formulas[code] || '0'
-}
+  showPopup.value = true
+})
 
-const formatOutputValue = (value: any): string => {
-  if (value === null || value === undefined) return '-'
-  if (typeof value === 'object') return JSON.stringify(value)
-  if (typeof value === 'number') return value.toFixed(2)
-  return String(value)
+const handleClose = () => {
+  emit('hide')
 }
 
 const handleConfirm = async () => {
-  if (isSubmitting.value) return
-
-  isSubmitting.value = true
+  const fee = parseFloat(actualFee.value) || 0
 
   try {
-    const totalCost = parseFloat(feeCalculateJson.value.totalCost) || 0
+    uni.showLoading({ title: '提交中...' })
+
     const params = {
       id: props.checkItem.id,
-      fee: totalCost,
-      feeCalculateJson: JSON.stringify({
-        ...feeCalculateJson.value,
-        totalCost
-      })
+      fee,
     }
 
-    if (isOnline.value) {
-      await updateCheckerReportItemFee(params)
-    } else {
-      const localStorageKey = `checkItem_${props.checkItem.id}`
-      uni.setStorageSync(localStorageKey, params)
-    }
-
-    uni.showToast({
-      title: '保存成功',
-      icon: 'success'
-    })
-
+    uni.hideLoading()
+    uni.showToast({ title: '保存成功', icon: 'success' })
     emit('updateRenderItem', params)
     emit('hide')
   } catch (error) {
+    uni.hideLoading()
     console.error('保存费用失败:', error)
-    uni.showToast({
-      title: '保存失败',
-      icon: 'none'
-    })
-  } finally {
-    isSubmitting.value = false
-  }
-}
-
-const handleClose = () => {
-  if (calculateTimer) {
-    clearTimeout(calculateTimer)
+    uni.showToast({ title: '保存失败', icon: 'none' })
   }
-  emit('hide')
 }
 </script>
 
-<style scoped>
-.popup-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: rgba(0, 0, 0, 0.5);
+<style lang="scss" scoped>
+.popup-header {
   display: flex;
   align-items: center;
   justify-content: center;
-  z-index: 1000;
-}
-
-.popup-container {
-  background-color: #fff;
-  border-radius: 10px;
-  overflow: hidden;
-  display: flex;
-  flex-direction: column;
-}
-
-.head-title {
-  padding: 15px;
-  align-items: center;
-  border-bottom: 1px solid #bbb;
-}
-
-.title-text {
-  font-size: 21px;
-  color: #333;
-}
-
-.scroll-content {
-  flex: 1;
-  max-height: calc(57vh - 140px);
-}
-
-.form-section {
   padding: 12px;
-  border-bottom: 1px solid #f2f2f2;
+  border-bottom: 1px solid #eeeeee;
 }
 
-.section-title {
-  font-size: 18px;
-  color: #333;
-  font-weight: bold;
-  text-align: center;
-  padding-bottom: 12px;
+.popup-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #333333;
 }
 
-.input-list {
-  max-height: 200px;
+.popup-body {
+  padding: 16px 12px;
 }
 
-.input-row {
+.form-row {
   display: flex;
+  flex-direction: row;
   align-items: center;
-  padding: 8px 0;
-  gap: 8px;
+  margin-bottom: 12px;
 }
 
-.input-label {
-  flex: 1;
-  text-align: right;
-  font-size: 16px;
-  color: #333;
+.form-row:last-child {
+  margin-bottom: 0;
 }
 
-.input-field {
-  flex: 1;
-  border: 1px solid #ccc;
-  border-radius: 2px;
-  padding: 8px;
-  font-size: 16px;
-  background-color: #fff;
+.form-label {
+  flex-shrink: 0;
+  width: 70px;
+  font-size: 13px;
+  color: #333333;
 }
 
-.input-desc {
+.form-value {
   flex: 1;
-  font-size: 16px;
-  color: #666;
+  font-size: 13px;
+  color: #767575;
 }
 
-.output-value {
+.form-input {
   flex: 1;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-
-.output-text {
-  font-size: 16px;
-  color: #333;
-}
-
-.loading-indicator {
-  width: 16px;
-  height: 16px;
-  border: 2px solid #999;
-  border-top-color: transparent;
-  border-radius: 50%;
-  margin-left: 16px;
-  animation: spin 1s linear infinite;
+  height: 32px;
+  padding: 0 8px;
+  font-size: 13px;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
 }
 
-.fee-summary {
+.popup-footer {
   display: flex;
   flex-direction: row;
-  justify-content: space-around;
-  align-items: center;
-  padding: 24px 12px;
+  border-top: 1px solid #eeeeee;
 }
 
-.fee-item {
+.footer-btn {
   display: flex;
-  flex-direction: row;
+  flex: 1;
   align-items: center;
-  gap: 8px;
-}
-
-.fee-label {
-  font-size: 16px;
-  color: #333;
-  font-weight: bold;
-}
-
-.fee-value {
-  font-size: 16px;
-  color: #333;
-  font-weight: bold;
-}
-
-.fee-input {
-  width: 118px;
-  border: 1px solid #ccc;
-  border-radius: 2px;
-  padding: 8px;
-  font-size: 16px;
-}
-
-.empty-state {
-  padding: 40px 20px;
-  text-align: center;
-  color: #999;
-  font-size: 14px;
-}
-
-.bottom-actions {
-  display: flex;
-  justify-content: space-around;
-  padding: 10px 0;
-  padding-bottom: calc(10px + env(safe-area-inset-bottom));
-  background-color: #fff;
-  border-top: 1px solid #f2f2f2;
+  justify-content: center;
+  height: 40px;
 }
 
-.cancel-btn,
-.confirm-btn {
-  width: 25vw;
-  height: 40px;
-  border-radius: 4px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: center;
-  font-size: 16px;
-  margin: 0 20px;
+.footer-btn:active {
+  background-color: #f5f5f5;
 }
 
 .cancel-btn {
-  background-color: #fff;
-  border: 1px solid #bbb;
-  color: #727272;
+  border-right: 1px solid #eeeeee;
 }
 
-.confirm-btn {
-  background-color: #2f8aff;
-  color: #f1f7ff;
+.footer-btn-text {
+  font-size: 14px;
+  color: #666666;
 }
 
-@keyframes spin {
-  to {
-    transform: rotate(360deg);
-  }
+.confirm-text {
+  font-weight: 500;
+  color: #4b8cd9;
 }
 </style>