<template>
<div class="record-form">
<!-- 头部标题动画 -->
<h1 class="title" v-motion="titleAnimation">人员信息记录表</h1>
<!-- 主表单区域 -->
<form @submit.prevent="submitForm">
<!-- 姓名 -->
<div class="form-group">
<label>姓名</label>
<input type="text" v-model="formData.name" placeholder="请输入姓名"
:class="{invalid: !validations.name}" @blur="validateName">
<transition name="fade">
<span class="error-msg" v-if="!validations.name">姓名不能为空</span>
</transition>
</div>
<!-- 性别选择 -->
<div class="form-group">
<label>性别</label>
<div class="radio-group">
<label v-for="option in genderOptions" :key="option.value">
<input type="radio" v-model="formData.gender" :value="option.value">
<span class="radio-icon" :class="{checked: formData.gender === option.value}"></span>
{{ option.label }}
</label>
</div>
</div>
<!-- 年龄滑块 -->
<div class="form-group">
<label>
年龄: {{ formData.age }} 岁
<transition name="bounce">
<span v-if="formData.age < 18" class="age-warning">(未满18岁)</span>
</transition>
</label>
<input type="range" min="1" max="100" v-model="formData.age">
</div>
<!-- 职业选择 -->
<div class="form-group">
<label>职业</label>
<select v-model="formData.occupation">
<option disabled value="">请选择职业</option>
<option v-for="job in occupations" :key="job" :value="job">{{ job }}</option>
</select>
</div>
<!-- 兴趣爱好 -->
<div class="form-group">
<label>兴趣爱好</label>
<div class="hobby-grid">
<div v-for="hobby in hobbies" :key="hobby"
class="hobby-item" :class="{selected: formData.hobbies.includes(hobby)}"
@click="toggleHobby(hobby)">
{{ hobby }}
</div>
</div>
</div>
<!-- 个人简介 -->
<div class="form-group">
<label>个人简介</label>
<textarea v-model="formData.bio" placeholder="请简要介绍自己..." rows="3"></textarea>
</div>
<!-- 提交按钮 -->
<div class="form-actions">
<button type="reset" @click="resetForm">重置</button>
<button type="submit" :class="{active: canSubmit}">提交记录</button>
</div>
</form>
<!-- 成功提交通知 -->
<transition name="fade">
<div v-if="submissionSuccess" class="success-modal">
<p>人员信息已成功记录!</p>
<button @click="submissionSuccess = false">关闭</button>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
// 表单数据结构
const formData = reactive({
name: '',
gender: '',
age: 25,
occupation: '',
hobbies: [],
bio: ''
})
// 选项数据
const genderOptions = [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' }
]
const occupations = ref([
'学生', '教师', '工程师', '医生', '设计师', '程序员', '创业者'
])
const hobbies = ref([
'阅读', '旅行', '音乐', '运动', '美食', '摄影', '游戏', '电影'
])
// 验证状态
const validations = reactive({
name: true
})
const submissionSuccess = ref(false)
// 动画定义
const titleAnimation = {
initial: {
opacity: 0,
y: -50
},
enter: {
opacity: 1,
y: 0,
transition: {
type: 'spring',
stiffness: 150,
damping: 15
}
}
}
// 验证方法
const validateName = () => {
validations.name = formData.name.trim().length > 0
}
// 切换爱好选择
const toggleHobby = (hobby) => {
const index = formData.hobbies.indexOf(hobby)
if (index > -1) {
formData.hobbies.splice(index, 1)
} else {
formData.hobbies.push(hobby)
}
}
// 提交表单
const submitForm = () => {
validateName()
if (canSubmit.value) {
// 实际应用中这里发送API请求
console.log('提交数据:', JSON.stringify(formData))
submissionSuccess.value = true
// 模拟延迟重置
setTimeout(() => {
resetForm()
}, 1500)
}
}
// 重置表单
const resetForm = () => {
Object.assign(formData, {
name: '',
gender: '',
age: 25,
occupation: '',
hobbies: [],
bio: ''
})
Object.assign(validations, { name: true })
}
// 计算属性
const canSubmit = computed(() => {
return formData.name.trim() && formData.gender && formData.occupation
})
</script>
<style scoped lang="scss">
.record-form {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.title {
text-align: center;
margin-bottom: 2rem;
color: #2c3e50;
font-weight: 600;
font-size: 2.2rem;
}
.form-group {
margin-bottom: 1.5rem;
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #34495e;
}
input, select, textarea {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s;
&:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
&.invalid {
border-color: #e74c3c;
}
}
textarea {
min-height: 80px;
resize: vertical;
}
}
.radio-group {
display: flex;
gap: 2rem;
margin-top: 0.5rem;
label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
font-weight: normal;
}
input[type="radio"] {
display: none;
& + .radio-icon {
width: 20px;
height: 20px;
border: 2px solid #95a5a6;
border-radius: 50%;
position: relative;
transition: all 0.3s;
&.checked::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
background: #3498db;
border-radius: 50%;
}
&.checked {
border-color: #3498db;
}
}
}
}
.hobby-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.8rem;
margin-top: 0.5rem;
}
.hobby-item {
padding: 0.6rem;
border: 1px solid #ddd;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: #f1f9ff;
}
&.selected {
background-color: #3498db;
color: white;
border-color: #3498db;
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
}
}
.error-msg {
display: block;
margin-top: 0.5rem;
color: #e74c3c;
font-size: 0.9rem;
}
.age-warning {
font-size: 0.85rem;
color: #e74c3c;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 1rem;
margin-top: 2rem;
button {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
&:first-child {
background-color: #ecf0f1;
color: #7f8c8d;
&:hover {
background-color: #d5dbdb;
}
}
&:last-child {
background-color: #3498db;
color: white;
&:hover {
background-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3);
}
&:active {
transform: translateY(0);
}
&:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
}
}
}
.success-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
text-align: center;
z-index: 100;
p {
font-size: 1.2rem;
margin-bottom: 1.5rem;
color: #2ecc71;
}
button {
padding: 0.6rem 1.2rem;
background-color: #2ecc71;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
}
/* 过渡动画 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.bounce-enter-active {
animation: bounce-in 0.5s;
}
@keyframes bounce-in {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.2); }
100% { transform: scale(1); opacity: 1; }
}
</style>
这段代码运行没反应,请帮我修改一下
最新发布