This commit is contained in:
wtq
2025-11-28 10:19:58 +08:00
commit 3e6f452aa5
275 changed files with 47800 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
@import '@/assets/_color.scss';
.zy-switch {
width: 2em;
height: 1em;
display: inline-block;
background: white;
border: 1rpx solid $border2;
border-radius: 1em;
font-size: 50rpx;
position: relative;
align-items: center;
transition: all .3s;
.zy-switch-item {
position: absolute;
width: 1em;
height: 1em;
border-radius: 100%;
box-shadow: 0 6rpx 2rpx 0 rgba(0, 0, 0, 0.05), 0 4rpx 4rpx 0 rgba(0, 0, 0, 0.1), 0 6rpx 6rpx 0 rgba(0, 0, 0, 0.05);
transition: .3s;
background: white;
left: 0;
}
}
.switch-on {
flex-direction: row-reverse;
background: $maincolor;
.zy-switch-item {
left: 1em;
}
}
.switch-disabled {
opacity: .5;
}

View File

@@ -0,0 +1,77 @@
/*
滑块组件
*/
<template>
<div
class="zy-switch"
@click="onChange"
:class="{
'switch-on': currentValue,
'switch-disabled': disabled
}"
:style="{'font-size': size + 'rpx'}"
>
<div class="zy-switch-item"></div>
</div>
</template>
<script>
export default {
name: 'ZySwitch',
props: {
disabled: {
type: Boolean,
default: false
},
value: {
type: [Boolean, Number, String],
default: false
},
size: {
type: String,
default: '50'
},
trueValue: {
type: [Boolean, Number, String],
default: true
},
falseValue: {
type: [Boolean, Number, String],
default: false
}
},
watch: {
value (val) {
if (val === this.trueValue || val === this.falseValue) {
this.updateModel()
} else {
throw 'Value should be trueValue or falseValue'
}
}
},
data () {
return {
currentValue: this.value
}
},
methods: {
onChange () {
console.log('onchange')
if (this.disabled) return false
let checked = !this.currentValue
this.currentValue = checked
let value = checked ? this.trueValue : this.falseValue
this.$emit('input:value', value)
// this.$emit('on-change', value)
},
updateModel () {
this.currentValue = this.value === this.trueValue
}
}
}
</script>
<style lang="scss">
@import './switch';
</style>

View File

@@ -0,0 +1,29 @@
@import '@/assets/bundle.scss';
.address-item {
@extend %pdtb;
border-bottom: 1rpx solid $border3;
margin: 0 50rpx;
position: relative;
#{&}-title {
color: $text1;
font-size: 34rpx;
@extend %oneline;
width: 500rpx;
}
#{&}-address {
color: $text3;
font-size: 28rpx;
padding-top: 10rpx;
@extend %oneline;
// border: 1px solid red;
}
.title{
display: flex;
justify-content: space-between;
}
&-distance{
color:$text3;
font-size: 28rpx;
}
}

View File

@@ -0,0 +1,33 @@
<template>
<div class="address-item">
<div class="title">
<div class="address-item-title">{{title}}</div>
<div class="address-item-distance" v-if="!isKM">{{distance.toFixed(1)}}</div>
<div class="address-item-distance" v-else >{{(distance/1000).toFixed(1)}}千米</div>
</div>
<div class="address-item-address">{{address}}</div>
</div>
</template>
<script>
export default {
props: {
title: String,
address: String,
distance:Number
},
computed: {
isKM(){
let isKM = true // 是否为千米
if(String(this.distance).split('.')[0].length<4){
isKM = false
}
return isKM
}
}
}
</script>
<style lang="scss">
@import "./address-item.scss";
</style>

View File

@@ -0,0 +1,43 @@
@import '@/assets/bundle.scss';
.waybill-address {
border-bottom: 1rpx solid $border2;
@extend %flex-center;
// @extend %pdlr;
padding: 30rpx;
min-height: 140rpx;
#{&}-left {
flex: auto;
}
#{&}-right {
flex: none;
// width: 100rpx;
.icon-editor {
@extend %icon-editor;
@extend %bg-no-repeat-center;
background-size: 40rpx;
width: 100rpx;
height: 100rpx;
}
}
#{&}-address {
font-size: 30rpx;
color: $text1;
margin-bottom: 10rpx;
}
#{&}-contact {
font-size: 30rpx;
color: $text3;
}
.isDefault {
font-size: 22rpx;
display: inline-block;
@extend %flex-center;
padding: 10rpx;
background: $danger;
color: white;
border-radius: 10rpx;;
margin-right: 10rpx;
}
}

View File

@@ -0,0 +1,43 @@
<template>
<div class="waybill-address">
<div class="waybill-address-left" @click="handlePick">
<div class="waybill-address-address">
<span class="isDefault" v-if="address.isDefault">默认</span>
{{address.address}}
</div>
<div class="waybill-address-contact">
{{address.consigneeName}} {{address.consigneeMobile}}
</div>
</div>
<!-- 编辑 -->
<div class="waybill-address-right" @click="toEditor">
<div class="icon-editor"></div>
</div>
</div>
</template>
<script>
export default {
props: {
address: Object,
isManager: {
type: Boolean,
default: false
}
},
methods: {
// 跳转到编辑页面
toEditor () {
this.$emit('on-edit', this.address)
},
async handlePick () {
console.log('选择')
this.$emit('on-pick', this.address)
}
}
}
</script>
<style lang="scss">
@import './waybill-address.scss';
</style>

