소스 검색

feat(api): 添加获取短信验证码和更新用户手机号接口

新增 getSmsCode 和 updateUserPhone 接口,用于支持用户修改手机号功能。
同时扩展了 UserForm 类型以支持验证码和新手机号字段。

feat(view): 实现用户手机号修改功能在用户详情页面中增加修改手机号的入口与弹窗,支持通过短信验证码验证后更新用户手机号。
新增倒计时逻辑控制验证码发送频率,并完善相关表单校验规则。

feat(view): 调整 tournaments 页面延迟卡配置项

为 tournaments 表单新增 delayCardNum 和 delayCardTime 字段,并添加相应的表单验证规则,限制输入范围分别为 1-99(数量)和10-15(时间)。
fugui001 3 달 전
부모
커밋
a54fdefcb0

+ 22 - 0
src/api/system/business/apiUsers/index.ts

@@ -73,3 +73,25 @@ export const sendRewardToos = (data: GrantForm) => {
     data: data
   });
 };
+
+/**
+ * 获取短信验证码
+ * @param phone 手机号
+ */
+export const getSmsCode = (phone: string) => {
+  return request({
+    url: '/business/user/getSmsCode',
+    method: 'get',
+    params: {
+      phone
+    }
+  });
+};
+
+export const updateUserPhone = (data: UserForm) => {
+  return request({
+    url: '/business/user/updateUserPhone',
+    method: 'put',
+    data: data
+  });
+};

+ 4 - 0
src/api/system/business/apiUsers/types.ts

@@ -259,6 +259,10 @@ export interface UserForm extends BaseEntity {
    * 真实姓名
    */
   realName?: string;
+
+  verifyCode?: string;
+
+  newPhone?: string;
 }
 
 export interface UserQuery extends PageQuery {

+ 163 - 16
src/views/system/business/apiUsers/index.vue

@@ -127,9 +127,7 @@
               </el-tooltip>
 
               <el-tooltip content="查看流水" placement="top">
-                <el-button link type="primary" icon="View" @click="viewPointsLog(scope.row)" v-hasPermi="['business:user:edit']"
-                  >查看流水</el-button
-                >
+                <el-button link type="primary" icon="View" @click="viewPointsLog(scope.row)" v-hasPermi="['business:user:edit']">查看流水</el-button>
               </el-tooltip>
             </div>
           </template>
@@ -156,7 +154,10 @@
           <el-input v-model="form.realName" disabled />
         </el-form-item>
         <el-form-item label="手机号" prop="phone">
-          <el-input v-model="form.phone" disabled />
+          <div style="display: flex; align-items: center; gap: 8px">
+            <el-input v-model="form.phone" disabled />
+            <el-button type="primary" size="small" @click="openEditPhoneDialog(form.id, form.phone)">修改</el-button>
+          </div>
         </el-form-item>
         <el-form-item label="注册时间" prop="createAt">
           <el-date-picker clearable v-model="form.createAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" disabled> </el-date-picker>
@@ -252,11 +253,47 @@
         </span>
       </template>
     </el-dialog>
+
+    <el-dialog :title="editPhoneDialog.title" v-model="editPhoneDialog.visible" width="400px" center>
+      <div class="dialog-content">
+        <!-- 提示信息 -->
+        <div class="tip-text">
+          此操作将替换原手机号:<strong>{{ editPhoneForm.oldPhone }}</strong
+          >,请谨慎操作
+        </div>
+
+        <el-form ref="editPhoneFormRef" :model="editPhoneForm" :rules="editPhoneRules" label-width="80px" style="margin-top: 20px">
+          <el-form-item label="新手机号" prop="newPhone">
+            <el-input v-model="editPhoneForm.newPhone" placeholder="请输入新的手机号" />
+          </el-form-item>
+
+          <el-form-item label="验证码" prop="verifyCode">
+            <div
+              STYLE=" display: flex;
+  align-items: center;
+  gap: 8px;"
+            >
+              <el-input v-model="editPhoneForm.verifyCode" placeholder="请输入验证码" style="width: 180px" />
+              <el-button type="primary" size="small" style="margin-left: 10px" :disabled="countdown > 0" @click="sendVerifyCode">
+                {{ countdown > 0 ? `${countdown}s` : '发送验证码' }}
+              </el-button>
+            </div>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="editPhoneDialog.visible = false">取消</el-button>
+          <el-button type="primary" @click="submitEditPhone">确认修改</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="User" lang="ts">
-import { listUser, getUser, delUser, addUser, updateUser, sendRewardToos } from '@/api/system/business/apiUsers';
+import { listUser, getUser, delUser, addUser, updateUser, sendRewardToos, getSmsCode, updateUserPhone } from '@/api/system/business/apiUsers';
 import { UserVO, UserQuery, UserForm } from '@/api/system/business/apiUsers/types';
 import { selectItemsSelList } from '@/api/system/business/items';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -273,16 +310,6 @@ const total = ref(0);
 const queryFormRef = ref<ElFormInstance>();
 const userFormRef = ref<ElFormInstance>();
 
-const dialog = reactive<DialogOption>({
-  visible: false,
-  title: ''
-});
-
-const editDialog = reactive<DialogOption>({
-  visible: false,
-  title: ''
-});
-
 const initFormData: UserForm = {
   id: undefined,
   loginName: undefined,
@@ -525,7 +552,22 @@ onMounted(() => {
   getList();
   loadItemOptions();
 });
-
+onUnmounted(() => {
+  if (countdownTimer.value) {
+    clearInterval(countdownTimer.value);
+  }
+});
+// 关闭弹窗时清除倒计时
+/*watch(
+  () => editPhoneDialog.visible,
+  (newVal) => {
+    if (!newVal && countdownTimer.value) {
+      clearInterval(countdownTimer.value);
+      countdownTimer.value = null;
+      countdown.value = 0;
+    }
+  }
+);*/
 // 下拉选项数据 selectBlingStructuresInfo
 const itemOptions = ref<{ id: number; label: string }[]>([]);
 
@@ -612,6 +654,111 @@ const viewPointsLog = (row: UserVO) => {
     }
   });
 };
