Vue3.0 detalles de enlace provinciales, urbanos y regionales escritos a mano

Tabla de contenido

1. Registrar una ciudad componente global 

 2. Usa componentes de la ciudad

 3. Cambiar entre mostrar y ocultar en el componente de la ciudad

4. Haga clic fuera del widget de la ciudad para cerrar el widget de la ciudad

5. El componente ciudad obtiene datos

 6. Interacción componentes-lógica de la ciudad

valor inicial

  7. Componente de ciudad - interacción - mostrar dirección predeterminada

8. Componente de la ciudad, haga clic en niño interactivo a padre

 9. Componente de la ciudad, haga clic en recibir padres interactivos

10. Agregue caché al componente de la ciudad

 11. Guarda el efecto de carga al solicitar datos de la ciudad

 código completo


lograr efecto 

 Debido a que los datos de provincias y ciudades son relativamente grandes, los datos se importan a través de enlaces

https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json

1. Registrar una ciudad componente global 

<template>
  <div class="xtx-city">
    <div class="select">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'XtxCity'
}
</script>
<style scoped lang="less">
.xtx-city {
  display: inline-block;
  position: relative;
  z-index: 400;
  .select {
    border: 1px solid #e4e4e4;
    height: 30px;
    padding: 0 5px;
    line-height: 28px;
    cursor: pointer;
    &.active {
      background: #fff;
    }
    .placeholder {
      color: #999;
    }
    .value {
      color: #666;
      font-size: 12px;
    }
    i {
      font-size: 12px;
      margin-left: 5px;
    }
  }
  .option {
    width: 542px;
    border: 1px solid #e4e4e4;
    position: absolute;
    left: 0;
    top: 29px;
    background: #fff;
    min-height: 30px;
    line-height: 30px;
    display: flex;
    flex-wrap: wrap;
    padding: 10px;
    > span {
      width: 130px;
      text-align: center;
      cursor: pointer;
      border-radius: 4px;
      padding: 0 3px;
      &:hover {
        background: #f5f5f5;
      }
    }
  }
}
</style>

Registre ligeramente los componentes globales 

 2. Usa componentes de la ciudad

nombre-mercancías.vue

     <dl>
      <dt>配送</dt>
      <dd>至 <XtxCity></XtxCity> 城市组件</dd>
    </dl>

efecto actual 

 

 3. Cambiar entre mostrar y ocultar en el componente de la ciudad

<template>
  <div class="xtx-city">
    <div class="select" 
  + @click="toggleCity"
  + :class="{active:visible}">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" 
+   v-show="visible">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'XtxCity',
  setup () {
    //   默认不显示
    const visible = ref(false)
    // 打开城市组件
    const openCity = () => {
      visible.value = true
    }
    // 关闭城市组件
    const closeCity = () => {
      visible.value = false
    }
    // 切换城市组件开关
    const toggleCity = () => {
      visible.value ? closeCity() : openCity()
    }
    return { visible, toggleCity }
  }
}
</script>

Agregue un clic para seleccionar la dirección de entrega para desactivar el cuadro desplegable de visualización de la ciudad. Cuando sea cierto, se mostrará abierto y le dará al nombre de la clase un fondo blanco.

4. Haga clic fuera del widget de la ciudad para cerrar el widget de la ciudad

 Cómo determinar si hacer clic dentro del elemento usando el método onClickOutside en la biblioteca de herramientas vueuse

npm i @vueuse/core
// 1. 导入方法
import { onClickOutside } from '@vueuse/core'

setup() {
  // 鼠标在目标之外点击,就会执行回调
  onClickOutside(监听的目标, (e) => {
    // 鼠标在目标之外点击,要做什么?
  })  
}

 usar

 <div class="xtx-city" ref="target">
<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
export default {
  name: 'XtxCity',
  setup () {
    // 省略其他 ...
    
    
    // 点击其他位置隐藏
    const target = ref(null)
    // 只要点击外部就触发
    onClickOutside(target, () => {
      closeCity()
    })
    return { visible, toggleDialog, target }
  }
}
</script>

5. El componente ciudad obtiene datos

