feat: 收货地址

This commit is contained in:
jqtmviyu 2025-05-04 17:03:15 +08:00
parent 10fd177322
commit 7009df90c7
9 changed files with 765 additions and 359 deletions

View File

@ -43,40 +43,41 @@
]
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4050720250324001",
"@dcloudio/uni-app-harmony": "3.0.0-4050720250324001",
"@dcloudio/uni-app-plus": "3.0.0-4050720250324001",
"@dcloudio/uni-components": "3.0.0-4050720250324001",
"@dcloudio/uni-h5": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-alipay": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-baidu": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-jd": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-lark": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-qq": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
"@dcloudio/uni-app": "3.0.0-4060420250429001",
"@dcloudio/uni-app-harmony": "3.0.0-4060420250429001",
"@dcloudio/uni-app-plus": "3.0.0-4060420250429001",
"@dcloudio/uni-components": "3.0.0-4060420250429001",
"@dcloudio/uni-h5": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-alipay": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-baidu": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-harmony": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-jd": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-lark": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-qq": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-weixin": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-xhs": "3.0.0-4060420250429001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4060420250429001",
"@dcloudio/uni-ui": "^1.5.7",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^3.2.3",
"vue": "^3.5.13",
"vue-i18n": "^9.14.4"
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@dcloudio/types": "^3.4.14",
"@dcloudio/uni-automator": "3.0.0-4050720250324001",
"@dcloudio/uni-cli-shared": "3.0.0-4050720250324001",
"@dcloudio/uni-stacktracey": "3.0.0-4050720250324001",
"@dcloudio/vite-plugin-uni": "3.0.0-4050720250324001",
"@dcloudio/types": "^3.4.8",
"@dcloudio/uni-automator": "3.0.0-4060420250429001",
"@dcloudio/uni-cli-shared": "3.0.0-4060420250429001",
"@dcloudio/uni-stacktracey": "3.0.0-4060420250429001",
"@dcloudio/vite-plugin-uni": "3.0.0-4060420250429001",
"@rushstack/eslint-patch": "^1.11.0",
"@types/node": "^22.14.1",
"@uni-helper/uni-app-types": "1.0.0-alpha.6",
"@uni-helper/uni-ui-types": "1.0.0-alpha.6",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/runtime-core": "^3.5.13",
"@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.7.0",
"eslint": "^8.22.0",
"eslint-plugin-prettier": "^5.1.3",

594
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -123,6 +123,18 @@
"navigationBarTextStyle": "white",
"navigationBarTitleText": "个人信息"
}
},
{
"path": "address/address",
"style": {
"navigationBarTitleText": "地址管理"
}
},
{
"path": "address/address-form",
"style": {
"navigationBarTitleText": "修改地址"
}
}
]
}

View File