View File

@@ -0,0 +1,34 @@
@import '@/assets/bundle.scss';
.create-address {
padding: 20rpx 50rpx;
.consigneeName,
.consigneeMobile,
.map-address,
.address,
.isDefault {
border-bottom: 1rpx solid $border2;
}
.btn-save {
@extend %btn-linearGradient;
margin-top: 60rpx;
width: 100%;
border-radius: 0;
height: 80rpx;
}
.btn-delete {
margin-top: 60rpx;
@extend %flex-center;
.icon-delete {
@extend %icon-clearAll;
@extend %bg-no-repeat-center;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-size: 40rpx;
@extend %bgwhite;
box-shadow: 0 4rpx 8rpx rgba(black, .3);
border: 1rpx solid $border1;
}
}
}

View File

@@ -0,0 +1,223 @@
<template>
<div class="create-address">
<!-- 联系人 -->
<div class="consigneeName">
<input-label
label="联系人:"
:maxlength="16"
placeholder="请填写收货人的姓名"
v-model:value="form.consigneeName"
></input-label>
</div>
<!-- 手机号 -->
<div class="consigneeMobile">
<input-label
label="手机号:"
type="number"
placeholder="请填写收货人的手机号码"
:maxlength="11"
v-model:value="form.consigneeMobile"
></input-label>
</div>
<!-- 收货地址 -->
<picker mode ="region" :value="addressInfo.value" @change="selectCity">
<input-label
label="省市区:"
placeholder="点击选择省市区"
v-model:value="modelValue"
isText
suffix
></input-label>
</picker>
<div class="map-address" @click="toPosition">
<input-label
label="收货地址:"
placeholder="点击选择收货地址"
v-model:value="mapAddress"
isText
suffix
></input-label>
</div>
<!-- 门牌号 -->
<div class="address">
<input-label
label="详细地址:"
:maxlength="30"
placeholder="详细地址1栋1单元801"
v-model:value="form.detailAddress"
></input-label>
</div>
<!-- 设为默认地址 -->
<div class="isDefault">
<switch-label
label="设为默认地址:"
v-model:value="form.isDefault"
></switch-label>
</div>
<!-- 保存 -->
<!-- <button @click="saveAddress">保存地址</button> -->
<div class="btn-save" @click="saveAddress">保存地址</div>
<!-- 删除按钮 -->
<div class="btn-delete" v-if="address" @click="deleteAddress">
<div class="icon-delete"></div>
</div>
<!-- loading -->
<ZyLoading></ZyLoading>
</div>
</template>
<script>
import ZyLoading from '@/components/Loading/loading'
import InputLabel from '../input-label/input-lebel.vue'
import SwitchLabel from '../switch-label/switch-label.vue'
import {isRequire, isMobile} from '@/utils/validate.js'
import {toast,jumpPage} from '@/utils/uniapi.js'
import {mapGetters, mapActions} from 'vuex'
import {errToast} from '@/utils/uniapi.js'
export default {
props: {
address: Object // 用来标识是否是编辑状态
},
components: {
ZyLoading, InputLabel, SwitchLabel
},
data () {
return {
id: 0,
form: {
consigneeName: '',
consigneeMobile: '',
detailAddress: '',
isDefault: false
},
lat: null,
lng: null,
mapAddress: '',
addressInfo:{
code:['510000','510100','510106'],
postcode:610036, // 邮编
value:['四川省','成都市','金牛区']
},
modelValue:'四川省/成都市/金牛区'
}
},
created () {
if (this.address) {
let item = this.address
this.id = item.id
this.form.consigneeName = item.consigneeName
this.form.consigneeMobile = item.consigneeMobile
this.form.detailAddress = item.detailAddress
this.mapAddress = item.address
this.form.isDefault = !!item.isDefault
}
},
destroyed () {
// console.log('销毁')
// 修改changeCity为当前地位的城市
this.$store.commit('indexPage/changeCity', this.location.city)
},
computed: {
...mapGetters({
location: 'indexPage/myLocation',
curPickAddress: 'addressVue/curPickAddress'
})
},
watch: {
curPickAddress (address) {
console.log(address)
if (address) {
this.mapAddress = address.address + address.title
this.lat = address.location.lat
this.lng = address.location.lng
}
}
},
methods: {
...mapActions({
addAddressList: 'addressVue/addListItem'
}),
async saveAddress () {
let {consigneeName, consigneeMobile, detailAddress} = this.form
let isDefault = this.form.isDefault ? 1 : 0
let mapAddress = this.mapAddress
consigneeName = consigneeName.trim()
consigneeMobile = consigneeMobile.trim()
detailAddress = detailAddress.trim()
mapAddress = mapAddress.trim()
const {lat, lng} = this
// console.log(consigneeName, consigneeMobile, detailAddress, mapAddress, lat, lng)
if (!isRequire(consigneeName)) {
toast('请填写正确的联系人')
return false
}
if (!isMobile(consigneeMobile)) {
toast('请填写正确的手机号')
return false
}
if (!isRequire(mapAddress)) {
toast('请选择收货地址')
return false
}
if (!isRequire(detailAddress)) {
toast('请填写正确门牌号')
return false
}
// 校验通过
if (this.address) {
// 保存编辑
let json = {
id: this.id,
consigneeName,
consigneeMobile,
detailAddress,
cityCode:this.addressInfo.code[1],
districtCode :this.addressInfo.code[2],
address: mapAddress,
isDefault
}
if (this.lat) json.lat = this.lat
if (this.lng) json.lng = this.lng
this.$emit('handle-update', json)
} else {
try {
await this.addAddressList({
consigneeName,
consigneeMobile,
address: mapAddress,
detailAddress,
cityCode:this.addressInfo.code[1],
districtCode :this.addressInfo.code[2],
lng,
lat,
isDefault
})
uni.navigateBack({delta: 1})
} catch (e) {
console.error(e)
errToast(e)
}
}
},
// 选择省市区
selectCity(e){
this.modelValue = e.detail.value.join('/')
this.addressInfo = e.detail
// store.commit('indexPage/changeCity', e.detail.value[1])
},
// 跳转到收货地址选择
toPosition () {
jumpPage('navigateTo',`/pagesAddress/position-address/index?showMine=false&districtName=${this.addressInfo.value[2]}`)
},
// 删除
deleteAddress () {
this.$emit('handle-delete', this.id)
}
}
}
</script>
<style lang="scss">
@import './create-address.scss';
</style>