<template>
// ...
 <span class="ellipsis" v-for="item in cityData" :key="item.code"> {
   
   {item.name}} </span>
</template>
import axios from 'axios'
const url = 'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'

// 获取省市区数据
const getCityData = () => {
    return  axios({ url })
}
// 城市数据
 const cityData = ref([])
 const openCity = () => {
      visible.value = true
      getCityData().then(res => { cityData.value = res.data })
    }

Tenga en cuenta que el método abierto se llama cuando se abre  

 efecto actual

 6. Interacción componentes-lógica de la ciudad

Muestre el texto de las provincias y ciudades, de modo que el componente pueda seleccionar provincias y ciudades y retroalimentar al componente principal.

valor inicial

  • El usuario no ha iniciado sesión: en los datos del producto actual, el backend pasará direcciones de usuario: nulo, en este momento, debemos usar la dirección predeterminada: distrito de Dongcheng, ciudad de Beijing

  • El usuario ha iniciado sesión: en los datos del producto actual, el backend pasará userAddresses: una matriz de direcciones, similar a la siguiente:

isDefault: 1 significa la dirección predeterminada. En este punto, deberíamos mostrar la dirección proporcionada por el usuario.  

De padre a hijo: la ubicación completa obtenida de los detalles del producto debe mostrarse en xtx-city

Pasar de hijo a padre: haga clic en los tres niveles de información en xtx-city, complete la configuración de la dirección y pase los datos seleccionados actualmente al componente principal.

Después de seleccionar provincias y ciudades: 4 datos para pasar al backend:

  1. guardar código

  2. código de ciudad

  3. código de región;

  4. las palabras que combinan

  7. Componente de ciudad - interacción - mostrar dirección predeterminada

En el componente principal nombre-mercancías:

(1) Preparar datos de acuerdo con los requisitos del documento de interfaz

(2) Analice la dirección actual del componente principal y pásela al componente secundario para su visualización.

El componente principal establece los datos de código de provincias y ciudades, y los datos de texto correspondientes.

 nombre-mercancías.vue

  name: 'GoodName',
  props: {
    list: {  //当前商品信息
      type: Object,
      default: () => ({})
    }
  },  
<script>
import { ref } from 'vue'
export default {
  name: 'GoodName',
  props: {
    list: {
      type: Object,
      default: () => ({})
    }
  },
  setup (props) {
    // 默认情况
    const provinceCode = ref('110000')
    const cityCode = ref('119900')
    const countyCode = ref('110101')
    const fullLocation = ref('北京市 市辖区 东城区')
    // 如果有默认地址
    if (props.list.userAddresses) {
    //找到默认值为1的那一项
      const defaultAddr = props.list.userAddresses.find(addr => addr.isDefault === 1)
    // 如果找到了就赋值
      if (defaultAddr) {
        provinceCode.value = defaultAddr.provinceCode
        cityCode.value = defaultAddr.cityCode
        countyCode.value = defaultAddr.countyCode
        fullLocation.value = defaultAddr.fullLocation
      }
    }
    return { fullLocation }
  }
}
</script>

 pase al componente ciudad

<XtxCity :fullLocation="fullLocation" ></XtxCity>

 Recepción del componente de la ciudad

  props: {
    fullLocation: { type: String, default: '' }
  },

mostrar dirección predeterminada

<span class="placeholder" v-if="!fullLocation">请选择配送地址</span>
<span class="value" v-else> {
   
   {fullLocation}} </span>

8. Componente de la ciudad, haga clic en niño interactivo a padre

Haga clic en la provincia --> mostrar la lista de ciudades

Haga clic en Ciudad --> Lista de áreas de visualización.

Haga clic en el área -> cierre la capa emergente y notifique al componente principal

El contenido mostrado está directamente relacionado con la selección del usuario, así que use la propiedad calculada para determinar

xtx-city.vue

    <div class="option" v-show="visible">
+      <span @click="changeItem(item)" class="ellipsis"></span>