@ -0,0 +1,251 @@
<template>
<view class="content">
<uni-forms ref="formRef" :modelValue="formData" :rules="rules">
<!-- 表单内容 -->
<uni-forms-item class="form-item" label="收货人" name="receiver">
<input class="input" placeholder="请填写收货人姓名" v-model="formData.receiver" />
</uni-forms-item>
<uni-forms-item class="form-item" label="手机号码" name="contact">
<input
class="input"
placeholder="请填写收货人手机号码"
type="tel"
v-model="formData.contact"
/>
</uni-forms-item>
<uni-forms-item class="form-item" label="所在地区" name="fullLocation">
<picker
class="picker"
mode="region"
:value="formData.fullLocation?.split(' ') || []"
@change="onRegionPickerChange"
>
<view v-if="formData.fullLocation">{{ formData.fullLocation }}</view>
<view v-else class="placeholder">请选择省//()</view>
</picker>
</uni-forms-item>
<uni-forms-item class="form-item" label="详细地址" name="address">
<input class="input" placeholder="街道、楼牌号等信息" v-model="formData.address" />
</uni-forms-item>
<uni-forms-item class="form-item" label="设为默认地址" name="isDefault">
<switch
class="switch"
color="#27ba9b"
:checked="formData.isDefault === 1"
@change="onSwitchChange"
/>
</uni-forms-item>
</uni-forms>
</view>
<!-- 提交按钮 -->
<button class="button" @tap="handleSave">保存并使用</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getAddressDetailAPI, postMemberAddressAPI, putMemberAddressAPI } from '@/services/address'
import type { AddressParams } from '@/types/address'
import { onLoad } from '@dcloudio/uni-app'
import type { UniFormsRules } from '@/types/uni-helper'
//
const formData = ref<AddressParams & { fullLocation: string }>({
receiver: '', //
contact: '', //
fullLocation: '', // ()
provinceCode: '', // ()
cityCode: '', // ()
countyCode: '', // /()
address: '', //
isDefault: 0, // 10
})
const formRef = ref<UniHelper.UniFormsInstance>()
//
const rules: UniFormsRules = {
receiver: {
rules: [
{ required: true, errorMessage: '请输入收货人姓名' },
{
minLength: 2,
maxLength: 5,
errorMessage: '姓名长度在 {minLength} 到 {maxLength} 个字符',
},
],
},
contact: {
rules: [
{ required: true, errorMessage: '请输入联系电话' },
{
pattern: /^1[3-9]\d{9}$/,
errorMessage: '手机号码为11位数字',
},
],
},
fullLocation: {
rules: [{ required: true, errorMessage: '请选择省市区' }],
},
address: {
rules: [
{ required: true, errorMessage: '请输入详细地址' },
{
minLength: 2,
maxLength: 50,
errorMessage: '详细地址长度在 {minLength} 到 {maxLength} 个字符',
},
],
},
}
const query = defineProps<{
id?: string
}>()
//
uni.setNavigationBarTitle({ title: query.id ? '修改地址' : '新增地址' })
//
const getAddressDetail = async () => {
const res = await getAddressDetailAPI(query.id!)
if (res.code === '1') {
Object.assign(formData.value, res.result)
}
}
onLoad(() => {
if (query.id) {
getAddressDetail()
}
})
//
const onRegionPickerChange = (e: UniHelper.PickerViewOnPickendEvent) => {
formData.value.fullLocation = e.detail.value.join(' ')
formData.value.provinceCode = e.detail.code[0]
formData.value.cityCode = e.detail.code[1]
formData.value.countyCode = e.detail.code[2]
}
//
const onSwitchChange = (e: UniHelper.SwitchOnChangeEvent) => {
formData.value.isDefault = e.detail.value ? 1 : 0
}
//
const handleSave = async () => {
try {
const data = {
receiver: formData.value.receiver,
contact: formData.value.contact,
provinceCode: formData.value.provinceCode,
cityCode: formData.value.cityCode,
countyCode: formData.value.countyCode,
address: formData.value.address,
isDefault: formData.value.isDefault,
}
await formRef.value?.validate!()
let res = null
if (query.id) {
res = await putMemberAddressAPI(query.id, data)
} else {
res = await postMemberAddressAPI(data)
}
if (res.code === '1') {
uni.showToast({
title: query.id ? '修改地址成功' : '保存地址成功',
icon: 'success',
success: () => {
setTimeout(() => {
uni.navigateBack()
}, 1000)
},
})
} else {
const errorMsg = res.msg
uni.showToast({
title: errorMsg,
icon: 'error',
})
}
} catch (error) {
uni.showToast({
title: '表单验证失败',
icon: 'error',
})
}
}
</script>
<style lang="scss">
page {
background-color: #f4f4f4;
}
.content {
margin: 20rpx 20rpx 0;
padding: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
.form-item,
.uni-forms-item {
display: flex;
align-items: center;
min-height: 96rpx;
padding: 25rpx 10rpx 40rpx;
background-color: #fff;
font-size: 28rpx;
border-bottom: 1rpx solid #ddd;
position: relative;
margin-bottom: 0;
// uni-forms
.uni-forms-item__content {
display: flex;
}
.uni-forms-item__error {
margin-left: 200rpx;
}
&:last-child {
border: none;
}
.label {
width: 200rpx;
color: #333;
}
.input {
flex: 1;
display: block;
height: 46rpx;
}
.switch {
position: absolute;
right: -20rpx;
transform: scale(0.8);
}
.picker {
flex: 1;
}
.placeholder {
color: #808080;
}
}
}
.button {
height: 80rpx;
margin: 30rpx 20rpx;
color: #fff;
border-radius: 80rpx;
font-size: 30rpx;
background-color: #27ba9b;
}
</style>

