|
|
@@ -31,7 +31,11 @@
|
|
|
<div class="profile-item">
|
|
|
<div class="item-left">
|
|
|
<div class="label">收货地址</div>
|
|
|
- <div class="value">{{ receiverInfo.shipAreaPath }}{{receiverInfo.shipAddress}}-{{ receiverInfo.shipName }}{{receiverInfo.shipMobile}}</div>
|
|
|
+ <div class="value">
|
|
|
+ {{ receiverInfo.shipAreaPath }}{{ receiverInfo.shipAddress }}-{{
|
|
|
+ receiverInfo.shipName
|
|
|
+ }}{{ receiverInfo.shipMobile }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="item-right">
|
|
|
<a-button @click="openAddrModal">修改地址</a-button>
|
|
|
@@ -47,10 +51,18 @@
|
|
|
cancel-text="取消"
|
|
|
@ok="submitNick"
|
|
|
>
|
|
|
- <a-form ref="nickRef" :model="nickForm" layout="vertical">
|
|
|
- <a-form-item name="nick" :rules="[{ required: true, message: '请输入昵称' }, { max: 50, message: '最多50个字符' }]">
|
|
|
- <a-input size="large" allowClear
|
|
|
- v-model:value="user.nickName"
|
|
|
+ <a-form ref="nickRef" :model="userInfo" layout="vertical">
|
|
|
+ <a-form-item
|
|
|
+ name="nickName"
|
|
|
+ :rules="[
|
|
|
+ { required: true, message: '请输入昵称' },
|
|
|
+ { max: 50, message: '最多50个字符' },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <a-input
|
|
|
+ size="large"
|
|
|
+ allowClear
|
|
|
+ v-model:value="userInfo.nickName"
|
|
|
placeholder="请输入昵称"
|
|
|
show-count
|
|
|
maxlength="50"
|
|
|
@@ -68,237 +80,301 @@
|
|
|
cancel-text="取消"
|
|
|
@ok="submitPwd"
|
|
|
>
|
|
|
- <a-form ref="pwdRef" :model="pwdForm" layout="vertical">
|
|
|
+ <a-form
|
|
|
+ ref="pwdRef"
|
|
|
+ :model="pwdForm"
|
|
|
+ :rules="formRules"
|
|
|
+ layout="vertical"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
<!-- 双区号+手机号行 -->
|
|
|
<div class="phone-row">
|
|
|
- <a-select size="large" :dropdownMatchSelectWidth="150" v-model:value="pwdForm.countryCode" :options="selectCall" style="width:130px">
|
|
|
+ <a-select
|
|
|
+ disabled
|
|
|
+ size="large"
|
|
|
+ :dropdownMatchSelectWidth="150"
|
|
|
+ v-model:value="pwdForm.countryCode"
|
|
|
+ :options="selectCall"
|
|
|
+ style="width: 130px"
|
|
|
+ >
|
|
|
</a-select>
|
|
|
<a-input
|
|
|
+ disabled
|
|
|
size="large"
|
|
|
v-model:value="user.userName"
|
|
|
placeholder="请输入手机号"
|
|
|
maxlength="11"
|
|
|
+ autocomplete="off"
|
|
|
+ readonly
|
|
|
/>
|
|
|
</div>
|
|
|
- <a-form-item
|
|
|
- name="code"
|
|
|
- :rules="[{ required: true, message: '请输入验证码' }]"
|
|
|
- >
|
|
|
- <a-input
|
|
|
- v-model:value="pwdForm.msgAuthCode"
|
|
|
- size="large"
|
|
|
- style="margin-bottom: 16px"
|
|
|
- placeholder="请输入密码"
|
|
|
- >
|
|
|
- <template #suffix>
|
|
|
- <div
|
|
|
- class="code-btn text-sm cursor-pointer pl-4"
|
|
|
- style="border-left: 1px solid #bfbfbf; height: 16px"
|
|
|
- :class="{ 'is-disabled': sendCodeExTime }"
|
|
|
- @click="sendCode"
|
|
|
- >
|
|
|
- {{
|
|
|
- sendCodeExTime
|
|
|
- ? $t("login.resendTime", { time: sendCodeExTime })
|
|
|
- : $t("login.sendCodeBtnText")
|
|
|
- }}
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </a-input>
|
|
|
+ <a-form-item name="msgAuthCode">
|
|
|
+ <a-input
|
|
|
+ v-model:value="pwdForm.msgAuthCode"
|
|
|
+ size="large"
|
|
|
+ :placeholder="$t('login.verCode')"
|
|
|
+ autocomplete="one-time-code"
|
|
|
+ name="msgAuthCode"
|
|
|
+ >
|
|
|
+ <template #suffix>
|
|
|
+ <div
|
|
|
+ class="code-btn text-sm pl-4"
|
|
|
+ style="border-left: 1px solid #bfbfbf"
|
|
|
+ :class="
|
|
|
+ sendCodeExTime
|
|
|
+ ? 'is-disabled cursor-not-allowed'
|
|
|
+ : 'cursor-pointer'
|
|
|
+ "
|
|
|
+ @click="sendCode"
|
|
|
+ >
|
|
|
+ {{
|
|
|
+ sendCodeExTime
|
|
|
+ ? $t("login.resendTime", { time: sendCodeExTime })
|
|
|
+ : $t("login.sendCodeBtnText")
|
|
|
+ }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </a-input>
|
|
|
</a-form-item>
|
|
|
<a-form-item
|
|
|
- name="newPwd"
|
|
|
- :rules="[{ required: true, message: '请设置新密码' }]"
|
|
|
+ name="password"
|
|
|
>
|
|
|
- <a-input-password size="large" v-model:value="pwdForm.newPwd" placeholder="设置密码" />
|
|
|
+ <a-input-password
|
|
|
+ size="large"
|
|
|
+ v-model:value="pwdForm.password"
|
|
|
+ placeholder="设置密码"
|
|
|
+ autocomplete="new-password"
|
|
|
+ name="new-password"
|
|
|
+ />
|
|
|
</a-form-item>
|
|
|
<a-form-item
|
|
|
name="confirmPwd"
|
|
|
- :rules="[
|
|
|
- { required: true, message: '请确认密码' },
|
|
|
- { validator: validateSamePwd, trigger: 'blur' }
|
|
|
- ]"
|
|
|
>
|
|
|
- <a-input-password size="large" v-model:value="pwdForm.confirmPwd" placeholder="确认密码" />
|
|
|
+ <a-input-password
|
|
|
+ size="large"
|
|
|
+ v-model:value="pwdForm.confirmPwd"
|
|
|
+ placeholder="确认密码"
|
|
|
+ autocomplete="new-password"
|
|
|
+ name="confirm-new-password"
|
|
|
+ />
|
|
|
</a-form-item>
|
|
|
</a-form>
|
|
|
</a-modal>
|
|
|
|
|
|
<!-- 3. 修改地址弹窗 优化版(省市区三级联动) -->
|
|
|
- <AddressModal
|
|
|
- v-model:open="addrVisible"
|
|
|
- @confirm="addrVisible = false"
|
|
|
- />
|
|
|
- <!-- <a-modal
|
|
|
- v-model:open="addrVisible"
|
|
|
- title="修改地址"
|
|
|
- :width="600"
|
|
|
- ok-text="确定"
|
|
|
- cancel-text="取消"
|
|
|
- @ok="submitAddr"
|
|
|
- >
|
|
|
- <a-form ref="addrRef" :model="addrForm"
|
|
|
- :label-col="{ style: { width: '120px' } }"
|
|
|
- :wrapper-col="{ span: 18 }">
|
|
|
- <a-form-item label="国家/地区" name="country">
|
|
|
- <a-select size="large" v-model:value="addrForm.country">
|
|
|
- <a-select-option value="CN">中国</a-select-option>
|
|
|
- </a-select>
|
|
|
- </a-form-item>
|
|
|
- <a-form-item label="所在地区" name="area" :rules="[{ required: true, message: '请选择省市区' }]">
|
|
|
- <a-cascader size="large"
|
|
|
- v-model:value="addrForm.area"
|
|
|
- :options="cityOptions"
|
|
|
- placeholder="省、市、区"
|
|
|
- style="width:100%"
|
|
|
- />
|
|
|
- </a-form-item>
|
|
|
- <a-form-item label="详细地址" name="detail" :rules="[{ required: true, message: '请输入详细地址' }, { max:50 }]">
|
|
|
- <a-input size="large" v-model:value="addrForm.detail" show-count maxlength="50" placeholder="请输入详细地址" />
|
|
|
- </a-form-item>
|
|
|
- <a-form-item label="收件人" name="name" :rules="[{ required: true, message: '请输入收件人' }, { max:10 }]">
|
|
|
- <a-input size="large" v-model:value="addrForm.name" show-count maxlength="10" placeholder="请输入名称" />
|
|
|
- </a-form-item>
|
|
|
- <a-form-item label="手机号码" name="mobile" :rules="[{ required: true, message: '请输入手机号' }, { len:11, message:'手机号必须11位' }]">
|
|
|
- <a-input size="large" v-model:value="addrForm.mobile" show-count maxlength="11" placeholder="请输入手机号码" />
|
|
|
- </a-form-item>
|
|
|
- </a-form>
|
|
|
- </a-modal> -->
|
|
|
+ <AddressModal v-model:open="addrVisible" @confirm="addrVisible = false" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, nextTick, computed } from 'vue'
|
|
|
-import type { FormInstance } from 'ant-design-vue'
|
|
|
-import { message } from 'ant-design-vue'
|
|
|
+import { ref, reactive, nextTick, computed } from "vue";
|
|
|
+import type { FormInstance } from "ant-design-vue";
|
|
|
+import { message } from "ant-design-vue";
|
|
|
import { useCartStore, useUserStore } from "@/stores";
|
|
|
-import AddressModal from '@/components/AddressModal.vue'
|
|
|
-import selectcall from '@/utils/country.js'
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+import { checkSms, getAuthCode } from "@/api/login";
|
|
|
+import AddressModal from "@/components/AddressModal.vue";
|
|
|
+import selectcall from "@/utils/country.js";
|
|
|
+const { t } = useI18n();
|
|
|
const user = useUserStore();
|
|
|
-
|
|
|
// 用户基础信息
|
|
|
const cart = useCartStore();
|
|
|
const receiverInfo = computed(() => cart.greceiverInfo);
|
|
|
const userInfo = reactive({
|
|
|
- account: '13631262785',
|
|
|
- nickname: '这是一个昵称',
|
|
|
- // 地址展示文本
|
|
|
- addressText: '广东省珠海市香洲区港湾一号11栋 张三 10688888888',
|
|
|
- // 地址弹窗回填字段
|
|
|
- country: 'CN',
|
|
|
- area: ['广东省', '珠海市', '香洲区'],
|
|
|
- detail: '港湾一号11栋',
|
|
|
- name: '张三',
|
|
|
- mobile: '10688888888'
|
|
|
-})
|
|
|
+ nickName: user.nickName,
|
|
|
+});
|
|
|
+const sendCodeExTime = ref(0);
|
|
|
+
|
|
|
const selectCall = computed(() => {
|
|
|
- return selectcall.map(item => {
|
|
|
+ return selectcall.map((item) => {
|
|
|
return {
|
|
|
- value: item[1],
|
|
|
- // label: item[1],
|
|
|
- label: `${item[1]}(${item[0]})`
|
|
|
- }
|
|
|
- })
|
|
|
-})
|
|
|
+ value: item[1],
|
|
|
+ // label: item[1],
|
|
|
+ label: `${item[1]}(${item[0]})`,
|
|
|
+ };
|
|
|
+ });
|
|
|
+});
|
|
|
// ========== 弹窗显示状态 ==========
|
|
|
-const nickVisible = ref(false)
|
|
|
-const pwdVisible = ref(false)
|
|
|
-const addrVisible = ref(false)
|
|
|
+const nickVisible = ref(false);
|
|
|
+const pwdVisible = ref(false);
|
|
|
+const addrVisible = ref(false);
|
|
|
|
|
|
// ========== 昵称弹窗表单 ==========
|
|
|
-const nickRef = ref<FormInstance>()
|
|
|
-const nickForm = reactive({ nick: '' })
|
|
|
+const nickRef = ref<FormInstance>();
|
|
|
+const nickForm = reactive({ nick: "" });
|
|
|
const openNickModal = () => {
|
|
|
- nickForm.nick = userInfo.nickname
|
|
|
- nickVisible.value = true
|
|
|
- nextTick(() => nickRef.value?.clearValidate())
|
|
|
-}
|
|
|
+ userInfo.nickName = user.nickName;
|
|
|
+ nickVisible.value = true;
|
|
|
+ nextTick(() => nickRef.value?.clearValidate());
|
|
|
+};
|
|
|
const submitNick = async () => {
|
|
|
- await nickRef.value?.validate()
|
|
|
- userInfo.nickname = nickForm.nick
|
|
|
- nickVisible.value = false
|
|
|
- message.success('昵称修改成功')
|
|
|
+ await nickRef.value?.validate();
|
|
|
+ await user.updateInfo(userInfo.nickName);
|
|
|
+ nickVisible.value = false;
|
|
|
+ message.success("昵称修改成功");
|
|
|
// 调用后端更新昵称接口
|
|
|
-}
|
|
|
-
|
|
|
+};
|
|
|
+// 表单校验规则
|
|
|
+const formRules: Record<string, Rule[]> = {
|
|
|
+ phone: [
|
|
|
+ { required: true, message: "请输入手机号", trigger: "blur" },
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: "手机号格式错误", trigger: "blur" },
|
|
|
+ ],
|
|
|
+ // 验证码:本地格式校验 + 异步远程校验
|
|
|
+ msgAuthCode: [
|
|
|
+ { required: true, message: "请输入验证码", trigger: "blur" },
|
|
|
+ { pattern: /^\d+$/, message: "验证码只能输入数字", trigger: "blur" },
|
|
|
+ {
|
|
|
+ validator: async (_rule, value) => {
|
|
|
+ if (!value) return Promise.resolve();
|
|
|
+ const res = await checkSms(value);
|
|
|
+ console.log('checkSms', res);
|
|
|
+ if (res.code !== 0) {
|
|
|
+ return Promise.reject(t('toast.3006'));
|
|
|
+ }
|
|
|
+ return Promise.resolve();
|
|
|
+ },
|
|
|
+ trigger: "blur",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ password: [
|
|
|
+ { required: true, message: "请设置密码", trigger: "blur" },
|
|
|
+ {
|
|
|
+ pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z0-9]{8,16}$/,
|
|
|
+ message: "密码需8-16位,同时包含大写字母、小写字母、数字",
|
|
|
+ trigger: "blur",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // 确认密码 联动密码校验
|
|
|
+ confirmPwd: [
|
|
|
+ { required: true, message: "请再次输入密码", trigger: "blur" },
|
|
|
+ {
|
|
|
+ validator: (_rule, value) => {
|
|
|
+ if (value !== formData.value.password) {
|
|
|
+ return Promise.reject("两次输入密码不一致");
|
|
|
+ }
|
|
|
+ return Promise.resolve();
|
|
|
+ },
|
|
|
+ trigger: "blur",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+};
|
|
|
// ========== 修改密码弹窗表单 + 验证码倒计时 ==========
|
|
|
-const pwdRef = ref<FormInstance>()
|
|
|
-const codeCount = ref(0)
|
|
|
-let timer: number | null = null
|
|
|
+const pwdRef = ref<FormInstance>();
|
|
|
+const codeCount = ref(0);
|
|
|
+let timer: number | null = null;
|
|
|
const pwdForm = reactive({
|
|
|
- countryCode: '+' + user.country,
|
|
|
- phone: userInfo.account,
|
|
|
- code: '',
|
|
|
- newPwd: '',
|
|
|
- confirmPwd: ''
|
|
|
-})
|
|
|
+ countryCode: "+" + user.country,
|
|
|
+ phone: user.userName,
|
|
|
+ phoneNum: user.userName,
|
|
|
+ msgAuthCode: "",
|
|
|
+ password: "",
|
|
|
+ confirmPwd: "",
|
|
|
+});
|
|
|
// 打开密码弹窗重置表单
|
|
|
const openPwdModal = () => {
|
|
|
- pwdForm.code = ''
|
|
|
- pwdForm.newPwd = ''
|
|
|
- pwdForm.confirmPwd = ''
|
|
|
- pwdVisible.value = true
|
|
|
- nextTick(() => pwdRef.value?.clearValidate())
|
|
|
-}
|
|
|
+ pwdForm.msgAuthCode = "";
|
|
|
+ pwdForm.password = "";
|
|
|
+ pwdForm.confirmPwd = "";
|
|
|
+ pwdVisible.value = true;
|
|
|
+ nextTick(() => pwdRef.value?.clearValidate());
|
|
|
+};
|
|
|
// 获取验证码倒计时
|
|
|
const getCode = () => {
|
|
|
- codeCount.value = 60
|
|
|
- message.success('验证码已发送')
|
|
|
+ codeCount.value = 60;
|
|
|
+ message.success("验证码已发送");
|
|
|
timer = window.setInterval(() => {
|
|
|
- codeCount.value--
|
|
|
- if (codeCount.value <= 0) clearInterval(timer!)
|
|
|
- }, 1000)
|
|
|
-}
|
|
|
+ codeCount.value--;
|
|
|
+ if (codeCount.value <= 0) clearInterval(timer!);
|
|
|
+ }, 1000);
|
|
|
+};
|
|
|
// 密码一致性校验
|
|
|
const validateSamePwd = (_rule: any, val: string) => {
|
|
|
- if (val && val !== pwdForm.newPwd) return Promise.reject('两次密码输入不一致')
|
|
|
- return Promise.resolve()
|
|
|
-}
|
|
|
+ if (val && val !== pwdForm.password)
|
|
|
+ return Promise.reject("两次密码输入不一致");
|
|
|
+ return Promise.resolve();
|
|
|
+};
|
|
|
// 提交密码
|
|
|
const submitPwd = async () => {
|
|
|
- await pwdRef.value?.validate()
|
|
|
- pwdVisible.value = false
|
|
|
- message.success('密码修改成功,请重新登录')
|
|
|
+ await pwdRef.value?.validate();
|
|
|
+ await user.changePaswd(pwdForm);
|
|
|
+ pwdVisible.value = false;
|
|
|
+ message.success("密码修改成功,请重新登录");
|
|
|
// 调用后端修改密码接口
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
// ========== 修改地址弹窗表单 + 省市区级联数据 ==========
|
|
|
-const addrRef = ref<FormInstance>()
|
|
|
+const addrRef = ref<FormInstance>();
|
|
|
// 简易省市区数据源,可替换真实后台接口
|
|
|
|
|
|
const addrForm = reactive({
|
|
|
- country: 'CN',
|
|
|
+ country: "CN",
|
|
|
area: [] as string[],
|
|
|
- detail: '',
|
|
|
- name: '',
|
|
|
- mobile: ''
|
|
|
-})
|
|
|
+ detail: "",
|
|
|
+ name: "",
|
|
|
+ mobile: "",
|
|
|
+});
|
|
|
// 打开地址弹窗回填数据
|
|
|
const openAddrModal = () => {
|
|
|
- addrForm.country = userInfo.country
|
|
|
- addrForm.area = [...userInfo.area]
|
|
|
- addrForm.detail = userInfo.detail
|
|
|
- addrForm.name = userInfo.name
|
|
|
- addrForm.mobile = userInfo.mobile
|
|
|
- addrVisible.value = true
|
|
|
- nextTick(() => addrRef.value?.clearValidate())
|
|
|
-}
|
|
|
+ addrForm.country = userInfo.country;
|
|
|
+ addrForm.area = [...userInfo.area];
|
|
|
+ addrForm.detail = userInfo.detail;
|
|
|
+ addrForm.name = userInfo.name;
|
|
|
+ addrForm.mobile = userInfo.mobile;
|
|
|
+ addrVisible.value = true;
|
|
|
+ nextTick(() => addrRef.value?.clearValidate());
|
|
|
+};
|
|
|
// 提交地址
|
|
|
const submitAddr = async () => {
|
|
|
- await addrRef.value?.validate()
|
|
|
+ await addrRef.value?.validate();
|
|
|
// 拼接展示文本
|
|
|
- const [prov, city, dist] = addrForm.area
|
|
|
- userInfo.addressText = `${prov}${city}${dist}${addrForm.detail} ${addrForm.name} ${addrForm.mobile}`
|
|
|
+ const [prov, city, dist] = addrForm.area;
|
|
|
+ userInfo.addressText = `${prov}${city}${dist}${addrForm.detail} ${addrForm.name} ${addrForm.mobile}`;
|
|
|
// 同步存储地址字段
|
|
|
- userInfo.country = addrForm.country
|
|
|
- userInfo.area = [...addrForm.area]
|
|
|
- userInfo.detail = addrForm.detail
|
|
|
- userInfo.name = addrForm.name
|
|
|
- userInfo.mobile = addrForm.mobile
|
|
|
- addrVisible.value = false
|
|
|
- message.success('地址修改成功')
|
|
|
+ userInfo.country = addrForm.country;
|
|
|
+ userInfo.area = [...addrForm.area];
|
|
|
+ userInfo.detail = addrForm.detail;
|
|
|
+ userInfo.name = addrForm.name;
|
|
|
+ userInfo.mobile = addrForm.mobile;
|
|
|
+ addrVisible.value = false;
|
|
|
+ message.success("地址修改成功");
|
|
|
// 调用后端更新地址接口
|
|
|
-}
|
|
|
+};
|
|
|
+// 发送验证码
|
|
|
+const sendCode = async () => {
|
|
|
+ // 倒计时中直接返回
|
|
|
+ if (sendCodeExTime.value > 0) return;
|
|
|
+
|
|
|
+ const res = await getAuthCode({
|
|
|
+ phone: user.userName,
|
|
|
+ areaNum: user.country,
|
|
|
+ });
|
|
|
+ console.log("sendCode", res);
|
|
|
+ if (!res) return;
|
|
|
+
|
|
|
+ // 默认倒计时60秒
|
|
|
+ sendCodeExTime.value = 60;
|
|
|
+
|
|
|
+ // 后端返回自定义倒计时
|
|
|
+ if (res.code === 3033) {
|
|
|
+ sendCodeExTime.value = Number(res.msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 成功/3033 开启倒计时
|
|
|
+ if (res.code === 0 || res.code === 3033) {
|
|
|
+ timer = window.setInterval(() => {
|
|
|
+ if (sendCodeExTime.value <= 1) {
|
|
|
+ clearInterval(timer!);
|
|
|
+ timer = null;
|
|
|
+ }
|
|
|
+ sendCodeExTime.value--;
|
|
|
+ }, 1000);
|
|
|
+ } else {
|
|
|
+ // 错误提示
|
|
|
+ const tipText = t(`toast.${res.code}`) ? t(`toast.${res.code}`) : res.msg;
|
|
|
+ message.warning(tipText);
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
@@ -324,7 +400,7 @@ const submitAddr = async () => {
|
|
|
.value {
|
|
|
font-weight: normal;
|
|
|
font-size: 12px;
|
|
|
- color: #4E5969;
|
|
|
+ color: #4e5969;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
.item-right {
|