View File

@@ -0,0 +1,35 @@
@import '@/assets/bundle.scss';
.create-address {
padding: 20rpx 50rpx;
.consigneeName,
.consigneeMobile,
.map-address,
.address,
.isDefault {
border-bottom: 1rpx solid $border2;
}
.btn-save {
@extend %btn-linearGradient;
margin-top: 60rpx;
width: 100%;
border-radius: 0;
height: 80rpx;
border-radius: 20rpx;
}
.btn-delete {
margin-top: 60rpx;
@extend %flex-center;
.icon-delete {
@extend %icon-clearAll;
@extend %bg-no-repeat-center;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-size: 40rpx;
@extend %bgwhite;
box-shadow: 0 4rpx 8rpx rgba(black, .3);
border: 1rpx solid $border1;
}
}
}

View File

@@ -0,0 +1,248 @@
<template>
<div class="create-address">
<!-- 联系人 -->
<div class="consigneeName">
<input-label
label="联系人:"
:maxlength="16"
placeholder="请填写收货人的姓名"
v-model:value="form.consigneeName"
></input-label>
</div>
<!-- 手机号 -->
<div class="consigneeMobile">
<input-label
label="手机号:"
type="number"
placeholder="请填写收货人的手机号码"
:maxlength="11"
v-model:value="form.consigneeMobile"
></input-label>
</div>
<!-- 收货地址 -->
<!-- <div class="map-address" @click="toPosition">
<input-label
label="收货地址:"
placeholder="点击选择收货地址"
v-model:value="mapAddress"
isText
suffix
></input-label>
</div> -->
<!-- /:value="modelValue" -->
<picker mode ="region" :value="addressInfo.value" @change="selectCity">
<input-label
label="省市区:"
placeholder="点击选择省市区"
v-model:value="modelValue"
isText
suffix
></input-label>
</picker>
<div class="map-address" @click="toPosition">
<input-label
label="收货地址:"
placeholder="点击选择收货地址"
v-model:value="mapAddress"
isText
suffix
></input-label>
</div>
<!-- 门牌号 -->
<div class="address">
<input-label
label="详细地址:"
:maxlength="30"
placeholder="例1栋1单元801"
v-model:value="form.detailAddress"
></input-label>
</div>
<!-- 设为默认地址 -->
<div class="isDefault">
<switch-label
label="设为默认地址:"
v-model:value="form.isDefault"
></switch-label>
</div>
<!-- 保存 -->
<!-- <button @click="saveAddress">保存地址</button> -->
<div class="btn-save" @click="saveAddress">保存地址</div>
<!-- 删除按钮 -->
<!-- #ifndef MP-TOUTIAO -->
<div class="btn-delete" v-if="address" @click="deleteAddress">
<div class="icon-delete"></div>
</div>
<!-- #endif -->
<!-- #ifdef MP-TOUTIAO -->
<div class="btn-delete" v-if="JSON.stringify(address)!=='{}'" @click="deleteAddress">
<div class="icon-delete"></div>
</div>
<!-- #endif -->
<!-- loading -->
<ZyLoading></ZyLoading>
</div>
</template>
<script>
import ZyLoading from '@/components/Loading/loading'
import InputLabel from '../input-label/input-lebel.vue'
import SwitchLabel from '../switch-label/switch-label.vue'
import {isRequire, isMobile} from '@/utils/validate.js'
import {toast,jumpPage} from '@/utils/uniapi.js'
import {mapGetters, mapActions} from 'vuex'
import { errToast } from '@/utils/uniapi.js'
import {store} from "@/store";
export default {
props: {
address: Object // 用来标识是否是编辑状态
},
components: {
ZyLoading, InputLabel, SwitchLabel
},
data () {
return {
id: 0,
form: {
consigneeName: '',
consigneeMobile: '',
detailAddress: '',
isDefault: false
},
lat: null,
lng: null,
mapAddress: '',
addressInfo:{
code:['510000','510100','510106'],
postcode:610036, // 邮编
value:['四川省','成都市','金牛区']
},
modelValue:'四川省/成都市/金牛区'
}
},
created () {
if (this.address) {
let item = this.address
this.id = item.id
this.form.consigneeName = item.consigneeName
this.form.consigneeMobile = item.consigneeMobile
this.form.detailAddress = item.detailAddress
this.mapAddress = item.address
this.form.isDefault = !!item.isDefault
}
},
destroyed () {
// console.log('销毁')
// 修改changeCity为当前地位的城市
store.commit('indexPage/changeCity', this.location.city)
},
computed: {
...mapGetters({
location: 'indexPage/myLocation',
curPickAddress: 'addressVue/curPickAddress'
})
},
watch: {
curPickAddress (address) {
if (address) {
this.mapAddress = address.address + address.title
this.lat = address.location.lat
this.lng = address.location.lng
}
}
},
methods: {
...mapActions({
addAddressList: 'addressVue/addListItem'
}),
async saveAddress () {
let {consigneeName, consigneeMobile, detailAddress} = this.form
let isDefault = this.form.isDefault ? 1 : 0
let mapAddress = this.mapAddress
consigneeName = consigneeName.trim()
consigneeMobile = consigneeMobile.trim()
detailAddress = detailAddress.trim()
mapAddress = mapAddress.trim()
const { lat, lng } = this
// console.log(consigneeName, consigneeMobile, detailAddress, mapAddress, lat, lng)
if (!isRequire(consigneeName)) {
toast('请填写正确的联系人')
return false
}
if (!isMobile(consigneeMobile)) {
toast('请填写正确的手机号')
return false
}
if (!isRequire(mapAddress)) {
toast('请选择收货地址')
return false
}
if (!isRequire(detailAddress)) {
toast('请填写详细地址')
return false
}
let flag = false
// #ifndef MP-TOUTIAO
flag = this.address ? true : false
// #endif
// #ifdef MP-TOUTIAO
flag = JSON.stringify(this.address)!=='{}' ? true : false
// #endif
// 校验通过
if (flag) {
// 保存编辑
let json = {
id: this.id,
consigneeName,
consigneeMobile,
detailAddress,
address: mapAddress,
isDefault
}
if (this.lat) json.lat = this.lat
if (this.lng) json.lng = this.lng
this.$emit('handle-update', json)
} else {
try {
await this.addAddressList({
consigneeName,
consigneeMobile,
address: mapAddress,
detailAddress,
cityCode:this.addressInfo.code[1],
districtCode :this.addressInfo.code[2],
lng,
lat,
isDefault
})
uni.navigateBack({delta: 1})
} catch (e) {
console.error(e)
errToast(e)
}
}
},
// 跳转到收货地址选择
toPosition () {
jumpPage('navigateTo',`/pagesAddress/position-address/index?showMine=false&districtName=${this.addressInfo.value[2]}`)
},
// 选择省市区
selectCity(e){
this.modelValue = e.detail.value.join('/')
this.addressInfo = e.detail
// store.commit('indexPage/changeCity', e.detail.value[1])
},
// 删除
deleteAddress () {
this.$emit('handle-delete', this.id)
}
}
}
</script>
<style lang="scss">
@import './create-address.scss';
</style>

