Forráskód Böngészése

1-20-zl-我的工作台

林小张 2 hónapja
szülő
commit
e06f22701b

+ 12 - 0
src/router/index.ts

@@ -31,6 +31,18 @@ const routes: RouteRecordRaw[] = [
             component: () => import('@/views/enterprise/securitySetting/index.vue'),
             meta: { title: '安全设置', hidden: true }
           },
+          {
+            path: 'securitySetting/resetPassword',
+            name: 'ResetPassword',
+            component: () => import('@/views/enterprise/securitySetting/resetPassword.vue'),
+            meta: { title: '重置密码', hidden: true }
+          },
+          {
+            path: 'securitySetting/changePhone',
+            name: 'ChangePhone',
+            component: () => import('@/views/enterprise/securitySetting/changePhone.vue'),
+            meta: { title: '更换手机号码', hidden: true }
+          },
           {
             path: 'purchaseHabit',
             name: 'PurchaseHabit',

+ 408 - 0
src/views/enterprise/securitySetting/changePhone.vue

@@ -0,0 +1,408 @@
+<template>
+  <div class="change-phone-container">
+    <!-- 面包屑导航 -->
+    <div class="breadcrumb">
+      <span class="breadcrumb-item" @click="$router.push('/')">首页</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item" @click="$router.push('/enterprise/companyInfo')">客户中心</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item" @click="$router.push('/enterprise/securitySetting')">安全设置</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item active">更换手机号码</span>
+    </div>
+
+    <!-- 主体内容 -->
+    <div class="change-content">
+      <div class="change-title">更换手机号码</div>
+
+      <!-- 步骤条 -->
+      <div class="steps-container">
+        <div class="step-item" :class="{ active: currentStep >= 1, done: currentStep > 1 }">
+          <div class="step-circle">
+            <el-icon v-if="currentStep > 1"><Check /></el-icon>
+            <span v-else>1</span>
+          </div>
+          <div class="step-label">验证身份</div>
+        </div>
+        <div class="step-line" :class="{ active: currentStep > 1 }"></div>
+        <div class="step-item" :class="{ active: currentStep >= 2, done: currentStep > 2 }">
+          <div class="step-circle">
+            <el-icon v-if="currentStep > 2"><Check /></el-icon>
+            <span v-else>2</span>
+          </div>
+          <div class="step-label">修改手机号码</div>
+        </div>
+        <div class="step-line" :class="{ active: currentStep > 2 }"></div>
+        <div class="step-item" :class="{ active: currentStep >= 3 }">
+          <div class="step-circle">
+            <span>3</span>
+          </div>
+          <div class="step-label">完成更换</div>
+        </div>
+      </div>
+
+      <!-- 步骤1:验证身份 -->
+      <div class="step-content" v-if="currentStep === 1">
+        <div class="phone-info">
+          <span>已绑定的手机:</span>
+          <span class="phone-number">180****7722</span>
+        </div>
+        <div class="phone-tip">若该手机号已无法使用请联系客服</div>
+
+        <el-form ref="step1FormRef" :model="step1Form" :rules="step1Rules" class="verify-form">
+          <el-form-item label="验证码:" prop="code">
+            <el-input v-model="step1Form.code" placeholder="短信验证码" class="code-input" />
+            <el-button link type="primary" class="send-code-btn" :disabled="countdown > 0" @click="handleSendCode">
+              {{ countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
+            </el-button>
+          </el-form-item>
+          <el-form-item class="verify-checkbox">
+            <el-checkbox v-model="step1Form.verified">点击验证</el-checkbox>
+          </el-form-item>
+        </el-form>
+
+        <div class="step-actions">
+          <el-button type="success" class="next-btn" @click="handleNextStep">下一步</el-button>
+        </div>
+
+        <!-- 温馨提醒 -->
+        <div class="tips-box">
+          <div class="tips-title">温馨提醒</div>
+          <ul class="tips-list">
+            <li>为保障您的帐号安全,变更重要信息需要身份验证</li>
+            <li>若有疑问请联系在线客服或拨打400-111-0027(周一至周日 8:00-18:00)</li>
+          </ul>
+        </div>
+      </div>
+
+      <!-- 步骤2:修改手机号码 -->
+      <div class="step-content" v-if="currentStep === 2">
+        <el-form ref="step2FormRef" :model="step2Form" :rules="step2Rules" label-width="120px" class="phone-form">
+          <el-form-item label="新手机号码:" prop="newPhone">
+            <el-input v-model="step2Form.newPhone" placeholder="请输入新手机号码" class="form-input" />
+          </el-form-item>
+          <el-form-item label="验证码:" prop="newCode">
+            <el-input v-model="step2Form.newCode" placeholder="短信验证码" class="code-input" />
+            <el-button link type="primary" class="send-code-btn" :disabled="newCountdown > 0" @click="handleSendNewCode">
+              {{ newCountdown > 0 ? `${newCountdown}s后重发` : '发送验证码' }}
+            </el-button>
+          </el-form-item>
+        </el-form>
+        <div class="step-actions">
+          <el-button @click="currentStep = 1">上一步</el-button>
+          <el-button type="success" class="next-btn" @click="handleSubmit">确认更换</el-button>
+        </div>
+      </div>
+
+      <!-- 步骤3:完成 -->
+      <div class="step-content" v-if="currentStep === 3">
+        <div class="success-content">
+          <el-icon class="success-icon" color="#52c41a" :size="60"><CircleCheckFilled /></el-icon>
+          <div class="success-title">手机号码更换成功!</div>
+          <div class="success-desc">您的安全手机已更换为 {{ step2Form.newPhone }}</div>
+          <el-button type="success" @click="$router.push('/enterprise/securitySetting')">返回安全设置</el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { Check, CircleCheckFilled } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+const currentStep = ref(1)
+
+// 步骤1表单
+const step1FormRef = ref()
+const step1Form = reactive({
+  code: '',
+  verified: false
+})
+
+const countdown = ref(0)
+
+const step1Rules = {
+  code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+}
+
+// 步骤2表单
+const step2FormRef = ref()
+const step2Form = reactive({
+  newPhone: '',
+  newCode: ''
+})
+
+const newCountdown = ref(0)
+
+const step2Rules = {
+  newPhone: [
+    { required: true, message: '请输入新手机号码', trigger: 'blur' },
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+  ],
+  newCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+}
+
+// 发送验证码(旧手机)
+const handleSendCode = () => {
+  countdown.value = 60
+  const timer = setInterval(() => {
+    countdown.value--
+    if (countdown.value <= 0) {
+      clearInterval(timer)
+    }
+  }, 1000)
+  ElMessage.success('验证码已发送')
+}
+
+// 发送验证码(新手机)
+const handleSendNewCode = () => {
+  if (!step2Form.newPhone) {
+    ElMessage.warning('请先输入新手机号码')
+    return
+  }
+  newCountdown.value = 60
+  const timer = setInterval(() => {
+    newCountdown.value--
+    if (newCountdown.value <= 0) {
+      clearInterval(timer)
+    }
+  }, 1000)
+  ElMessage.success('验证码已发送')
+}
+
+// 下一步
+const handleNextStep = async () => {
+  const valid = await step1FormRef.value?.validate().catch(() => false)
+  if (!valid) return
+  
+  if (!step1Form.verified) {
+    ElMessage.warning('请先点击验证')
+    return
+  }
+  
+  currentStep.value = 2
+}
+
+// 提交
+const handleSubmit = async () => {
+  const valid = await step2FormRef.value?.validate().catch(() => false)
+  if (!valid) return
+  
+  // TODO: 调用更换手机接口
+  ElMessage.success('手机号码更换成功')
+  currentStep.value = 3
+}
+</script>
+
+<style scoped lang="scss">
+.change-phone-container {
+  background: #f5f5f5;
+  min-height: 100%;
+  padding: 0;
+}
+
+// 面包屑
+.breadcrumb {
+  padding: 15px 20px;
+  font-size: 13px;
+  color: #666;
+  background: #fff;
+  border-bottom: 1px solid #eee;
+  
+  .breadcrumb-item {
+    cursor: pointer;
+    &:hover { color: #e60012; }
+    &.active { color: #333; cursor: default; &:hover { color: #333; } }
+  }
+  .separator { margin: 0 8px; color: #999; }
+}
+
+// 主体内容
+.change-content {
+  max-width: 800px;
+  margin: 30px auto;
+  padding: 30px 40px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.change-title {
+  font-size: 18px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 30px;
+}
+
+// 步骤条
+.steps-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 50px;
+  padding: 0 60px;
+}
+
+.step-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  
+  .step-circle {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    border: 2px solid #ddd;
+    background: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 14px;
+    color: #999;
+    margin-bottom: 10px;
+  }
+  
+  .step-label {
+    font-size: 14px;
+    color: #999;
+  }
+  
+  &.active {
+    .step-circle {
+      border-color: #52c41a;
+      color: #52c41a;
+    }
+    .step-label { color: #52c41a; }
+  }
+  
+  &.done {
+    .step-circle {
+      border-color: #52c41a;
+      background: #52c41a;
+      color: #fff;
+    }
+    .step-label { color: #52c41a; }
+  }
+}
+
+.step-line {
+  flex: 1;
+  height: 2px;
+  background: #ddd;
+  margin: 0 20px;
+  margin-bottom: 30px;
+  
+  &.active { background: #52c41a; }
+}
+
+// 步骤内容
+.step-content {
+  padding: 0 40px;
+}
+
+.phone-info {
+  text-align: center;
+  font-size: 16px;
+  color: #333;
+  margin-bottom: 10px;
+  
+  .phone-number {
+    font-weight: 500;
+  }
+}
+
+.phone-tip {
+  text-align: center;
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 30px;
+}
+
+// 表单
+.verify-form, .phone-form {
+  max-width: 400px;
+  margin: 0 auto;
+  
+  .code-input {
+    width: 180px;
+  }
+  
+  .form-input {
+    width: 280px;
+  }
+  
+  .send-code-btn {
+    margin-left: 15px;
+    color: #52c41a;
+  }
+  
+  .verify-checkbox {
+    :deep(.el-form-item__content) {
+      justify-content: center;
+    }
+  }
+}
+
+// 操作按钮
+.step-actions {
+  display: flex;
+  justify-content: center;
+  margin-top: 30px;
+  gap: 15px;
+  
+  .next-btn {
+    width: 200px;
+    height: 40px;
+  }
+}
+
+// 温馨提醒
+.tips-box {
+  margin-top: 40px;
+  padding: 20px;
+  background: #fffbe6;
+  border: 1px solid #ffe58f;
+  border-radius: 4px;
+  
+  .tips-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #d48806;
+    margin-bottom: 10px;
+  }
+  
+  .tips-list {
+    margin: 0;
+    padding-left: 20px;
+    font-size: 13px;
+    color: #666;
+    line-height: 1.8;
+  }
+}
+
+// 成功页面
+.success-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 40px 0;
+  
+  .success-icon {
+    margin-bottom: 20px;
+  }
+  
+  .success-title {
+    font-size: 20px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 10px;
+  }
+  
+  .success-desc {
+    font-size: 14px;
+    color: #999;
+    margin-bottom: 30px;
+  }
+}
+</style>

+ 3 - 3
src/views/enterprise/securitySetting/index.vue

@@ -42,7 +42,7 @@
           <div class="setting-title">安全手机{{ securityData.phone }}</div>
           <div class="setting-desc">安全手机可以用于登录帐号,重置密码或其他安全验证</div>
         </div>
-        <el-button type="danger" @click="handleModifyPhone">修改</el-button>
+        <el-button type="danger" @click="handleChangePhone">更换</el-button>
       </div>
     </div>
 
@@ -128,9 +128,9 @@ const phoneRules = {
 }
 
 const handleBack = () => { router.push('/enterprise/companyInfo') }
-const handleModifyPassword = () => { passwordForm.oldPassword = ''; passwordForm.newPassword = ''; passwordForm.confirmPassword = ''; passwordDialogVisible.value = true }
+const handleModifyPassword = () => { router.push('/enterprise/securitySetting/resetPassword') }
 const handleSavePassword = async () => { const valid = await passwordFormRef.value?.validate(); if (!valid) return; ElMessage.success('密码修改成功'); passwordDialogVisible.value = false }
-const handleModifyPhone = () => { phoneForm.phone = ''; phoneForm.code = ''; phoneDialogVisible.value = true }
+const handleChangePhone = () => { router.push('/enterprise/securitySetting/changePhone') }
 const handleSendCode = () => { if (!phoneForm.phone) { ElMessage.warning('请先输入手机号'); return }; countdown.value = 60; const timer = setInterval(() => { countdown.value--; if (countdown.value <= 0) clearInterval(timer) }, 1000); ElMessage.success('验证码已发送') }
 const handleSavePhone = async () => { const valid = await phoneFormRef.value?.validate(); if (!valid) return; ElMessage.success('手机号修改成功'); phoneDialogVisible.value = false }
 </script>

+ 350 - 0
src/views/enterprise/securitySetting/resetPassword.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="reset-password-container">
+    <!-- 面包屑导航 -->
+    <div class="breadcrumb">
+      <span class="breadcrumb-item" @click="$router.push('/')">首页</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item" @click="$router.push('/enterprise/companyInfo')">客户中心</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item" @click="$router.push('/enterprise/securitySetting')">安全设置</span>
+      <span class="separator">&gt;</span>
+      <span class="breadcrumb-item active">重置密码</span>
+    </div>
+
+    <!-- 主体内容 -->
+    <div class="reset-content">
+      <div class="reset-title">重置密码</div>
+
+      <!-- 步骤条 -->
+      <div class="steps-container">
+        <div class="step-item" :class="{ active: currentStep >= 1, done: currentStep > 1 }">
+          <div class="step-circle">
+            <el-icon v-if="currentStep > 1"><Check /></el-icon>
+            <span v-else>1</span>
+          </div>
+          <div class="step-label">验证身份</div>
+        </div>
+        <div class="step-line" :class="{ active: currentStep > 1 }"></div>
+        <div class="step-item" :class="{ active: currentStep >= 2, done: currentStep > 2 }">
+          <div class="step-circle">
+            <el-icon v-if="currentStep > 2"><Check /></el-icon>
+            <span v-else>2</span>
+          </div>
+          <div class="step-label">设置新密码</div>
+        </div>
+        <div class="step-line" :class="{ active: currentStep > 2 }"></div>
+        <div class="step-item" :class="{ active: currentStep >= 3 }">
+          <div class="step-circle">
+            <span>3</span>
+          </div>
+          <div class="step-label">完成</div>
+        </div>
+      </div>
+
+      <!-- 步骤1:验证身份 -->
+      <div class="step-content" v-if="currentStep === 1">
+        <el-form ref="step1FormRef" :model="step1Form" :rules="step1Rules" label-width="100px" class="verify-form">
+          <el-form-item label="手机号码:" prop="phone">
+            <el-input v-model="step1Form.phone" disabled class="form-input" />
+            <span class="form-tip">若该手机号已无法使用请联系客服</span>
+          </el-form-item>
+          <el-form-item label="验证码:" prop="code">
+            <el-input v-model="step1Form.code" placeholder="短信验证码" class="form-input code-input" />
+            <el-button link type="primary" class="send-code-btn" :disabled="countdown > 0" @click="handleSendCode">
+              {{ countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
+            </el-button>
+          </el-form-item>
+          <el-form-item label="" class="verify-checkbox">
+            <el-checkbox v-model="step1Form.verified">点击验证</el-checkbox>
+          </el-form-item>
+        </el-form>
+        <div class="step-actions">
+          <el-button type="danger" class="next-btn" @click="handleNextStep">下一步</el-button>
+        </div>
+      </div>
+
+      <!-- 步骤2:设置新密码 -->
+      <div class="step-content" v-if="currentStep === 2">
+        <el-form ref="step2FormRef" :model="step2Form" :rules="step2Rules" label-width="100px" class="password-form">
+          <el-form-item label="新密码:" prop="newPassword">
+            <el-input v-model="step2Form.newPassword" type="password" show-password placeholder="请输入新密码" class="form-input" />
+          </el-form-item>
+          <el-form-item label="确认密码:" prop="confirmPassword">
+            <el-input v-model="step2Form.confirmPassword" type="password" show-password placeholder="请再次输入新密码" class="form-input" />
+          </el-form-item>
+        </el-form>
+        <div class="step-actions">
+          <el-button @click="currentStep = 1">上一步</el-button>
+          <el-button type="danger" class="next-btn" @click="handleSubmit">确认修改</el-button>
+        </div>
+      </div>
+
+      <!-- 步骤3:完成 -->
+      <div class="step-content" v-if="currentStep === 3">
+        <div class="success-content">
+          <el-icon class="success-icon" color="#52c41a" :size="60"><CircleCheckFilled /></el-icon>
+          <div class="success-title">密码重置成功!</div>
+          <div class="success-desc">您的登录密码已重置成功,请使用新密码登录</div>
+          <el-button type="danger" @click="$router.push('/enterprise/securitySetting')">返回安全设置</el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { Check, CircleCheckFilled } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+const currentStep = ref(1)
+
+// 步骤1表单
+const step1FormRef = ref()
+const step1Form = reactive({
+  phone: '18062697722',
+  code: '',
+  verified: false
+})
+
+const countdown = ref(0)
+
+const step1Rules = {
+  code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+}
+
+// 步骤2表单
+const step2FormRef = ref()
+const step2Form = reactive({
+  newPassword: '',
+  confirmPassword: ''
+})
+
+const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
+  if (value !== step2Form.newPassword) {
+    callback(new Error('两次输入的密码不一致'))
+  } else {
+    callback()
+  }
+}
+
+const step2Rules = {
+  newPassword: [
+    { required: true, message: '请输入新密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
+  ],
+  confirmPassword: [
+    { required: true, message: '请再次输入新密码', trigger: 'blur' },
+    { validator: validateConfirmPassword, trigger: 'blur' }
+  ]
+}
+
+// 发送验证码
+const handleSendCode = () => {
+  countdown.value = 60
+  const timer = setInterval(() => {
+    countdown.value--
+    if (countdown.value <= 0) {
+      clearInterval(timer)
+    }
+  }, 1000)
+  ElMessage.success('验证码已发送')
+}
+
+// 下一步
+const handleNextStep = async () => {
+  const valid = await step1FormRef.value?.validate().catch(() => false)
+  if (!valid) return
+  
+  if (!step1Form.verified) {
+    ElMessage.warning('请先点击验证')
+    return
+  }
+  
+  currentStep.value = 2
+}
+
+// 提交
+const handleSubmit = async () => {
+  const valid = await step2FormRef.value?.validate().catch(() => false)
+  if (!valid) return
+  
+  // TODO: 调用修改密码接口
+  ElMessage.success('密码重置成功')
+  currentStep.value = 3
+}
+</script>
+
+<style scoped lang="scss">
+.reset-password-container {
+  background: #f5f5f5;
+  min-height: 100%;
+  padding: 0;
+}
+
+// 面包屑
+.breadcrumb {
+  padding: 15px 20px;
+  font-size: 13px;
+  color: #666;
+  background: #fff;
+  border-bottom: 1px solid #eee;
+  
+  .breadcrumb-item {
+    cursor: pointer;
+    &:hover { color: #e60012; }
+    &.active { color: #333; cursor: default; &:hover { color: #333; } }
+  }
+  .separator { margin: 0 8px; color: #999; }
+}
+
+// 主体内容
+.reset-content {
+  max-width: 800px;
+  margin: 30px auto;
+  padding: 30px 40px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.reset-title {
+  font-size: 18px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 30px;
+}
+
+// 步骤条
+.steps-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 50px;
+  padding: 0 60px;
+}
+
+.step-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  
+  .step-circle {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    border: 2px solid #ddd;
+    background: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 14px;
+    color: #999;
+    margin-bottom: 10px;
+  }
+  
+  .step-label {
+    font-size: 14px;
+    color: #999;
+  }
+  
+  &.active {
+    .step-circle {
+      border-color: #e60012;
+      color: #e60012;
+    }
+    .step-label { color: #e60012; }
+  }
+  
+  &.done {
+    .step-circle {
+      border-color: #52c41a;
+      background: #52c41a;
+      color: #fff;
+    }
+    .step-label { color: #52c41a; }
+  }
+}
+
+.step-line {
+  flex: 1;
+  height: 2px;
+  background: #ddd;
+  margin: 0 20px;
+  margin-bottom: 30px;
+  
+  &.active { background: #52c41a; }
+}
+
+// 表单
+.step-content {
+  padding: 0 60px;
+}
+
+.verify-form, .password-form {
+  max-width: 500px;
+  margin: 0 auto;
+  
+  .form-input {
+    width: 280px;
+  }
+  
+  .code-input {
+    width: 180px;
+  }
+  
+  .form-tip {
+    margin-left: 15px;
+    font-size: 12px;
+    color: #e60012;
+    cursor: pointer;
+  }
+  
+  .send-code-btn {
+    margin-left: 15px;
+    color: #e60012;
+  }
+  
+  .verify-checkbox {
+    :deep(.el-form-item__content) {
+      margin-left: 100px !important;
+    }
+  }
+}
+
+// 操作按钮
+.step-actions {
+  display: flex;
+  justify-content: center;
+  margin-top: 40px;
+  
+  .next-btn {
+    width: 200px;
+    height: 40px;
+  }
+}
+
+// 成功页面
+.success-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 40px 0;
+  
+  .success-icon {
+    margin-bottom: 20px;
+  }
+  
+  .success-title {
+    font-size: 20px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 10px;
+  }
+  
+  .success-desc {
+    font-size: 14px;
+    color: #999;
+    margin-bottom: 30px;
+  }
+}
+</style>