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
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
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:
-
guardar código
-
código de ciudad
-
código de región;
-
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>