LocationPicker.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <!-- <view class="form-item"> -->
  3. <!-- <view class="item-label">
  4. <text class="label-text">{{ label }}</text>
  5. <text v-if="required" class="required">*</text>
  6. </view> -->
  7. <!-- 编辑模式:输入框 -->
  8. <uni-data-picker
  9. v-if="mode === 'edit'"
  10. v-model="innerValue"
  11. :localdata="localData"
  12. :popup-title="popupTitle"
  13. @change="onChange"
  14. >
  15. <u-input
  16. :value="getLocationLabel(innerValue)"
  17. :placeholder="placeholder"
  18. readonly
  19. suffix-icon="arrow-down"
  20. >
  21. <!-- 清除按钮 -->
  22. <template #suffix>
  23. <view
  24. v-if="innerValue"
  25. @click.stop="clearAddress"
  26. style="padding: 0 8rpx; display: flex; align-items: center;"
  27. >
  28. <uni-icons type="close" color="#999" size="20"></uni-icons>
  29. </view>
  30. </template>
  31. </u-input>
  32. </uni-data-picker>
  33. <!-- 展示模式:纯文本 -->
  34. <text v-else class="location-text">
  35. {{ getLocationLabel(innerValue) || "无" }}
  36. </text>
  37. <!-- </view> -->
  38. </template>
  39. <script setup>
  40. import { ref, watch } from 'vue'
  41. import cityRows from '@/utils/data.json'
  42. // Props definition
  43. const props = defineProps({
  44. mode: { // 新增模式属性
  45. type: String,
  46. default: "edit" // edit / view
  47. },
  48. value: { // v-model
  49. type: [String, Number],
  50. default: ""
  51. },
  52. label: { // 左侧文字
  53. type: String,
  54. default: "所在地"
  55. },
  56. placeholder: {
  57. type: String,
  58. default: "请选择省市区"
  59. },
  60. popupTitle: {
  61. type: String,
  62. default: "请选择省市区"
  63. },
  64. required: {
  65. type: Boolean,
  66. default: false
  67. }
  68. })
  69. // Emits definition
  70. const emit = defineEmits(['input', 'clear'])
  71. // Reactive data
  72. const innerValue = ref(props.value)
  73. const localData = ref([]) // 省市区树数据
  74. // Watchers
  75. watch(() => props.value, (newVal) => {
  76. innerValue.value = newVal
  77. })
  78. watch(innerValue, (newVal) => {
  79. emit("input", newVal)
  80. })
  81. // Methods
  82. /** 点击选择后的回调 */
  83. const onChange = (e) => {
  84. const lastNode = e.detail.value[e.detail.value.length - 1]
  85. innerValue.value = lastNode.value // 只存最底层的 code
  86. }
  87. /** 清空地址 */
  88. const clearAddress = () => {
  89. innerValue.value = ""
  90. emit("clear")
  91. }
  92. /** 回显文字(递归找路径) */
  93. const getLocationLabel = (value) => {
  94. if (!value) return ""
  95. let label = ""
  96. const traverse = (nodes) => {
  97. for (const node of nodes) {
  98. if (node.value === value) {
  99. label = node.text
  100. return true
  101. }
  102. if (node.children && traverse(node.children)) {
  103. label = node.text + " - " + label
  104. return true
  105. }
  106. }
  107. return false
  108. }
  109. traverse(localData.value)
  110. return label
  111. }
  112. /** 生成树数据 */
  113. const get_city_tree = () => {
  114. let res = []
  115. if (cityRows.length) {
  116. res = handleTree(cityRows)
  117. }
  118. return res
  119. }
  120. /** 递归组装树 */
  121. const handleTree = (data, parent_code = null) => {
  122. let res = []
  123. let keys = {
  124. id: "code",
  125. pid: "parent_code",
  126. children: "children",
  127. text: "name",
  128. value: "code"
  129. }
  130. for (let item of data) {
  131. if (parent_code === null) {
  132. // 顶级
  133. if (!item.hasOwnProperty(keys.pid) || item[keys.pid] == parent_code) {
  134. let node = {
  135. text: item[keys.text],
  136. value: item[keys.value],
  137. children: handleTree(data, item[keys.id])
  138. }
  139. res.push(node)
  140. }
  141. } else {
  142. // 子级
  143. if (item.hasOwnProperty(keys.pid) && item[keys.pid] == parent_code) {
  144. let node = {
  145. text: item[keys.text],
  146. value: item[keys.value],
  147. children: handleTree(data, item[keys.id])
  148. }
  149. res.push(node)
  150. }
  151. }
  152. }
  153. return res
  154. }
  155. // Initialize data (replaces created lifecycle)
  156. localData.value = get_city_tree()
  157. </script>
  158. <style scoped>
  159. .form-item {
  160. display: flex;
  161. flex-direction: column;
  162. /* margin-bottom: 20rpx; */
  163. }
  164. .item-label {
  165. display: flex;
  166. align-items: center;
  167. margin-bottom: 10rpx;
  168. }
  169. .label-text {
  170. font-size: 28rpx;
  171. color: #333;
  172. }
  173. .required {
  174. color: red;
  175. margin-left: 5rpx;
  176. }
  177. .location-text {
  178. /* font-size: 28rpx;
  179. color: #333; */
  180. /* padding: 16rpx 0; */
  181. }
  182. </style>