mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-11-12 20:08:53 +08:00
feat(wechat): 集成微信原生地址导入、头像选择与昵称填写能力
- 新增微信收货地址导入功能(chooseAddress),含地区匹配与错误处理 - 个人资料页接入微信头像选择(chooseAvatar)与昵称填写(type="nickname") - 重构收货地址添加页与管理页的UI布局,抽取公共样式 address-page.scss - 地址页与个人页增加加载失败提示、请求防抖与保存状态反馈 - 下拉刷新改用 Promise.allSettled 避免单接口失败阻断其他数据更新 - manifest 新增 chooseAddress 隐私权限声明,版本号更新至 5.03
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
<template>
|
||||
<view :style="themeStyle" class="add-address">
|
||||
<view :style="themeStyle" class="address-page add-address">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="wechat-import">
|
||||
<button class="wechat-import-button" :disabled="importing" @click="importWechatAddress">
|
||||
<view class="wechat-icon"><u-icon name="weixin-fill" color="#07b75b" size="26" /></view>
|
||||
<view class="wechat-copy">
|
||||
<text class="wechat-title">{{ importing ? '正在导入微信地址…' : '导入微信收货地址' }}</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#77a38a" size="14" />
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view v-if="importNotice" class="import-notice">{{ importNotice }}</view>
|
||||
<view class="section-heading">收货信息</view>
|
||||
<view class="address-card form-card">
|
||||
<up-form
|
||||
:model="form"
|
||||
ref="uForm"
|
||||
@@ -7,30 +21,31 @@
|
||||
:rules="rules"
|
||||
label-position="left"
|
||||
label-width="180rpx"
|
||||
:label-style="{ color: '#5b606b', fontSize: '28rpx' }"
|
||||
>
|
||||
<up-form-item label="收货人" label-width="180rpx" prop="name" :border-bottom="true">
|
||||
<up-form-item label="收货人" label-width="160rpx" prop="name" :border-bottom="true" :custom-style="fieldStyle">
|
||||
<u-input
|
||||
v-model="form.name"
|
||||
border="none"
|
||||
input-align="right"
|
||||
input-align="left"
|
||||
clearable
|
||||
placeholder="请输入收货人姓名"
|
||||
placeholder="姓名"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="手机号码" label-width="180rpx" prop="mobile" :border-bottom="true">
|
||||
<up-form-item label="手机号码" label-width="160rpx" prop="mobile" :border-bottom="true" :custom-style="fieldStyle">
|
||||
<u-input
|
||||
v-model="form.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入收货人手机号码"
|
||||
input-align="left"
|
||||
placeholder="收货人的手机号码"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="所在区域" label-width="180rpx" prop="___path" :border-bottom="true">
|
||||
<view class="form-value" @click="showPicker">
|
||||
<up-form-item label="所在地区" label-width="160rpx" prop="___path" :border-bottom="true" :custom-style="fieldStyle" @click="showPicker">
|
||||
<view class="form-value" :class="{ 'is-placeholder': !form.___path }">
|
||||
{{ form.___path || '请选择所在地区' }}
|
||||
</view>
|
||||
<template #right>
|
||||
@@ -38,40 +53,38 @@
|
||||
</template>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item class="detailAddress" label="详细地址" label-width="180rpx" prop="detail" :border-bottom="true">
|
||||
<u-input
|
||||
type="textarea"
|
||||
<up-form-item label="详细地址" label-width="160rpx" prop="detail" :border-bottom="true" :custom-style="{ ...fieldStyle, alignItems: 'flex-start' }">
|
||||
<textarea
|
||||
class="address-textarea"
|
||||
v-model="form.detail"
|
||||
maxlength="100"
|
||||
height="150"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="街道楼牌号等"
|
||||
:auto-height="true"
|
||||
placeholder="街道、门牌号等详细信息"
|
||||
placeholder-style="color: #b1b5bd; font-size: 28rpx;"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="地址别名" label-width="180rpx" :border-bottom="false">
|
||||
<up-form-item label="地址标签" label-width="160rpx" :border-bottom="false" :custom-style="fieldStyle">
|
||||
<u-input
|
||||
v-model="form.alias"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入地址别名"
|
||||
input-align="left"
|
||||
placeholder="如:家、公司(选填)"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<view class="default-row">
|
||||
<u-checkbox
|
||||
usedAlone
|
||||
shape="circle"
|
||||
size="30"
|
||||
:active-color="lightColor"
|
||||
v-model:checked="form.isDefault"
|
||||
label="设为默认地址"
|
||||
></u-checkbox>
|
||||
</view>
|
||||
|
||||
<view class="saveBtn" @click="save">保存</view>
|
||||
</up-form>
|
||||
</view>
|
||||
<view class="address-card default-row">
|
||||
<view>
|
||||
<text class="default-title">设为默认地址</text>
|
||||
<text class="default-hint">下单时优先使用这个地址</text>
|
||||
</view>
|
||||
<switch :checked="!!form.isDefault" :color="mainColor" @change="form.isDefault = $event.detail.value" />
|
||||
</view>
|
||||
<view class="address-footer">
|
||||
<button class="address-primary-button" :disabled="importing" @click="save">保存收货地址</button>
|
||||
</view>
|
||||
|
||||
<m-city
|
||||
:provinceData="list"
|
||||
@@ -93,16 +106,114 @@ import MCity from '@/components/m-city/m-city.vue'
|
||||
import uniMap from '@/components/uniMap'
|
||||
import permision from '@/js_sdk/wa-permission/permission.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import { getRegionsById } from '@/api/common.js'
|
||||
import { resolveWechatRegion, getWechatAddressErrorType } from '@/utils/wechatAddress.js'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
const mainColor = computed(() => store.getters.mainColor)
|
||||
const fieldStyle = { padding: '28rpx 0', minHeight: '100rpx', boxSizing: 'border-box', alignItems: 'center' }
|
||||
const mapFlag = ref(false)
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const uForm = ref<any>(null)
|
||||
const cityPicker = ref<any>(null)
|
||||
const importing = ref(false)
|
||||
const importNotice = ref('')
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
function importWechatAddress() {
|
||||
if (importing.value) return
|
||||
importing.value = true
|
||||
uni.chooseAddress({
|
||||
success: async (address: any) => {
|
||||
uni.showLoading({ title: '正在匹配地区', mask: true })
|
||||
// Clear the old region and coordinates, including when editing an existing address.
|
||||
Object.assign(form, {
|
||||
name: address.userName || '',
|
||||
mobile: address.telNumber || '',
|
||||
detail: address.detailInfoNew || address.detailInfo || '',
|
||||
consigneeAddressIdPath: [],
|
||||
consigneeAddressPath: [],
|
||||
___path: '',
|
||||
lat: '',
|
||||
lon: '',
|
||||
})
|
||||
importNotice.value = ''
|
||||
try {
|
||||
const regions = await resolveWechatRegion(address, getRegionsById)
|
||||
if (regions) {
|
||||
if (regions.length < 4 && address.streetName) {
|
||||
form.detail = address.detailInfo || [address.streetName, address.detailInfoNew].filter(Boolean).join('')
|
||||
}
|
||||
const names = regions.map((item: any) => item.name || item.localName)
|
||||
form.consigneeAddressIdPath = regions.map((item: any) => item.id)
|
||||
form.consigneeAddressPath = names
|
||||
form.___path = names.join('')
|
||||
const center = String(regions[regions.length - 1].center || '').split(',')
|
||||
if (center.length === 2) [form.lon, form.lat] = center
|
||||
importNotice.value = '已导入微信地址,请核对后保存'
|
||||
} else {
|
||||
showImportedRegionHint(address)
|
||||
}
|
||||
} catch {
|
||||
showImportedRegionHint(address)
|
||||
} finally {
|
||||
importing.value = false
|
||||
uni.hideLoading()
|
||||
uForm.value?.clearValidate()
|
||||
}
|
||||
},
|
||||
fail: (error: any) => {
|
||||
importing.value = false
|
||||
const type = getWechatAddressErrorType(error)
|
||||
if (type === 'cancel') return
|
||||
let platform = ''
|
||||
try { platform = (globalThis as any).wx?.getDeviceInfo?.()?.platform || '' } catch {}
|
||||
// Log diagnostics only, never the selected address or other personal information.
|
||||
console.warn('[微信地址导入失败]', {
|
||||
errMsg: String(error?.errMsg || error?.message || ''),
|
||||
errCode: error?.errCode ?? error?.errno ?? '',
|
||||
type,
|
||||
platform,
|
||||
})
|
||||
if (type === 'auth') {
|
||||
uni.showModal({
|
||||
title: '开启收货地址授权',
|
||||
content: '需要允许使用微信收货地址才能导入,也可以继续手动填写。',
|
||||
confirmText: '去设置',
|
||||
success: (result) => { if (result.confirm) uni.openSetting({}) },
|
||||
})
|
||||
return
|
||||
}
|
||||
if (type === 'privacy') {
|
||||
console.warn('[微信地址导入] 请核对小程序后台的用户隐私保护指引是否声明收货地址,以及用户隐私授权状态。')
|
||||
}
|
||||
if (type === 'configuration') {
|
||||
console.warn('[微信地址导入] manifest.json 的 mp-weixin.requiredPrivateInfos 必须包含 chooseAddress,修改后需重新编译小程序。')
|
||||
}
|
||||
const content = type === 'configuration'
|
||||
? '收货地址导入暂未配置完成,请稍后再试,也可以手动填写。'
|
||||
: type === 'privacy'
|
||||
? '暂时无法获取微信收货地址,请稍后重试,也可以手动填写。'
|
||||
: platform === 'devtools'
|
||||
? '当前开发工具中未能打开微信地址簿,请使用手机微信扫码预览测试,也可以手动填写。'
|
||||
: type === 'unsupported'
|
||||
? '当前微信环境不支持导入收货地址,请更新微信后重试,也可以手动填写。'
|
||||
: '暂时无法打开微信地址簿,请稍后重试,也可以手动填写。'
|
||||
uni.showModal({ title: '微信地址未导入', content, showCancel: false })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function showImportedRegionHint(address: any) {
|
||||
const region = [address.provinceName, address.cityName, address.countyName, address.streetName].filter(Boolean).join('')
|
||||
// Preserve the street if the user has to select it manually and the new detail field omits it.
|
||||
form.detail = address.detailInfo || [address.streetName, address.detailInfoNew].filter(Boolean).join('')
|
||||
importNotice.value = `已导入姓名、电话和详细地址,请补选所在地区${region ? '(微信地址:' + region + ')' : ''}`
|
||||
}
|
||||
// #endif
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
detail: '',
|
||||
@@ -160,18 +271,24 @@ const rules = {
|
||||
|
||||
onShow(() => {})
|
||||
|
||||
onLoad((option) => {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
onLoad(async (option) => {
|
||||
routerVal.value = option || {}
|
||||
if (option.id) {
|
||||
getAddressDetail(option.id).then((res) => {
|
||||
const params = res.data.result
|
||||
params.___path = params.consigneeAddressPath
|
||||
Object.assign(form, params)
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
})
|
||||
uni.setNavigationBarTitle({ title: option?.id ? '编辑收货地址' : '新增收货地址' })
|
||||
if (!option?.id) return
|
||||
uni.showLoading({ title: '加载中' })
|
||||
try {
|
||||
const res = await getAddressDetail(option.id)
|
||||
const params = res.data?.result
|
||||
if (!res.data?.success || !params) {
|
||||
uni.showToast({ title: '地址加载失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
Object.assign(form, params, { ___path: params.consigneeAddressPath || '' })
|
||||
} catch {
|
||||
uni.showToast({ title: '地址加载失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
onReady(() => {
|
||||
@@ -233,6 +350,7 @@ function callBackAddress(val: any) {
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (importing.value) return
|
||||
uForm.value?.validate().then(() => {
|
||||
const params = { ...form }
|
||||
delete params.___path
|
||||
@@ -278,11 +396,12 @@ function getpickerParentValue(e: any[]) {
|
||||
form.___path = name
|
||||
}
|
||||
if (index == e.length - 1) {
|
||||
const _town = item.children.filter((_child: any) => _child.id == item.id)
|
||||
form.lat = _town[0].center.split(',')[1]
|
||||
form.lon = _town[0].center.split(',')[0]
|
||||
const center = String(item.center || item.children?.find((_child: any) => _child.id == item.id)?.center || '').split(',')
|
||||
form.lat = center.length === 2 ? center[1] : ''
|
||||
form.lon = center.length === 2 ? center[0] : ''
|
||||
}
|
||||
})
|
||||
importNotice.value = ''
|
||||
}
|
||||
|
||||
function showPicker() {
|
||||
@@ -290,99 +409,25 @@ function showPicker() {
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background: #f5f5f5;
|
||||
@import "./address-page.scss";
|
||||
.wechat-import { margin-bottom: 28rpx; }
|
||||
.wechat-import-button {
|
||||
display: flex; align-items: center; padding: 28rpx; margin: 0; width: 100%; text-align: left;
|
||||
background: #eef9f2; border-radius: 24rpx; line-height: 1.5;
|
||||
&::after { border: 1rpx solid #dcefe3; border-radius: 24rpx; }
|
||||
&[disabled] { background: #eef9f2; opacity: .65; }
|
||||
}
|
||||
|
||||
.add-address {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item) {
|
||||
padding: 0 30rpx;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body) {
|
||||
min-height: 100rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__left__content__label) {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__right__content__slot) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.u-input) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.u-input__content__field-wrapper__field) {
|
||||
font-size: 28rpx !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.form-value {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detailAddress {
|
||||
:deep(.u-form-item__body) {
|
||||
align-items: flex-start;
|
||||
padding-top: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__left) {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__right__content__slot) {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
:deep(.u-textarea__field),
|
||||
:deep(.u-input__content__field-wrapper__field) {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
:deep(.uni-textarea-placeholder),
|
||||
:deep(.input-placeholder) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.default-row {
|
||||
padding: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
:deep(.default-row .u-checkbox__label) {
|
||||
font-size: 28rpx;
|
||||
color: $font-color-light;
|
||||
}
|
||||
|
||||
.saveBtn {
|
||||
margin: 40rpx 30rpx 0;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
</style>
|
||||
.wechat-icon { width: 76rpx; height: 76rpx; border-radius: 20rpx; background: #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-right: 22rpx; }
|
||||
.wechat-copy { flex: 1; min-width: 0; }
|
||||
.wechat-title { display: block; color: #253e30; font-size: 30rpx; font-weight: 600; }
|
||||
.import-notice { margin-bottom: 24rpx; padding: 20rpx 24rpx; border-radius: 16rpx; background: #fff5e5; color: #a47a3b; font-size: 24rpx; line-height: 1.6; }
|
||||
.section-heading { padding: 0 8rpx 20rpx; font-size: 26rpx; color: #858b96; }
|
||||
.form-card { padding: 0 28rpx; }
|
||||
.form-value { width: 100%; line-height: 1.6; font-size: 28rpx; color: #303744; overflow-wrap: anywhere; }
|
||||
.is-placeholder { color: #b1b5bd; }
|
||||
.address-textarea { width: 100%; min-height: 88rpx; font-size: 28rpx; color: #303744; line-height: 1.6; padding: 0; }
|
||||
.default-row { display: flex; justify-content: space-between; align-items: center; margin-top: 24rpx; padding: 28rpx; }
|
||||
.default-title { display: block; font-size: 28rpx; color: #353b47; }
|
||||
.default-hint { display: block; font-size: 23rpx; color: #a0a5af; margin-top: 8rpx; }
|
||||
.default-row switch { transform: scale(.8); transform-origin: right center; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user