+
+// 弹窗状态和表单数据
+const editPhoneDialog = reactive({
+  visible: false,
+  title: '修改手机号'
+});
+
+const editPhoneForm = reactive({
+  id: null,
+  oldPhone: '',
+  newPhone: '',
+  verifyCode: ''
+});
+const openEditPhoneDialog = (userId: string | number, phone: string) => {
+  editPhoneForm.id = userId;
+  editPhoneForm.oldPhone = phone;
+  editPhoneForm.newPhone = '';
+  editPhoneForm.verifyCode = '';
+  editPhoneDialog.visible = true;
+};
+const editPhoneRules = {
+  newPhone: [
+    { required: true, message: '请输入新手机号', trigger: 'blur' },
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
+  ],
+  verifyCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+};
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const editDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+// 倒计时相关
+const countdown = ref(0); // 当前倒计时秒数
+const countdownTimer = ref<NodeJS.Timeout | null>(null);
+
+// 发送验证码
+const sendVerifyCode = async () => {
+  if (!editPhoneForm.newPhone) {
+    ElMessage.warning('请先填写新手机号');
+    return;
+  }
+  if (countdown.value > 0) {
+    // 正在倒计时中,不允许重复发送
+    return;
+  }
+  try {
+    await getSmsCode(editPhoneForm.newPhone);
+    ElMessage.success('验证码已发送');
+
+    // 启动倒计时(60秒)
+    startCountdown(60);
+  } catch (error) {
+    ElMessage.error('发送失败,请重试');
+  }
+};
+// 启动倒计时
+const startCountdown = (seconds: number) => {
+  countdown.value = seconds;
+
+  if (countdownTimer.value) {
+    clearInterval(countdownTimer.value);
+  }
+
+  countdownTimer.value = setInterval(() => {
+    if (countdown.value > 0) {
+      countdown.value--;
+    } else {
+      if (countdownTimer.value) {
+        clearInterval(countdownTimer.value);
+        countdownTimer.value = null;
+      }
+    }
+  }, 1000);
+};
+const editPhoneFormRef = ref<ElFormInstance>();
+// 提交修改
+const submitEditPhone = async () => {
+  try {
+    // 1. 表单校验
+    await editPhoneFormRef.value?.validate();
+
+    const formData: Partial<UserForm> = {
+      id: editPhoneForm.id,
+      phone: form.value.phone,
+      newPhone: editPhoneForm.newPhone,
+      verifyCode: editPhoneForm.verifyCode
+    };
+    await updateUserPhone(formData).finally(() => (buttonLoading.value = false));
+
+    ElMessage.success('手机号修改成功');
+    editPhoneDialog.visible = false;
+
+    // 更新主表单中的 phone 字段
+    form.value.phone = editPhoneForm.newPhone;
+  } catch (error) {
+    console.error(error);
+  }
+};
 </script>
 <style scoped>
 .action-container {

+ 39 - 1
src/views/system/business/tournaments/index.vue

@@ -770,7 +770,9 @@ const initFormData: TournamentsForm = {
   itemsId: null,
   itemsNum: null,
   blindStructureId: null,
-  itemsPrizeList: []
+  itemsPrizeList: [],
+  delayCardNum: 4,
+  delayCardTime: 15
 };
 const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
   form: { ...initFormData },
@@ -843,6 +845,42 @@ const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
         },
         trigger: 'blur'
       }
+    ],
+    delayCardTime: [
+      { required: false, message: '延迟卡时间不能为空', trigger: 'blur' },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          const num = Number(value);
+          if (!value) {
+            callback(new Error('请输入延迟卡时间'));
+          } else if (!/^\d+$/.test(value)) {
+            callback(new Error('只能输入正整数'));
+          } else if (num < 10 || num > 15) {
+            callback(new Error('延迟卡时间必须在10-15秒之间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'blur'
+      }
+    ],
+    delayCardNum: [
+      { required: false, message: '延迟卡数量不能为空', trigger: 'blur' },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          const num = Number(value);
+          if (!value) {
+            callback(new Error('请输入延迟卡数量'));
+          } else if (!/^\d+$/.test(value)) {
+            callback(new Error('只能输入正整数'));
+          } else if (num < 1 || num > 99) {
+            callback(new Error('延迟卡数量必须在1-99之间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'blur'
+      }
     ]
   }
 });