Definir los componentes de la ciudad

 // 子组件选中的数据
    const changeResult = reactive({
      provinceCode: '', // 省code
      provinceName: '', // 省 名字
      cityCode: '', // 市code
      cityName: '', // 市 名字
      countyCode: '', // 区 code
      countyName: '', // 去 名字
      fullLocation: '' // 省区市连起来的名字
    })
    const changeItem = (item) => {
      //   省
      if (item.level === 0) {  //如果拿到0说明有选中省了
        changeResult.provinceName = item.name
        changeResult.provinceCode = item.code
      }
      //   市
      if (item.level === 1) { //如果拿到1说明有选中市了
        changeResult.cityName = item.name
        changeResult.cityCode = item.code
      }
      // 地区
      if (item.level === 2) { //如果拿到2说明有选中区了
        changeResult.countyCode = item.code
        changeResult.countyName = item.name
        changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
        emit('change', changeResult)  //给父组件发送联动数据
        closeCity()   //选完市以后关闭弹框  代表三个都点击完了
      }
    }

 Defina la propiedad calculada para obtener el valor actual que se representará. Por ejemplo, primero represente la provincia, haga clic para representar la ciudad, haga clic en la ciudad para representar el área

 const curList = computed(() => {
      // 省
      let curList = cityData.value  
      // 市
      if (changeResult.provinceCode) {  // 找到当前code和省中相等的对象 拿到市 
        curList = curList.find(it => it.code === changeResult.provinceCode).areaList
      }
      // 区
      if (changeResult.cityCode) {  // 找到当前code和市中相等的对象 拿到区 
        curList = curList.find(it => it.code === changeResult.cityCode).areaList
      }
      return curList
    })
return {...省略,curList }

cambiar datos de poligonal poligonal curList

  <span class="ellipsis" @click="changeItem(item)" v-for="item in curList" :key="item.code"> {
   
   {item.name}} </span>

 Borrar la última opción al abrir el componente

    // 打开城市组件
    const openCity = () => {
      visible.value = true
      getCityData().then(res => { cityData.value = res.data })
      //   清空上次的结果  例如用户点错了 重新点开应该重新选择省
      for (const key in changeResult) {
        changeResult[key] = ''
      }
    }

 9. Componente de la ciudad, haga clic en recibir padres interactivos

<XtxCity :fullLocation="fullLocation" @change="changeCity"></XtxCity>
 const changeCity = (result) => {
      provinceCode.value = result.provinceCode
      cityCode.value = result.cityCode
      countyCode.value = result.countyCode
      fullLocation.value = result.fullLocation
    }

    return { fullLocation, changeCity }

10. Agregue caché al componente de la ciudad

La situación actual es que se enviará un nuevo ajax cada vez que se abra, enviar muchos de los mismos datos desperdicia recursos de memoria.

 Guarde la solución en la ventana, también puede guardarla en vuex

  const openCity = () => {
      visible.value = true
    //如果window下的cityData中有值就从window下拿
      if (window.cityData) {
        cityData.value = window.cityData
      } else {
    // 如果没有就在请求数据的时候也给window一份
        getCityData().then(res => { cityData.value = res.data; window.cityData = res.data })
      }
      //   清空上次的结果  例如用户点错了 重新点开应该重新选择省
      for (const key in changeResult) {
        changeResult[key] = ''
      }
    }

 11. Guarda el efecto de carga al solicitar datos de la ciudad

Cuando la red es lenta, no se pueden obtener los datos y se debe mostrar un efecto de carga

<div class="option" v-show="visible">
 +     <div v-if="loading" class="loading"></div>
 +     <template v-else>
        <span class="ellipsis" 
					v-for="item in curList" 
          :key="item.code">
            {
   
   {item.name}}
        </span>
      </template>
</div>

// 补充对应的样式
.loading {
      height: 290px;
      width: 100%;
      background: url('~@/assets/images/loading.gif') no-repeat center;
    }

 El efecto de carga cambia

const loading = ref(false)

const open = () => {
      visible.value = true
      // 检查是否在window中有数据
      if (window.cityData) {
        console.log('有上传保存的数据')
        cityData.value = window.cityData
      } else {
        console.log('没有上传保存的数据,发ajax')
+        loading.value = true // 正在加载
        // ajax加载数据
        getCityData().then(res => {
          cityData.value = res.data
          // 向window这个超级对象中存入属性
          window.cityData = res.data

+         loading.value = false // 加载完成
        })
      }
    }