View File

@@ -0,0 +1,3 @@
@import '@/assets/bundle.scss';
.edit-address {}

View File

@@ -0,0 +1,72 @@
<template>
<div class="edit-address">
<create-address
v-if="address"
:address="address"
@handle-update="handleUpdate"
@handle-delete="handleDelete"
></create-address>
</div>
</template>
<script>
import CreateAddress from '@/pagesAddress/create-address-cop/index.vue'
import {mapActions} from 'vuex'
import {msgData} from '@/config.js'
import {modal, errToast} from '@/utils/uniapi.js'
export default {
components: {
CreateAddress
},
data () {
return {
address: null
}
},
onLoad (query) {
console.log('onload')
let address = JSON.parse(decodeURIComponent(query.address))
// console.log(address)
this.address = address
},
methods: {
...mapActions({
delAddressItem: 'addressVue/delListItem',
updateAddressItem: 'addressVue/updateListItem'
}),
async handleUpdate (json) {
try {
this.showLoad()
await this.updateAddressItem(json)
uni.navigateBack({delta: 1})
} catch (e) {
console.error(e)
errToast(e)
} finally {
this.hideLoad()
}
},
async handleDelete (id) {
try {
this.showLoad()
console.log(id)
const confirm = await modal('', msgData.delWaybillAddress)
if (confirm) {
// 删除地址
await this.delAddressItem(id)
uni.navigateBack({delta: 1})
}
} catch (e) {
console.error(e)
errToast(e)
} finally {
this.hideLoad()
}
}
}
}
</script>
<style lang="scss">
@import './edit-address.scss';
</style>

View File

