gaoluyang
3 天以前 92230c9a97dc9ce9df3313d11d26999c04bb6b26
src/pages_template/pages/address/addSite.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,444 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import tab from '@/plugins/tab';
import citySelect from '@/components/u-city-select/u-city-select.vue';
import { useAddressEditPage } from './index';
// ä½¿ç”¨ç¼–辑页面Hook
const {
  isEdit,
  form,
  defaultAddress,
  selectedTag,
  addressTags,
  initEditPage,
  saveAddress,
  deleteAddress
} = useAddressEditPage();
// åœ°åŒºé€‰æ‹©å™¨æ˜¾ç¤ºçŠ¶æ€
const showRegionPicker = ref(false);
// è¡¨å•错误状态 - ç§»åˆ°Vue组件中
const formErrors = reactive({
  name: false,
  phone: false,
  region: false,
  address: false
});
// é‡ç½®è¡¨å•错误
function resetFormErrors() {
  formErrors.name = false;
  formErrors.phone = false;
  formErrors.region = false;
  formErrors.address = false;
}
// åˆå§‹åŒ–页面数据
onMounted(() => {
  const pages = getCurrentPages();
  const currentPage: any = pages[pages.length - 1];
  const options = currentPage.$page?.options;
  // è°ƒç”¨hook的初始化方法
  initEditPage(options?.id);
  // é‡ç½®è¡¨å•错误状态
  resetFormErrors();
});
// è®¾ç½®é»˜è®¤åœ°å€
function handleSetDefault(e: any) {
  defaultAddress.value = e.detail.value;
}
// æ˜¾ç¤ºåœ°åŒºé€‰æ‹©å™¨
function handleShowRegionPicker() {
  showRegionPicker.value = true;
}
// ç¡®è®¤é€‰æ‹©åœ°åŒº
function handleCityChange(e: any) {
  form.region = e.province.label + e.city.label + e.area.label;
  formErrors.region = false;
}
// é€‰æ‹©æ ‡ç­¾
function handleSelectTag(tag: string) {
  selectedTag.value = tag;
}
// è¡¨å•验证
function validateForm(): boolean {
  // éªŒè¯å§“名
  formErrors.name = !form.name.trim();
  // éªŒè¯æ‰‹æœºå·
  const phoneReg = /^1[3-9]\d{9}$/;
  formErrors.phone = !phoneReg.test(form.phone);
  // éªŒè¯åœ°åŒº
  formErrors.region = !form.region;
  // éªŒè¯è¯¦ç»†åœ°å€
  formErrors.address = !form.address.trim();
  // å¦‚果有任何错误,返回false
  return !(formErrors.name || formErrors.phone || formErrors.region || formErrors.address);
}
// ä¿å­˜åœ°å€
function handleSaveAddress() {
  // ä½¿ç”¨æœ¬åœ°éªŒè¯æ–¹æ³•
  if (!validateForm()) {
    uni.showToast({
      title: '请填写完整信息',
      icon: 'none'
    });
    return;
  }
  try {
    const success = saveAddress();
    if (success) {
      uni.showToast({
        title: isEdit.value ? '修改成功' : '添加成功',
        icon: 'success'
      });
      // å»¶è¿Ÿè¿”回,让用户看到提示
      setTimeout(() => {
        tab.navigateBack();
      }, 1000);
    } else {
      uni.showToast({
        title: '保存失败',
        icon: 'none'
      });
    }
  } catch (e) {
    console.error('保存地址失败', e);
    uni.showToast({
      title: '保存失败',
      icon: 'none'
    });
  }
}
// åˆ é™¤åœ°å€
function handleDeleteAddress() {
  if (!isEdit.value) return;
  uni.showModal({
    title: '提示',
    content: '确定要删除此地址吗?',
    success: (res) => {
      if (res.confirm) {
        try {
          const success = deleteAddress();
          if (success) {
            uni.showToast({
              title: '删除成功',
              icon: 'success'
            });
            setTimeout(() => {
              tab.navigateBack();
            }, 1000);
          } else {
            uni.showToast({
              title: '删除失败',
              icon: 'none'
            });
          }
        } catch (e) {
          console.error('删除地址失败', e);
          uni.showToast({
            title: '删除失败',
            icon: 'none'
          });
        }
      }
    }
  });
}
</script>
<template>
  <view class="wrap">
    <view class="container">
      <view class="top">
        <view class="item">
          <view class="left">
            <text class="required">*</text>收货人
          </view>
          <input type="text" v-model="form.name" placeholder-class="line" placeholder="请填写收货人姓名"
            :class="{ 'error-input': formErrors.name }" />
          <u-icon name="account" size="36rpx" color="#999"></u-icon>
        </view>
        <view class="error-msg" v-if="formErrors.name">请输入收货人姓名</view>
        <view class="item">
          <view class="left">
            <text class="required">*</text>手机号码
          </view>
          <input type="number" v-model="form.phone" placeholder-class="line" placeholder="请填写收货人手机号" maxlength="11"
            :class="{ 'error-input': formErrors.phone }" />
          <u-icon name="phone" size="36rpx" color="#999"></u-icon>
        </view>
        <view class="error-msg" v-if="formErrors.phone">请输入正确的手机号码</view>
        <view class="item" @tap="handleShowRegionPicker">
          <view class="left">
            <text class="required">*</text>所在地区
          </view>
          <input disabled v-model="form.region" type="text" placeholder-class="line" placeholder="省市区县、乡镇等"
            :class="{ 'error-input': formErrors.region }" />
          <u-icon name="arrow-right" size="36rpx" color="#999"></u-icon>
        </view>
        <view class="error-msg" v-if="formErrors.region">请选择所在地区</view>
        <view class="item address">
          <view class="left">
            <text class="required">*</text>详细地址
          </view>
          <textarea v-model="form.address" type="text" placeholder-class="line" placeholder="街道、楼牌等"
            :class="{ 'error-textarea': formErrors.address }" />
        </view>
        <view class="error-msg" v-if="formErrors.address">请输入详细地址</view>
      </view>
      <view class="bottom">
        <view class="tag">
          <view class="left">标签</view>
          <view class="right">
            <text v-for="tag in addressTags" :key="tag" class="tags" :class="{ 'active': selectedTag === tag }"
              @tap="handleSelectTag(tag)">
              {{ tag }}
            </text>
            <view class="tags plus"><u-icon size="22" name="plus" color="#999"></u-icon></view>
          </view>
        </view>
        <view class="default">
          <view class="left">
            <view class="set">设置默认地址</view>
            <view class="tips">提醒:每次下单会默认推荐该地址</view>
          </view>
          <view class="right">
            <switch color="#fa3534" :checked="defaultAddress" @change="handleSetDefault" />
          </view>
        </view>
      </view>
      <view class="button-group">
        <view class="save-btn" @tap="handleSaveAddress">
          {{ isEdit ? '保存修改' : '保存地址' }}
        </view>
        <view v-if="isEdit" class="delete-btn" @tap="handleDeleteAddress">
          åˆ é™¤åœ°å€
        </view>
      </view>
    </view>
    <city-select v-model="showRegionPicker" @city-change="handleCityChange"></city-select>
  </view>
</template>
<style lang="scss" scoped>
:v-deep(.line) {
  color: $u-light-color;
  font-size: 28rpx;
}
.wrap {
  background-color: #f5f5f5;
  min-height: 100vh;
  padding: 20rpx;
  box-sizing: border-box;
  .container {
    border-radius: 16rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  }
  .top {
    background-color: #ffffff;
    padding: 30rpx;
    .item {
      display: flex;
      font-size: 32rpx;
      line-height: 100rpx;
      align-items: center;
      border-bottom: solid 1rpx #eeeeee;
      position: relative;
      .left {
        width: 180rpx;
        font-weight: 500;
        color: #333;
        .required {
          color: #fa3534;
          margin-right: 4rpx;
        }
      }
      input {
        text-align: left;
        flex: 1;
        height: 100rpx;
        font-size: 30rpx;
        &.error-input {
          border-bottom: 1px solid #fa3534;
        }
      }
      u-icon {
        margin-left: 10rpx;
      }
    }
    .error-msg {
      color: #fa3534;
      font-size: 24rpx;
      padding-left: 180rpx;
      margin-top: -10rpx;
      margin-bottom: 10rpx;
    }
    .address {
      padding: 20rpx 0;
      align-items: flex-start;
      .left {
        padding-top: 20rpx;
      }
      textarea {
        flex: 1;
        height: 180rpx;
        background-color: #f9f9f9;
        line-height: 60rpx;
        margin: 20rpx 0;
        padding: 20rpx;
        border-radius: 12rpx;
        font-size: 30rpx;
        &.error-textarea {
          border: 1px solid #fa3534;
        }
      }
    }
  }
  .bottom {
    margin-top: 20rpx;
    padding: 30rpx;
    background-color: #ffffff;
    font-size: 28rpx;
    border-radius: 16rpx;
    .tag {
      display: flex;
      align-items: center;
      .left {
        width: 160rpx;
        font-weight: 500;
        color: #333;
      }
      .right {
        display: flex;
        flex-wrap: wrap;
        flex: 1;
        .tags {
          width: 150rpx;
          padding: 20rpx 10rpx;
          border: solid 2rpx #eeeeee;
          text-align: center;
          border-radius: 100rpx;
          margin: 0 20rpx 20rpx 0;
          display: flex;
          font-size: 28rpx;
          align-items: center;
          justify-content: center;
          color: #333;
          line-height: 1;
          transition: all 0.3s;
          &.active {
            background-color: #ffebec;
            color: #fa3534;
            border-color: #fa3534;
          }
        }
        .plus {
          background-color: #f5f5f5;
        }
      }
    }
    .default {
      margin-top: 30rpx;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding-bottom: 20rpx;
      .left {
        .set {
          font-weight: 500;
          color: #333;
          font-size: 30rpx;
        }
        .tips {
          font-size: 24rpx;
          color: #999;
          margin-top: 10rpx;
        }
      }
    }
  }
  .button-group {
    display: flex;
    flex-direction: column;
    margin-top: 60rpx;
    .save-btn {
      background: linear-gradient(90deg, #ff4034, #fa3534);
      color: #fff;
      height: 90rpx;
      line-height: 90rpx;
      text-align: center;
      font-size: 32rpx;
      border-radius: 45rpx;
      font-weight: bold;
      box-shadow: 0 10rpx 20rpx rgba(250, 53, 52, 0.2);
      letter-spacing: 2rpx;
    }
    .delete-btn {
      margin-top: 30rpx;
      background: #ffffff;
      color: #fa3534;
      border: 1px solid #fa3534;
      height: 90rpx;
      line-height: 90rpx;
      text-align: center;
      font-size: 32rpx;
      border-radius: 45rpx;
      letter-spacing: 2rpx;
    }
  }
}
</style>