Un pequeño error hizo que la red ralentizara 3g y descubrió que cuando la página se abría al principio, estaba vacía y tardó un tiempo en mostrar el efecto de carga. 

 La razón es que se tarda en pedir la imagen, es decir, el tiempo de petición de la imagen del efecto de carga es de 2,32s y el tamaño es de 8,2kb

 Resuelva el error de carga y convierta la imagen al formato base64  

Recuerde reiniciar el servidor después de modificar los elementos de configuración en vue.config.js


module.exports = {
  // 省略其他...
  chainWebpack: config => {
   config.module
     .rule('images')
     .use('url-loader')
     .loader('url-loader')
     .tap(options => Object.assign(options, { limit: 10000 }))   //小于10kb都转换为base64
	}
}

 Después de convertir al formato base64, la solicitud no se enviará 

 código completo

NombreProductos.vue

<template>
  <p class="g-name">{
   
   { list.name }}</p>
  <p class="g-desc">{
   
   { list.desc }}</p>
  <p class="g-price">
    <span> {
   
   { list.price }} </span>
    <span> {
   
   { list.oldPrice }} </span>
  </p>
  <div class="g-service">
    <dl>
      <dt>促销</dt>
      <dd>12月好物放送,App领券购买直降120元</dd>
    </dl>
    <dl>
      <dt>配送</dt>
      <dd>
        至 <XtxCity :fullLocation="fullLocation" @change="changeCity"></XtxCity>
      </dd>
    </dl>
    <dl>
      <dt>服务</dt>
      <dd>
        <span>无忧退货</span>
        <span>快速退款</span>
        <span>免费包邮</span>
        <a href="javascript:;">了解详情</a>
      </dd>
    </dl>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'GoodName',
  props: {
    list: {
      type: Object,
      default: () => ({})
    }
  },
  setup (props) {
    // 默认情况
    const provinceCode = ref('110000')
    const cityCode = ref('119900')
    const countyCode = ref('110101')
    const fullLocation = ref('北京市 市辖区 东城区')
    // 有默认地址
    if (props.list.userAddresses) {
      const defaultAddr = props.list.userAddresses.find(addr => addr.isDefault === 1)
      if (defaultAddr) {
        provinceCode.value = defaultAddr.provinceCode
        cityCode.value = defaultAddr.cityCode
        countyCode.value = defaultAddr.countyCode
        fullLocation.value = defaultAddr.fullLocation
      }
    }
    const changeCity = (result) => {
      provinceCode.value = result.provinceCode
      cityCode.value = result.cityCode
      countyCode.value = result.countyCode
      fullLocation.value = result.fullLocation
    }

    return { fullLocation, changeCity }
  }
}
</script>

<style lang="less" scoped>
.g-name {
  font-size: 22px;
}
.g-desc {
  color: #999;
  margin-top: 10px;
}
.g-price {
  margin-top: 10px;
  span {
    &::before {
      content: "¥";
      font-size: 14px;
    }
    &:first-child {
      color: @priceColor;
      margin-right: 10px;
      font-size: 22px;
    }
    &:last-child {
      color: #999;
      text-decoration: line-through;
      font-size: 16px;
    }
  }
}
.g-service {
  background: #f5f5f5;
  width: 500px;
  padding: 20px 10px 0 10px;
  margin-top: 10px;
  dl {
    padding-bottom: 20px;
    display: flex;
    align-items: center;
    dt {
      width: 50px;
      color: #999;
    }
    dd {
      color: #666;
      &:last-child {
        span {
          margin-right: 10px;
          &::before {
            content: "•";
            color: @xtxColor;
            margin-right: 2px;
          }
        }
        a {
          color: @xtxColor;
        }
      }
    }
  }
}
</style>

XtxCity.vue

