New file |
| | |
| | | <template> |
| | | <view class="edit-address-page"> |
| | | <!-- 订单信息 --> |
| | | <view class="order-info"> |
| | | <view class="order-title">订单信息</view> |
| | | <view class="order-sn">订单号:{{ orderDetail.order.sn || '' }}</view> |
| | | |
| | | <!-- 商品列表 --> |
| | | <view class="goods-list"> |
| | | <view class="goods-item" v-for="item in orderDetail.orderItems" :key="item.id"> |
| | | <image class="goods-image" :src="item.image" mode="aspectFill"></image> |
| | | <view class="goods-info"> |
| | | <view class="goods-name">{{ item.goodsName }}</view> |
| | | <view class="goods-price-qty"> |
| | | <text class="price">¥{{ item.flowPrice }}</text> |
| | | <text class="qty">x{{ item.num }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 地址填写表单 --> |
| | | <view class="address-form"> |
| | | <view class="form-title">配送地址</view> |
| | | |
| | | <view class="form-item"> |
| | | <text class="label">收货人</text> |
| | | <input class="input" v-model="addressForm.consigneeName" placeholder="请输入收货人姓名" /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <text class="label">联系电话</text> |
| | | <input class="input" v-model="addressForm.consigneeMobile" placeholder="请输入联系电话" type="number" /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <text class="label">所在地区</text> |
| | | <view class="picker-text" @click="showPicker"> |
| | | {{ addressForm.___path || '请选择所在地区' }} |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <text class="label">详细地址</text> |
| | | <textarea class="textarea" v-model="addressForm.consigneeDetail" |
| | | placeholder="请输入详细地址(如街道、门牌号等)"></textarea> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 提交按钮(已适配安全区域) --> |
| | | <view class="submit-btn" @tap="submitAddress">保存地址</view> |
| | | |
| | | <!-- 地址选择组件 --> |
| | | <m-city :provinceData="list" headTitle="区域选择" ref="cityPicker" @funcValue="getpickerParentValue" pickerSize="4"> |
| | | </m-city> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | getOrderDetailEdit |
| | | } from '@/api/order.js' |
| | | import { |
| | | http |
| | | } from '@/utils/request.js' |
| | | import '@/components/uview-components/uview-ui'; |
| | | import city from "../m-city/m-city.vue"; |
| | | export default { |
| | | components: { |
| | | "m-city": city |
| | | }, |
| | | data() { |
| | | return { |
| | | orderSn: '', // 订单编号 |
| | | orderDetail: { |
| | | orderItems: [] |
| | | }, // 订单详情 |
| | | addressForm: { |
| | | consigneeName: '', |
| | | consigneeMobile: '', |
| | | consigneeAddressPath: '', |
| | | consigneeAddressIdPath: '', |
| | | consigneeDetail: '', |
| | | ___path: '' // 显示用的地区文本 |
| | | }, |
| | | regionData: [ |
| | | [], |
| | | [], |
| | | [] |
| | | ], // 省市区数据 |
| | | regionIndex: [0, 0, 0], // 选中的索引 |
| | | regionText: '', // 显示的地区文本 |
| | | regionCodes: [], // 地区编码 |
| | | list: [{ |
| | | id: "", |
| | | localName: "请选择", |
| | | children: [], |
| | | }, ] |
| | | } |
| | | }, |
| | | onShow(){ |
| | | console.log('触发onShow-------------------------->',this.orderSn) |
| | | this.loadOrderDetail() |
| | | }, |
| | | onLoad(options) { |
| | | console.log('页面参数:', options) |
| | | if (options.q) { |
| | | // 双重解码:微信对URL进行了两次编码 |
| | | const decodedUrl = decodeURIComponent(decodeURIComponent(options.q)); |
| | | console.log('原始URL:', decodedUrl); |
| | | |
| | | // 解析URL中的查询参数 |
| | | const params = this.parseUrlParams(decodedUrl); |
| | | this.orderSn = params.orderSn; |
| | | this.loadOrderDetail() |
| | | } |
| | | }, |
| | | methods: { |
| | | // 安全的toast方法,适配小程序环境 |
| | | safeShowToast(options) { |
| | | // 确保loading和之前的toast已关闭 |
| | | uni.hideLoading(); |
| | | uni.hideToast(); |
| | | // 使用nextTick确保在下一个事件循环中执行 |
| | | this.$nextTick(() => { |
| | | uni.showToast({ |
| | | ...options, |
| | | duration: options.duration || 2000 |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 解析URL参数 |
| | | parseUrlParams(url) { |
| | | const params = {}; |
| | | // 处理可能存在的hash(如果有的话) |
| | | const cleanUrl = url.split('#')[0]; |
| | | const queryStr = cleanUrl.split('?')[1] || ''; |
| | | |
| | | queryStr.split('&').forEach(pair => { |
| | | const [key, value] = pair.split('='); |
| | | if (key) { |
| | | // 如果值存在,则解码,否则设为空字符串 |
| | | params[key] = value ? decodeURIComponent(value) : ''; |
| | | } |
| | | }); |
| | | |
| | | return params; |
| | | }, |
| | | // 加载订单详情 |
| | | async loadOrderDetail() { |
| | | if (!this.orderSn) { |
| | | console.log('订单号为空,无法加载订单详情'); |
| | | this.safeShowToast({ |
| | | title: '订单号不能为空', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | let loadingShown = false; |
| | | try { |
| | | uni.showLoading({ |
| | | title: '加载中...' |
| | | }); |
| | | loadingShown = true; |
| | | |
| | | console.log('开始请求订单详情,订单号:', this.orderSn); |
| | | const res = await getOrderDetailEdit(this.orderSn); |
| | | console.log('----------获取订单返回结果------------->',res); |
| | | |
| | | // 检查响应状态 |
| | | if (res && res.data) { |
| | | if (res.data.success) { |
| | | this.orderDetail = res.data.result; |
| | | console.log('----------------------->订单数据', JSON.stringify(this.orderDetail)); |
| | | } else { |
| | | console.log('API返回失败:', res.data.message); |
| | | // 先关闭loading再显示toast |
| | | uni.hideLoading(); |
| | | loadingShown = false; |
| | | this.safeShowToast({ |
| | | title: res.data.message || '获取订单信息失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | console.log('服务器响应异常:', res); |
| | | uni.hideLoading(); |
| | | loadingShown = false; |
| | | this.safeShowToast({ |
| | | title: '服务器响应异常', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取订单详情失败:', error); |
| | | // 网络异常或其他错误 |
| | | if (loadingShown) { |
| | | uni.hideLoading(); |
| | | loadingShown = false; |
| | | } |
| | | this.safeShowToast({ |
| | | title: '网络异常,请稍后重试', |
| | | icon: 'none' |
| | | }); |
| | | } finally { |
| | | // 确保loading被关闭 |
| | | if (loadingShown) { |
| | | uni.hideLoading(); |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // 地区选择改变 |
| | | async onColumnChange(e) { |
| | | const { |
| | | column, |
| | | value |
| | | } = e.detail |
| | | this.regionIndex[column] = value |
| | | |
| | | if (column === 0) { |
| | | // 选择省份时,加载城市数据 |
| | | const provinceId = this.regionData[0][value].id |
| | | try { |
| | | const cityRes = await http.request({ |
| | | url: '/common/common/region/tree', |
| | | method: 'GET', |
| | | params: { |
| | | parentId: provinceId |
| | | } |
| | | }) |
| | | if (cityRes.data.success) { |
| | | this.regionData[1] = cityRes.data.result.map(item => ({ |
| | | name: item.name, |
| | | id: item.id |
| | | })) |
| | | this.regionData[2] = [] |
| | | this.regionIndex[1] = 0 |
| | | this.regionIndex[2] = 0 |
| | | } |
| | | } catch (error) { |
| | | console.error('加载城市数据失败:', error) |
| | | } |
| | | } else if (column === 1) { |
| | | // 选择城市时,加载区县数据 |
| | | const cityId = this.regionData[1][value].id |
| | | try { |
| | | const areaRes = await http.request({ |
| | | url: '/common/common/region/tree', |
| | | method: 'GET', |
| | | params: { |
| | | parentId: cityId |
| | | } |
| | | }) |
| | | if (areaRes.data.success) { |
| | | this.regionData[2] = areaRes.data.result.map(item => ({ |
| | | name: item.name, |
| | | id: item.id |
| | | })) |
| | | this.regionIndex[2] = 0 |
| | | } |
| | | } catch (error) { |
| | | console.error('加载区县数据失败:', error) |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // 地区选择确认 |
| | | onRegionChange(e) { |
| | | const { |
| | | value |
| | | } = e.detail |
| | | this.regionIndex = value |
| | | |
| | | const province = this.regionData[0][value[0]] |
| | | const city = this.regionData[1][value[1]] |
| | | const area = this.regionData[2][value[2]] |
| | | |
| | | if (province && city && area) { |
| | | this.regionText = `${province.name} ${city.name} ${area.name}` |
| | | this.addressForm.consigneeAddressIdPath = `${province.id},${city.id},${area.id}` |
| | | } |
| | | }, |
| | | |
| | | // 显示地址选择器 |
| | | showPicker() { |
| | | this.$nextTick(() => { |
| | | if (this.$refs.cityPicker) { |
| | | this.$refs.cityPicker.show(); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 地址选择回调 |
| | | getpickerParentValue(e) { |
| | | // 将需要绑定的地址设置为空,并赋值 |
| | | this.addressForm.consigneeAddressIdPath = ''; |
| | | let name = ""; |
| | | let idPath = []; |
| | | let namePath = [] |
| | | |
| | | e.forEach((item, index) => { |
| | | if (item.id) { |
| | | // 遍历数据 |
| | | idPath.push(item.id); |
| | | namePath.push(item.localName); |
| | | name += item.localName; |
| | | this.addressForm.___path = name; |
| | | } |
| | | }); |
| | | |
| | | // 设置地址路径 |
| | | this.addressForm.consigneeAddressIdPath = idPath.join(','); |
| | | this.addressForm.consigneeAddressPath = namePath.join(','); |
| | | }, |
| | | |
| | | // 提交地址 |
| | | async submitAddress() { |
| | | // 表单验证 |
| | | if (!this.addressForm.consigneeName) { |
| | | uni.showToast({ |
| | | title: '请输入收货人姓名', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | if (!this.addressForm.consigneeMobile) { |
| | | uni.showToast({ |
| | | title: '请输入联系电话', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | if (!/^1[3-9]\d{9}$/.test(this.addressForm.consigneeMobile)) { |
| | | uni.showToast({ |
| | | title: '请输入正确的手机号', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | if (!this.addressForm.consigneeAddressIdPath || !this.addressForm.___path) { |
| | | uni.showToast({ |
| | | title: '请选择所在地区', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | if (!this.addressForm.consigneeDetail) { |
| | | uni.showToast({ |
| | | title: '请输入详细地址', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | |
| | | try { |
| | | uni.showLoading({ |
| | | title: '保存中...' |
| | | }) |
| | | console.log('-------------------->', JSON.stringify(this.addressForm)) |
| | | // 提交地址到订单(注释部分为实际接口调用逻辑) |
| | | const res = await http.request({ |
| | | url: `/order/order/update/editAddress/${this.orderSn}/consignee`, |
| | | method: 'POST', |
| | | needToken: true, |
| | | data: this.addressForm |
| | | }) |
| | | console.log("地址保存") |
| | | if (res.data.success) { |
| | | // uni.showToast({ |
| | | // title: '地址保存成功', |
| | | // icon: 'success' |
| | | // }) |
| | | uni.redirectTo({ |
| | | url:'/pages/tabbar/index/home', |
| | | fail(e) { |
| | | console.log("跳转失败原因",e) |
| | | } |
| | | }) |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.data.message || '保存失败', |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('提交地址失败:', error) |
| | | uni.showToast({ |
| | | title: '保存失败,请重试', |
| | | icon: 'none' |
| | | }) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* 根容器:底部内边距适配按钮+安全区域 */ |
| | | .edit-address-page { |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | /* 按钮高度(100rpx) + 安全区域高度 + 额外间距(20rpx),避免内容被按钮遮挡 */ |
| | | padding-bottom: calc(100rpx + 20rpx + constant(safe-area-inset-bottom)); |
| | | padding-bottom: calc(100rpx + 20rpx + env(safe-area-inset-bottom)); |
| | | } |
| | | |
| | | /* 订单信息区域 */ |
| | | .order-info { |
| | | background: white; |
| | | margin: 20rpx; |
| | | padding: 30rpx; |
| | | border-radius: 16rpx; |
| | | } |
| | | |
| | | .order-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 20rpx; |
| | | color: #333; |
| | | } |
| | | |
| | | .order-sn { |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | /* 商品列表 */ |
| | | .goods-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | } |
| | | |
| | | .goods-item:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .goods-image { |
| | | width: 120rpx; |
| | | height: 120rpx; |
| | | border-radius: 12rpx; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .goods-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .goods-name { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin-bottom: 10rpx; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .goods-price-qty { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .price { |
| | | font-size: 28rpx; |
| | | color: #ff6b35; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .qty { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | /* 地址表单区域 */ |
| | | .address-form { |
| | | background: white; |
| | | margin: 20rpx; |
| | | padding: 30rpx; |
| | | border-radius: 16rpx; |
| | | } |
| | | |
| | | .form-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 30rpx; |
| | | color: #333; |
| | | } |
| | | |
| | | .form-item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | margin-bottom: 30rpx; |
| | | min-height: 80rpx; |
| | | } |
| | | |
| | | .label { |
| | | width: 160rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | line-height: 80rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .input { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | line-height: 80rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | padding: 0 20rpx; |
| | | border: 1rpx solid #e0e0e0; |
| | | border-radius: 8rpx; |
| | | } |
| | | |
| | | .picker-text { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | line-height: 80rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | padding: 0 20rpx; |
| | | border: 1rpx solid #e0e0e0; |
| | | border-radius: 8rpx; |
| | | background: white; |
| | | } |
| | | |
| | | .textarea { |
| | | flex: 1; |
| | | min-height: 120rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | padding: 20rpx; |
| | | border: 1rpx solid #e0e0e0; |
| | | border-radius: 8rpx; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | /* 底部提交按钮(核心:适配安全区域) */ |
| | | .submit-btn { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 100rpx; |
| | | line-height: 100rpx; |
| | | /* 保证文字垂直居中 */ |
| | | background: #ff6b35; |
| | | color: white; |
| | | text-align: center; |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | /* 兼容新旧iOS和安卓的安全区域 */ |
| | | padding-bottom: constant(safe-area-inset-bottom); |
| | | /* iOS 11.0-11.1 */ |
| | | padding-bottom: env(safe-area-inset-bottom); |
| | | /* iOS 11.2+ 及安卓 */ |
| | | } |
| | | </style> |