• 组件封装 - 省市区联动组件


    首先, 讲述一下这个组件需要实现的需求:

    1. 在页面显示完整用户选择的省市区信息

    2. 此组件是作用在别的组件里面, 接收父组件传入完整的省市区信息; 提供模板中使用

    3. 根据后端规定的字段名定义一个对象; 将用户修改后的省市区数据放入这个对象中, 供需要的父组件使用

    有三种情况, 父组件会向此组件传入完整的省市区信息

    我所讲述的这一个组件是应用在一个电商平台所需要的省市区联动组件

    1. 用户没有登录时, 父组件传入一个定死的收货地址信息

    2. 用户登录后, 父组件传入从数据库中获取当前登录账号的默认收货地址信息

    3. 用户修改后, 因为所有的省市区数据都是在此组件里面; 所以记录用户修改后的省市区数据会 emit 给父组件; 由父组件传给此组件, 然后此组件通过 props 接收提供给模板使用

    为什么第三种情况这样做, 是因为第一种和第二种情况都是父组件传给此组件一个完整的省市区数据; 这就说明, 此组件如何显示省市区都是有父组件来决定的

    所以, 此组件里面不能直接进行修改, 而是由父组件传入

    好了, 说了些基本逻辑; 大家可能还是一头雾水, 不知道我在说啥, 现在就上代码显示

    首先, 完成基本布局

    布局分析:

    1. 大盒子里面包含两个子盒子, 一是显示配送地址信息盒子; 二是所有省市区信息盒子

    2. 配送地址信息盒子里面就是两个 span , 一个是显示信息盒子; 另外一个是 icon

    1. <template>
    2. <div class="xtx-city">
    3. <div class="select">
    4. <span class="placeholder">请选择配送地址span>
    5. <span class="value">span>
    6. <i class="iconfont icon-angle-down">i>
    7. div>
    8. <div class="option">
    9. <span class="ellipsis" v-for="i in 24" :key="i">北京市span>
    10. div>
    11. div>
    12. template>
    13. <script>
    14. export default {
    15. name: 'XtxCity'
    16. }
    17. script>
    18. <style scoped lang="less">
    19. .xtx-city {
    20. display: inline-block;
    21. position: relative;
    22. z-index: 400;
    23. .select {
    24. border: 1px solid #e4e4e4;
    25. height: 30px;
    26. padding: 0 5px;
    27. line-height: 28px;
    28. cursor: pointer;
    29. &.active {
    30. background: #fff;
    31. }
    32. .placeholder {
    33. color: #999;
    34. }
    35. .value {
    36. color: #666;
    37. font-size: 12px;
    38. }
    39. i {
    40. font-size: 12px;
    41. margin-left: 5px;
    42. }
    43. }
    44. .option {
    45. width: 542px;
    46. border: 1px solid #e4e4e4;
    47. position: absolute;
    48. left: 0;
    49. top: 29px;
    50. background: #fff;
    51. min-height: 30px;
    52. line-height: 30px;
    53. display: flex;
    54. flex-wrap: wrap;
    55. padding: 10px;
    56. > span {
    57. width: 130px;
    58. text-align: center;
    59. cursor: pointer;
    60. border-radius: 4px;
    61. padding: 0 3px;
    62. &:hover {
    63. background: #f5f5f5;
    64. }
    65. }
    66. }
    67. }
    68. style>

    现在来完成基本的交互

    思路分析:

    1. 定义控制省市区盒子(option), 显示隐藏的变量(active); 默认值为 false

    2. 定义 option 元素显示的方法(open), 将 active 变量值变成 true

    3. 定义 option 元素隐藏的方法(close), 将 active 变量值变成 false

    4. 定义一个调用 open 和 close 的方法(toggleOption), 通过判断 active 的值; 动态的调用 open 和close方法

    5. 当用户点击页面其他地方的时候, 应当将 option 元素隐藏

    1. <template>
    2. <div class="xtx-city" ref="target">
    3. <div class="select" @click="toggleOption" :class="{active}">
    4. <span class="placeholder">请选择配送地址span>
    5. <span class="value">span>
    6. <i class="iconfont icon-angle-down">i>
    7. div>
    8. <div class="option" v-if="active">
    9. <span class="ellipsis" v-for="i in 24" :key="i">北京市span>
    10. div>
    11. div>
    12. template>
    13. <script>
    14. import { ref } from 'vue'
    15. import { onClickOutside } from '@vueuse/core'
    16. export default {
    17. name: 'XtxCity',
    18. setup () {
    19. // 控制option选项显示隐藏变量, 默认隐藏
    20. const active = ref(false)
    21. // 展开option元素方法
    22. const open = () => {
    23. active.value = true
    24. }
    25. // 关闭option元素方法
    26. const close = () => {
    27. active.value = false
    28. }
    29. // 根据active状态来改变options元素的显示隐藏
    30. const toggleOption = () => {
    31. if (active.value) close()
    32. else open()
    33. }
    34. // 点击其他地方, 关闭option元素方法
    35. const target = ref(null)
    36. onClickOutside(target, () => {
    37. close()
    38. })
    39. return { active, toggleOption, target }
    40. }
    41. }
    42. script>
    43. <style scoped lang="less">
    44. .xtx-city {
    45. display: inline-block;
    46. position: relative;
    47. z-index: 400;
    48. .select {
    49. border: 1px solid #e4e4e4;
    50. height: 30px;
    51. padding: 0 5px;
    52. line-height: 28px;
    53. cursor: pointer;
    54. &.active {
    55. background: #fff;
    56. }
    57. .placeholder {
    58. color: #999;
    59. }
    60. .value {
    61. color: #666;
    62. font-size: 12px;
    63. }
    64. i {
    65. font-size: 12px;
    66. margin-left: 5px;
    67. }
    68. }
    69. ......
    70. }
    71. style>

     最后完成省市区联动的逻辑交互

    思路分析:

    1. 首先封装调用接口函数, 获取所有的省市区数据(使用的是阿里的省市区json数据)

    2. 用户可以频繁的进行点击, 所以; 需要做缓存

    3. 定义变量(cityList), 接收返回回来的数据(在数据还没有返回时, 显示loading效果)

    4. 在 open 方法被调用的时候, 调用接口函数; 向 cityList 赋值(cityList的值是全部的初始的数据, 页面需要的数据是计算属性计算得到的数据)

    5. 定义后端需要的字段对象(changeResult), 其中有省市区的地域编号和名称

    6. 定义一个方法(changeOption), 用户点击进行选择时; 将用户选择的当前数据传给此方法

    7. changeOption 方法内判断, 用户点击的是省或市或区; 对 changeResult 对象中的字段进行赋值

    8. 使用计算属性(currList), 内部再返回一个变量(currList), 通过 changeResult 对象中的省市区的地域编号动态的改变 currList 的值; 最终 currList 提给给模板渲染数据

    9. 用户选择到区一级时, 将完整的省市区数据 emit 给父组件, 调用 close 方法

    10. 用户再次进行修改省市区数据时, 情况 changeResult 对象中先前的数据

    11. 当用户选择错误时, 点击页面其他地方; 需要对 changeResult 对象中的数据进行重置

    1. <template>
    2. <div class="xtx-city" ref="target">
    3. <div class="select" @click="toggleOption" :class="{active}">
    4. <span class="placeholder" v-if="!fullLocation">请选择配送地址span>
    5. <span class="value" v-else>{{ fullLocation }}span>
    6. <i class="iconfont icon-angle-down">i>
    7. div>
    8. <div class="option" v-if="active">
    9. <div v-if="loading" class="loading">div>
    10. <template v-else>
    11. <span class="ellipsis" @click="changeOption(item)" v-for="item in currList" :key="item.code">{{ item.name }}span>
    12. template>
    13. div>
    14. div>
    15. template>
    16. <script>
    17. import { ref, computed, reactive } from 'vue'
    18. import { onClickOutside } from '@vueuse/core'
    19. import axios from 'axios'
    20. export default {
    21. name: 'XtxCity',
    22. props: {
    23. fullLocation: {
    24. type: String,
    25. default: ''
    26. }
    27. },
    28. setup (props, { emit }) {
    29. // 1. 获取省市区数据方法(初始数据)
    30. const getCityData = () => {
    31. const url = 'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
    32. return new Promise((resolve, reject) => {
    33. // 2. 做缓存
    34. // 有缓存
    35. if (window.cityData) {
    36. resolve(window.cityData)
    37. } else {
    38. // 没有缓存
    39. axios.get(url).then(({ data }) => {
    40. window.cityData = data
    41. resolve(window.cityData)
    42. })
    43. }
    44. })
    45. }
    46. // 3. 存储获取的省市区数据
    47. const cityList = ref([])
    48. // 当数据还在加载时, 显示loading效果
    49. const loading = ref(false)
    50. // 4. 展开option元素方法
    51. const open = () => {
    52. active.value = true
    53. loading.value = true
    54. getCityData().then(res => {
    55. cityList.value = res
    56. loading.value = false
    57. }).catch(err => err)
    58. // 10. 再次打开的时候, 清空先前的数据
    59. for (const key in changeResult) {
    60. changeResult[key] = ''
    61. }
    62. }
    63. // 5. 定义依据后端字段需要的数据和父组件需要的数据
    64. const changeResult = reactive({
    65. // 省地域编号和名称
    66. provinceCode: '',
    67. provinceName: '',
    68. // 市地域编号和名称
    69. cityCode: '',
    70. cityName: '',
    71. // 区地域编号和名称
    72. countyCode: '',
    73. countyName: '',
    74. // 完整的省市区数据
    75. fullLocation: ''
    76. })
    77. // 6. 修改省市区数据的方法
    78. const changeOption = (item) => {
    79. // 7. 用户点击当前的省数据
    80. if (item.level === 0) {
    81. changeResult.provinceCode = item.code
    82. changeResult.provinceName = item.name
    83. } else if (item.level === 1) {
    84. changeResult.cityCode = item.code
    85. changeResult.cityName = item.name
    86. } else {
    87. changeResult.countyCode = item.code
    88. changeResult.countyName = item.name
    89. // 拼接好完整的数据
    90. changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
    91. // 9. 提供给父组件使用
    92. emit('change', changeResult)
    93. close()
    94. }
    95. }
    96. // 8. 根据当前的省市区数据, 获取对应的下一级数据列表
    97. const currList = computed(() => {
    98. // 全部数据(不能直接的去影响cityList的数据, 重新定义变量; 对此变量进行修改即可)
    99. let currList = cityList.value
    100. // 用户选择省数据
    101. // 主要是判断changeResult中省市区的地域编码是否存在; 从而动态的改变的currList值
    102. // 且 currList 是模板渲染的主要数据, 所以用户选择的数据会随着选择变化而变化
    103. if (changeResult.provinceCode) {
    104. currList = currList.find(item => item.code === changeResult.provinceCode).areaList
    105. }
    106. // 用户选择当前省的市数据
    107. if (changeResult.cityCode) {
    108. currList = currList.find(item => item.code === changeResult.cityCode).areaList
    109. }
    110. // 用户选择当前市的区数据
    111. if (changeResult.countyCode) {
    112. currList = currList.find(item => item.code === changeResult.countyCode).areaList
    113. }
    114. return currList
    115. })
    116. // 11. 点击其他地方, 关闭option元素方法
    117. const target = ref(null)
    118. onClickOutside(target, () => {
    119. close()
    120. // 重置数据
    121. for (const key in changeResult) {
    122. changeResult[key] = ''
    123. }
    124. })
    125. // 控制option选项显示隐藏变量, 默认隐藏
    126. const active = ref(false)
    127. // 根据active状态来改变options元素的显示隐藏
    128. const toggleOption = () => {
    129. if (active.value) close()
    130. else open()
    131. }
    132. // 关闭option元素方法
    133. const close = () => {
    134. active.value = false
    135. }
    136. return { active, toggleOption, target, loading, cityList, currList, changeOption, changeResult }
    137. }
    138. }
    139. script>
    140. <style scoped lang="less">
    141. .xtx-city {
    142. ......
    143. .option {
    144. ......
    145. .loading {
    146. height: 290px;
    147. width: 100%;
    148. background: url(../../assets/images/loading.gif) no-repeat center;
    149. }
    150. }
    151. }
    152. style>

    父组件代码 

    1. <template>
    2. <p class="g-name">{{ goods.name }}p>
    3. <p class="g-desc">{{ goods.desc }}p>
    4. <p class="g-price">
    5. <span>{{ goods.price }}span>
    6. <span>{{ goods.oldPrice }}span>
    7. p>
    8. <div class="g-service">
    9. <dl>
    10. <dt>促销dt>
    11. <dd>12月好物放送,App领券购买直降120元dd>
    12. dl>
    13. <dl>
    14. <dt>配送dt>
    15. <dd><XtxCity @change="changeCity " :fullLocation="fullLocation" />dd>
    16. dl>
    17. <dl>
    18. <dt>服务dt>
    19. <dd>
    20. <span>无忧退货span>
    21. <span>快速退款span>
    22. <span>免费包邮span>
    23. <a href="javascript:;">了解详情a>
    24. dd>
    25. dl>
    26. div>
    27. template>
    28. <script>
    29. import { ref } from 'vue'
    30. export default {
    31. name: 'GoodName',
    32. props: {
    33. goods: {
    34. type: Object,
    35. default: () => {}
    36. }
    37. },
    38. setup (props) {
    39. // 当用户没有登录的时候, 需要显示默认的数据
    40. const provinceCode = ref('110000')
    41. const cityCode = ref('119900')
    42. const countyCode = ref('110101')
    43. const fullLocation = ref('北京市 市辖区 东城区')
    44. // 判断用户时候有登录
    45. if (props.goods.userAddresses) {
    46. // 如果存在, city组件就需要渲染用户的默认收货地址信息
    47. const defaultAddr = props.goods.userAddresses.find(addr => addr.isDefault === 1)
    48. if (defaultAddr) {
    49. provinceCode.value = defaultAddr.provinceCode
    50. cityCode.value = defaultAddr.cityCode
    51. countyCode.value = defaultAddr.countyCode
    52. fullLocation.value = defaultAddr.fullLocation
    53. }
    54. }
    55. // 接收子组件传入的数据
    56. const changeCity = (data) => {
    57. provinceCode.value = data.provinceCode
    58. cityCode.value = data.cityCode
    59. countyCode.value = data.countyCode
    60. fullLocation.value = data.fullLocation
    61. }
    62. return { fullLocation, changeCity }
    63. }
    64. }
    65. script>
    66. <style lang="less" scoped>
    67. .g-name {
    68. font-size: 22px
    69. }
    70. .g-desc {
    71. color: #999;
    72. margin-top: 10px;
    73. }
    74. .g-price {
    75. margin-top: 10px;
    76. span {
    77. &::before {
    78. content: "¥";
    79. font-size: 14px;
    80. }
    81. &:first-child {
    82. color: @priceColor;
    83. margin-right: 10px;
    84. font-size: 22px;
    85. }
    86. &:last-child {
    87. color: #999;
    88. text-decoration: line-through;
    89. font-size: 16px;
    90. }
    91. }
    92. }
    93. .g-service {
    94. background: #f5f5f5;
    95. width: 500px;
    96. padding: 20px 10px 0 10px;
    97. margin-top: 10px;
    98. dl {
    99. padding-bottom: 20px;
    100. display: flex;
    101. align-items: center;
    102. dt {
    103. width: 50px;
    104. color: #999;
    105. }
    106. dd {
    107. color: #666;
    108. &:last-child {
    109. span {
    110. margin-right: 10px;
    111. &::before {
    112. content: "•";
    113. color: @xtxColor;
    114. margin-right: 2px;
    115. }
    116. }
    117. a {
    118. color: @xtxColor;
    119. }
    120. }
    121. }
    122. }
    123. }
    124. style>

      

  • 相关阅读:
    TDSQL-H LibraDB初步调研
    macOS磁盘分区调整软件--Paragon Camptune X 中文
    微服务保护-初识Sentinel
    [CM311-1A]-安卓设备视频分辨率 DPI 以及刷新率问题
    我理解的反射
    【nlp】2.6 注意力机制Attention
    【Linux】《Linux命令行与shell脚本编程大全 (第4版) 》笔记-Chapter1-初始 Linux Shell
    SpringBoot3项目框架搭建
    SAP UX 用户体验师这个职位的技能要求和日常工作内容介绍
    企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图
  • 原文地址:https://blog.csdn.net/weixin_48524561/article/details/127368958