@@ -0,0 +1,30 @@
@import '@/assets/bundle.scss';
.input-label {
@extend %flex-left;
min-height: 100rpx;
@extend %pdtb;
box-sizing: border-box;
.label {
width: 160rpx;
flex: none;
font-size: 30rpx;
color: $text1;
}
.input, .isText {
flex: auto;
font-size: 30rpx;
color: $text1;
}
.placeholder {
color: $info;
}
.icon-right-aw {
flex: none;
@extend %icon-aw-right-slim;
width: 30rpx;
height: 30rpx;
@extend %bg-no-repeat-center;
@extend %bg-size-100;
}
}

View File

@@ -0,0 +1,81 @@
<template>
<div class="input-label">
<div class="label">{{label}}</div>
<input
v-if="!isText"
class="input"
:type="type"
placeholder-class="placeholder"
:placeholder="placeholder"
@input="handleInput"
:maxlength="maxlength"
:disabled="disabled"
:value="currentValue"
>
<div v-if="isText" :class="{
'isText': true,
'placeholder': !currentValue
}">{{currentValue || placeholder}}</div>
<div class="icon-right-aw" v-if="suffix"></div>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ''
},
value: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: '请填写'
},
maxlength: {
type: Number,
default: 140
},
disabled: {
type: Boolean,
default: false
},
suffix: {
type: Boolean,
default: false
},
isText: {
type: Boolean,
default: false
}
},
data () {
return {
currentValue: this.value
}
},
watch: {
value (val) {
this.currentValue = val
}
},
methods: {
handleInput(e) {
let value = e.target.value
this.currentValue = value
this.$emit('update:value',value)
}
}
}
</script>
<style lang="scss">
@import './input-label.scss';
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div class="mine-address">
<div class="mine-address-top">
<WaybillAddress
class="address-item"
v-for="address in addressListFilter"
:key="address.id"
:address="address"
@on-pick="handlePick"
@on-edit="goEdit"
:isManager="true"
></WaybillAddress>
</div>
<div
class="mine-address-bottom"
@click="toCreateAddress"
>
新建收货地址
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
// import WaybillAddress from '@/components/addressCmp/waybill-address.vue'
import WaybillAddress from '../addressCmp/waybill-address.vue'
import { errToast,jumpPage } from '@/utils/uniapi.js'
export default {
// props: {
// manager: {
// type: Boolean,
// default: false
// }
// },
components: {
WaybillAddress
},
data() {
return {
isManager: false,
addressListFilter: []
}
},
async onShow() {
try {
await this.getAddressList()
let arr = this.addressList || []
this.addressListFilter = arr.filter(item => item.detailAddress !== '自提订单:详细地址')
} catch (e) {
console.error(e)
errToast(e)
}
},
onLoad(query) {
let { isManager = false } = query
this.isManager = isManager
// console.log('isManager', isManager)
},
computed: {
...mapGetters({
addressList: 'addressVue/addressList'
})
},
methods: {
...mapActions({
getAddressList: 'addressVue/getList'
}),
...mapMutations({
storeWaybillAd: 'addressVue/storeWaybillAd'
}),
toCreateAddress() {
jumpPage('navigateTo','/pagesAddress/create-address/index')
},
// 选择地址
handlePick(address) {
console.log(address)
if (this.isManager) {
} else {
// 存储地址
this.storeWaybillAd([address])
uni.navigateBack({ delta: 1 })
}
},
// 跳转到编辑页面
goEdit(address) {
jumpPage('navigateTo',`/pagesAddress/edit-address/index?address=${encodeURIComponent(JSON.stringify(address))}`)
}
}
}
</script>
<style lang="scss">
@import "./mine-address.scss";
</style>

View File

@@ -0,0 +1,19 @@
@import '@/assets/bundle.scss';
.mine-address {
$bottomH: 100rpx;
#{&}-top {
height: calc(100vh - 100rpx);
overflow-y: auto;
box-sizing: border-box;
border-top: 1rpx solid $border2;
}
#{&}-bottom {
height: $bottomH;
box-sizing: border-box;;
background-color: $maincolor;
color: white;
@extend %flex-center;
font-size: 36rpx;
}
}

View File