View File

@ -2,45 +2,32 @@
<view class="viewport">
<!-- 地址列表 -->
<scroll-view class="scroll-view" scroll-y>
<view v-if="true" class="address">
<view class="address-list">
<!-- 收货地址项 -->
<view class="item">
<view class="item-content">
<view v-if="addressList.length > 0" class="address">
<!-- 收货地址项 -->
<uni-swipe-action class="item" v-for="item in addressList" :key="item.id">
<uni-swipe-action-item class="item-content" :text="item.receiver">
<view class="left">
<view class="user">
黑马小王子
<text class="contact">13111111111</text>
<text v-if="true" class="badge">默认</text>
{{ item.receiver }}
<text class="contact">{{ item.contact }}</text>
<text v-if="item.isDefault === 1" class="badge">默认</text>
</view>
<view class="locate">广东省 广州市 天河区 黑马程序员</view>
<navigator
class="edit"
hover-class="none"
:url="`/pagesMember/address/address-form?id=1`"
>
修改
</navigator>
<view class="locate">{{ item.fullLocation }} {{ item.address }}</view>
</view>
</view>
<!-- 收货地址项 -->
<view class="item">
<view class="item-content">
<view class="user">
黑马小公主
<text class="contact">13222222222</text>
<text v-if="false" class="badge">默认</text>
</view>
<view class="locate">北京市 北京市 顺义区 黑马程序员</view>
<navigator
class="edit"
hover-class="none"
:url="`/pagesMember/address/address-form?id=2`"
>
修改
</navigator>
</view>
</view>
</view>
<navigator
class="edit right"
hover-class="none"
:url="`/pagesMember/address/address-form?id=${item.id}`"
>
修改
</navigator>
<!-- 删除按钮 -->
<template #right>
<button class="delete-button" @tap="handleDelete(item.id)">删除</button>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
<!-- 收货地址项 -->
</view>
<view v-else class="blank">暂无收货地址</view>
</scroll-view>
@ -52,7 +39,39 @@
</template>
<script setup lang="ts">
//
import { deleteMemberAddressAPI, getMemberAddressAPI } from '@/services/address'
import type { AddressItem } from '@/types/address'
import { onShow } from '@dcloudio/uni-app'
import { ref } from 'vue'
const addressList = ref<AddressItem[]>([])
//
const getAddressList = async () => {
const res = await getMemberAddressAPI()
if (res.code === '1') {
addressList.value = res.result
}
}
onShow(() => {
getAddressList()
})
const handleDelete = (id: string) => {
uni.showModal({
content: '确定要删除该地址吗?',
success: async (res) => {
if (res.confirm) {
const response = await deleteMemberAddressAPI(id)
console.log(response)
if (response.code === '1') {
getAddressList()
}
}
},
})
}
</script>
<style lang="scss">
@ -93,15 +112,18 @@ page {
background-color: #fff;
.item-content {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 1;
padding: 40rpx 10rpx 38rpx;
border-bottom: 1rpx solid #ddd;
position: relative;
.edit {
position: absolute;
top: 36rpx;
right: 30rpx;
.left {
flex: 1;
}
.right {
padding: 2rpx 0 2rpx 20rpx;
border-left: 1rpx solid #666;
font-size: 26rpx;

View File

@ -56,10 +56,10 @@
<picker
class="picker"
mode="region"
:value="profile?.fullLocation?.split(' ')"
:value="profile?.fullLocation?.split(' ') || []"
@change="onRegionPickerChange"
>
<view v-if="true">{{ profile?.fullLocation }}</view>
<view v-if="profile?.fullLocation">{{ profile?.fullLocation }}</view>
<view class="placeholder" v-else>请选择城市</view>
</picker>
</view>
@ -88,6 +88,9 @@ const { safeAreaInsets } = uni.getSystemInfoSync()
//
const profile = ref({} as ProfileDetail)
const provinceCode = ref('')
const cityCode = ref('')
const countyCode = ref('')
//
const getProfile = async () => {
@ -146,14 +149,16 @@ const onDatePickerChange: UniHelper.DatePickerOnChange = (e) => {
}
//
const onRegionPickerChange: UniHelper.RegionPickerOnChange = (e) => {
let fullLocationCode: [string, string, string] = ['', '', '']
const onRegionPickerChange = (e: UniHelper.PickerViewOnPickendEvent) => {
profile.value.fullLocation = e.detail.value.join(' ')
fullLocationCode = e.detail.code
}
//
const handleSave = async () => {
const { nickname, gender, birthday, fullLocation, profession } = profile.value
const [provinceCode, cityCode, countyCode] = fullLocation!.split(' ')
const { nickname, gender, birthday, profession } = profile.value
const [provinceCode, cityCode, countyCode] = fullLocationCode
const res = await putProfileAPI({
nickname,
gender,

61
src/services/address.ts Normal file
View File

@ -0,0 +1,61 @@
import { http } from '@/utils/http'
import type { AddressItem, AddressParams } from '@/types/address'
type EditAddressSuccessParams = { id: string }
/**
*
* @param data -
*/
export const postMemberAddressAPI = (data: AddressParams) => {
return http<EditAddressSuccessParams>({
url: '/member/address',
method: 'POST',
data,
})
}
/**
*
*/
export const getMemberAddressAPI = () => {
return http<AddressItem[]>({
url: '/member/address',
method: 'GET',
})
}
/**
*
* @param id - ID
*/
export const getAddressDetailAPI = (id: string) => {
return http<AddressItem>({
url: `/member/address/${id}`,
method: 'GET',
})
}
/**
*
* @param id - ID
* @param data -
*/
export const putMemberAddressAPI = (id: string, data: AddressParams) => {
return http<EditAddressSuccessParams>({
url: `/member/address/${id}`,
method: 'PUT',
data,
})
}
/**
*
* @param id - ID
*/
export const deleteMemberAddressAPI = (id: string) => {
return http<EditAddressSuccessParams>({
url: `/member/address/${id}`,
method: 'DELETE',
})
}

25
src/types/address.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
/** 添加收货地址: 请求参数 */
export type AddressParams = {
/** 收货人姓名 */
receiver: string
/** 联系方式 */
contact: string
/** 省份编码 */
provinceCode: string
/** 城市编码 */
cityCode: string
/** 区/县编码 */
countyCode: string
/** 详细地址 */
address: string
/** 默认地址1为是0为否 */
isDefault: 0 | 1
}
/** 收货地址项 */
export type AddressItem = AddressParams & {
/** 收货地址 id */
id: string
/** 省市区 */
fullLocation: string
}

13
src/types/uni-helper.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import type { _UniFormsRulesRule } from '@uni-helper/uni-ui-types'
export interface _UniFormsRulesRule {
/** 校验数据最小长度 */
minLength?: number
}
export interface UniFormsRules {
[x: string]: {
rules?: Array<_UniFormsRulesRule> | _UniFormsRulesRule
label?: string
}
}