Files
wxQrcodeScan/src/components/UserForm.vue
2025-11-28 21:24:28 +08:00

559 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="user-form">
<div class="form-header">
<h3>请填写您的资料</h3>
<p class="form-subtitle">二维码扫描成功请完善您的个人信息</p>
</div>
<form @submit.prevent="submitForm" class="user-data-form">
<div class="form-group">
<label for="name" class="required">姓名</label>
<input
id="name"
v-model="formData.name"
type="text"
required
placeholder="请输入您的姓名"
class="form-input"
/>
<span v-if="errors.name" class="error-text">{{ errors.name }}</span>
</div>
<div class="form-group">
<label for="phone" class="required">手机号码</label>
<input
id="phone"
v-model="formData.phone"
type="tel"
required
placeholder="请输入您的手机号码"
class="form-input"
@blur="validatePhone"
/>
<span v-if="errors.phone" class="error-text">{{ errors.phone }}</span>
</div>
<div class="form-group">
<label for="email">邮箱地址</label>
<input
id="email"
v-model="formData.email"
type="email"
placeholder="请输入您的邮箱地址(可选)"
class="form-input"
@blur="validateEmail"
/>
<span v-if="errors.email" class="error-text">{{ errors.email }}</span>
</div>
<div class="form-group">
<label for="company">公司/组织</label>
<input
id="company"
v-model="formData.company"
type="text"
placeholder="请输入您的公司或组织名称(可选)"
class="form-input"
/>
</div>
<div class="form-group">
<label for="position">职位</label>
<input
id="position"
v-model="formData.position"
type="text"
placeholder="请输入您的职位(可选)"
class="form-input"
/>
</div>
<div class="form-group">
<label for="purpose">参与目的</label>
<select id="purpose" v-model="formData.purpose" class="form-select">
<option value="">请选择您的参与目的</option>
<option value="business">商务合作</option>
<option value="technical">技术交流</option>
<option value="partnership">合作伙伴</option>
<option value="feedback">产品反馈</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label for="message">备注信息</label>
<textarea
id="message"
v-model="formData.message"
placeholder="请输入其他需要说明的信息(可选)"
class="form-textarea"
rows="3"
></textarea>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input
v-model="formData.agreement"
type="checkbox"
required
class="checkbox-input"
/>
<span class="checkbox-text">
我已阅读并同意 <a href="#" @click.prevent="showAgreement">用户协议</a> <a href="#" @click.prevent="showPrivacy">隐私政策</a>
</span>
</label>
<span v-if="errors.agreement" class="error-text">{{ errors.agreement }}</span>
</div>
<div class="form-actions">
<button type="button" @click="cancel" class="btn btn-secondary" :disabled="isSubmitting">
取消
</button>
<button type="submit" class="btn btn-primary" :disabled="isSubmitting || !isFormValid">
{{ isSubmitting ? '提交中...' : '提交资料' }}
</button>
</div>
<div v-if="submitSuccess" class="success-message">
<div class="success-icon"></div>
<p>资料提交成功感谢您的参与</p>
</div>
<div v-if="submitError" class="error-message">
<div class="error-icon"></div>
<p>{{ submitError }}</p>
</div>
</form>
<!-- 用户协议弹窗 -->
<div v-if="showAgreementModal" class="modal-overlay" @click="hideAgreement">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>用户协议</h3>
<button @click="hideAgreement" class="modal-close">×</button>
</div>
<div class="modal-body">
<p>这里是用户协议的内容...</p>
<!-- 实际项目中这里应该是完整的用户协议 -->
</div>
<div class="modal-footer">
<button @click="hideAgreement" class="btn btn-primary">我知道了</button>
</div>
</div>
</div>
<!-- 隐私政策弹窗 -->
<div v-if="showPrivacyModal" class="modal-overlay" @click="hidePrivacy">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>隐私政策</h3>
<button @click="hidePrivacy" class="modal-close">×</button>
</div>
<div class="modal-body">
<p>这里是隐私政策的内容...</p>
<!-- 实际项目中这里应该是完整的隐私政策 -->
</div>
<div class="modal-footer">
<button @click="hidePrivacy" class="btn btn-primary">我知道了</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import wechatSdk from '../services/wechatSdk'
interface Props {
scanResult: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
formSubmitted: [userData: any]
formCancelled: []
}>()
// 表单数据
const formData = ref({
name: '',
phone: '',
email: '',
company: '',
position: '',
purpose: '',
message: '',
agreement: false
})
// 错误信息
const errors = ref({
name: '',
phone: '',
email: '',
agreement: ''
})
// 状态
const isSubmitting = ref(false)
const submitSuccess = ref(false)
const submitError = ref('')
const showAgreementModal = ref(false)
const showPrivacyModal = ref(false)
// 计算属性
const isFormValid = computed(() => {
return formData.value.name.trim() &&
formData.value.phone.trim() &&
formData.value.agreement &&
!errors.value.name &&
!errors.value.phone &&
!errors.value.email
})
// 方法
const validatePhone = () => {
const phone = formData.value.phone
const phoneRegex = /^1[3-9]\d{9}$/
if (!phone) {
errors.value.phone = '请输入手机号码'
} else if (!phoneRegex.test(phone)) {
errors.value.phone = '请输入正确的手机号码'
} else {
errors.value.phone = ''
}
}
const validateEmail = () => {
const email = formData.value.email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (email && !emailRegex.test(email)) {
errors.value.email = '请输入正确的邮箱地址'
} else {
errors.value.email = ''
}
}
const showAgreement = () => {
showAgreementModal.value = true
}
const hideAgreement = () => {
showAgreementModal.value = false
}
const showPrivacy = () => {
showPrivacyModal.value = true
}
const hidePrivacy = () => {
showPrivacyModal.value = false
}
const getWeChatUserInfo = async () => {
try {
const userInfo = await wechatSdk.getUserProfile()
if (userInfo) {
// 如果获取到微信用户信息,可以预填充一些字段
if (userInfo.nickName && !formData.value.name) {
formData.value.name = userInfo.nickName
}
}
} catch (error) {
console.log('用户未授权获取微信信息或获取失败:', error)
}
}
const submitForm = async () => {
// 最终验证
validatePhone()
validateEmail()
if (!formData.value.agreement) {
errors.value.agreement = '请阅读并同意用户协议和隐私政策'
return
}
if (!isFormValid.value) {
return
}
isSubmitting.value = true
submitError.value = ''
try {
// 模拟提交数据到服务器
const userData = {
...formData.value,
scanResult: props.scanResult,
submitTime: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
}
// 这里应该调用实际的 API
console.log('提交用户数据:', userData)
// 模拟 API 调用延迟
await new Promise(resolve => setTimeout(resolve, 1000))
submitSuccess.value = true
// 延迟发送成功事件,让用户看到成功消息
setTimeout(() => {
emit('formSubmitted', userData)
}, 1500)
} catch (error) {
console.error('提交失败:', error)
submitError.value = '提交失败,请稍后重试'
} finally {
isSubmitting.value = false
}
}
const cancel = () => {
emit('formCancelled')
}
// 生命周期
onMounted(() => {
getWeChatUserInfo()
})
</script>
<style scoped>
.user-form {
max-width: 500px;
margin: 0 auto;
padding: 24px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.form-header {
text-align: center;
margin-bottom: 24px;
}
.form-header h3 {
color: #333;
margin: 0 0 8px 0;
font-size: 20px;
}
.form-subtitle {
color: #666;
margin: 0;
font-size: 14px;
}
.user-data-form {
space-y: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #333;
}
.form-group label.required::after {
content: ' *';
color: #f44336;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.3s;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
.checkbox-group {
margin-top: 24px;
}
.checkbox-label {
display: flex;
align-items: flex-start;
cursor: pointer;
}
.checkbox-input {
margin-right: 8px;
margin-top: 2px;
}
.checkbox-text {
font-size: 14px;
line-height: 1.4;
}
.checkbox-text a {
color: #1976d2;
text-decoration: none;
}
.checkbox-text a:hover {
text-decoration: underline;
}
.error-text {
display: block;
color: #f44336;
font-size: 12px;
margin-top: 4px;
}
.form-actions {
display: flex;
gap: 12px;
margin-top: 24px;
}
.btn {
flex: 1;
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #1976d2;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #1565c0;
}
.btn-secondary {
background-color: #f5f5f5;
color: #666;
border: 1px solid #ddd;
}
.btn-secondary:hover:not(:disabled) {
background-color: #eeeeee;
}
.success-message,
.error-message {
margin-top: 16px;
padding: 16px;
border-radius: 6px;
text-align: center;
}
.success-message {
background-color: #e8f5e8;
color: #2e7d32;
}
.error-message {
background-color: #ffebee;
color: #c62828;
}
.success-icon,
.error-icon {
font-size: 24px;
margin-bottom: 8px;
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 8px;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.modal-header h3 {
margin: 0;
color: #333;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: #666;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-body {
padding: 20px;
max-height: 60vh;
overflow-y: auto;
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #eee;
text-align: right;
}
</style>