@@ -0,0 +1,111 @@
<template>
<div class="pick-city">
<!-- 输入框 -->
<div class="pick-city-search-input">
<search-input placeholder="输入城市名进行搜索" :searchBtnShow="false" @on-change="keywordChange" :focus="false"></search-input>
</div>
<!-- 当前定位城市 -->
<div class="content">
<div class="address-label">
<div class="icon-position"></div>
<div class="label-text">当前定位城市<span class="span" @click="pickCity(location.city)">{{location.city}}</span></div>
</div>
<!-- 热门城市 -->
<div class="hot-city">
<div class="label-title">热门城市</div>
<div class="city-list">
<div v-for="city in hotCity" :key="city.id" class="list-item" @click="pickCity(city.name)">
{{city.nickName}}
</div>
</div>
</div>
<!-- 搜索城市 -->
<div class="search-list">
<div class="city-item" v-for="city in cityList" :key="city.id" @click="pickCity(city)">
{{city.name}}
</div>
</div>
</div>
<!-- loading -->
<ZyLoading></ZyLoading>
</div>
</template>
<script>
import ZyLoading from '@/components/Loading/loading'
import SearchInput from '@/components/commonCmp/search-input/search-input.vue'
import {errToast} from '@/utils/uniapi.js'
import { mapGetters } from 'vuex'
import apiAddress from '@/apis/address'
import {hotCity} from "@/apis/config"
import {store} from "@/store"
export default {
components: {
ZyLoading, SearchInput
},
data () {
return {
cityList: [], // 搜索历史
hotCity: hotCity
}
},
async created () {},
onShow () {
const historyCity = uni.getStorageSync('historyCity')
this.cityList = historyCity ? JSON.parse(historyCity) : []
},
computed: {
...mapGetters({
location: 'indexPage/myLocation'
})
},
methods: {
async keywordChange (keyword) {
this.cityList=[]
keyword = keyword.trim()
if (!keyword) return false
this.showLoad()
try {
let province = await apiAddress.getCity({keyword:keyword,level:1}) // 省
let city = await apiAddress.getCity({keyword:keyword,level:2}) // 市
let area = await apiAddress.getCity({keyword:keyword,level:3}) // 区
this.cityList = [...province, ...city, ...area]
} catch (e) {
console.error(e)
this.cityList = []
errToast(e)
} finally {
this.hideLoad()
}
// console.log(this.cityList)
},
pickCity (city) {
if (typeof city === 'string') {
store.commit('indexPage/changeCity', city)
} else {
store.commit('indexPage/changeCity', city.name)
// 存储历史城市
const historyCity = uni.getStorageSync('historyCity')
const history = historyCity ? JSON.parse(historyCity) : []
history.unshift(city)
// 找到后面位置的相同ID移除
// 去除重复
const noRepeatHistory = []
for (let i = 0; i < history.length; i++) {
const cityItem = history[i]
if (noRepeatHistory.find(item => item.code === cityItem.code)) continue
else noRepeatHistory.push(cityItem)
}
if (noRepeatHistory.length > 10) history.pop()
uni.setStorageSync('historyCity', JSON.stringify(noRepeatHistory))
}
uni.navigateBack({delta: 1})
}
}
}
</script>
<style lang="scss">
@import "./pick-city.scss";
</style>

View File

@@ -0,0 +1,68 @@
@import '@/assets/bundle.scss';
.pick-city {
#{&}-search-input {
@extend %pdlr;
box-sizing: border-box;
height: 100rpx;
border-bottom: 1rpx solid $border3;
}
.content {
height: calc(100vh - 100rpx);
overflow-y: auto;
}
.address-label {
@extend %flex-left;
@extend %pd20;
font-size: 30rpx;
color: $text2;
height: 60rpx;
border-bottom: 1rpx solid $border3;
.icon-position {
@extend %icon-position-small-text2;
@extend %bg-no-repeat-center;
@extend %bg-size-100;
width: 30rpx;
height: 30rpx;
flex: none;
margin-right: 10rpx;
}
.span {
padding-left: 10rpx;
font-weight: 600;
color: $text1;
}
}
.hot-city {
@extend %pd20;
.label-title {
font-size: 28rpx;
color: $text3;
}
.city-list {
font-size: 28rpx;
@extend %flex-between;
flex-flow: row wrap;
}
.list-item {
flex: none;
@extend %flex-center;
width: 160rpx;
height: 70rpx;
background-color: $border3;
margin-top: 20rpx;
color: $text1;
border-radius: 10rpx;
}
}
.search-list {
@extend %pd20;
.city-item {
font-size: 32rpx;
color: $text1;
@extend %flex-left;
border-bottom: 1rpx solid $border3;
height: 100rpx;
}
}
}

View File

@@ -0,0 +1,67 @@
<template>
<div class="pick-store">
<div class="shop-item" :class="{'is-open': item.status === 1, 'is-here': item.storeID == storeID[0]}" @click="changeStore(item.storeID)" v-for="(item, index) in storeArr" :key="index">
<div class="store-name">
<div class="status">{{item.status === 1 ? '营业中' : '休息中'}}</div>
<div class="name">{{item.name}}</div>
</div>
<div class="store-distance">距离{{item.distance}}m</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import {store} from "@/store";
import apiStore from '@/apis/apiStore'
import loginApi from '@/apis/login'
export default {
data() {
return {
storeArr: [],
};
},
computed: {
...mapGetters({
storeMap:"indexPage/storeMap",
storeID:"indexPage/storeID",
brandID: 'indexPage/brandID',
waybillSLG:"indexPage/waybillSLG",
isLogin: "login/isLogin"
}),
},
created() {
// 重排序storeMap
// console.log(this.storeMap);
if (this.storeMap) {
const arr = [];
for (let key in this.storeMap) {
arr[this.storeMap[key].index] = {
...this.storeMap[key],
storeID: +key,
};
}
// 排除empty
this.storeArr = arr.filter((item) => item);
}
// console.log("this.storeArr", this.storeArr);
},
methods: {
async changeStore(storeID) {
if(storeID === this.storeID[0]) return
store.commit('indexPage/changeStoreID', [+storeID]) // 改变当前门店id
uni.navigateBack({ delta: 1 });
await apiStore.getWayBillFeeMinus(storeID) // 获取该店铺的运营策略
if (this.isLogin) await loginApi.updateUserLastInfo(+storeID, this.brandID)
},
},
};
</script>
<style lang="scss">
@import "./pick-store.scss";
</style>

View File

