Quellcode durchsuchen

调整所在行政区、街道的pinia逻辑;调整dict的pinia逻辑

yangguanjin vor 1 Woche
Ursprung
Commit
898880a624

+ 5 - 27
src/App.vue

@@ -1,8 +1,8 @@
 <script lang="ts">
 import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
 import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
-import { useDictStore } from '@/store/dict'
-import { useUserStore } from '@/store/user'
+import { useDictStoreWithOut } from '@/store/dict'
+import { useAreaStoreWithOut } from '@/store/area'
 import { beforEach } from '@/router/index'
  // #ifdef APP-PLUS
 import appUpdate from "@/common/appUpdate";
@@ -14,36 +14,14 @@ export default {
     // #ifdef APP-PLUS
     // 检测升级
     appUpdate()
-    // 初始化字典数据
-    useDictStore().setDictMap()
     // #endif
-
-    // 监听 index.html 中 appToken 登录完成事件,刷新 store
-    const handleTokenLogin = (e: any) => {
-      const detail = e.detail || {}
-      console.log('[App] 收到 app-token-login 事件:', detail)
-      const userStore = useUserStore()
-      // 重新从 localStorage 读取 token(initToken 内部调用 uni.getStorageSync)
-      // userStore.initToken()
-      // 读取 localStorage 中的用户信息写入 store
-      const userInfoStr = uni.getStorageSync('APP_USER_INFO')
-      if (userInfoStr) {
-        try {
-          const userInfo = typeof userInfoStr === 'string' ? JSON.parse(userInfoStr) : userInfoStr
-          userStore.setUserInfo({ ...userInfo, token: detail.accessToken })
-          console.log('[App] store 已刷新, token:', !!detail.accessToken)
-        } catch (err) {
-          console.error('[App] 解析 APP_USER_INFO 失败:', err)
-        }
-      }
-      // 只需监听一次
-      window.removeEventListener('app-token-login', handleTokenLogin)
-    }
-    window.addEventListener('app-token-login', handleTokenLogin)
   },
   onShow: function (options) {
     console.log('App Show')
     console.log('应用启动路径:', options.path)
+    // 初始化字典数据
+    useDictStoreWithOut().registerTimer()
+    useAreaStoreWithOut().loadAreaTree()
     // 首次进入页面时路由拦截
     setTimeout(() => {
       const currentPage = options.path

+ 3 - 7
src/components/AreaCascader/AreaCascader.vue

@@ -25,7 +25,7 @@ export default {
 <script lang="ts" setup>
 import { ref, watch, onMounted } from 'vue'
 import type { PropType } from 'vue'
-import { useAreaStore, type AreaNode } from '@/store/area'
+import { useAreaStoreWithOut, type AreaNode } from '@/store/area'
 import CascaderComponent from '@/components/Cascader/Cascader.vue'
 
 const props = defineProps({
@@ -45,8 +45,8 @@ const props = defineProps({
 
 const emit = defineEmits(['update:modelValue', 'update:visible', 'confirm', 'close'])
 
-const areaStore = useAreaStore()
-const areaOptions = ref<AreaNode[]>([])
+const areaStore = useAreaStoreWithOut()
+const areaOptions = areaStore.cascaderOptions
 
 const innerValue = ref<string | number | (string | number)[]>(props.modelValue)
 const innerVisible = ref(props.visible)
@@ -56,10 +56,6 @@ watch(() => props.visible, (val) => { innerVisible.value = val })
 watch(innerValue, (val) => { emit('update:modelValue', val) })
 watch(innerVisible, (val) => { emit('update:visible', val) })
 
-onMounted(async () => {
-  areaOptions.value = await areaStore.getCascaderOptions()
-})
-
 function handleConfirm(data: { value: string | number | (string | number)[]; selectedOptions: any[] }) {
   emit('confirm', data)
 }

+ 104 - 0
src/components/AreaCascader/AreaCascaderV2.vue

@@ -0,0 +1,104 @@
+<template>
+  <wd-col-picker
+    ref="colPickerRef"
+    v-model="innerValue"
+    :columns="columns"
+    :title="title"
+    value-key="id"
+    label-key="name"
+    :column-change="columnChange"
+    auto-complete
+    use-default-slot
+    @confirm="handleConfirm"
+    @close="handleClose"
+  >
+    <view />
+  </wd-col-picker>
+</template>
+
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+import type { PropType } from 'vue'
+import { useAreaStoreWithOut } from '@/store/area'
+import type { ColPickerColumnChange, ColPickerInstance } from '@/uni_modules/wot-design-uni'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array] as PropType<string | number | (string | number)[]>,
+    default: ''
+  },
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  title: {
+    type: String,
+    default: '请选择区域'
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'update:visible', 'confirm', 'close'])
+
+const areaStore = useAreaStoreWithOut()
+const areaOptions = areaStore.cascaderOptions
+
+const colPickerRef = ref<ColPickerInstance>()
+
+// 将树形数据首层转为 columns 二维数组
+const columns = ref<any[]>([areaOptions])
+
+// 将单值(叶子节点 id)转为路径数组
+function valueToPath(val: string | number | (string | number)[]): (string | number)[] {
+  if (Array.isArray(val)) return val
+  if (!val && val !== 0) return []
+  const numVal = Number(val)
+  const ancestors = areaStore.areaAncestorsMap.value?.get(numVal) || []
+  return [...ancestors.map((a) => a.id), numVal]
+}
+
+// 将路径数组转为单值(最后一个元素)
+function pathToValue(path: (string | number)[]): string | number {
+  return path.length > 0 ? path[path.length - 1] : ''
+}
+
+const innerValue = ref<(string | number)[]>(valueToPath(props.modelValue))
+
+watch(() => props.modelValue, (val) => {
+  // innerValue.value = valueToPath(val)
+  console.log("model value", val)
+
+})
+
+// 通过 visible prop 控制弹窗开关
+watch(() => props.visible, (val) => {
+  if (val) {
+    colPickerRef.value?.open()
+  } else {
+    colPickerRef.value?.close()
+  }
+})
+
+// 列切换:根据选中项查找子节点
+const columnChange: ColPickerColumnChange = ({ selectedItem, resolve, finish }) => {
+  if (selectedItem?.children && selectedItem.children.length > 0) {
+    resolve(selectedItem.children)
+  } else {
+    finish()
+  }
+}
+
+function handleConfirm(data: { value: (string | number)[]; selectedItems: Record<string, any>[] }) {
+  emit('update:modelValue', pathToValue(data.value))
+  emit('update:visible', false)
+  emit('confirm', {
+    value: pathToValue(data.value),
+    selectedOptions: data.selectedItems
+  })
+}
+
+function handleClose() {
+  emit('update:visible', false)
+  emit('close')
+}
+</script>

+ 39 - 30
src/components/StreetSelect/StreetSelect.vue

@@ -1,14 +1,19 @@
 <template>
-  <picker
-    :range="streetNames"
-    @change="onChange"
+  <wd-picker
+    ref="pickerRef"
+    v-model="innerValue"
+    :columns="pickerColumns"
+    :placeholder="placeholder"
     :disabled="!isEdit"
-    :class="{ 'cell-text': !isEdit, 'edit-input-text': isEdit }"
+    value-key="id"
+    label-key="name"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
   >
-    <text class="cell-text">
-      {{ selectedStreet?.name || placeholder }}
-    </text>
-  </picker>
+    <view class="edit-input">
+      <text>{{ innerValueName }}</text>
+    </view>
+  </wd-picker>
 </template>
 
 <script lang="ts">
@@ -23,7 +28,7 @@ export default {
 </script>
 
 <script lang="ts" setup>
-import { ref, computed, watch } from 'vue'
+import { ref, watch } from 'vue'
 import { useAreaStore, type AreaNode } from '@/store/area'
 
 const props = defineProps({
@@ -52,33 +57,34 @@ const emit = defineEmits<{
 
 const areaStore = useAreaStore()
 const streets = ref<AreaNode[]>([])
-const selectedStreet = ref<AreaNode | undefined>(undefined)
-
-const streetNames = computed(() => streets.value.map((s) => s.name))
+const innerValue = ref<number | string | undefined>(props.modelValue)
+const innerValueName = computed(() => streets.value?.find((item) => item.id == innerValue.value)?.name)
+const pickerColumns = ref<{ id: number; name: string }[]>([])
 
 async function loadStreets() {
   if (!props.districtId) {
     streets.value = []
-    selectedStreet.value = undefined
+    pickerColumns.value = []
+    innerValue.value = undefined
     return
   }
   streets.value = await areaStore.getStreetsByDistrict(props.districtId)
+  pickerColumns.value = streets.value.map((s) => ({ id: s.id, name: s.name }))
   syncSelection()
 }
 
 function syncSelection() {
   if (props.modelValue != null) {
-    selectedStreet.value = streets.value.find((s) => s.id === props.modelValue)
+    innerValue.value = props.modelValue
   } else {
-    selectedStreet.value = undefined
+    innerValue.value = undefined
   }
 }
 
-function onChange(e: { detail: { value: number } }) {
-  const index = e.detail.value
-  const street = streets.value[index]
-  selectedStreet.value = street
-  emit('update:modelValue', street?.id)
+function handleConfirm(data: { value: string | number; selectedItems: Record<string, any>[] }) {
+  const streetId = data.value != null && data.value !== '' ? Number(data.value) : undefined
+  const street = streetId != null ? streets.value.find((s) => s.id === streetId) : undefined
+  emit('update:modelValue', streetId)
   emit('change', street)
 }
 
@@ -98,18 +104,21 @@ watch(
 )
 </script>
 
-<style lang="scss" scoped>
-.cell-text {
-  font-size: 14px;
-  color: rgb(106, 106, 106);
-}
-
-.edit-input-text {
+<style scoped>
+.edit-input {
+  display: flex;
   flex: 1;
-  height: 30px;
-  padding: 0 10px;
-  font-size: 14px;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 5px 10px;
   border: 1px solid #ddd;
   border-radius: 5px;
+  height: 28px;
+
+  text {
+    font-size: 14px;
+    color: rgb(51, 51, 51);
+  }
 }
 </style>

+ 19 - 28
src/pages/equipment/detail/components/BoilerBaseInfo.vue

@@ -57,7 +57,7 @@
               v-model="equipment.zipCode"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="6"
+              :maxlength="6"
             />
             <text v-else class="cell-text">{{ equipment.zipCode }}</text>
           </view>
@@ -84,13 +84,15 @@
             <text class="cell-title">设备所在行政区</text>
           </view>
           <view class="table-cell content">
-            <view v-if="isEdit" class="edit-input" @click="showCascader = true">
-              <text>{{ districtFullName || '请选择' }}</text>
+            <text v-if="!isEdit" class="cell-text">
+              {{ areaStore.getDistrictFullName(equipment.equipDistrict) }}
+            </text>
+            <view v-else class="edit-input" @click="showCascader = true">
+              <text>{{ areaStore.getDistrictFullName(equipment.equipDistrict) || '请选择' }}</text>
             </view>
-            <text v-else class="cell-text">{{ getDistrictFullName(equipment.equipDistrict) }}</text>
             <AreaCascader
               v-show="isEdit"
-              v-model="selectedDistrict"
+              v-model="equipment.equipDistrict"
               v-model:visible="showCascader"
               title="请选择行政区"
               @confirm="handleAreaConfirm"
@@ -103,11 +105,15 @@
             <text class="cell-title">设备所在街道</text>
           </view>
           <view class="table-cell content">
+            <text v-if="!isEdit" class="cell-text">
+              {{ areaStore.getStreetName(equipment.equipStreet, equipment.equipDistrict) }}
+            </text>
             <StreetSelect
+              v-show="isEdit"
               :isEdit="isEdit"
-              v-model="equipment.equipStreet"
               :districtId="equipment.equipDistrict"
-              placeholder="请选择街道"
+              v-model:modelValue="equipment.equipStreet"
+              placeholder=""
               style="width: 100%"
             />
           </view>
@@ -140,7 +146,7 @@
               v-model="equipment.contactPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.contactPhone }}</text>
           </view>
@@ -322,7 +328,7 @@
               v-model="equipment.recipientPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.recipientPhone }}</text>
           </view>
@@ -371,7 +377,7 @@
               v-model="equipment.paymentPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.paymentPhone }}</text>
           </view>
@@ -436,7 +442,7 @@
               v-model="equipment.saferydh"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.saferydh }}</text>
           </view>
@@ -948,20 +954,11 @@
 <script lang="ts" setup>
 import { ref, reactive, watchEffect, watch, computed } from 'vue'
 import { updateEquipBoiler } from '@/api/boiler/boilerEquip'
-import AreaCascader from '@/components/AreaCascader/AreaCascader.vue'
+import AreaCascader from '@/components/AreaCascader/AreaCascaderV2.vue'
 import StreetSelect from '@/components/StreetSelect/StreetSelect.vue'
 import { useAreaStore } from '@/store/area'
 
 const areaStore = useAreaStore()
-const getDistrictFullName = areaStore.getDistrictFullName
-
-const districtFullName = computed(() => {
-  return getDistrictFullName(Number(selectedDistrict.value))
-})
-
-const streetNameDisplay = computed(() => {
-  return areaStore.getStreetName(Number(equipment.equipStreet), Number(equipment.equipDistrict))
-})
 
 interface Props {
   dataSource: any
@@ -1034,14 +1031,13 @@ const equipment = reactive<any>({
   overseeCheckEndDate: '',
   lastRepairReformContent: '',
 })
-const selectedDistrict = ref<string | number | (string | number)[]>('')
+
 const showCascader = ref(false)
 
 function handleAreaConfirm(data: {
   value: string | number | (string | number)[]
   selectedOptions: any[]
 }) {
-  selectedDistrict.value = data.value
   if (data.selectedOptions.length > 0) {
     const lastOption = data.selectedOptions[data.selectedOptions.length - 1]
     if (equipment.equipDistrict !== lastOption.id) {
@@ -1061,11 +1057,6 @@ watchEffect(() => {
 })
 
 const isEdit = ref(false)
-watch(isEdit, (editing) => {
-  if (editing) {
-    selectedDistrict.value = equipment.equipDistrict || ''
-  }
-})
 const originalData = ref<any>(null)
 const handleEdit = () => {
   if (isEdit.value) {

+ 12 - 12
src/pages/equipment/detail/components/BoilerEquipmentInfo.vue

@@ -1535,10 +1535,10 @@
 <script lang="ts" setup>
 import { ref, reactive, onMounted, watch } from 'vue'
 import { updateEquipBoiler } from '@/api/boiler/boilerEquip'
-import { getDictOptions, DICT_TYPE } from '@/utils/dictMap'
+import { DICT_TYPE } from '@/utils/dictMap'
+import { getDictOptions } from '@/utils/dictStoreUtil'
 
 const equipTypeOptions = getDictOptions(DICT_TYPE.SYSTEM_EQUIP_BOILER_TYPE)
-console.log(equipTypeOptions)
 const useStatusOptions = getDictOptions(DICT_TYPE.SYSTEM_EQUIP_BOILER_STATUS)
 
 const selectedEquipTypeIndex = ref(0)
@@ -1674,9 +1674,9 @@ watch(
   (newVal) => {
     if (newVal) {
       Object.assign(equipment, newVal)
-      selectedEquipTypeIndex.value = equipTypeOptions.findIndex((item: any) => item.value == equipment.type)
-      selectedUseStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value == equipment.useStatus)
-      selectedStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value == equipment.status)
+      selectedEquipTypeIndex.value = equipTypeOptions.value.findIndex((item: any) => item.value == equipment.type)
+      selectedUseStatusIndex.value = useStatusOptions.value.findIndex((item: any) => item.value == equipment.useStatus)
+      selectedStatusIndex.value = useStatusOptions.value.findIndex((item: any) => item.value == equipment.status)
     }
   },
   { immediate: true, deep: true }
@@ -1696,9 +1696,9 @@ const handleEdit = () => {
 const handleCancel = () => {
   if (originalData.value) {
     Object.assign(equipment, originalData.value)
-    selectedEquipTypeIndex.value = equipTypeOptions.findIndex((item: any) => item.value === equipment.type)
-    selectedUseStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value === equipment.useStatus)
-    selectedStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value === equipment.status)
+    selectedEquipTypeIndex.value = equipTypeOptions.value.findIndex((item: any) => item.value === equipment.type)
+    selectedUseStatusIndex.value = useStatusOptions.value.findIndex((item: any) => item.value === equipment.useStatus)
+    selectedStatusIndex.value = useStatusOptions.value.findIndex((item: any) => item.value === equipment.status)
   }
   isEdit.value = false
 }
@@ -1831,18 +1831,18 @@ const parseDate = (dateStr: string): number => {
 const handleEquipTypeChange = (e: any) => {
   console.log('@@@@@', e.detail.value)
   selectedEquipTypeIndex.value = e.detail.value
-  console.log('selectedEquipTypeIndex.....', selectedEquipTypeIndex.value, equipTypeOptions[selectedEquipTypeIndex.value]?.label)
-  equipment.type = equipTypeOptions[e.detail.value].value
+  console.log('selectedEquipTypeIndex.....', selectedEquipTypeIndex.value, equipTypeOptions.value[selectedEquipTypeIndex.value]?.label)
+  equipment.type = equipTypeOptions.value[e.detail.value].value
 }
 
 const handleUseStatusChange = (e: any) => {
   selectedUseStatusIndex.value = e.detail.value
-  equipment.useStatus = useStatusOptions[e.detail.value].value
+  equipment.useStatus = useStatusOptions.value[e.detail.value].value
 }
 
 const handleStatusChange = (e: any) => {
   selectedStatusIndex.value = e.detail.value
-  equipment.status = useStatusOptions[e.detail.value].value
+  equipment.status = useStatusOptions.value[e.detail.value].value
 }
 
 

+ 6 - 9
src/pages/equipment/detail/components/PipeBaseInfo.vue

@@ -6,10 +6,7 @@
         <view class="section-header-left">
           <text class="section-title">使用单位信息</text>
           <view v-if="pipeSetList.length > 0" class="pipe-set-picker">
-            <wd-picker
-              v-model="currentPipeSetIndexModel"
-              :columns="pipeSetColumns"
-            />
+            <wd-picker v-model="currentPipeSetIndexModel" :columns="pipeSetColumns" />
           </view>
         </view>
         <view v-if="canEdit" class="action-buttons">
@@ -65,7 +62,7 @@
               v-model="equipment.postalCode"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="6"
+              :maxlength="6"
             />
             <text v-else class="cell-text">{{ equipment.postalCode }}</text>
           </view>
@@ -130,7 +127,7 @@
               v-model="equipment.securityManPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.securityManPhone }}</text>
           </view>
@@ -179,7 +176,7 @@
               v-model="equipment.contactPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.contactPhone }}</text>
           </view>
@@ -228,7 +225,7 @@
               v-model="equipment.recipientPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.recipientPhone }}</text>
           </view>
@@ -277,7 +274,7 @@
               v-model="equipment.paymentPhone"
               class="edit-input-text"
               placeholder="请输入"
-              maxlength="11"
+              :maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.paymentPhone }}</text>
           </view>

+ 34 - 38
src/pages/equipment/detail/components/PipeEquipmentInfo.vue

@@ -6,10 +6,7 @@
         <view class="section-header-left">
           <text class="section-title">管道设备信息</text>
           <view v-if="pipeSetList.length > 0" class="pipe-set-picker">
-            <wd-picker
-              v-model="currentPipeSetIndexModel"
-              :columns="pipeSetColumns"
-            />
+            <wd-picker v-model="currentPipeSetIndexModel" :columns="pipeSetColumns" />
           </view>
         </view>
         <view v-if="canEdit" class="action-buttons">
@@ -84,7 +81,9 @@
               :disabled="!isEdit"
               @change="handlePipeCategoryChange"
             >
-              <text class="cell-text">{{ pipeCategoryOptions[selectedPipeCategoryIndex]?.label }}</text>
+              <text class="cell-text">
+                {{ pipeCategoryOptions[selectedPipeCategoryIndex]?.label }}
+              </text>
             </picker>
           </view>
         </view>
@@ -127,14 +126,15 @@
             <text class="cell-title">所在行政区</text>
           </view>
           <view class="table-cell content">
-            <view v-if="isEdit" class="edit-input" @click="showCascader = true">
-              <text>{{ districtFullName || '请选择' }}</text>
-              <image class="arrow-icon" src="/static/images/arrow-right.png" />
+            <text v-if="!isEdit" class="cell-text">
+              {{ areaStore.getDistrictFullName(equipment.equipDistrict) }}
+            </text>
+            <view v-else class="edit-input" @click="showCascader = true">
+              <text>{{ areaStore.getDistrictFullName(equipment.equipDistrict) || '请选择' }}</text>
             </view>
-            <text v-else class="cell-text">{{ getDistrictFullName(equipment.equipDistrict) }}</text>
             <AreaCascader
               v-show="isEdit"
-              v-model="selectedDistrict"
+              v-model="equipment.equipDistrict"
               v-model:visible="showCascader"
               title="请选择行政区"
               @confirm="handleAreaConfirm"
@@ -148,14 +148,17 @@
             <text class="cell-title">所在街道</text>
           </view>
           <view class="table-cell content">
+            <text v-if="!isEdit" class="cell-text">
+              {{ areaStore.getStreetName(equipment.equipStreet, equipment.equipDistrict) }}
+            </text>
             <StreetSelect
-              v-if="isEdit"
-              v-model="equipment.equipStreet"
+              v-show="isEdit"
+              :isEdit="isEdit"
               :districtId="equipment.equipDistrict"
-              placeholder="请选择街道"
+              v-model:model-value="equipment.equipStreet"
+              placeholder=""
               style="width: 100%"
             />
-            <text v-else class="cell-text">{{ streetNameDisplay }}</text>
           </view>
         </view>
 
@@ -1344,15 +1347,15 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, computed, onMounted, watch } from 'vue'
+import { ref, reactive, computed, watch } from 'vue'
 import { updateEquipPipe } from '@/api/pipe/pipeEquip'
-import { getDictOptions, DICT_TYPE } from '@/utils/dictMap'
-import AreaCascader from '@/components/AreaCascader/AreaCascader.vue'
+import { DICT_TYPE } from '@/utils/dictMap'
+import { getDictOptions } from '@/utils/dictStoreUtil'
+import AreaCascader from '@/components/AreaCascader/AreaCascaderV2.vue'
 import StreetSelect from '@/components/StreetSelect/StreetSelect.vue'
 import { useAreaStore } from '@/store/area'
 
 const areaStore = useAreaStore()
-const getDistrictFullName = areaStore.getDistrictFullName
 
 const pipeCategoryOptions = getDictOptions(DICT_TYPE.PIPE_TYPE)
 const useStatusOptions = getDictOptions(DICT_TYPE.SYSTEM_EQUIP_BOILER_STATUS)
@@ -1360,14 +1363,6 @@ const useStatusOptions = getDictOptions(DICT_TYPE.SYSTEM_EQUIP_BOILER_STATUS)
 const selectedPipeCategoryIndex = ref(0)
 const selectedUseStatusIndex = ref(0)
 
-const districtFullName = computed(() => {
-  return getDistrictFullName(Number(selectedDistrict.value))
-})
-
-const streetNameDisplay = computed(() => {
-  return areaStore.getStreetName(Number(equipment.equipStreet), Number(equipment.equipDistrict))
-})
-
 interface Props {
   dataSource?: any
   equipmentData?: any
@@ -1461,14 +1456,17 @@ const equipment = reactive<any>({
   categories: '',
 })
 const pipeList = ref<any[]>([])
-const selectedDistrict = ref<string | number | (string | number)[]>('')
 const showCascader = ref(false)
 
 const updateEquipmentFromData = (data: any) => {
   if (!data) return
   Object.assign(equipment, data)
-  selectedPipeCategoryIndex.value = pipeCategoryOptions.findIndex((item: any) => item.value == equipment.pipeCategory)
-  selectedUseStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value == equipment.useStatus)
+  selectedPipeCategoryIndex.value = pipeCategoryOptions.value.findIndex(
+    (item: any) => item.value == equipment.pipeCategory,
+  )
+  selectedUseStatusIndex.value = useStatusOptions.value.findIndex(
+    (item: any) => item.value == equipment.useStatus,
+  )
   pipeList.value = !data?.detailSaveReqVOS
     ? []
     : data.detailSaveReqVOS.map((item: any) => ({
@@ -1539,14 +1537,13 @@ watch(
     if (props.currentPipeSet) return
     updateEquipmentFromData(newVal)
   },
-  { immediate: true, deep: true }
+  { immediate: true, deep: true },
 )
 
 function handleAreaConfirm(data: {
   value: string | number | (string | number)[]
   selectedOptions: any[]
 }) {
-  selectedDistrict.value = data.value
   if (data.selectedOptions.length > 0) {
     const lastOption = data.selectedOptions[data.selectedOptions.length - 1]
     if (equipment.equipDistrict !== lastOption.id) {
@@ -1562,11 +1559,6 @@ const handleCollapseChange = (detail: any) => {
 }
 
 const isEdit = ref(false)
-watch(isEdit, (editing) => {
-  if (editing) {
-    selectedDistrict.value = equipment.equipDistrict || ''
-  }
-})
 const originalData = ref<any>(null)
 const handleEdit = () => {
   if (isEdit.value) {
@@ -1580,8 +1572,12 @@ const handleEdit = () => {
 const handleCancel = () => {
   if (originalData.value) {
     Object.assign(equipment, originalData.value)
-    selectedPipeCategoryIndex.value = pipeCategoryOptions.findIndex((item: any) => item.value == equipment.pipeCategory)
-    selectedUseStatusIndex.value = useStatusOptions.findIndex((item: any) => item.value == equipment.useStatus)
+    selectedPipeCategoryIndex.value = pipeCategoryOptions.value.findIndex(
+      (item: any) => item.value == equipment.pipeCategory,
+    )
+    selectedUseStatusIndex.value = useStatusOptions.value.findIndex(
+      (item: any) => item.value == equipment.useStatus,
+    )
   }
   isEdit.value = false
 }

+ 41 - 59
src/store/area.ts

@@ -1,4 +1,5 @@
 import { defineStore } from 'pinia'
+import store from '@/store'
 import { ref } from 'vue'
 import * as AreaApi from '@/api/system/area'
 
@@ -10,13 +11,15 @@ export interface AreaNode {
 
 export const useAreaStore = defineStore('area', () => {
   const areaTree = ref<AreaNode[]>([])
-  const streetCache = ref<Record<number, AreaNode[]>>({})
-  const cascaderOptions = ref<AreaNode[]>([])
-  const cascaderOptionsLoaded = ref(false)
+  const cascaderOptions = computed(() => {
+    const guangdong = areaTree.value.find((province) => province.name === '广东省')
+    const guangzhou = guangdong?.children?.find((city) => city.name === '广州市')
+    return [{ id: 0, name: '全国', children: areaTree }, ...(guangzhou ? [guangzhou] : [])]
+  })
 
-  async function getAreaTree() {
+  async function loadAreaTree() {
     if (areaTree.value.length > 0) {
-      return areaTree.value
+      return
     }
     const res = await AreaApi.getAreaTree()
     if (res.code === 0) {
@@ -25,38 +28,7 @@ export const useAreaStore = defineStore('area', () => {
     return areaTree.value
   }
 
-  async function getCascaderOptions() {
-    if (cascaderOptionsLoaded.value) {
-      return cascaderOptions.value
-    }
-    await getAreaTree()
-    const guangdong = areaTree.value.find((province) => province.name === '广东省')
-    const guangzhou = guangdong?.children?.find((city) => city.name === '广州市')
-    cascaderOptions.value = [
-      { id: 0, name: '全国', children: areaTree.value } as AreaNode,
-      ...(guangzhou ? [guangzhou] : [])
-    ]
-    cascaderOptionsLoaded.value = true
-    return cascaderOptions.value
-  }
-
-  async function getStreetsByDistrict(districtId: number) {
-    if (streetCache.value[districtId]) {
-      return streetCache.value[districtId]
-    }
-    const res = await AreaApi.getAreaTreeById(districtId)
-    if (res.code === 0) {
-      streetCache.value[districtId] = res.data || []
-    }
-    return streetCache.value[districtId] || []
-  }
-
-  const areaNodeMap = ref<Map<number, AreaNode>>(new Map())
-  const areaAncestorsMap = ref<Map<number, AreaNode[]>>(new Map())
-  const areaIndexBuilt = ref(false)
-
-  function buildAreaIndex() {
-    if (areaIndexBuilt.value) return
+  const areaIdx = computed(() => {
     const nodeMap = new Map<number, AreaNode>()
     const ancestorsMap = new Map<number, AreaNode[]>()
     const stack: { node: AreaNode; parents: AreaNode[] }[] = []
@@ -74,31 +46,40 @@ export const useAreaStore = defineStore('area', () => {
         }
       }
     }
-    areaNodeMap.value = nodeMap
-    areaAncestorsMap.value = ancestorsMap
-    areaIndexBuilt.value = true
-  }
+    return [nodeMap, ancestorsMap]
+  })
+  const areaNodeMap = computed(() => areaIdx.value[0])
+  const areaAncestorsMap = computed(() => areaIdx.value[1])
 
-  function ensureAreaIndex() {
-    if (!areaIndexBuilt.value) {
-      getAreaTree().then(() => {
-        buildAreaIndex()
-      })
+  function getDistrictName(districtId: number): string {
+    if (areaNodeMap.value == null) {
+      return ''
     }
-  }
-
-  async function getDistrictName(districtId: number): Promise<string> {
-    if (!areaIndexBuilt.value) await ensureAreaIndex()
-    return areaNodeMap.value.get(districtId)?.name || `区域${districtId}`
+    return areaNodeMap.value.get(districtId)?.name || `${districtId}`
   }
 
   function getDistrictFullName(districtId: number): string {
-    if (!areaIndexBuilt.value) ensureAreaIndex()
+    if (areaNodeMap.value == null || areaAncestorsMap.value == null) {
+      return ''
+    }
     const ancestors = areaAncestorsMap.value.get(districtId) || []
     const node = areaNodeMap.value.get(districtId)
     const parts = [...ancestors.map((a) => a.name)]
     if (node) parts.push(node.name)
-    return parts.join('') || `区域${districtId}`
+    return parts.join('') || `${districtId}`
+  }
+
+  const streetCache = ref<Record<number, AreaNode[]>>({})
+  // 获取街道同时缓存
+  async function getStreetsByDistrict(districtId: number) {
+    if (streetCache.value[districtId]) {
+      return streetCache.value[districtId]
+    }
+    const res = await AreaApi.getAreaTreeById(districtId)
+    if (res.code === 0) {
+      streetCache.value[districtId] = res.data || []
+    }
+    return streetCache.value[districtId] || []
   }
 
   function getStreetName(streetId: number, districtId?: number): string {
@@ -111,17 +92,18 @@ export const useAreaStore = defineStore('area', () => {
 
   return {
     areaTree,
-    streetCache,
     cascaderOptions,
+    areaIdx,
     areaNodeMap,
     areaAncestorsMap,
-    getAreaTree,
-    getCascaderOptions,
+    streetCache,
+    loadAreaTree,
     getStreetsByDistrict,
-    buildAreaIndex,
-    ensureAreaIndex,
-    getDistrictName,
     getDistrictFullName,
     getStreetName,
   }
 })
+
+export const useAreaStoreWithOut = () => {
+  return useAreaStore(store)
+}

+ 54 - 55
src/store/dict.ts

@@ -15,7 +15,12 @@ interface CachePayload {
 }
 
 const DICT_CACHE_KEY = 'DICT_CACHE'
+// 10 分钟过期
 const CACHE_EXPIRE_MS = 10 * 60 * 1000
+// 2 分钟检查一次
+const TIMER_CHECK_INTERVAL = 60 * 1000
+
+let dictTimer: number | null = null
 
 function buildDictMap(list: DictDataVO[]): Record<string, DictValueType[]> {
   const dictDataMap: Record<string, DictValueType[]> = {}
@@ -36,15 +41,13 @@ function buildDictMap(list: DictDataVO[]): Record<string, DictValueType[]> {
 export const useDictStore = defineStore('dict', {
   state: () => ({
     dictMap: {} as Record<string, DictValueType[]>,
+    expiredTime: 0 as number,
     isRefreshing: false,
   }),
   getters: {
-    getDictMap(): Record<string, DictValueType[]> {
-      this._ensureLoaded()
-      return this.dictMap
-    },
   },
   actions: {
+    // 读取持久化数据
     _readCache(): CachePayload | null {
       try {
         const raw = uni.getStorageSync(DICT_CACHE_KEY)
@@ -59,15 +62,7 @@ export const useDictStore = defineStore('dict', {
       return null
     },
 
-    _writeCache(data: Record<string, DictValueType[]>) {
-      try {
-        const payload: CachePayload = { data, expire: Date.now() + CACHE_EXPIRE_MS }
-        uni.setStorageSync(DICT_CACHE_KEY, JSON.stringify(payload))
-      } catch (e) {
-        // ignore
-      }
-    },
-
+    // 清除持久化数据
     _clearCache() {
       try {
         uni.removeStorageSync(DICT_CACHE_KEY)
@@ -76,32 +71,25 @@ export const useDictStore = defineStore('dict', {
       }
     },
 
-    _isCacheExpired(cache: CachePayload): boolean {
-      return cache.expire <= Date.now()
-    },
-
-    _ensureLoaded() {
-      if (Object.keys(this.dictMap).length) return
-
-      const cache = this._readCache()
-      if (cache) {
-        this.dictMap = cache.data
-        if (this._isCacheExpired(cache) && !this.isRefreshing) {
-          this._refreshInBackground()
-        }
-      } else {
-        this.setDictMap()
-      }
+    // 判断当前内存缓存是否过期
+    _isExpired(): boolean {
+      return this.expiredTime > 0 && this.expiredTime <= Date.now()
     },
 
-    async _refreshInBackground() {
+    // 拉取后端数据,持久化并更新内存缓存
+    async _refreshCache() {
+      if (this.isRefreshing) return
       this.isRefreshing = true
       try {
         const res = await getSimpleDictDataList()
         const list = (res as any)?.data || res || []
         const dictDataMap = buildDictMap(list as DictDataVO[])
+        // 先写 DICT_CACHE(持久化),再写 pinia
+        const expire = Date.now() + CACHE_EXPIRE_MS
+        const payload: CachePayload = { data: dictDataMap, expire }
+        uni.setStorageSync(DICT_CACHE_KEY, JSON.stringify(payload))
         this.dictMap = dictDataMap
-        this._writeCache(dictDataMap)
+        this.expiredTime = expire
       } catch (e) {
         // ignore
       } finally {
@@ -109,37 +97,48 @@ export const useDictStore = defineStore('dict', {
       }
     },
 
-    async setDictMap() {
-      if (Object.keys(this.dictMap).length) return
-
-      const cache = this._readCache()
-      if (cache) {
-        this.dictMap = cache.data
-        if (this._isCacheExpired(cache) && !this.isRefreshing) {
-          this._refreshInBackground()
-        }
+    registerTimer() {
+      // 重复执行立即返回
+      if (dictTimer !== null) {
         return
       }
+      // 如果 dictMap 为空,立即从 DICT_CACHE 加载(即使是旧数据也先用),然后立即加载后端数据一次
+      if (Object.keys(this.dictMap).length === 0) {
+        const cache = this._readCache()
+        if (cache) {
+          this.dictMap = cache.data
+          this.expiredTime = cache.expire
+        } else {
+          this._refreshCache()
+        }
+      }
+
+      // 如果已过期,立即异步调用后端接口更新缓存
+      if (this._isExpired()) {
+        this._refreshCache()
+      }
 
-      const res = await getSimpleDictDataList()
-      const list = (res as any)?.data || res || []
-      const dictDataMap = buildDictMap(list as DictDataVO[])
-      this.dictMap = dictDataMap
-      this._writeCache(dictDataMap)
+      // 清除已有定时器,避免重复注册
+      this.clearTimer()
+
+      // 设置定时器,定时检查 expiredTime
+      dictTimer = setInterval(() => {
+        if (this._isExpired()) {
+          console.log("refresh dict.....")
+          this._refreshCache()
+        }
+      }, TIMER_CHECK_INTERVAL) as unknown as number
     },
 
-    getDictByType(type: string): DictValueType[] {
-      this._ensureLoaded()
-      return this.dictMap[type] || []
+    clearTimer() {
+      if (dictTimer !== null) {
+        clearInterval(dictTimer)
+        dictTimer = null
+      }
     },
 
-    async resetDict() {
-      this._clearCache()
-      const res = await getSimpleDictDataList()
-      const list = (res as any)?.data || res || []
-      const dictDataMap = buildDictMap(list as DictDataVO[])
-      this.dictMap = dictDataMap
-      this._writeCache(dictDataMap)
+    getDictByType(type: string): DictValueType[] {
+      return this.dictMap[type] || []
     },
   },
 })

+ 16 - 0
src/utils/areaStoreUtils.ts

@@ -0,0 +1,16 @@
+import { useAreaStoreWithOut } from '@/store/area'
+import { computed, ComputedRef } from 'vue'
+
+const getCascaderOptions = (): ComputedRef => {
+  const areaStore = useAreaStoreWithOut()
+  const areaTree = areaStore.areaTree
+
+  return computed(() => {
+    const guangdong = areaTree.find((province) => province.name === '广东省')
+    const guangzhou = guangdong?.children?.find((city) => city.name === '广州市')
+    return [
+      { id: 0, name: '全国', children: areaTree },
+      ...(guangzhou ? [guangzhou] : []),
+    ]
+  })
+}

+ 0 - 75
src/utils/dictMap.ts

@@ -1,5 +1,3 @@
-import { useDictStoreWithOut, type DictValueType } from '@/store/dict'
-
 export enum PressureCheckerMyTaskStatus {
   WAIT_CONFIRM = 100,
   CANCELLATION = 200,
@@ -111,79 +109,6 @@ export function getCheckTypeFromText(checkTypeName?: string): number | undefined
   }
 }
 
-// ========================== 数据字典工具函数 ==========================
-
-export interface DictDataType {
-  dictType: string
-  label: string
-  value: string | number | boolean
-  colorType: string
-  cssClass: string
-}
-
-export interface NumberDictDataType extends DictDataType {
-  value: number
-}
-
-export interface StringDictDataType extends DictDataType {
-  value: string
-}
-
-export const getDictOptions = (dictType: string): DictDataType[] => {
-  const dictStore = useDictStoreWithOut()
-  const list: DictValueType[] = dictStore.getDictByType(dictType)
-  return list.map((item) => ({
-    dictType,
-    label: item.label,
-    value: item.value,
-    colorType: item.colorType || '',
-    cssClass: item.cssClass || '',
-  }))
-}
-
-export const getIntDictOptions = (dictType: string): NumberDictDataType[] => {
-  const dictOptions: DictDataType[] = getDictOptions(dictType)
-  return dictOptions.map((dict) => ({
-    ...dict,
-    value: parseInt(dict.value + ''),
-  }))
-}
-
-export const getStrDictOptions = (dictType: string): StringDictDataType[] => {
-  const dictOptions: DictDataType[] = getDictOptions(dictType)
-  return dictOptions.map((dict) => ({
-    ...dict,
-    value: dict.value + '',
-  }))
-}
-
-export const getBoolDictOptions = (dictType: string): DictDataType[] => {
-  const dictOptions: DictDataType[] = getDictOptions(dictType)
-  return dictOptions.map((dict) => ({
-    ...dict,
-    value: dict.value + '' === 'true',
-  }))
-}
-
-export const getDictObj = (dictType: string, value: any): DictDataType | undefined => {
-  const dictOptions: DictDataType[] = getDictOptions(dictType)
-  for (const dict of dictOptions) {
-    if (dict.value === value + '') {
-      return dict
-    }
-  }
-}
-
-export const getDictLabel = (dictType: string, value: any): string => {
-  const dictOptions: DictDataType[] = getDictOptions(dictType)
-  for (const dict of dictOptions) {
-    if (dict.value === value + '') {
-      return dict.label
-    }
-  }
-  return ''
-}
-
 export enum DICT_TYPE {
   USER_TYPE = 'user_type',
   COMMON_STATUS = 'common_status',

+ 74 - 0
src/utils/dictStoreUtil.ts

@@ -0,0 +1,74 @@
+import { useDictStoreWithOut } from '@/store/dict'
+import { computed, ComputedRef } from 'vue'
+
+// ========================== 数据字典工具函数 ==========================
+
+export interface DictDataType {
+  dictType: string
+  label: string
+  value: string | number | boolean
+  colorType: string
+  cssClass: string
+}
+
+export interface NumberDictDataType extends DictDataType {
+  value: number
+}
+
+export interface StringDictDataType extends DictDataType {
+  value: string
+}
+
+export const getDictOptions = (dictType: string): ComputedRef<DictDataType[]> => {
+  const dictStore = useDictStoreWithOut()
+  return computed(() => dictStore.dictMap[dictType].map?.((item) => ({
+    dictType,
+    label: item.label,
+    value: item.value,
+    colorType: item.colorType || '',
+    cssClass: item.cssClass || '',
+  })))
+}
+
+export const getIntDictOptions = (dictType: string): ComputedRef<NumberDictDataType[]> => {
+  const dictOptions = getDictOptions(dictType)
+  return computed(() => dictOptions.value.map((dict) => ({
+    ...dict,
+    value: parseInt(dict.value + ''),
+  })))
+}
+
+export const getStrDictOptions = (dictType: string): ComputedRef<StringDictDataType[]> => {
+  const dictOptions = getDictOptions(dictType)
+  return computed(() => dictOptions.value.map((dict) => ({
+    ...dict,
+    value: dict.value + '',
+  })))
+}
+
+export const getBoolDictOptions = (dictType: string): ComputedRef<DictDataType[]> => {
+  const dictOptions = getDictOptions(dictType)
+  return computed(() => dictOptions.value.map((dict) => ({
+    ...dict,
+    value: dict.value + '' === 'true',
+  })))
+}
+
+export const getDictObj = (dictType: string, value: any): DictDataType | undefined => {
+  const dictOptions = getDictOptions(dictType)
+  for (const dict of dictOptions.value) {
+    if (dict.value === value + '') {
+      return dict
+    }
+  }
+}
+
+export const getDictLabel = (dictType: string, value: any): string => {
+  const dictOptions = getDictOptions(dictType)
+  for (const dict of dictOptions.value) {
+    if (dict.value === value + '') {
+      return dict.label
+    }
+  }
+  return ''
+}