<template>
  <div class="xtx-city" ref="target">
    <div class="select" @click="toggleCity" :class="{active:visible}">
      <span class="placeholder" v-if="!fullLocation">请选择配送地址</span>
      <span class="value" v-else> {
   
   {fullLocation}} </span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" v-show="visible">
         <div v-if="loading" class="loading"></div>
    <template v-else>
          <span class="ellipsis" @click="changeItem(item)" v-for="item in curList" :key="item.code"> {
   
   {item.name}} </span>
    </template>
    </div>
  </div>
</template>

<script>
import { ref, reactive, computed } from 'vue'
import { onClickOutside } from '@vueuse/core'
import axios from 'axios'
export default {
  name: 'XtxCity',
  props: {
    fullLocation: { type: String, default: '' }
  },
  setup (props, { emit }) {
    //   默认不显示
    const visible = ref(false)
    // 打开城市组件
    // loading效果
    const loading = ref(false)
    const openCity = () => {
      visible.value = true
      if (window.cityData) {
        cityData.value = window.cityData
      } else {
        getCityData().then(res => {
          cityData.value = res.data
          window.cityData = res.data
          loading.value = false
        })
      }
      //   清空上次的结果  例如用户点错了 重新点开应该重新选择省
      for (const key in changeResult) {
        changeResult[key] = ''
      }
    }
    // 关闭城市组件
    const closeCity = () => {
      visible.value = false
    }
    // 切换城市组件开关
    const toggleCity = () => {
      visible.value ? closeCity() : openCity()
    }
    // 点击其他位置隐藏
    const target = ref(null)
    onClickOutside(target, () => closeCity())

    // 城市数据
    const cityData = ref([])
    const url = 'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
    // 获取城市数据
    const getCityData = () => {
      return axios({ url })
    }
    // 子组件选中的数据
    const changeResult = reactive({
      provinceCode: '', // 省code
      provinceName: '', // 省 名字
      cityCode: '', // 市code
      cityName: '', // 市 名字
      countyCode: '', // 区 code
      countyName: '', // 去 名字
      fullLocation: '' // 省区市连起来的名字
    })
    const changeItem = (item) => {
      //   省
      if (item.level === 0) {
        changeResult.provinceName = item.name
        changeResult.provinceCode = item.code
      }
      //   市
      if (item.level === 1) {
        changeResult.cityName = item.name
        changeResult.cityCode = item.code
      }
      // 地区
      if (item.level === 2) {
        changeResult.countyCode = item.code
        changeResult.countyName = item.name
        changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
        emit('change', changeResult)
        closeCity()
      }
    }

    const curList = computed(() => {
      // 省
      let curList = cityData.value
      // 市
      if (changeResult.provinceCode) {
        curList = curList.find(it => it.code === changeResult.provinceCode).areaList
      }
      // 区
      if (changeResult.cityCode) {
        curList = curList.find(it => it.code === changeResult.cityCode).areaList
      }
      return curList
    })

    return { visible, toggleCity, target, cityData, changeResult, changeItem, curList, loading }
  }
}
</script>
<style scoped lang="less">
.xtx-city {
  display: inline-block;
  position: relative;
  z-index: 400;
  .select {
    border: 1px solid #e4e4e4;
    height: 30px;
    padding: 0 5px;
    line-height: 28px;
    cursor: pointer;
    &.active {
      background: #fff;
    }
    .placeholder {
      color: #999;
    }
    .value {
      color: #666;
      font-size: 12px;
    }
    i {
      font-size: 12px;
      margin-left: 5px;
    }
  }
  .option {
    width: 542px;
    border: 1px solid #e4e4e4;
    position: absolute;
    left: 0;
    top: 29px;
    background: #fff;
    min-height: 30px;
    line-height: 30px;
    display: flex;
    flex-wrap: wrap;
    padding: 10px;
    > span {
      width: 130px;
      text-align: center;
      cursor: pointer;
      border-radius: 4px;
      padding: 0 3px;
      &:hover {
        background: #f5f5f5;
      }
    }
  }
}
.loading {
      height: 290px;
      width: 100%;
      background: url('~@/assets/images/loading.gif') no-repeat center;
    }
</style>

Supongo que te gusta

Origin blog.csdn.net/m0_46846526/article/details/119116407#comments_26423426
Recomendado
Clasificación