@@ -0,0 +1,44 @@
@import '@/assets/bundle.scss';
.pick-store {
.shop-item {
font-size: 32rpx;
border-bottom: 1rpx solid $border2;
@extend %pd20;
@extend %flex-between;
height: 100rpx;
box-sizing: border-box;
margin: 0 20rpx;
.name, .store-distance {
color: $info;
}
.store-distance {
font-size: 24rpx;
}
}
.store-name {
@extend %flex-left;
.status {
background-color: $text3;
color: white;
border-radius: 50rpx;
font-size: 20rpx;
padding: 5rpx 10rpx;
margin-right: 10rpx;
}
}
.is-here {
background-color: rgba($maincolor, .1);
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
.is-open {
.status {
background-color: $maincolor;
@extend %ani-bounceIn;
}
.name, .store-distance {
color: $text1;
}
}
}

View File

@@ -0,0 +1,279 @@
<template>
<div class="position-address">
<!-- 顶部搜索 -->
<div class="search-address">
<div class="left">
<div
class="btn-city-pick"
@click="toCityPick"
>
<div class="city-name">{{districtName ? districtName : cityChange}}</div>
<div class="icon-arraw-bottom"></div>
</div>
</div>
<div class="right">
<search-input
placeholder="请输入收货地址"
:searchBtnShow="false"
@on-change="keywordChange"
:focus="false"
></search-input>
</div>
</div>
<!-- 列表 -->
<div
class="near-address"
v-if="nearPois.length > 0 && !keyword "
>
<!-- 我的收货地址 -->
<div v-if="showMine && isLogin && isShowShop">
<div
class="address-label"
v-if="addressList.length"
>
<div class="icon-home"></div>
<div class="label-text">我的收货地址</div>
</div>
<div
v-for="item in addressListFilter"
:key="item.id"
@click="pickAddress(item)"
>
<!-- @click.native="pickAddress(item)" -->
<AddressItem
:title="item.address"
:address="item.consigneeName + ' ' + item.consigneeMobile"
:distance="item.distance"
></AddressItem>
</div>
<!-- #ifndef MP-TOUTIAO -->
<!-- 新增地址 -->
<div
v-if="iSExistWeixinApp"
class="add-newAddress"
@click="toCreateAddress"
>
<div class="add">新增地址</div>
<div class="right-aw"></div>
</div>
<!-- #endif -->
</div>
<!-- 附近地址 -->
<div class="address-label">
<div class="icon-position"></div>
<div class="label-text">附近地址</div>
</div>
<div
v-for="item in nearPois"
:key="item.id"
@click="pickAddress(item)"
>
<!-- @click.native="pickAddress(item)" -->
<AddressItem
:title="item.title"
:address="item.address"
:distance="item.distance"
></AddressItem>
</div>
</div>
<!-- 搜索地址 -->
<div
class="search-list"
v-if="keyword"
>
<div
v-for="item in searchPois"
:key="item.id"
@click="pickAddress(item)"
>
<!-- @click.native="pickAddress(item)" -->
<AddressItem
:title="item.title"
:address="item.address"
:distance="item.distance"
></AddressItem>
</div>
</div>
<!-- loading -->
<ZyLoading></ZyLoading>
</div>
</template>
<script>
import ZyLoading from '@/components/Loading/loading'
import SearchInput from '@/components/commonCmp/search-input/search-input.vue'
import { mapGetters, mapMutations } from 'vuex'
import { toast,errToast,jumpPage } from '@/utils/uniapi.js'
import AddressItem from '../addressCmp/address-item.vue'
import {getDistance} from '@/utils/index.js'
import {store} from "@/store";
import apiAddress from '@/apis/address'
export default {
components: {
ZyLoading, SearchInput, AddressItem
},
data() {
return {
keyword: '',
nearPois: [],
searchPois: [],
choisePosition: false,
showMine: true,
type: '',
curLat:0,
curLng:0,
addressListFilter: [],
districtName:''
}
},
mounted() {
this.curLat = this.location.lat
this.curLng = this.location.lat
},
computed: {
...mapGetters({
location: 'indexPage/myLocation',
vuexPois: 'indexPage/pois',
cityChange: 'indexPage/cityChange',
addressList: 'addressVue/addressList',
isLogin: 'login/isLogin',
isShowShop: "isShowShop",
iSExistWeixinApp: "login/iSExistWeixinApp",
})
},
onLoad(query) {
if (this.addressList && this.addressList.length > 0) {
this.addressList.sort(this.ArraySort('distance'))
}
this.choisePosition = query.choisePosition
this.districtName = query.districtName || ""
if (query.showMine) {
this.showMine = query.showMine && JSON.parse(query.showMine)
}
this.type = query.type
},
async created() {
try {
this.showLoad()
this.addressListFilter = this.addressList.filter(item => item.detailAddress !== '自提订单:详细地址')
let latitude = this.location.lat
let longitude = this.location.lng
let pois = this.vuexPois && this.vuexPois.length > 0 ? this.vuexPois : (await apiAddress.posToCity({ latitude, longitude, extensions: 1 })).pois
this.nearPois = pois.map(item => ({
id: item.id,
title: item.name,
address: item.address,
location: {
lat: item.location.split(',')[1],
lng: item.location.split(',')[0]
},
distance: getDistance(item.location.split(',')[1],item.location.split(',')[0])
}))
this.nearPois.sort(this.ArraySort('distance'))
} catch (e) {
console.error(e)
this.nearPois = []
errToast(e)
} finally {
this.hideLoad()
}
},
methods: {
...mapMutations({
storeWaybillAd: 'addressVue/storeWaybillAd',
updateMapPick: 'waybill/updateMapPick'
}),
//新增地址
toCreateAddress() {
jumpPage('navigateTo','/pagesAddress/create-address/index')
},
async keywordChange(val) {
try {
this.keyword = val.trim()
if (!this.keyword) return false
this.searchPois = []
let res = await apiAddress.getSuggestion(this.keyword, this.districtName ? this.districtName : this.cityChange)
res.pois.map((item)=>{
this.searchPois.push({
id: item.id,
title: item.name,
address: item.address,
location: {
lat: item.location.split(',')[1],
lng: item.location.split(',')[0]
},
distance:getDistance(item.location.split(',')[1],item.location.split(',')[0])
})
})
this.searchPois.sort(this.ArraySort('distance'))
} catch (e) {
console.error(e)
this.searchPois = []
errToast(e)
}
},
// 跳转到城市选择
toCityPick() {
if(!this.showMine) return toast('禁止选择城市')
jumpPage('navigateTo','/pagesAddress/pick-city/index')
},
// 选择地址
pickAddress(address) {
console.log('选中一个地址信息',address)
if (this.choisePosition) {
// console.log(address)
let reLocation = {}
if (address.hasOwnProperty('location')) {
// 其他地址
reLocation.lat = address.location.lat
reLocation.lng = address.location.lng
reLocation.address = address.title
} else {
// 我的收货地址
reLocation.lat = address.lat
reLocation.lng = address.lng
reLocation.address = address.address
this.storeWaybillAd([address])
}
// 设置重新加载标识
store.commit('indexPage/indexNeedReload', true) // 首页
store.commit('goodPage/changeIsNeedReload', true) // 分类
store.commit('cartPage/cartNeedReload', true) // 购物车
// 存储切换的坐标地址
store.commit('indexPage/saveReLocation', reLocation)
} else {
if (this.type === 'waybill') {
// 处理物流地址选择
this.updateMapPick(address)
} else {
this.$store.commit('addressVue/setCurPickAddress', address)
}
}
uni.navigateBack({ delta: 1 })
},
// 数组排序
ArraySort(property){
return function (a,b){
let value1=a[property]
let value2=b[property]
return value1-value2
}
}
}
}
</script>
<style lang="scss">
@import "./position-address.scss";
</style>

View File

@@ -0,0 +1,77 @@
@import '@/assets/bundle.scss';
.position-address {
.search-address {
box-sizing: border-box;
@extend %flex-center;
@extend %pd20;
height: 100rpx;
// background: #ccc;
border-bottom: 1rpx solid $border2;
}
.left {
flex: none;
margin-right: 10rpx;
}
.right {
flex: auto;
}
.btn-city-pick {
@extend %flex-center;
font-size: 30rpx;
color: $text2;
.icon-arraw-bottom {
@extend %icon-aw-bottom;
@extend %bg-no-repeat-center;
width: 20rpx;
height: 20rpx;
@extend %bg-size-100;
margin-left: 10rpx;
}
}
.near-address, .search-list {
height: calc(100vh - 100rpx);
overflow-y: auto;
}
.add-newAddress{
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 50rpx;
padding: 16rpx 0;
border-bottom: 1rpx solid #EBEEF5;
.add{
font-size: 28rpx;
color: rgb(64, 158, 255);
}
.right-aw{
flex: none;
@extend %icon-aw-right-slim;
width: 50rpx;
height: 30rpx;
background-position: right center;
background-repeat: no-repeat;
background-size: 30rpx;
}
}
.address-label {
@extend %flex-left;
@extend %pd20;
font-size: 28rpx;
color: $text3;
.icon-position, .icon-home {
@extend %bg-no-repeat-center;
@extend %bg-size-100;
width: 30rpx;
height: 30rpx;
flex: none;
margin-right: 10rpx;
}
.icon-position {
@extend %icon-position-small;
}
.icon-home {
@extend %icon-home;
}
}
}

View File

@@ -0,0 +1,12 @@
@import '@/assets/bundle.scss';
.switch-label {
@extend %flex-left;
height: 100rpx;
.label {
width: 240rpx;
flex: none;
font-size: 30rpx;
color: $text1;
}
}

View File

@@ -0,0 +1,53 @@
<template>
<div class="switch-label">
<div class="label">{{label}}</div>
<y-switch
v-model:value="currentValue"
@on-change="onChange"
:size="size"
></y-switch>
</div>
</template>
<script>
import YSwitch from '../Switch/switch'
export default {
components: {
YSwitch
},
props: {
label: {
type: String,
default: ''
},
value: {
type: Boolean,
default: false
},
size: {
type: String,
default: '50'
}
},
data () {
return {
currentValue: this.value
}
},
watch: {
value (val) {
this.currentValue = val
}
},
methods: {
onChange (val) {
this.currentValue = val
this.$emit('update:value',val)
}
}
}
</script>
<style lang="scss">
@import "./switch-label.scss";
</style>