feat: 添加二维码和加载组件功能

- 新增 qrcode.vue 依赖用于生成二维码
- 实现 Loading 组件,支持多种动画类型和配置
- 在首页表单中添加二维码显示功能
- 优化表单验证流程,添加加载状态管理
- 完善用户数据本地存储功能
This commit is contained in:
前端小啊白 2025-11-15 14:42:39 +08:00
parent 0d3462a8f5
commit b515b793b6
6 changed files with 903 additions and 11 deletions

View File

@ -12,6 +12,7 @@
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"axios": "^1.13.2", "axios": "^1.13.2",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"qrcode.vue": "^3.6.0",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"vue": "^3.5.24", "vue": "^3.5.24",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"

View File

@ -17,6 +17,9 @@ importers:
dayjs: dayjs:
specifier: ^1.11.19 specifier: ^1.11.19
version: 1.11.19 version: 1.11.19
qrcode.vue:
specifier: ^3.6.0
version: 3.6.0(vue@3.5.24(typescript@5.9.3))
tailwindcss: tailwindcss:
specifier: ^4.1.17 specifier: ^4.1.17
version: 4.1.17 version: 4.1.17
@ -751,6 +754,11 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
qrcode.vue@3.6.0:
resolution: {integrity: sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==}
peerDependencies:
vue: ^3.0.0
rollup@4.53.2: rollup@4.53.2:
resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -1406,6 +1414,10 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
qrcode.vue@3.6.0(vue@3.5.24(typescript@5.9.3)):
dependencies:
vue: 3.5.24(typescript@5.9.3)
rollup@4.53.2: rollup@4.53.2:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8

333
src/components/Loading.vue Normal file
View File

