2025-10-22 13:54:05 +08:00
|
|
|
<template>
|
|
|
|
|
<!-- 离场见证-线下页面 -->
|
|
|
|
|
<view class="offline-witness-container">
|
|
|
|
|
<!-- 导航栏 -->
|
|
|
|
|
<view class="nav-bar">
|
|
|
|
|
<view class="nav-left" @tap="goBack">
|
|
|
|
|
<up-icon name="arrow-left" size="20" color="#333" />
|
|
|
|
|
<text class="nav-text">返回</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="nav-title">离场见证-线下</view>
|
|
|
|
|
<view class="nav-right"></view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 进度指示器 -->
|
|
|
|
|
<view>
|
|
|
|
|
<up-steps :current="currentStep">
|
|
|
|
|
<up-steps-item
|
|
|
|
|
:key="item.title"
|
|
|
|
|
:title="item.title"
|
|
|
|
|
:error="item.isError"
|
|
|
|
|
v-for="item in stepList"
|
|
|
|
|
/>
|
|
|
|
|
</up-steps>
|
|
|
|
|
</view>
|
|
|
|
|
<!-- 表单内容 -->
|
|
|
|
|
|
|
|
|
|
<template v-if="currentStep === 0">
|
|
|
|
|
<view class="form-container">
|
|
|
|
|
<up-form ref="formRef" :model="formData" :rules="formRules" labelWidth="auto">
|
|
|
|
|
<!-- 手机号码输入 -->
|
|
|
|
|
<up-form-item prop="phone">
|
|
|
|
|
<up-input
|
|
|
|
|
v-model="formData.phone"
|
|
|
|
|
placeholder="请输入手机号码"
|
|
|
|
|
prefixIcon="phone"
|
|
|
|
|
clearable
|
|
|
|
|
class="custom-input"
|
|
|
|
|
/>
|
|
|
|
|
</up-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 验证码输入 -->
|
|
|
|
|
<up-form-item prop="code" class="code-form-item">
|
2025-10-23 17:49:28 +08:00
|
|
|
<view style="width: 63%">
|
2025-10-22 13:54:05 +08:00
|
|
|
<up-input
|
|
|
|
|
v-model="formData.code"
|
|
|
|
|
placeholder="请输入验证码"
|
|
|
|
|
prefixIcon="email"
|
|
|
|
|
clearable
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<up-button
|
|
|
|
|
:text="codeButtonText"
|
|
|
|
|
:disabled="codeButtonDisabled"
|
|
|
|
|
:loading="codeLoading"
|
|
|
|
|
type="primary"
|
|
|
|
|
class="code-button"
|
|
|
|
|
color="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
|
|
|
|
@tap="getVerificationCode"
|
|
|
|
|
/>
|
|
|
|
|
</up-form-item>
|
|
|
|
|
</up-form>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 下一步按钮 -->
|
|
|
|
|
<view class="button-container">
|
|
|
|
|
<up-button
|
|
|
|
|
text="下一步"
|
|
|
|
|
type="primary"
|
|
|
|
|
:loading="submitLoading"
|
|
|
|
|
class="next-button"
|
|
|
|
|
@tap="handleNext"
|
|
|
|
|
color="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template v-if="currentStep === 1">
|
2025-10-23 17:49:28 +08:00
|
|
|
<Upload @goBack="onGoBack" :phone="formData.phone" />
|
2025-10-22 13:54:05 +08:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 加载页面 -->
|
|
|
|
|
<up-loading-page
|
|
|
|
|
color="#fff"
|
|
|
|
|
fontSize="16"
|
|
|
|
|
loadingColor="#2979ff"
|
|
|
|
|
:loading="submitLoading"
|
|
|
|
|
bg-color="rgba(0,0,0,0.7)"
|
|
|
|
|
loading-text="正在验证..."
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed, onUnmounted } from 'vue'
|
|
|
|
|
import { debounce } from 'lodash-es'
|
2025-10-23 17:49:28 +08:00
|
|
|
import { useMemberStore } from '@/stores'
|
2025-10-22 13:54:05 +08:00
|
|
|
import Upload from './upload.vue'
|
2025-10-23 17:49:28 +08:00
|
|
|
import { getSmsCodeApi, nextLoginApi } from '@/services/offline-witness'
|
|
|
|
|
|
|
|
|
|
const memberStore = useMemberStore() // 用户信息
|
2025-10-22 13:54:05 +08:00
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const formRef = ref(null)
|
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
|
const codeLoading = ref(false)
|
|
|
|
|
const countdown = ref(0)
|
|
|
|
|
const countdownTimer = ref(null)
|
|
|
|
|
const currentStep = ref(0)
|
|
|
|
|
const stepList = ref([
|
|
|
|
|
{
|
|
|
|
|
title: '短信验证',
|
|
|
|
|
isError: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '见证上传',
|
|
|
|
|
isError: false,
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
const formData = ref({
|
|
|
|
|
phone: '',
|
|
|
|
|
code: '',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 表单验证规则
|
|
|
|
|
const formRules = ref({
|
|
|
|
|
phone: [
|
|
|
|
|
{
|
|
|
|
|
type: 'string',
|
|
|
|
|
required: true,
|
|
|
|
|
message: '请输入手机号码',
|
|
|
|
|
trigger: ['blur', 'change'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: /^1[3-9]\d{9}$/,
|
|
|
|
|
message: '请输入正确的手机号码',
|
|
|
|
|
trigger: ['blur', 'change'],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
code: [
|
|
|
|
|
{
|
|
|
|
|
type: 'string',
|
|
|
|
|
required: true,
|
|
|
|
|
message: '请输入验证码',
|
|
|
|
|
trigger: ['blur', 'change'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: /^\d{6}$/,
|
|
|
|
|
message: '请输入6位数字验证码',
|
|
|
|
|
trigger: ['blur', 'change'],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
const codeButtonText = computed(() => {
|
|
|
|
|
return countdown.value > 0 ? `${countdown.value}s后重新获取` : '获取验证码'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const codeButtonDisabled = computed(() => {
|
|
|
|
|
return (
|
|
|
|
|
countdown.value > 0 || !formData.value.phone || !/^1[3-9]\d{9}$/.test(formData.value.phone)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const submitDisabled = computed(() => {
|
|
|
|
|
return (
|
|
|
|
|
!formData.value.phone ||
|
|
|
|
|
!formData.value.code ||
|
|
|
|
|
!/^1[3-9]\d{9}$/.test(formData.value.phone) ||
|
|
|
|
|
!/^\d{6}$/.test(formData.value.code)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 返回上一页
|
|
|
|
|
const goBack = () => {
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回上一页
|
|
|
|
|
const onGoBack = () => {
|
|
|
|
|
currentStep.value = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取验证码
|
|
|
|
|
const getVerificationCode = debounce(async () => {
|
|
|
|
|
if (codeButtonDisabled.value) return
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
codeLoading.value = true
|
|
|
|
|
|
|
|
|
|
// 这里应该调用获取验证码的API
|
2025-10-23 17:49:28 +08:00
|
|
|
const res = await getSmsCodeApi({
|
|
|
|
|
username: formData.value.phone,
|
|
|
|
|
verificationCodeType: 'WORKER_LOGIN',
|
|
|
|
|
})
|
|
|
|
|
// console.log(res, '获取验证码')
|
2025-10-22 13:54:05 +08:00
|
|
|
|
|
|
|
|
// 模拟API调用
|
|
|
|
|
|
|
|
|
|
uni.$u.toast('验证码已发送')
|
|
|
|
|
|
|
|
|
|
// 开始倒计时
|
|
|
|
|
startCountdown()
|
|
|
|
|
} catch (error) {
|
2025-10-23 17:49:28 +08:00
|
|
|
uni.$u.toast(error?.data?.msg)
|
2025-10-22 13:54:05 +08:00
|
|
|
} finally {
|
|
|
|
|
codeLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}, 500)
|
|
|
|
|
|
|
|
|
|
// 开始倒计时
|
|
|
|
|
const startCountdown = () => {
|
|
|
|
|
countdown.value = 60
|
|
|
|
|
|
|
|
|
|
countdownTimer.value = setInterval(() => {
|
|
|
|
|
countdown.value--
|
|
|
|
|
if (countdown.value <= 0) {
|
|
|
|
|
clearInterval(countdownTimer.value)
|
|
|
|
|
countdownTimer.value = null
|
|
|
|
|
}
|
|
|
|
|
}, 1000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 下一步处理
|
|
|
|
|
const handleNext = debounce(() => {
|
|
|
|
|
if (submitDisabled.value) return
|
|
|
|
|
|
|
|
|
|
formRef.value
|
|
|
|
|
.validate()
|
|
|
|
|
.then(async (valid) => {
|
|
|
|
|
if (valid) {
|
|
|
|
|
try {
|
|
|
|
|
submitLoading.value = true
|
|
|
|
|
|
|
|
|
|
// 这里应该调用验证短信验证码的API
|
2025-10-23 17:49:28 +08:00
|
|
|
const res = await nextLoginApi({
|
|
|
|
|
username: formData.value.phone,
|
|
|
|
|
loginType: 'PHONE_OTP_WORKER',
|
|
|
|
|
verificationCode: formData.value.code,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
console.log(res, '验证结果')
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
memberStore.setToken(res.data.access_token)
|
|
|
|
|
}
|
2025-10-22 13:54:05 +08:00
|
|
|
uni.$u.toast('验证成功')
|
|
|
|
|
|
2025-10-23 17:49:28 +08:00
|
|
|
// 打开见证上传页面
|
|
|
|
|
currentStep.value = 1
|
2025-10-22 13:54:05 +08:00
|
|
|
} catch (error) {
|
2025-10-23 17:49:28 +08:00
|
|
|
uni.$u.toast(error?.data?.msg)
|
2025-10-22 13:54:05 +08:00
|
|
|
} finally {
|
|
|
|
|
submitLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
submitLoading.value = false
|
|
|
|
|
})
|
|
|
|
|
}, 500)
|
|
|
|
|
|
|
|
|
|
// 页面卸载时清理定时器
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
if (countdownTimer.value) {
|
|
|
|
|
clearInterval(countdownTimer.value)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.offline-witness-container {
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 导航栏
|
|
|
|
|
.nav-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 20rpx 30rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
|
|
.nav-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
.nav-text {
|
|
|
|
|
margin-left: 10rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-title {
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-right {
|
|
|
|
|
width: 80rpx; // 占位,保持标题居中
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 进度指示器
|
|
|
|
|
.progress-container {
|
|
|
|
|
padding: 60rpx 30rpx 40rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
|
|
|
|
.progress-line {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
.progress-step {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
|
|
|
|
.step-circle {
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
border: 4rpx solid #e0e0e0;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
background-color: #2979ff;
|
|
|
|
|
border-color: #2979ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-text {
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
transition: color 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.active .step-text {
|
|
|
|
|
color: #2979ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-connector {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 20rpx;
|
|
|
|
|
left: 50%;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
width: 200rpx;
|
|
|
|
|
height: 4rpx;
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
transition: background-color 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
background-color: #2979ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 表单容器
|
|
|
|
|
.form-container {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 40rpx 30rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
|
|
|
|
.code-form-item {
|
|
|
|
|
:deep(.up-form-item__body) {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-input {
|
|
|
|
|
width: 70%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-button {
|
|
|
|
|
// min-width: 180rpx;
|
|
|
|
|
flex: 1;
|
2025-10-23 17:49:28 +08:00
|
|
|
// height: 80rpx;
|
2025-10-22 13:54:05 +08:00
|
|
|
font-size: 24rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按钮容器
|
|
|
|
|
.button-container {
|
|
|
|
|
padding: 40rpx 30rpx 60rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
|
|
|
|
.next-button {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 88rpx;
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
border-radius: 44rpx;
|
|
|
|
|
// background: linear-gradient(135deg, #2979ff, #5e35b1);
|
|
|
|
|
// box-shadow: 0 8rpx 20rpx rgba(41, 121, 255, 0.3);
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
transform: translateY(2rpx);
|
|
|
|
|
box-shadow: 0 4rpx 10rpx rgba(41, 121, 255, 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
background: #e0e0e0;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
transform: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自定义输入框样式
|
|
|
|
|
:deep(.custom-input) {
|
|
|
|
|
.up-input__content {
|
|
|
|
|
height: 88rpx;
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
border: 2rpx solid #e0e0e0;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
transition: border-color 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&:focus-within {
|
|
|
|
|
border-color: #2979ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.up-input__content__field-wrapper__field {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
|
|
|
|
|
&::placeholder {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.up-input__content__prefix-icon {
|
|
|
|
|
color: #999;
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 响应式适配
|
|
|
|
|
@media (max-width: 480px) {
|
|
|
|
|
.progress-container {
|
|
|
|
|
padding: 40rpx 20rpx 30rpx;
|
|
|
|
|
|
|
|
|
|
.progress-line {
|
|
|
|
|
.progress-connector {
|
|
|
|
|
width: 150rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-container {
|
|
|
|
|
padding: 30rpx 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-container {
|
|
|
|
|
padding: 30rpx 20rpx 40rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|