Initial commit
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<scroll-view class="content-scroll" direction="vertical">
|
||||
<!-- 步骤进度 -->
|
||||
<view class="step-indicator">
|
||||
<view class="step-item step-active">
|
||||
<view class="step-dot step-dot-active"></view>
|
||||
<text class="step-text step-text-active">扫码绑定孩子</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-dot"></view>
|
||||
<text class="step-text">家长账号与密码</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-dot"></view>
|
||||
<text class="step-text">注册完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="info-banner">
|
||||
<text class="info-text">先扫描孩子的学生二维码,或输入学生账号完成绑定。再设置家长账号,最多可绑定 3 个孩子。</text>
|
||||
</view>
|
||||
|
||||
<!-- 切换按钮 -->
|
||||
<view class="toggle-buttons">
|
||||
<view class="toggle-btn toggle-btn-inactive toggle-btn-first" @tap="handleTabChange('scan')">
|
||||
<text class="toggle-btn-text toggle-btn-text-inactive">扫码绑定</text>
|
||||
</view>
|
||||
<view class="toggle-btn toggle-btn-active" @tap="handleTabChange('input')">
|
||||
<text class="toggle-btn-text toggle-btn-text-active">输入学生账号</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-section">
|
||||
<view class="form-header">
|
||||
<text class="form-title">填写要绑定的学生信息</text>
|
||||
<text class="form-description">学生账号与姓名需与学生信息保持一致,校验通过后即可加入下方已绑定列表。</text>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">学生账号</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
v-model="studentForm.account"
|
||||
placeholder="输入学生的账号或账号"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">学生姓名</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
v-model="studentForm.name"
|
||||
placeholder="需与学生本人姓名一致"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="add-student-btn"
|
||||
@tap="handleAddStudent"
|
||||
>
|
||||
<text class="add-student-btn-text">确认添加学生</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已绑定学生列表 -->
|
||||
<view class="student-list" v-if="studentList.length > 0">
|
||||
<view class="list-title-row">
|
||||
<text class="list-title">已绑定学生</text>
|
||||
<uni-badge-view :text="studentList.length + '/3'" badge-class="student-count-badge"></uni-badge-view>
|
||||
</view>
|
||||
<view class="student-item" v-for="(student, index) in studentList" :key="student.id">
|
||||
<image class="student-avatar" :src="student.avatar || '/static/avatar-default.png'" mode="aspectFill"></image>
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ student.name }}</text>
|
||||
<text class="student-detail">{{ student.grade }} {{ student.class }} | {{ student.account }}</text>
|
||||
</view>
|
||||
<button class="remove-btn" @tap="handleRemoveStudent(index)">移除</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="footer-button-container">
|
||||
<view
|
||||
class="footer-button"
|
||||
@tap="handleNext"
|
||||
>
|
||||
<text class="footer-button-text">下一步</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { apiVerifyStudentAccount } from '../../api/student'
|
||||
import { registrationStudents } from '../../store/registration'
|
||||
|
||||
type StudentForm = {
|
||||
account: string
|
||||
name: string
|
||||
}
|
||||
|
||||
const studentForm = reactive<StudentForm>({
|
||||
account: '',
|
||||
name: ''
|
||||
})
|
||||
|
||||
const studentList = registrationStudents
|
||||
const MAX_STUDENTS = 3
|
||||
|
||||
const canAdd = computed((): boolean => {
|
||||
return studentForm.account.trim().length > 0 && studentForm.name.trim().length > 0
|
||||
})
|
||||
|
||||
const canNext = computed((): boolean => {
|
||||
return studentList.value.length > 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 切换标签页
|
||||
*/
|
||||
function handleTabChange(tab: string) {
|
||||
if (tab === 'scan') {
|
||||
uni.redirectTo({
|
||||
url: '/pages/register/scan-student'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加学生
|
||||
*/
|
||||
async function handleAddStudent() {
|
||||
if (!canAdd.value) return
|
||||
const account = studentForm.account.trim()
|
||||
const name = studentForm.name.trim()
|
||||
|
||||
// 检查是否达到上限
|
||||
if (studentList.value.length >= MAX_STUDENTS) {
|
||||
uni.showToast({
|
||||
title: `最多只能绑定${MAX_STUDENTS}个孩子`,
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (studentList.value.some(student => student.account === account)) {
|
||||
uni.showToast({
|
||||
title: '该学生已添加',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证姓名格式
|
||||
const nameReg = /^[\u4e00-\u9fa5]{2,10}$/
|
||||
if (!nameReg.test(name)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的学生姓名(2-10个汉字)',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '验证中...' })
|
||||
|
||||
const res = await apiVerifyStudentAccount({
|
||||
account,
|
||||
name
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0 && res.data) {
|
||||
// 检查是否重复绑定
|
||||
const exists = studentList.value.find(s => s.account === res.data!.account)
|
||||
if (exists) {
|
||||
uni.showToast({
|
||||
title: '该学生已添加',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到列表
|
||||
studentList.value.push(res.data)
|
||||
|
||||
// 清空表单
|
||||
studentForm.account = ''
|
||||
studentForm.name = ''
|
||||
|
||||
uni.showToast({
|
||||
title: '添加成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.message || '学生信息验证失败,请检查账号和姓名是否正确',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除学生
|
||||
*/
|
||||
function handleRemoveStudent(index: number) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要移除该学生吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
studentList.value.splice(index, 1)
|
||||
uni.showToast({
|
||||
title: '已移除',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一步
|
||||
*/
|
||||
function handleNext() {
|
||||
if (!canNext.value) {
|
||||
uni.showToast({
|
||||
title: '请至少绑定一个学生',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/register/parent-account'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
padding: 16px 16px calc(96px + var(--uni-safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 步骤指示器 - 复用扫码页样式 */
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #E5E5E5;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-dot-active {
|
||||
background-color: #6C63FF;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 11px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-text-active {
|
||||
color: #6C63FF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background-color: #E5E5E5;
|
||||
margin: 0 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 提示信息 */
|
||||
.info-banner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 12px;
|
||||
background-color: #EEF2FF;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #4F46E5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 切换按钮 */
|
||||
.toggle-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.toggle-btn-first {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toggle-btn-active {
|
||||
background-color: #6C63FF;
|
||||
}
|
||||
|
||||
.toggle-btn-inactive {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #E5E5E5;
|
||||
}
|
||||
|
||||
.toggle-btn-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.toggle-btn-text-active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.toggle-btn-text-inactive {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-description {
|
||||
font-size: 13px;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
background-color: #F8F9FA;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #E5E5E5;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.add-student-btn {
|
||||
height: 40px;
|
||||
background-color: #EEF2FF;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.add-student-btn-text {
|
||||
font-size: 14px;
|
||||
color: #6C63FF;
|
||||
}
|
||||
|
||||
/* 学生列表 */
|
||||
.student-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.list-title-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.student-count-badge {
|
||||
background-color: #6C63FF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
margin-right: 12px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.student-detail {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
width: 56px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: #EF4444;
|
||||
background-color: #FEF2F2;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.footer-button-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px 16px calc(16px + var(--uni-safe-area-inset-bottom));
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
height: 48px;
|
||||
background-color: #6C63FF;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-button-text {
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user