@ -0,0 +1,333 @@
<template>
<div
v-if="visible"
class="loading-container"
:class="{
'loading-container-fullscreen': fullscreen,
'loading-container-inline': !fullscreen
}"
:style="containerStyle"
>
<div class="loading-spinner-wrapper" :style="spinnerWrapperStyle">
<!-- 旋转圆环样式 -->
<div v-if="type === 'spinner'" class="loading-spinner" :style="spinnerStyle">
<svg class="loading-svg" viewBox="0 0 50 50" :style="svgStyle">
<circle
class="loading-path"
cx="25"
cy="25"
r="20"
fill="none"
stroke="currentColor"
:stroke-width="strokeWidth"
:stroke-linecap="strokeLinecap"
stroke-dasharray="94.2 94.2"
stroke-dashoffset="94.2"
></circle>
</svg>
</div>
<!-- 点状加载样式 -->
<div v-else-if="type === 'dots'" class="loading-dots" :style="dotsContainerStyle">
<div
v-for="n in 3"
:key="n"
class="loading-dot"
:style="[
dotStyle,
{
animationDelay: `${n * 0.2}s`
}
]"
></div>
</div>
<!-- 脉冲样式 -->
<div v-else-if="type === 'pulse'" class="loading-pulse" :style="pulseStyle"></div>
<!-- 环形样式 -->
<div v-else-if="type === 'ring'" class="loading-ring" :style="ringStyle">
<div class="loading-ring-circle" :style="ringCircleStyle"></div>
</div>
<!-- 默认样式旋转圆环 -->
<div v-else class="loading-spinner" :style="spinnerStyle">
<svg class="loading-svg" viewBox="0 0 50 50" :style="svgStyle">
<circle
class="loading-path"
cx="25"
cy="25"
r="20"
fill="none"
stroke="currentColor"
:stroke-width="strokeWidth"
:stroke-linecap="strokeLinecap"
stroke-dasharray="94.2 94.2"
stroke-dashoffset="94.2"
></circle>
</svg>
</div>
<!-- 加载文本 -->
<div v-if="text" class="loading-text" :style="textStyle">
{{ text }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
//
type LoadingType = 'spinner' | 'dots' | 'pulse' | 'ring'
// Props
const props = defineProps<{
//
visible: boolean
//
type?: LoadingType
//
size?: number | string
//
color?: string
//
fullscreen?: boolean
//
text?: string
//
background?: string
//
opacity?: number
// 线spinnerring
strokeWidth?: number
// 线spinner
strokeLinecap?: 'round' | 'butt' | 'square'
}>()
//
const containerStyle = computed(() => {
const style: any = {}
if (props.fullscreen) {
style.backgroundColor = props.background || 'rgba(255, 255, 255, 0.9)'
style.opacity = props.opacity ?? 1
}
return style
})
// spinner
const spinnerWrapperStyle = computed(() => {
return {
color: props.color || '#1890ff'
}
})
// spinner
const spinnerStyle = computed(() => {
return {
width: typeof props.size === 'number' ? `${props.size}px` : props.size || '40px',
height: typeof props.size === 'number' ? `${props.size}px` : props.size || '40px'
}
})
// svg
const svgStyle = computed(() => {
return {
animationDuration: '1.4s'
}
})
// 线
const strokeWidth = computed(() => {
return props.strokeWidth || 3
})
// 线
const strokeLinecap = computed(() => {
return props.strokeLinecap || 'round'
})
//
const dotsContainerStyle = computed(() => {
return {
gap: typeof props.size === 'number' ? `${props.size / 6}px` : '6px'
}
})
//
const dotStyle = computed(() => {
const dotSize = typeof props.size === 'number' ? props.size / 5 : 8
return {
width: `${dotSize}px`,
height: `${dotSize}px`,
backgroundColor: props.color || '#1890ff'
}
})
//
const pulseStyle = computed(() => {
const pulseSize = typeof props.size === 'number' ? props.size : 40
return {
width: `${pulseSize}px`,
height: `${pulseSize}px`,
backgroundColor: props.color || '#1890ff'
}
})
//
const ringStyle = computed(() => {
const ringSize = typeof props.size === 'number' ? props.size : 40
return {
width: `${ringSize}px`,
height: `${ringSize}px`
}
})
//
const ringCircleStyle = computed(() => {
const circleBorderWidth = props.strokeWidth || 3
return {
borderWidth: `${circleBorderWidth}px`,
borderColor: `${props.color || '#1890ff'} transparent ${props.color || '#1890ff'} transparent`
}
})
//
const textStyle = computed(() => {
return {
marginTop: typeof props.size === 'number' ? `${props.size / 4}px` : '10px',
color: props.color || '#666'
}
})
</script>
<style scoped>
/* 全屏容器样式 */
.loading-container-fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
/* 内联容器样式 */
.loading-container-inline {
display: inline-flex;
justify-content: center;
align-items: center;
}
/* 旋转圆环样式 */
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
}
.loading-svg {
animation: rotate 1.4s linear infinite;
transform-origin: center;
}
.loading-path {
animation: dash 1.4s ease-in-out infinite;
}
/* 点状加载样式 */
.loading-dots {
display: flex;
align-items: center;
justify-content: center;
}
.loading-dot {
border-radius: 50%;
animation: bounce 1.4s ease-in-out infinite;
}
/* 脉冲样式 */
.loading-pulse {
border-radius: 50%;
animation: pulse 1.4s ease-in-out infinite;
}
/* 环形样式 */
.loading-ring {
display: flex;
justify-content: center;
align-items: center;
}
.loading-ring-circle {
width: 100%;
height: 100%;
border-style: solid;
border-radius: 50%;
animation: spin 1.4s linear infinite;
}
/* 加载文本样式 */
.loading-text {
font-size: 14px;
text-align: center;
white-space: nowrap;
}
/* 动画定义 */
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dashoffset: 94.2;
}
50% {
stroke-dashoffset: 23.55;
transform: rotate(45deg);
}
100% {
stroke-dashoffset: 94.2;
transform: rotate(360deg);
}
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
@keyframes pulse {
0% {
transform: scale(0.3);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

304
src/components/loading.md Normal file
View File

@ -0,0 +1,304 @@
# Loading 组件使用文档
## 组件介绍
Loading 组件是一个灵活的加载动画组件,支持多种动画类型、自定义大小、颜色等配置,可用于页面加载、数据请求等场景。
## 组件类型
- `spinner`:旋转圆环(默认)
- `dots`:点状加载动画
- `pulse`:脉冲加载动画
- `ring`:环形旋转动画
## 使用方式
### 方式一:组件方式使用
```vue
<template>
<div>
<!-- 基本使用 -->
<Loading :visible="loadingVisible" />
<!-- 自定义配置 -->
<Loading
:visible="loadingVisible"
type="dots"
size="60"
color="#1890ff"
text="加载中..."
:fullscreen="true"
background="rgba(0, 0, 0, 0.7)"
:opacity="0.8"
:strokeWidth="4"
strokeLinecap="round"
/>
<button @click="showLoading">显示加载</button>
<button @click="hideLoading">隐藏加载</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Loading from './Loading.vue'
const loadingVisible = ref(false)
function showLoading() {
loadingVisible.value = true
}
function hideLoading() {
loadingVisible.value = false
}
</script>
```
### 方式二:编程方式使用
```vue
<template>
<div>
<button @click="showFullscreenLoading">显示全屏加载</button>
<button @click="showLoadingWithText">显示带文本的加载</button>
<button @click="showInlineLoading">显示内联加载</button>
<button @click="hideAllLoading">隐藏所有加载</button>
</div>
</template>
<script setup lang="ts">
import { showLoading, hideLoading, showLoadingWithText, showInlineLoading } from './loading'
import type { LoadingInstance } from './loading'
// 保存加载实例
let loadingInstance: LoadingInstance | null = null
// 显示全屏加载
function showFullscreenLoading() {
loadingInstance = showLoading({
type: 'spinner',
size: 50,
color: '#1890ff',
fullscreen: true,
background: 'rgba(255, 255, 255, 0.9)'
})
}
// 显示带文本的加载
function showLoadingWithText() {
loadingInstance = showLoadingWithText('数据加载中,请稍候...', '#52c41a')
}
// 显示内联加载
function showInlineLoading() {
loadingInstance = showInlineLoading(32, '#fa8c16', 'dots')
}
// 隐藏所有加载
function hideAllLoading() {
// 方式1隐藏特定实例
if (loadingInstance) {
loadingInstance.hide()
loadingInstance = null
}
// 方式2隐藏当前活动的全屏加载如果有的话
hideLoading()
}
// 模拟异步请求
function mockAsyncRequest() {
const loading = showLoadingWithText('处理中...')
setTimeout(() => {
// 请求完成后隐藏加载
loading.hide()
console.log('请求完成')
}, 3000)
}
</script>
```
## API 文档
### Loading 组件 Props
| 属性名 | 类型 | 默认值 | 说明 |
|-------|------|-------|------|
| visible | boolean | false | 是否显示加载组件 |
| type | 'spinner' \| 'dots' \| 'pulse' \| 'ring' | 'spinner' | 加载动画类型 |
| size | number \| string | 40 | 加载动画大小 |
| color | string | '#1890ff' | 加载动画颜色 |
| fullscreen | boolean | true | 是否全屏显示 |
| text | string | '' | 加载文本 |
| background | string | 'rgba(255, 255, 255, 0.9)' | 遮罩层背景色(全屏模式下) |
| opacity | number | 1 | 遮罩层透明度(全屏模式下) |
| strokeWidth | number | 3 | 线条宽度仅适用于spinner和ring类型 |
| strokeLinecap | 'round' \| 'butt' \| 'square' | 'round' | 线条末端样式仅适用于spinner类型 |
### loading.ts 函数API
#### showLoading(options?: LoadingOptions): LoadingInstance
创建并显示一个加载组件。
**参数**
- options: LoadingOptions - 加载组件配置选项与组件Props相同
**返回值**
- LoadingInstance - 加载组件实例,包含 show()、hide()、close() 方法
#### hideLoading(): void
隐藏当前活动的全屏加载组件。
#### showLoadingWithText(text: string, color?: string): LoadingInstance
显示一个带有文本的全屏加载组件。
**参数**
- text: string - 加载文本
- color: string - 加载动画颜色,默认为 '#1890ff'
**返回值**
- LoadingInstance - 加载组件实例
#### showInlineLoading(size?: number, color?: string, type?: LoadingType): LoadingInstance
显示一个小型内联加载组件。
**参数**
- size: number - 加载组件大小,默认为 24
- color: string - 加载动画颜色,默认为 '#1890ff'
- type: LoadingType - 加载动画类型,默认为 'spinner'
**返回值**
- LoadingInstance - 加载组件实例
### LoadingInstance 接口
| 方法名 | 说明 |
|-------|------|
| show() | 显示加载组件 |
| hide() | 隐藏加载组件并卸载 |
| close() | 同 hide(),隐藏加载组件并卸载 |
## 示例代码
### 场景1页面加载
```vue
<template>
<div v-if="!loading">
<!-- 页面内容 -->
</div>
<Loading v-else :visible="loading" text="页面加载中..." />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import Loading from './components/Loading.vue'
const loading = ref(true)
onMounted(() => {
// 模拟页面数据加载
setTimeout(() => {
loading.value = false
}, 2000)
})
</script>
```
### 场景2API请求加载
```vue
<template>
<div>
<button @click="fetchData" :disabled="loading">获取数据</button>
<div v-if="data">
<!-- 数据展示 -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { showLoading, hideLoading } from './components/loading'
const loading = ref(false)
const data = ref(null)
async function fetchData() {
loading.value = true
try {
// 显示加载
const loadingInstance = showLoadingWithText('数据请求中...')
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 2000))
// 获取数据
data.value = { /* 数据内容 */ }
} catch (error) {
console.error('请求失败:', error)
} finally {
loading.value = false
// 隐藏加载
hideLoading()
}
}
</script>
```
### 场景3提交表单时的加载
```vue
<template>
<form @submit.prevent="submitForm">
<!-- 表单内容 -->
<button type="submit" :disabled="submitting">
{{ submitting ? '提交中...' : '提交' }}
<Loading
v-if="submitting"
:visible="true"
:fullscreen="false"
type="spinner"
:size="16"
style="display: inline-block; margin-left: 8px;"
/>
</button>
</form>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Loading from './components/Loading.vue'
const submitting = ref(false)
async function submitForm() {
submitting.value = true
try {
// 模拟表单提交
await new Promise(resolve => setTimeout(resolve, 1500))
console.log('表单提交成功')
} catch (error) {
console.error('表单提交失败:', error)
} finally {
submitting.value = false
}
}
</script>
```
## 注意事项
1. 当使用编程方式创建多个全屏加载组件时,最新创建的会替换之前的。
2. 请确保在不需要加载动画时调用 hide() 方法来卸载组件,避免内存泄漏。
3. 对于长时间运行的操作,建议添加加载文本以提高用户体验。
4. 内联加载组件不会自动居中,需要根据具体场景调整其位置。

174
src/components/loading.ts Normal file
View File

@ -0,0 +1,174 @@
import { createApp, h } from 'vue'
import Loading from './Loading.vue'
// 加载组件配置接口
export interface LoadingOptions {
// 加载动画类型
type?: 'spinner' | 'dots' | 'pulse' | 'ring'
// 加载动画大小
size?: number | string
// 加载动画颜色
color?: string
// 是否全屏显示
fullscreen?: boolean
// 加载文本
text?: string
// 遮罩层背景色
background?: string
// 遮罩层透明度
opacity?: number
// 线条宽度仅适用于spinner和ring类型
strokeWidth?: number
// 线条末端样式仅适用于spinner类型
strokeLinecap?: 'round' | 'butt' | 'square'
}
// Loading 实例接口
interface LoadingInstance {
show: () => void
hide: () => void
close: () => void
}
// 当前活动的Loading实例
let activeInstance: LoadingInstance | null = null
/**
*
* @param options
* @returns
*/
export function showLoading(options: LoadingOptions = {}): LoadingInstance {
// 如果已经有一个活动的全屏Loading实例先隐藏它
if (activeInstance && options.fullscreen !== false) {
activeInstance.hide()
}
// 默认配置
const defaultOptions: LoadingOptions = {
type: 'spinner',
size: 40,
color: '#1890ff',
fullscreen: true,
text: '',
background: 'rgba(0, 0, 0, 0.3)',
opacity: 1,
strokeWidth: 3,
strokeLinecap: 'round'
}
// 合并配置
const mergedOptions = { ...defaultOptions, ...options }
// 创建一个容器元素
const container = document.createElement('div')
// 加载组件状态
let visible = true
// 创建应用实例
const app = createApp({
render() {
return h(Loading, {
visible,
type: mergedOptions.type,
size: mergedOptions.size,
color: mergedOptions.color,
fullscreen: mergedOptions.fullscreen,
text: mergedOptions.text,
background: mergedOptions.background,
opacity: mergedOptions.opacity,
strokeWidth: mergedOptions.strokeWidth,
strokeLinecap: mergedOptions.strokeLinecap
})
}
})
// 隐藏加载组件
function hideLoading() {
visible = false
// 卸载组件
setTimeout(() => {
app.unmount()
if (container.parentNode) {
container.parentNode.removeChild(container)
}
// 如果当前实例是活动实例,清除活动实例引用
if (activeInstance === instance) {
activeInstance = null
}
}, 300)
}
// 显示加载组件(默认为显示状态)
function showLoadingComp() {
visible = true
}
// 挂载组件
app.mount(container)
document.body.appendChild(container)
// 创建实例对象
const instance: LoadingInstance = {
show: showLoadingComp,
hide: hideLoading,
close: hideLoading
}
// 如果是全屏Loading保存为活动实例
if (mergedOptions.fullscreen) {
activeInstance = instance
}
// 返回控制方法
return instance
}
/**
*
*/
export function hideLoading() {
if (activeInstance) {
activeInstance.hide()
activeInstance = null
}
}
/**
*
* @param text
* @param color
* @returns
*/
export function showLoadingWithText(text: string, color: string = '#1890ff'): LoadingInstance {
return showLoading({
text,
color,
fullscreen: true
})
}
/**
*
* @param size
* @param color
* @param type
* @returns
*/
export function showInlineLoading(size: number = 24, color: string = '#1890ff', type: 'spinner' | 'dots' | 'pulse' | 'ring' = 'spinner'): LoadingInstance {
return showLoading({
size,
color,
type,
fullscreen: false
})
}
// 默认导出
export default {
show: showLoading,
hide: hideLoading,
showWithText: showLoadingWithText,
showInline: showInlineLoading
}

View File

@ -50,7 +50,7 @@
</div> </div>
</div> </div>
<div v-if="formData.verifyCode"> <div v-if="formData.verifyCode && !formData.qrCode">
<!-- 结婚证信息 --> <!-- 结婚证信息 -->
<div class="mb-6"> <div class="mb-6">
<h3 class="text-base text-gray-800 mb-3 font-medium">结婚证信息:</h3> <h3 class="text-base text-gray-800 mb-3 font-medium">结婚证信息:</h3>
@ -69,15 +69,15 @@
class="w-full flex flex-col items-start border border-gray-200 rounded-lg p-5 bg-[#FAFAFA]"> class="w-full flex flex-col items-start border border-gray-200 rounded-lg p-5 bg-[#FAFAFA]">
<div> <div>
<span class="inline-block w-25 text-right">结婚证字号</span> <span class="inline-block w-25 text-right">结婚证字号</span>
<span class="pl-2">{{ 11111 }}</span> <span class="pl-2">{{ formData.marriageNo }}</span>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<span class="inline-block w-25 text-right">男方姓名</span> <span class="inline-block w-25 text-right">男方姓名</span>
<span class="pl-2">{{ 11111 }}</span> <span class="pl-2">{{ formData.husbandName }}</span>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<span class="inline-block w-25 text-right">女方姓名</span> <span class="inline-block w-25 text-right">女方姓名</span>
<span class="pl-2">{{ 11111 }}</span> <span class="pl-2">{{ formData.wifeName }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -88,6 +88,15 @@
提交领取喜礼 提交领取喜礼
</button> </button>
</div> </div>
<div v-if="formData.qrCode">
<div>
<h3 class="text-base text-gray-800 mb-3 font-medium">请使用微信扫描下方二维码:</h3>
</div>
<div class="mt-4 flex justify-center items-center">
<qrcode-vue :value="formData.qrCode" :size="200" level="H" render-as="canvas" />
</div>
</div>
</div> </div>
<div v-if="activityInfo.status === 2"> <div v-if="activityInfo.status === 2">
@ -119,6 +128,8 @@ import message from '../../components/message';
import apiService from '../../services/apiService' import apiService from '../../services/apiService'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import QrcodeVue from 'qrcode.vue'
import { showLoading, hideLoading } from '../../components/loading'
const ocrUploadId = ref<HTMLInputElement>(); const ocrUploadId = ref<HTMLInputElement>();
@ -131,11 +142,13 @@ const formData = ref({
husbandName: '', // husbandName: '', //
wifeName: '', // wifeName: '', //
registerDate: '', // registerDate: '', //
qrCode: '', //
}) })
const activityInfo = ref<any>({}); // const activityInfo = ref<any>({}); //
onMounted(() => { onMounted(() => {
// //
showLoading();
apiService.getCurrentActivity().then((response: any) => { apiService.getCurrentActivity().then((response: any) => {
if (response.data) { if (response.data) {
activityInfo.value = { activityInfo.value = {
@ -147,11 +160,20 @@ onMounted(() => {
if (new Date(response.data.activityEndTime) < new Date()) { if (new Date(response.data.activityEndTime) < new Date()) {
message.error('活动已结束'); message.error('活动已结束');
activityInfo.value.status = 2; activityInfo.value.status = 2;
} else {
const user = JSON.parse(localStorage.getItem('userAs') || '{}');
if (user.phone && user.smsCode) {
formData.value.phone = user.phone;
formData.value.smsCode = user.smsCode;
verifyCode();
}
} }
} }
}).catch((error) => { }).catch((error) => {
message.error('获取活动信息失败,请稍后重试') message.error('获取活动信息失败,请稍后重试')
activityInfo.value.status = 3; activityInfo.value.status = 3;
}).finally(() => {
hideLoading();
}) })
}) })
@ -168,7 +190,7 @@ const sendVerificationCode = () => {
message.error('请输入正确的手机号码') message.error('请输入正确的手机号码')
return return
} }
showLoading();
// //
apiService.sendSms({ apiService.sendSms({
mobile: phone, mobile: phone,
@ -176,6 +198,7 @@ const sendVerificationCode = () => {
}).then(() => { }).then(() => {
formData.value.verifyCode = false; formData.value.verifyCode = false;
formData.value.smsCode = ''; formData.value.smsCode = '';
formData.value.qrCode = '';
// //
countdown.value = 60 countdown.value = 60
@ -196,6 +219,8 @@ const sendVerificationCode = () => {
}).catch((error) => { }).catch((error) => {
console.error('发送验证码失败:', error) console.error('发送验证码失败:', error)
message.error('验证码发送失败,请稍后重试') message.error('验证码发送失败,请稍后重试')
}).finally(() => {
hideLoading();
}) })
} }
@ -208,16 +233,53 @@ const verifyCode = () => {
return return
} }
showLoading();
// //
apiService.verifySms({ apiService.verifySms({
mobile: formData.value.phone, mobile: formData.value.phone,
smsCode: smsCode, smsCode: smsCode,
type: 3, type: 3,
}).then(() => { }).then((res) => {
localStorage.setItem('userAs', JSON.stringify({
phone: formData.value.phone,
smsCode: formData.value.smsCode,
}));
formData.value.verifyCode = true; formData.value.verifyCode = true;
//
countdown.value = 0;
//
if (res.data.code) {
formData.value = {
...formData.value,
qrCode: res.data.code,
}
} else {
formData.value = {
...formData.value,
marriageNo: "",
husbandName: "",
wifeName: "",
registerDate: "",
qrCode: ""
}
}
}).catch((error) => { }).catch((error) => {
console.error('验证验证码失败:', error) localStorage.removeItem('userAs');
message.error('验证码验证失败,请重试')
message.error('验证码验证失败,请重试');
formData.value = {
...formData.value,
marriageNo: "",
husbandName: "",
wifeName: "",
registerDate: "",
qrCode: ""
}
}).finally(() => {
hideLoading();
}) })
} }
@ -236,6 +298,7 @@ const handleImageChange = () => {
const formFormData = new FormData() const formFormData = new FormData()
formFormData.append('file', file) formFormData.append('file', file)
showLoading();
apiService.uploadOcrImage(formFormData).then((response: any) => { apiService.uploadOcrImage(formFormData).then((response: any) => {
apiService.parseOcrInfo({ apiService.parseOcrInfo({
mobile: formData.value.phone, mobile: formData.value.phone,
@ -254,10 +317,13 @@ const handleImageChange = () => {
}).catch((error) => { }).catch((error) => {
message.error('解析OCR信息失败请稍后重试') message.error('解析OCR信息失败请稍后重试')
ocrUploadId.value && (ocrUploadId.value.value = ''); ocrUploadId.value && (ocrUploadId.value.value = '');
}).finally(() => {
hideLoading();
}) })
}).catch((error) => { }).catch((error) => {
message.error('图片上传失败,请稍后重试') message.error('图片上传失败,请稍后重试')
ocrUploadId.value && (ocrUploadId.value.value = ''); ocrUploadId.value && (ocrUploadId.value.value = '');
hideLoading();
}) })
} }
@ -270,16 +336,18 @@ const submitForm = () => {
return return
} }
showLoading();
apiService.receiveCheck({ apiService.receiveCheck({
marriageNo: formData.value.marriageNo, marriageNo: formData.value.marriageNo,
receiveName: formData.value.husbandName, receiveName: formData.value.husbandName,
receiveMobile: formData.value.phone, receiveMobile: formData.value.phone,
code: formData.value.smsCode,
smsCode: formData.value.smsCode, smsCode: formData.value.smsCode,
}).then((res: any) => { }).then((res: any) => {
console.log(res); formData.value.qrCode = res.data.code;
}).catch((error) => { }).catch((error) => {
message.error('领取失败,请稍后重试') message.error(error?.msg || '提交失败,请稍后重试')
}).finally(() => {
hideLoading();
}) })
} }