Browse Source
feat(地址管理): 新增收货地址添加页面及相关功能
feat(地址管理): 新增收货地址添加页面及相关功能
refactor(键盘组件): 优化键盘组件逻辑并添加与store的交互 fix(购物车): 修复购物车数量修改逻辑及结算流程 style: 调整多个页面的z-index值及样式细节 perf(验证工具): 优化validates函数返回布尔值的处理逻辑master
18 changed files with 965 additions and 102 deletions
-
205components/keyboard/keyboard copy.vue
-
13components/keyboard/keyboard.vue
-
31libs/api/index.js
-
16libs/utils/index.js
-
0md.md
-
6pages.json
-
106pages/allDish/allDish.vue
-
150pages/deliveryAddress/addDeliveryAddress.scss
-
264pages/deliveryAddress/addDeliveryAddress.vue
-
2pages/deliveryAddress/deliveryAddress.vue
-
10pages/home/home.scss
-
114pages/home/home.vue
-
4pages/login/login.vue
-
8pages/personalInformation/personallInformation.scss
-
2pages/shoppingCart/shoppingCart.scss
-
122pages/shoppingCart/shoppingCart.vue
-
5store/index.js
-
1uni.scss
@ -0,0 +1,205 @@ |
|||
<script setup> |
|||
import { defineEmits, ref } from "vue"; |
|||
|
|||
const emit = defineEmits(["confirm", "cancel"]); |
|||
|
|||
const inputValue = ref(""); |
|||
function onTap(key) { |
|||
if (key === "confirm") { |
|||
// 处理确定按钮点击事件 |
|||
if (inputValue.value === "") { |
|||
uni.showToast({ |
|||
title: "请输入数量", |
|||
icon: "none", |
|||
// duration: 99999999, |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
emit("confirm", Number(inputValue.value)); |
|||
} |
|||
else if (key === "cancel") { |
|||
// 处理取消按钮点击事件 |
|||
emit("cancel"); |
|||
} |
|||
else if (key === "clear") { |
|||
// 处理清空按钮点击事件 |
|||
inputValue.value = ""; |
|||
} |
|||
else if (key === "delete") { |
|||
// 处理删除按钮点击事件 |
|||
inputValue.value = inputValue.value.slice(0, -1); |
|||
} |
|||
else { |
|||
// 处理数字键点击事件 |
|||
const nextValue = inputValue.value + key; |
|||
if (nextValue > 999) { |
|||
uni.showToast({ |
|||
title: "数量不能超过999", |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} |
|||
inputValue.value = `${Number(nextValue)}`; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<view class="keyboard"> |
|||
<view class="keyboard-body"> |
|||
<input class="select-num-top" placeholder="限0~999" disabled :value="inputValue"> |
|||
<view class="key" @tap="() => onTap('1')"> |
|||
1 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('2')"> |
|||
2 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('3')"> |
|||
3 |
|||
</view> |
|||
<view class="key action delete" @tap="() => onTap('delete')"> |
|||
⌫ |
|||
</view> |
|||
|
|||
<view class="key" @tap="() => onTap('4')"> |
|||
4 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('5')"> |
|||
5 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('6')"> |
|||
6 |
|||
</view> |
|||
<view class="key action clear" @tap="() => onTap('clear')"> |
|||
清空 |
|||
</view> |
|||
|
|||
<view class="key" @tap="() => onTap('7')"> |
|||
7 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('8')"> |
|||
8 |
|||
</view> |
|||
<view class="key" @tap="() => onTap('9')"> |
|||
9 |
|||
</view> |
|||
<view class="key action confirm" @tap="() => onTap('confirm')"> |
|||
确定 |
|||
</view> |
|||
|
|||
<view class="key zero span-2" @tap="() => onTap('0')"> |
|||
0 |
|||
</view> |
|||
<view class="key cancel" @tap="() => onTap('cancel')"> |
|||
取消 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.keyboard { |
|||
width: 100vw; |
|||
height: 100%; |
|||
position: fixed; |
|||
left: 0; |
|||
bottom: 0; |
|||
z-index: $z-index-popup; |
|||
background: rgba(0, 0, 0, 0.03); |
|||
} |
|||
|
|||
/* Grid 容器:3列数字 + 1列操作 */ |
|||
.keyboard-body { |
|||
box-sizing: border-box; |
|||
background: #f2f3f5; |
|||
padding: 20rpx 20rpx env(safe-area-inset-bottom) 20rpx; |
|||
left: 0; |
|||
bottom: 0; |
|||
display: grid; |
|||
position: fixed; |
|||
width: 100%; |
|||
grid-template-columns: repeat(3, 1fr) 1fr; /* 四列 */ |
|||
/* 顶部输入占一整行 + 下方4行按键 */ |
|||
grid-template-rows: 100rpx repeat(4, 84rpx); |
|||
gap: 12rpx; |
|||
} |
|||
|
|||
/* 键通用样式 */ |
|||
.key { |
|||
background-color: #fff; |
|||
border-radius: 12rpx; |
|||
// border: 1px solid #d8d8d8; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: $text-4xl; |
|||
font-weight: 430; |
|||
&:active { |
|||
background-color: #e0e2e7; |
|||
} |
|||
} |
|||
|
|||
/* 0 跨两列:第4行的第1-2列 */ |
|||
.span-2 { |
|||
grid-column: 1 / span 2; |
|||
} |
|||
|
|||
/* 取消:第4行第3列 */ |
|||
.cancel { |
|||
grid-column: 3; |
|||
grid-row: 4; |
|||
background-color: #e0e2e7; |
|||
&:active { |
|||
background-color: #fff; |
|||
} |
|||
} |
|||
|
|||
/* 右侧操作列(第4列) */ |
|||
.action { |
|||
background-color: #e0e2e7; |
|||
&:active { |
|||
background-color: #fff; |
|||
} |
|||
} |
|||
|
|||
/* 删除:第1行第4列 */ |
|||
.action.delete { |
|||
grid-column: 4; |
|||
grid-row: 2; |
|||
} |
|||
|
|||
/* 清空:第2行第4列 */ |
|||
.action.clear { |
|||
grid-column: 4; |
|||
grid-row: 3; |
|||
} |
|||
|
|||
/* 确定:第3行开始,跨两行占第3-4行第4列 */ |
|||
.action.confirm { |
|||
grid-column: 4; |
|||
grid-row: 4 / span 2; |
|||
} |
|||
|
|||
/* 取消:第5行第3列 */ |
|||
.cancel { |
|||
grid-column: 3; |
|||
grid-row: 5; |
|||
} |
|||
|
|||
/* 顶部输入:跨4列、位于第1行 */ |
|||
.keyboard .select-num-top { |
|||
height: 100rpx; |
|||
border: none; |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #000; |
|||
padding: 0rpx 10rpx; |
|||
text-align: center; |
|||
border-radius: 12rpx; |
|||
background: #fff; |
|||
/* 关键定位 */ |
|||
grid-column: 1 / -1; |
|||
grid-row: 1; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,150 @@ |
|||
.section{ |
|||
padding:20rpx 30rpx; |
|||
overflow: hidden; |
|||
font-size: 28rpx; |
|||
border-top: solid 1px #e7e7e7; |
|||
position: relative; |
|||
} |
|||
.section input{ |
|||
float:left; |
|||
} |
|||
.section_title{ |
|||
width:130rpx; |
|||
line-height:48rpx; |
|||
float: left; |
|||
} |
|||
.section_addressTxt{ |
|||
float: left; |
|||
padding-left: 8rpx; |
|||
} |
|||
.section_right{ |
|||
overflow: hidden; |
|||
position: relative; |
|||
width:110rpx; |
|||
margin-left:auto; |
|||
} |
|||
.section_right text{ |
|||
float: left; |
|||
margin-right: 10rpx; |
|||
color: #737373 |
|||
} |
|||
.arrow{ |
|||
width: 20rpx; |
|||
height: 20rpx; |
|||
border-top: 4rpx solid #737373; |
|||
border-right: 4rpx solid #737373; |
|||
transform: rotate(45deg); |
|||
float: right; |
|||
position: absolute; |
|||
right: 5rpx; |
|||
margin-top: 10rpx; |
|||
} |
|||
.section textarea{ |
|||
margin-top:10rpx; |
|||
line-height: 40rpx; |
|||
height: 120rpx; |
|||
} |
|||
.section switch{ |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
.btn-area{ |
|||
padding-top: 40rpx; |
|||
width:96%; |
|||
margin: 0 2%; |
|||
} |
|||
.primary{ |
|||
line-height: 80rpx; |
|||
font-size: 28rpx; |
|||
background-color: #3aa24b !important; |
|||
} |
|||
switch{ |
|||
margin-top: 4rpx; |
|||
} |
|||
.wx-switch-input{width:88rpx !important;height:52rpx !important;} |
|||
.wx-switch-input::before{width:86rpx !important;height: 52rpx !important;} |
|||
.wx-switch-input::after{width: 48rpx !important;height: 48rpx !important;} |
|||
.icon-dingwei{ |
|||
position: absolute; |
|||
right: 20rpx; |
|||
top: 50rpx; |
|||
/* padding: 20rpx; */ |
|||
z-index: 9999; |
|||
font-size: 50rpx; |
|||
} |
|||
|
|||
|
|||
|
|||
.content { |
|||
background: #FFFFFF; |
|||
box-shadow: 0rpx 3rpx 14rpx 0rpx rgba(54,77,65,0.1); |
|||
border-radius: 14rpx; |
|||
// margin: 20rpx 14rpx 0; |
|||
box-sizing: border-box; |
|||
padding: 40rpx 28rpx; |
|||
} |
|||
|
|||
.content .item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding-bottom: 35rpx; |
|||
border-bottom: 1px solid #E6E6E6; |
|||
margin-bottom: 35rpx; |
|||
} |
|||
|
|||
.content .item .left { |
|||
width: 30%; |
|||
font-weight: 400; |
|||
font-size: 29rpx; |
|||
color: #333333; |
|||
} |
|||
|
|||
.content .item .right { |
|||
flex: 1; |
|||
margin-left: 20rpx; |
|||
} |
|||
|
|||
.content .item .right .input { |
|||
font-size: 26rpx; |
|||
color: #333333; |
|||
text-align: left; |
|||
} |
|||
|
|||
.content .item .right .select-box{ |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.content .item .right .select-box .text{ |
|||
font-weight: 400; |
|||
font-size: 26rpx; |
|||
color: #999999; |
|||
flex: 1; |
|||
margin-right: 15rpx; |
|||
} |
|||
|
|||
.content .item .right .select-box .icon { |
|||
width: 13rpx; |
|||
height: 26rpx; |
|||
} |
|||
|
|||
.content .item .right .select-box .icon-two { |
|||
width: 29rpx; |
|||
height: 28rpx; |
|||
} |
|||
|
|||
.content .item .right .select-box .select-box-input{ |
|||
flex: 1; |
|||
margin-right: 15rpx; |
|||
color: #333333; |
|||
} |
|||
|
|||
.add-btn{ |
|||
background: #06CA64; |
|||
border-radius: 60rpx; |
|||
margin: 60rpx 34rpx 0; |
|||
font-weight: bold; |
|||
font-size: 33rpx; |
|||
color: #FFFFFF; |
|||
} |
|||
@ -0,0 +1,264 @@ |
|||
<script setup> |
|||
import { onLoad } from "@dcloudio/uni-app"; |
|||
import { ref } from "vue"; |
|||
import navv from "@/components/nav/nav.vue"; |
|||
import topTitle from "@/components/topTitle/topTitle.vue"; |
|||
import { addAddressApi, getAreaInfoApi } from "@/libs/api"; |
|||
import { gotoBack, validates } from "@/libs/utils"; |
|||
|
|||
const cityLists = []; |
|||
|
|||
const address = ref({ |
|||
receiverName: "", |
|||
telephone: "", |
|||
region: "", |
|||
street: "", |
|||
defaultAddr: false, |
|||
}); |
|||
/** |
|||
* 区域选择器索引 |
|||
*/ |
|||
const multiIndex = ref([0, 0, 0]); |
|||
/** |
|||
* 区域选择器数据 |
|||
*/ |
|||
const multiArray = ref([]); |
|||
/** |
|||
* 提交按钮文本 |
|||
*/ |
|||
const buttonTxt = ref(""); |
|||
|
|||
/** |
|||
* 多级选择器值改变事件 |
|||
*/ |
|||
function bindMultiPickerChange(e) { |
|||
const { value } = e.detail; |
|||
const info = multiArray.value; |
|||
// eslint-disable-next-line max-len |
|||
address.value.region = `${info[0][value[0]].address} ${info[1][value[1]].address} ${info[2][value[2]].address}`; |
|||
address.value.city = info[0][value[0]].address; |
|||
address.value.cityId = info[0][value[0]].id; |
|||
address.value.county = info[1][value[1]].address; |
|||
address.value.countyId = info[1][value[1]].id; |
|||
address.value.town = info[2][value[2]].address; |
|||
address.value.townId = info[2][value[2]].id; |
|||
} |
|||
|
|||
/** |
|||
* 多级选择器列改变事件 |
|||
*/ |
|||
function bindMultiPickerColumnChange(e) { |
|||
const { column, value } = e.detail; |
|||
multiIndex.value[column] = value; |
|||
if (column == 0) { |
|||
multiArray.value[1] = cityLists[value].children; |
|||
multiArray.value[2] = cityLists[value].children[0].children; |
|||
multiIndex.value[1] = 0; |
|||
multiIndex.value[2] = 0; |
|||
} |
|||
else if (column == 1) { |
|||
multiArray.value = multiArray.value[1][value].children; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 选择地图位置事件 |
|||
*/ |
|||
function map() { |
|||
uni.chooseLocation({ |
|||
success(res) { |
|||
console.log(res); |
|||
address.value.street = res.name; |
|||
address.value.map = `${res.longitude},${res.latitude}`; |
|||
}, |
|||
fail() { |
|||
// fail |
|||
}, |
|||
complete() { |
|||
// complete |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
// 开关切换事件 |
|||
// function switchChange() {} |
|||
|
|||
/** |
|||
* 提交按钮点击事件 |
|||
*/ |
|||
async function submit() { |
|||
uni.showLoading({ |
|||
title: "提交中....", |
|||
mask: true, |
|||
}); |
|||
const msg = validates([ |
|||
() => address.value.receiverName == "" && "收货人不能为空", |
|||
() => address.value.telephone == "" && "联系电话不能为空", |
|||
() => address.value.region == "" && "所在区域不能为空", |
|||
() => address.value.street == "" && "街道地址信息不能为空", |
|||
], { returnBoolean: false }); |
|||
if (msg.length) { |
|||
uni.hideLoading(); |
|||
uni.showToast({ |
|||
title: msg, |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} |
|||
if (buttonTxt.value == "确认添加") { |
|||
const res = await addAddressApi(address.value); |
|||
uni.hideLoading(); |
|||
if (res.code !== "0") { |
|||
return; |
|||
} |
|||
gotoBack(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取城市列表 |
|||
*/ |
|||
async function getCityList() { |
|||
const res = await getAreaInfoApi(); |
|||
if (res.code !== "0") { |
|||
return; |
|||
} |
|||
cityLists.push(...res.data); |
|||
multiArray.value = [cityLists, cityLists[0].children, cityLists[0].children[0].children]; |
|||
} |
|||
|
|||
onLoad((options) => { |
|||
getCityList(); |
|||
if (options.key == "edit") { |
|||
uni.setNavigationBarTitle({ |
|||
title: "修改收货地址", |
|||
}); |
|||
buttonTxt.value = "确认修改"; |
|||
address.value = JSON.parse(options.address); |
|||
} |
|||
else { |
|||
uni.setNavigationBarTitle({ |
|||
title: "添加收货地址", |
|||
}); |
|||
buttonTxt.value = "确认添加"; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<navv> |
|||
<template #default="{ style, content }"> |
|||
<topTitle title="添加收货地址" :style="style" /> |
|||
<view |
|||
class="content" |
|||
:style="{ |
|||
height: `${content}px`, |
|||
overflowY: 'auto', |
|||
}" |
|||
> |
|||
<view class="item"> |
|||
<view class="left"> |
|||
收货人 |
|||
</view> |
|||
<view class="right"> |
|||
<input |
|||
v-model="address.receiverName" |
|||
class="input" |
|||
type="text" |
|||
name="name" |
|||
placeholder-style="color:#999999" |
|||
placeholder="收货人姓名" |
|||
> |
|||
</view> |
|||
</view> |
|||
<view class="item"> |
|||
<view class="left"> |
|||
手机号 |
|||
</view> |
|||
<view class="right"> |
|||
<input |
|||
v-model="address.telephone" |
|||
class="input" |
|||
name="tel" |
|||
type="number" |
|||
data-item="telephone" |
|||
placeholder="收货人手机号" |
|||
placeholder-style="color:#999999" |
|||
> |
|||
</view> |
|||
</view> |
|||
<view class="item"> |
|||
<view class="left"> |
|||
所在地址 |
|||
</view> |
|||
<view class="right"> |
|||
<view class="select-box"> |
|||
<picker |
|||
v-model="multiIndex" |
|||
style="flex: 1;" |
|||
mode="multiSelector" |
|||
:range="multiArray" |
|||
range-key="address" |
|||
@change="bindMultiPickerChange" |
|||
@columnchange="bindMultiPickerColumnChange" |
|||
> |
|||
<text v-if="!address.region" class="text"> |
|||
请选择 |
|||
</text> |
|||
<text v-else class="text" style="color: #333;"> |
|||
{{ address.region }} |
|||
</text> |
|||
</picker> |
|||
<text class="iconfont icon-right" /> |
|||
<!-- <image class="icon" src="../../../images/myinfo/arrow.png" mode="" /> --> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="item"> |
|||
<view class="left"> |
|||
详细地址 |
|||
</view> |
|||
<view class="right"> |
|||
<view class="select-box"> |
|||
<input |
|||
v-model="address.street" |
|||
class="select-box-input" |
|||
type="text" |
|||
placeholder="点击定位图标,选择详细地址" |
|||
placeholder-style="color:#999999" |
|||
data-item="street" |
|||
> |
|||
<text class="iconfont icon-right" @tap="map" /> |
|||
<!-- <image |
|||
class="icon-two" |
|||
src="../../../images/nearby/addr.png" |
|||
mode="" |
|||
@tap="map" |
|||
/> --> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="item"> |
|||
<view class="left"> |
|||
设为默认地址 |
|||
</view> |
|||
<view class="right" style="text-align: right;"> |
|||
<switch |
|||
:checked="address.defaultAddr" |
|||
style="font-size: 28rpx;" |
|||
color="#06CA64" |
|||
@change="(e) => address.defaultAddr = e.detail.value" |
|||
/> |
|||
</view> |
|||
</view> |
|||
<button form-type="submit" class="add-btn" @tap="submit"> |
|||
{{ buttonTxt }} |
|||
</button> |
|||
</view> |
|||
</template> |
|||
</navv> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
@import './addDeliveryAddress.scss'; |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue