vue实现省市区等四级联动--详细含代码

选择省市区:
    <select v-model="user.selectAddrs.p">
        <option :value="p" v-for="p in addrs">{{p.province}}</option>
    </select>
    <select v-model="user.selectAddrs.c">
        <option :value="c" v-for="c in user.selectAddrs.p.cities">{{c.city}}</option>
    </select>
    <select v-model="user.selectAddrs.a">
        <option v-for="a in user.selectAddrs.c.areas">
            {{a}}
        </option>
    </select>
<br>


(function () {
    new Vue({
        el:"#app",
        data:{
            addrs:[
                {
                    province:"陕西",
                    cities:[
                    {
                        city:"西安",
                        areas:["高新区","长安区"]
                    },
                    {
                        city:"咸阳",
                        areas:["渭城区","秦都区"]
                    }
                    ]
                },
                {
                    province:"山西",
                    cities:[
                        {
                            city:"太原",
                            areas:["太原区1","太原区2"]
                        },
                        {
                            city:"运城",
                            areas:["运城区1","运城区2"]
                        }
                    ]
                },

            ],
            user:{
                selectAddrs:{
                    p:"",
                    c:"",
                    a:""
                }
            }
        }
    })


})()

实现思路

通过 watch 监视所选省,省份改变后将所有城市数组根据省id进行过滤。

<template>
  <div>
    <el-drawer
      v-model="dialogShow"
      size="35%"
      :show-close="false"
      @opened="opened"
      @closed="onClosed"
    >
      <template #header>
        <e-block title="买家退货寄回地址" class="flex-1" sider />
      </template>
      <e-form
        ref="returnFormRef"
        :model="returnForm"
        :rules="rules"
        :show-ellipsis="false"
        label-width="110px"
        class="returnForm"
      >
        <e-form-item label="收货人" prop="consignee">
          <e-input v-model="returnForm.consignee" style="width: var(--from-width-small)" />
        </e-form-item>
        <e-form-item label="收货电话" prop="mobile">
          <e-input v-model="returnForm.mobile" style="width: var(--from-width-small)" />
        </e-form-item>
        <e-form-item label="收货地址" prop="returnAddresses">
          <e-select v-model="province" placeholder="请选择省">
            <e-option
              v-for="item in allAreasList"
              :key="item.value"
              :label="item.name"
              :value="item.code"
            />
          </e-select>
          <e-select v-model="city" placeholder="请选择市">
            <e-option
              v-for="item in selectCityList"
              :key="item.value"
              :label="item.name"
              :value="item.code"
            />
          </e-select>
          <e-select v-model="district" placeholder="请选择区">
            <e-option
              v-for="item in selectDistrictList"
              :key="item.value"
              :label="item.name"
              :value="item.code"
            />
          </e-select>
          <!-- <el-cascader
            v-model="returnForm.returnAddresses"
            :options="areaData"
            :props="addressProps"
            @change="handleChangeAddress"
          /> -->
        </e-form-item>
        <e-form-item label prop="detailedAddress">
          <e-input
            v-model="returnForm.detailedAddress"
            placeholder="请在此输入详细地址"
            type="textarea"
            style="width: var(--from-width-large)"
          />
        </e-form-item>
        <e-form-item label="是否默认地址" prop="first">
          <e-radio-group v-model="returnForm.first">
            <e-radio v-for="item in defaultOptions" :key="item.label" :label="item.label">
              {{ item.text }}
            </e-radio>
          </e-radio-group>
        </e-form-item>
      </e-form>
      <template #footer>
        <e-button type="main" @click="dialogConfirm(returnFormRef)">确 认</e-button>
        <e-button type="secondary" @click="dialogCancel">取 消</e-button>
      </template>
    </el-drawer>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, computed, defineProps, defineEmits, watch } from 'vue';
import { ElMessage as Message } from 'element-plus';
import type { ElForm } from 'element-plus';
import { reg_tel_mobile } from '@/assets/regex';
import { defaultOptions } from '@/config/index';
import api from '@/api/request';

import { saveConfig } from '@/api/order';

type FormInstance = InstanceType<typeof ElForm>;
interface AreaItem {
  id: number;
  name: string;
  code: string;
  level: number;
  districtCount?: number; // 区的数量
  parent_code: string;
  status: number;
  subAddresses?: AreaItem[];
}
const props = defineProps<{
  dialogVisible: Boolean;
}>();

const returnFormRef = ref<FormInstance>();

const returnForm = ref<OrderConfig.ReturnAddressesItem[]>([]);
const rules = reactive({
  consignee: [
    {
      required: true,
      message: '请输入收货人',
      trigger: 'blur'
    }
  ],
  mobile: [
    {
      required: true,
      pattern: reg_tel_mobile,
      message: '请输入正确的手机号或座机号',
      trigger: ['blur', 'change']
    }
  ],
  returnAddresses: [
    {
      message: '请选择地址',
      trigger: 'change'
    }
  ],
  detailedAddress: [
    {
      required: true,
      message: '请输入详细地址',
      trigger: 'blur'
    }
  ],
  first: [
    {
      required: true,
      message: '请选择是否默认地址',
      trigger: 'blur'
    }
  ]
});

const dialogShow = computed({
  get: () => props.dialogVisible,
  set: (val) => emit('update: dialogVisible', val)
});
const emit = defineEmits(['on-confirm', 'on-cancel', 'update: dialogVisible', 'on-closed']);
const opened = () => {
  getAllProvinceData();
  getAreaData();
};
// 地址相关
// const areaData = ref<AreaItem[]>([]);
const addressProps = ref({
  value: 'code',
  label: 'name',
  children: 'subAddresses'
});

// 获取省市区-全量地址树 TODO 层级为4就报 !!!!timeout of 1000ms exceeded
const getAllProvinceData = () =>
  api.post<{ address: AreaItem }>('/address/tree', {
    root_code: '086', // 根结点编码,默认取086/中国
    max_level: 3 // 最大地址层级
  });
const getAreaData = async () => {
  const { code, data } = await getAllProvinceData();
  if (`${code}` === '0' && data) {
    console.log(data.address);
    // areaData.value = data.address.subAddresses || [];
    allAreasList.value = data.address.subAddresses || [];
  }
};
// const handleChangeAddress = (arr: string[]) => {
//   console.log('arr:', arr);
//   const [provinceCode, cityCode, districtCode] = arr;

//   returnForm.value.returnAddresses = {
//     provinceCode,
//     cityCode,
//     districtCode
//   };
// };
const allAreasList = ref([]);
const selectCityList = ref<AreaItem[]>([]);
const selectDistrictList = ref<AreaItem[]>([]);
const province = ref<string>('');
const city = ref<string>('');
const district = ref<string>('');
// 监听选择省份--确定市
watch(
  () => province.value,
  (val) => {
    if (val) {
      const cities = allAreasList.value.find((item) => item.code === province.value)!.subAddresses;
      selectCityList.value = cities;
    }
    city.value = '';
    district.value = '';
  }
);
// 监听选择城--确定街道
watch(
  () => city.value,
  (val) => {
    if (val) {
      const districts = selectCityList.value.find((item) => item.code === city.value)!.subAddresses;
      selectDistrictList.value = districts;
    }
    district.value = '';
  }
);

const dialogConfirm = async (formEl) => {
  if (!formEl) return;
  formEl.validate((valid) => {
    if (valid) {
      console.log(returnForm.value);
      saveConfig().then((res) => {});
    }
  });
};
// 弹窗确定
const dialogConfirm1 = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  console.log(222, formEl);

  let param = {};
  formEl.validate((valid) => {
    if (valid) {
      param = {
        operatorId: '51',
        source: 'admin'
      };
      const res = api.post('/order-admin/afterSaleRefundCreate', param);
      const { code, message } = res;
      if (`${code}` === '0') {
        Message({
          type: 'success',
          message: '操作成功'
        });
        emit('on-confirm');
      } else {
        Message({
          type: 'error',
          message: `${message}`
        });
      }
    } else {
      console.log('error submit!');
      return false;
    }
  });
};
// 弹窗取消
const dialogCancel = () => {
  emit('on-cancel');
};

const onClosed = () => {
  emit('on-closed');
};
</script>

<style lang="scss" scoped>
:deep(.el-drawer__header) {
  padding: 0;
  margin-bottom: 0;
}

:deep(.el-drawer__body) {
  padding: 20px;
}

:deep(.e-block) {
  margin-bottom: 0;
}

:deep(.el-drawer__footer) {
  text-align: unset;
  border-top: 1px solid #e9ebf3;
}

.e-select.el-input__wrapper {
  width: 160px;
}
</style>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小可爱的小飞云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值