巴青农资商城

RegionFields.vue 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. <template>
  2. <view class="region-cascader">
  3. <view class="form-row" @click="openPicker">
  4. <text class="form-row__label form-row__label--req">所在地区</text>
  5. <view class="form-row__picker region-cascader__value">
  6. <text :class="{ 'form-row__placeholder': !displayText }">
  7. {{ displayText || '请选择省 / 市 / 区' }}
  8. </text>
  9. </view>
  10. <u-icon name="arrow-right" color="#ccc" size="16" />
  11. </view>
  12. <up-cascader
  13. v-if="treeReady"
  14. :show="pickerShow"
  15. :data="cascaderTree"
  16. v-model="pathCodes"
  17. value-key="code"
  18. label-key="name"
  19. children-key="children"
  20. :closeable="true"
  21. @update:show="pickerShow = $event"
  22. @confirm="onConfirm"
  23. />
  24. </view>
  25. </template>
  26. <script setup>
  27. import { ref, computed, watch, onMounted } from 'vue'
  28. import {
  29. loadRegionCascaderTree,
  30. findRegionPath,
  31. parseRegionSelection,
  32. formatRegionDisplay
  33. } from '@/utils/region'
  34. const props = defineProps({
  35. modelValue: {
  36. type: Object,
  37. default: () => ({
  38. regionCode: '',
  39. regionName: '',
  40. code: '',
  41. name: '',
  42. pathCodes: []
  43. })
  44. }
  45. })
  46. const emit = defineEmits(['update:modelValue'])
  47. const pickerShow = ref(false)
  48. const treeReady = ref(false)
  49. const rawTree = ref([])
  50. const cascaderTree = ref([])
  51. const pathCodes = ref([])
  52. const displayText = computed(() => {
  53. const name = props.modelValue?.regionName || props.modelValue?.name || ''
  54. return formatRegionDisplay(name)
  55. })
  56. watch(
  57. () => props.modelValue,
  58. (v) => {
  59. if (!v || !rawTree.value.length) return
  60. syncPathFromModel(v)
  61. },
  62. { deep: true }
  63. )
  64. onMounted(() => {
  65. loadRegionCascaderTree()
  66. .then(({ raw, cascader }) => {
  67. rawTree.value = raw
  68. cascaderTree.value = cascader
  69. treeReady.value = true
  70. syncPathFromModel(props.modelValue)
  71. })
  72. .catch(() => {
  73. uni.showToast({ title: '地区数据加载失败', icon: 'none' })
  74. })
  75. })
  76. function syncPathFromModel(v) {
  77. if (!v) return
  78. if (Array.isArray(v.pathCodes) && v.pathCodes.length) {
  79. pathCodes.value = [...v.pathCodes]
  80. return
  81. }
  82. const code = v.regionCode || v.code
  83. if (code && rawTree.value.length) {
  84. const path = findRegionPath(rawTree.value, code)
  85. pathCodes.value = path || []
  86. }
  87. }
  88. function openPicker() {
  89. if (!treeReady.value) {
  90. uni.showToast({ title: '地区数据加载中', icon: 'none' })
  91. return
  92. }
  93. pickerShow.value = true
  94. }
  95. function onConfirm(codes) {
  96. const parsed = parseRegionSelection(rawTree.value, codes)
  97. if (!parsed.valid) {
  98. uni.showToast({ title: '请选择完整的省市区', icon: 'none' })
  99. return
  100. }
  101. emit('update:modelValue', {
  102. regionCode: parsed.code,
  103. regionName: parsed.name,
  104. code: parsed.code,
  105. name: parsed.name,
  106. pathCodes: codes
  107. })
  108. }
  109. </script>
  110. <style lang="scss" scoped>
  111. @import '@/styles/mine.scss';
  112. .region-cascader .form-row {
  113. border-bottom: none;
  114. }
  115. .region-cascader__value {
  116. flex: 1;
  117. min-width: 0;
  118. }
  119. </style>