21 changed files with 4310 additions and 9 deletions
@ -0,0 +1,214 @@ |
|||
<!--孩子选择组件--> |
|||
<template> |
|||
<view class="child-selector"> |
|||
<view class="parent-info"> |
|||
<view class="parent-name">{{ parentInfo.name }}</view> |
|||
<view class="parent-phone">{{ parentInfo.phone_number }}</view> |
|||
</view> |
|||
|
|||
<view class="children-tabs"> |
|||
<view |
|||
v-for="child in childrenList" |
|||
:key="child.id" |
|||
:class="['child-tab', selectedChild && selectedChild.id === child.id ? 'active' : '']" |
|||
@click="selectChild(child)" |
|||
> |
|||
<view class="child-avatar"> |
|||
<image :src="child.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child-info"> |
|||
<view class="child-name">{{ child.name }}</view> |
|||
<view class="child-details"> |
|||
<text class="gender">{{ child.gender === 1 ? '男' : '女' }}</text> |
|||
<text class="age">{{ Math.floor(child.age) }}岁</text> |
|||
</view> |
|||
<view class="child-campus">{{ child.campus_name || '未分配校区' }}</view> |
|||
<view class="child-class">{{ child.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
<view class="child-status"> |
|||
<view class="courses-info"> |
|||
<text>{{ child.remaining_courses || 0 }}课时</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState, mapMutations } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
name: 'ChildSelector', |
|||
data() { |
|||
return { |
|||
parentInfo: {}, |
|||
childrenList: [], |
|||
loading: false |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild', 'userRole']) |
|||
}, |
|||
mounted() { |
|||
this.loadChildrenList() |
|||
}, |
|||
methods: { |
|||
...mapMutations(['SET_SELECTED_CHILD', 'SET_CHILDREN_LIST']), |
|||
|
|||
async loadChildrenList() { |
|||
this.loading = true |
|||
try { |
|||
console.log('开始加载孩子列表...') |
|||
const response = await apiRoute.parent_getChildrenList() |
|||
console.log('孩子列表响应:', response) |
|||
|
|||
if (response.code === 1) { |
|||
// Mock数据结构:response.data.data 是孩子列表,response.data.parent_info 是家长信息 |
|||
this.childrenList = response.data.data || [] |
|||
this.parentInfo = response.data.parent_info || {} |
|||
this.SET_CHILDREN_LIST(this.childrenList) |
|||
|
|||
console.log('加载到的孩子列表:', this.childrenList) |
|||
console.log('家长信息:', this.parentInfo) |
|||
|
|||
// 如果没有选中的孩子且有孩子列表,默认选中第一个 |
|||
if (!this.selectedChild && this.childrenList.length > 0) { |
|||
this.selectChild(this.childrenList[0]) |
|||
} |
|||
} else { |
|||
console.error('获取孩子列表失败:', response) |
|||
uni.showToast({ |
|||
title: response.msg || '获取孩子列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取孩子列表失败:', error) |
|||
uni.showToast({ |
|||
title: '获取孩子列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
selectChild(child) { |
|||
this.SET_SELECTED_CHILD(child) |
|||
this.$emit('childSelected', child) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.child-selector { |
|||
background: #fff; |
|||
border-radius: 20rpx; |
|||
padding: 32rpx; |
|||
margin: 24rpx; |
|||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.parent-info { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
margin-bottom: 24rpx; |
|||
|
|||
.parent-name { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.parent-phone { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.children-tabs { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.child-tab { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 24rpx; |
|||
border-radius: 16rpx; |
|||
background: #f8f9fa; |
|||
border: 2rpx solid transparent; |
|||
transition: all 0.3s; |
|||
|
|||
&.active { |
|||
background: rgba(41, 211, 180, 0.1); |
|||
border-color: #29d3b4; |
|||
} |
|||
|
|||
.child-avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
margin-right: 24rpx; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child-info { |
|||
flex: 1; |
|||
|
|||
.child-name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child-details { |
|||
display: flex; |
|||
gap: 16rpx; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.gender, .age { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
background: #e9ecef; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
} |
|||
} |
|||
|
|||
.child-campus, .child-class { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
margin-bottom: 4rpx; |
|||
} |
|||
} |
|||
|
|||
.child-status { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
.courses-info { |
|||
background: #29d3b4; |
|||
color: #fff; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 20rpx; |
|||
font-size: 22rpx; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,177 @@ |
|||
<!--合同详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 合同基本信息 --> |
|||
<view class="contract_info_card" v-if="contractInfo"> |
|||
<view class="contract_header"> |
|||
<view class="contract_title">{{ contractInfo.title }}</view> |
|||
<view class="contract_status" :class="contractInfo.status"> |
|||
{{ contractInfo.status_text }} |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="contract_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">合同金额</view> |
|||
<view class="detail_value amount">¥{{ contractInfo.amount }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">签订日期</view> |
|||
<view class="detail_value">{{ contractInfo.sign_date }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">有效期</view> |
|||
<view class="detail_value">{{ contractInfo.valid_date }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !contractInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无合同信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
contractInfo: null, |
|||
loading: false, |
|||
contractId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.contractId = options.contractId |
|||
this.childId = options.childId |
|||
this.loadContractInfo() |
|||
}, |
|||
methods: { |
|||
async loadContractInfo() { |
|||
// 模拟合同详情数据 |
|||
this.contractInfo = { |
|||
title: '少儿篮球培训合同', |
|||
status: 'active', |
|||
status_text: '有效', |
|||
amount: '2880.00', |
|||
sign_date: '2024-01-01', |
|||
valid_date: '2024-01-01 至 2024-12-31' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.contract_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.contract_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.contract_title { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.contract_status { |
|||
font-size: 24rpx; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.active { |
|||
background: rgba(40, 167, 69, 0.1); |
|||
color: #28a745; |
|||
} |
|||
|
|||
&.expired { |
|||
background: rgba(220, 53, 69, 0.1); |
|||
color: #dc3545; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.contract_details { |
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
|
|||
&.amount { |
|||
color: #e67e22; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,296 @@ |
|||
<!--家长端合同管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 合同列表 --> |
|||
<view class="contracts_list"> |
|||
<view class="section_title">合同列表</view> |
|||
<view class="contracts_items"> |
|||
<view |
|||
v-for="contract in contractsList" |
|||
:key="contract.id" |
|||
class="contract_item" |
|||
@click="viewContractDetail(contract)" |
|||
> |
|||
<view class="contract_main"> |
|||
<view class="contract_header"> |
|||
<view class="contract_title">{{ contract.title }}</view> |
|||
<view class="contract_status" :class="contract.status">{{ contract.status_text }}</view> |
|||
</view> |
|||
<view class="contract_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">合同金额:</text> |
|||
<text class="detail_value amount">¥{{ contract.amount }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">签订日期:</text> |
|||
<text class="detail_value">{{ contract.sign_date }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">有效期:</text> |
|||
<text class="detail_value">{{ contract.valid_date }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="contract_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && contractsList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无合同信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
contractsList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadContractsList() |
|||
}, |
|||
methods: { |
|||
async loadContractsList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildContracts({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.contractsList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取合同列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取合同列表失败:', error) |
|||
uni.showToast({ |
|||
title: '获取合同列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewContractDetail(contract) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/contracts/contract-detail?contractId=${contract.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.contracts_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.contracts_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.contract_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.contract_main { |
|||
flex: 1; |
|||
|
|||
.contract_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.contract_title { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.contract_status { |
|||
font-size: 22rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.active { |
|||
background: rgba(40, 167, 69, 0.1); |
|||
color: #28a745; |
|||
} |
|||
|
|||
&.expired { |
|||
background: rgba(220, 53, 69, 0.1); |
|||
color: #dc3545; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.contract_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
|
|||
&.amount { |
|||
color: #e67e22; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.contract_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,187 @@ |
|||
<!--课程详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 课程基本信息 --> |
|||
<view class="course_info_card" v-if="courseInfo"> |
|||
<view class="course_header"> |
|||
<view class="course_name">{{ courseInfo.course_name }}</view> |
|||
<view class="course_status" :class="courseInfo.status"> |
|||
{{ courseInfo.status === 'active' ? '进行中' : '已结束' }} |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="course_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">授课教师</view> |
|||
<view class="detail_value">{{ courseInfo.teacher_name }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">上课校区</view> |
|||
<view class="detail_value">{{ courseInfo.campus_name }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">上课时间</view> |
|||
<view class="detail_value">{{ courseInfo.schedule_time }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">课程进度</view> |
|||
<view class="detail_value">{{ courseInfo.progress }}</view> |
|||
</view> |
|||
<view class="detail_item" v-if="courseInfo.next_class"> |
|||
<view class="detail_label">下节课时间</view> |
|||
<view class="detail_value next_class">{{ courseInfo.next_class }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !courseInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无课程信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
courseInfo: null, |
|||
loading: false, |
|||
courseId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.courseId = options.courseId |
|||
this.childId = options.childId |
|||
this.loadCourseInfo() |
|||
}, |
|||
methods: { |
|||
async loadCourseInfo() { |
|||
// 这里可以添加具体的课程详情获取逻辑 |
|||
// 暂时使用模拟数据 |
|||
this.courseInfo = { |
|||
course_name: '少儿篮球训练', |
|||
teacher_name: '王教练', |
|||
campus_name: '总部校区', |
|||
schedule_time: '周六 09:00-10:30', |
|||
progress: '8/12节', |
|||
status: 'active', |
|||
next_class: '2024-01-20 09:00' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.course_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.course_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.course_name { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.course_status { |
|||
font-size: 24rpx; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.active { |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
|
|||
&.inactive { |
|||
background: #f0f0f0; |
|||
color: #999; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.course_details { |
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
|
|||
&.next_class { |
|||
color: #29d3b4; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,330 @@ |
|||
<!--家长端课程管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
<view class="course_stats"> |
|||
<view class="stat_item"> |
|||
<text class="stat_number">{{ selectedChild.remaining_courses || 0 }}</text> |
|||
<text class="stat_label">剩余课时</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 课程列表 --> |
|||
<view class="course_list"> |
|||
<view class="section_title">课程信息</view> |
|||
<view class="course_items"> |
|||
<view |
|||
v-for="course in courseList" |
|||
:key="course.id" |
|||
class="course_item" |
|||
@click="viewCourseDetail(course)" |
|||
> |
|||
<view class="course_main"> |
|||
<view class="course_header"> |
|||
<view class="course_name">{{ course.course_name }}</view> |
|||
<view class="course_status" :class="course.status">{{ course.status === 'active' ? '进行中' : '已结束' }}</view> |
|||
</view> |
|||
<view class="course_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">授课教师:</text> |
|||
<text class="detail_value">{{ course.teacher_name }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">上课校区:</text> |
|||
<text class="detail_value">{{ course.campus_name }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">上课时间:</text> |
|||
<text class="detail_value">{{ course.schedule_time }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">课程进度:</text> |
|||
<text class="detail_value">{{ course.progress }}</text> |
|||
</view> |
|||
<view class="detail_row" v-if="course.next_class"> |
|||
<text class="detail_label">下节课时间:</text> |
|||
<text class="detail_value next_class">{{ course.next_class }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="course_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && courseList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无课程信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
courseList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadCourseList() |
|||
}, |
|||
methods: { |
|||
async loadCourseList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildCourses({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.courseList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取课程列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取课程列表失败:', error) |
|||
uni.showToast({ |
|||
title: '获取课程列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewCourseDetail(course) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/courses/course-detail?courseId=${course.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
|
|||
.course_stats { |
|||
.stat_item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
.stat_number { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #29d3b4; |
|||
margin-bottom: 4rpx; |
|||
} |
|||
|
|||
.stat_label { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.course_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.course_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.course_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.course_main { |
|||
flex: 1; |
|||
|
|||
.course_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.course_name { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.course_status { |
|||
font-size: 22rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.active { |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
|
|||
&.inactive { |
|||
background: #f0f0f0; |
|||
color: #999; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.course_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
|
|||
&.next_class { |
|||
color: #29d3b4; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.course_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,279 @@ |
|||
<!--家长端教学资料页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 教学资料列表 --> |
|||
<view class="materials_list"> |
|||
<view class="section_title">教学资料</view> |
|||
<view class="materials_items"> |
|||
<view |
|||
v-for="material in materialsList" |
|||
:key="material.id" |
|||
class="material_item" |
|||
@click="viewMaterialDetail(material)" |
|||
> |
|||
<view class="material_main"> |
|||
<view class="material_header"> |
|||
<view class="material_title">{{ material.title }}</view> |
|||
<view class="material_type">{{ material.type }}</view> |
|||
</view> |
|||
<view class="material_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">发布时间:</text> |
|||
<text class="detail_value">{{ material.created_at }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">资料类型:</text> |
|||
<text class="detail_value">{{ material.type }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="material_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && materialsList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无教学资料</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
materialsList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadMaterialsList() |
|||
}, |
|||
methods: { |
|||
async loadMaterialsList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildMaterials({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.materialsList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取教学资料失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取教学资料失败:', error) |
|||
uni.showToast({ |
|||
title: '获取教学资料失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewMaterialDetail(material) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/materials/material-detail?materialId=${material.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.materials_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.materials_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.material_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.material_main { |
|||
flex: 1; |
|||
|
|||
.material_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.material_title { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.material_type { |
|||
font-size: 22rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
} |
|||
|
|||
.material_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.material_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,181 @@ |
|||
<!--教学资料详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 资料基本信息 --> |
|||
<view class="material_info_card" v-if="materialInfo"> |
|||
<view class="material_header"> |
|||
<view class="material_title">{{ materialInfo.title }}</view> |
|||
<view class="material_type">{{ materialInfo.type }}</view> |
|||
</view> |
|||
|
|||
<view class="material_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">发布时间</view> |
|||
<view class="detail_value">{{ materialInfo.created_at }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">资料类型</view> |
|||
<view class="detail_value">{{ materialInfo.type }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="material_content"> |
|||
<view class="content_title">资料内容</view> |
|||
<view class="content_text">{{ materialInfo.content }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !materialInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无资料信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
materialInfo: null, |
|||
loading: false, |
|||
materialId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.materialId = options.materialId |
|||
this.childId = options.childId |
|||
this.loadMaterialInfo() |
|||
}, |
|||
methods: { |
|||
async loadMaterialInfo() { |
|||
// 模拟教学资料详情数据 |
|||
this.materialInfo = { |
|||
title: '篮球基础训练视频', |
|||
type: '视频资料', |
|||
created_at: '2024-01-15 14:30:00', |
|||
content: '这是一套专门为少儿设计的篮球基础训练视频,包含运球、投篮、传球等基本技能的教学内容。' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.material_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.material_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.material_title { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.material_type { |
|||
font-size: 24rpx; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 12rpx; |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
} |
|||
|
|||
.material_details { |
|||
margin-bottom: 32rpx; |
|||
|
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.material_content { |
|||
.content_title { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.content_text { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
line-height: 1.6; |
|||
padding: 16rpx; |
|||
background: #f8f9fa; |
|||
border-radius: 8rpx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,280 @@ |
|||
<!--家长端消息管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 消息列表 --> |
|||
<view class="messages_list"> |
|||
<view class="section_title">消息记录</view> |
|||
<view class="messages_items"> |
|||
<view |
|||
v-for="message in messagesList" |
|||
:key="message.id" |
|||
class="message_item" |
|||
@click="viewMessageDetail(message)" |
|||
> |
|||
<view class="message_main"> |
|||
<view class="message_header"> |
|||
<view class="message_title">{{ message.title }}</view> |
|||
<view class="message_time">{{ message.created_at }}</view> |
|||
</view> |
|||
<view class="message_content">{{ message.content }}</view> |
|||
<view class="message_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">发送者:</text> |
|||
<text class="detail_value">{{ message.sender }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="message_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && messagesList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无消息记录</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
messagesList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadMessagesList() |
|||
}, |
|||
methods: { |
|||
async loadMessagesList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildMessages({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.messagesList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取消息记录失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取消息记录失败:', error) |
|||
uni.showToast({ |
|||
title: '获取消息记录失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewMessageDetail(message) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/messages/message-detail?messageId=${message.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.messages_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.messages_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.message_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.message_main { |
|||
flex: 1; |
|||
|
|||
.message_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 12rpx; |
|||
|
|||
.message_title { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.message_time { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.message_content { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
line-height: 1.4; |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.message_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 120rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.message_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,174 @@ |
|||
<!--消息详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 消息基本信息 --> |
|||
<view class="message_info_card" v-if="messageInfo"> |
|||
<view class="message_header"> |
|||
<view class="message_title">{{ messageInfo.title }}</view> |
|||
<view class="message_time">{{ messageInfo.created_at }}</view> |
|||
</view> |
|||
|
|||
<view class="message_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">发送者</view> |
|||
<view class="detail_value">{{ messageInfo.sender }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="message_content"> |
|||
<view class="content_title">消息内容</view> |
|||
<view class="content_text">{{ messageInfo.content }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !messageInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无消息信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
messageInfo: null, |
|||
loading: false, |
|||
messageId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.messageId = options.messageId |
|||
this.childId = options.childId |
|||
this.loadMessageInfo() |
|||
}, |
|||
methods: { |
|||
async loadMessageInfo() { |
|||
// 模拟消息详情数据 |
|||
this.messageInfo = { |
|||
title: '课程提醒', |
|||
sender: '王教练', |
|||
created_at: '2024-01-15 09:30:00', |
|||
content: '提醒您的孩子明天有篮球课,请准时参加。上课时间:10:00-11:30,地点:篮球馆A。' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.message_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.message_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.message_title { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.message_time { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.message_details { |
|||
margin-bottom: 32rpx; |
|||
|
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.message_content { |
|||
.content_title { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.content_text { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
line-height: 1.6; |
|||
padding: 16rpx; |
|||
background: #f8f9fa; |
|||
border-radius: 8rpx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,305 @@ |
|||
<!--家长端订单管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 订单列表 --> |
|||
<view class="order_list"> |
|||
<view class="section_title">订单列表</view> |
|||
<view class="order_items"> |
|||
<view |
|||
v-for="order in orderList" |
|||
:key="order.id" |
|||
class="order_item" |
|||
@click="viewOrderDetail(order)" |
|||
> |
|||
<view class="order_main"> |
|||
<view class="order_header"> |
|||
<view class="order_no">订单号:{{ order.order_no }}</view> |
|||
<view class="order_status" :class="order.status">{{ order.status_text }}</view> |
|||
</view> |
|||
<view class="order_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">课程名称:</text> |
|||
<text class="detail_value">{{ order.course_name }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">订单金额:</text> |
|||
<text class="detail_value amount">¥{{ order.amount }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">下单时间:</text> |
|||
<text class="detail_value">{{ order.created_at }}</text> |
|||
</view> |
|||
<view class="detail_row" v-if="order.pay_time"> |
|||
<text class="detail_label">支付时间:</text> |
|||
<text class="detail_value">{{ order.pay_time }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="order_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && orderList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无订单信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
orderList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadOrderList() |
|||
}, |
|||
methods: { |
|||
async loadOrderList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildOrders({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.orderList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取订单列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取订单列表失败:', error) |
|||
uni.showToast({ |
|||
title: '获取订单列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewOrderDetail(order) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/orders/order-detail?orderId=${order.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.order_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.order_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.order_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.order_main { |
|||
flex: 1; |
|||
|
|||
.order_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.order_no { |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.order_status { |
|||
font-size: 22rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.paid { |
|||
background: rgba(40, 167, 69, 0.1); |
|||
color: #28a745; |
|||
} |
|||
|
|||
&.unpaid { |
|||
background: rgba(220, 53, 69, 0.1); |
|||
color: #dc3545; |
|||
} |
|||
|
|||
&.refund { |
|||
background: rgba(108, 117, 125, 0.1); |
|||
color: #6c757d; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.order_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
|
|||
&.amount { |
|||
color: #e67e22; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.order_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,182 @@ |
|||
<!--订单详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 订单基本信息 --> |
|||
<view class="order_info_card" v-if="orderInfo"> |
|||
<view class="order_header"> |
|||
<view class="order_no">订单号:{{ orderInfo.order_no }}</view> |
|||
<view class="order_status" :class="orderInfo.status"> |
|||
{{ orderInfo.status_text }} |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="order_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">课程名称</view> |
|||
<view class="detail_value">{{ orderInfo.course_name }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">订单金额</view> |
|||
<view class="detail_value amount">¥{{ orderInfo.amount }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">下单时间</view> |
|||
<view class="detail_value">{{ orderInfo.created_at }}</view> |
|||
</view> |
|||
<view class="detail_item" v-if="orderInfo.pay_time"> |
|||
<view class="detail_label">支付时间</view> |
|||
<view class="detail_value">{{ orderInfo.pay_time }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !orderInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无订单信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
orderInfo: null, |
|||
loading: false, |
|||
orderId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.orderId = options.orderId |
|||
this.childId = options.childId |
|||
this.loadOrderInfo() |
|||
}, |
|||
methods: { |
|||
async loadOrderInfo() { |
|||
// 模拟订单详情数据 |
|||
this.orderInfo = { |
|||
order_no: 'ORD202401001', |
|||
course_name: '少儿篮球课程包', |
|||
amount: '2880.00', |
|||
status: 'paid', |
|||
status_text: '已支付', |
|||
created_at: '2024-01-01 10:00:00', |
|||
pay_time: '2024-01-01 10:05:00' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.order_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.order_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.order_no { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.order_status { |
|||
font-size: 24rpx; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.paid { |
|||
background: rgba(40, 167, 69, 0.1); |
|||
color: #28a745; |
|||
} |
|||
|
|||
&.unpaid { |
|||
background: rgba(220, 53, 69, 0.1); |
|||
color: #dc3545; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.order_details { |
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
|
|||
&.amount { |
|||
color: #e67e22; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,279 @@ |
|||
<!--家长端服务管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 选中孩子信息 --> |
|||
<view class="child_info_bar" v-if="selectedChild"> |
|||
<view class="child_avatar"> |
|||
<image :src="selectedChild.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_details"> |
|||
<view class="child_name">{{ selectedChild.name }}</view> |
|||
<view class="child_class">{{ selectedChild.class_name || '未分配班级' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 服务列表 --> |
|||
<view class="services_list"> |
|||
<view class="section_title">服务记录</view> |
|||
<view class="services_items"> |
|||
<view |
|||
v-for="service in servicesList" |
|||
:key="service.id" |
|||
class="service_item" |
|||
@click="viewServiceDetail(service)" |
|||
> |
|||
<view class="service_main"> |
|||
<view class="service_header"> |
|||
<view class="service_title">{{ service.title }}</view> |
|||
<view class="service_status" :class="service.status">{{ service.status_text }}</view> |
|||
</view> |
|||
<view class="service_details"> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">服务时间:</text> |
|||
<text class="detail_value">{{ service.service_time }}</text> |
|||
</view> |
|||
<view class="detail_row"> |
|||
<text class="detail_label">服务内容:</text> |
|||
<text class="detail_value">{{ service.content }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="service_arrow"> |
|||
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && servicesList.length === 0"> |
|||
<image :src="$util.img('/uniapp_src/static/images/common/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无服务记录</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
servicesList: [], |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadServicesList() |
|||
}, |
|||
methods: { |
|||
async loadServicesList() { |
|||
if (!this.childId) { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildServices({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.servicesList = response.data.data || [] |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取服务记录失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取服务记录失败:', error) |
|||
uni.showToast({ |
|||
title: '获取服务记录失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
viewServiceDetail(service) { |
|||
this.$navigateTo({ |
|||
url: `/pages/parent/services/service-detail?serviceId=${service.id}&childId=${this.childId}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_bar { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.child_class { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.services_list { |
|||
.section_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
padding-left: 8rpx; |
|||
} |
|||
|
|||
.services_items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
} |
|||
|
|||
.service_item { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 28rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.service_main { |
|||
flex: 1; |
|||
|
|||
.service_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.service_title { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.service_status { |
|||
font-size: 22rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
} |
|||
|
|||
.service_details { |
|||
.detail_row { |
|||
display: flex; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.detail_label { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.service_arrow { |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,159 @@ |
|||
<!--服务详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 服务基本信息 --> |
|||
<view class="service_info_card" v-if="serviceInfo"> |
|||
<view class="service_header"> |
|||
<view class="service_title">{{ serviceInfo.title }}</view> |
|||
<view class="service_status" :class="serviceInfo.status"> |
|||
{{ serviceInfo.status_text }} |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="service_details"> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">服务时间</view> |
|||
<view class="detail_value">{{ serviceInfo.service_time }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">服务内容</view> |
|||
<view class="detail_value">{{ serviceInfo.content }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !serviceInfo"> |
|||
<image src="/static/icon-img/empty.png" class="empty_icon"></image> |
|||
<view class="empty_text">暂无服务信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
serviceInfo: null, |
|||
loading: false, |
|||
serviceId: null, |
|||
childId: null |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.serviceId = options.serviceId |
|||
this.childId = options.childId |
|||
this.loadServiceInfo() |
|||
}, |
|||
methods: { |
|||
async loadServiceInfo() { |
|||
// 模拟服务详情数据 |
|||
this.serviceInfo = { |
|||
title: '课程跟踪服务', |
|||
status: 'completed', |
|||
status_text: '已完成', |
|||
service_time: '2024-01-15 10:00:00', |
|||
content: '针对学员的课程进度进行跟踪辅导,提供个性化建议。' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.service_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.service_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
padding-bottom: 24rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.service_title { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.service_status { |
|||
font-size: 24rpx; |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 12rpx; |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
} |
|||
|
|||
.service_details { |
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,308 @@ |
|||
<!--孩子详情页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<!-- 孩子基本信息 --> |
|||
<view class="child_info_card" v-if="childInfo"> |
|||
<view class="child_header"> |
|||
<view class="child_avatar"> |
|||
<image :src="childInfo.avatar" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="child_basic"> |
|||
<view class="child_name">{{ childInfo.name }}</view> |
|||
<view class="child_tags"> |
|||
<view class="tag gender">{{ childInfo.gender === 1 ? '男' : '女' }}</view> |
|||
<view class="tag age">{{ Math.floor(childInfo.age) }}岁</view> |
|||
<view class="tag label">{{ childInfo.member_label }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="child_details"> |
|||
<view class="detail_section"> |
|||
<view class="section_title">基本信息</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">生日</view> |
|||
<view class="detail_value">{{ childInfo.birthday }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">年龄</view> |
|||
<view class="detail_value">{{ childInfo.age }}岁</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">紧急联系人</view> |
|||
<view class="detail_value">{{ childInfo.emergency_contact }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">联系电话</view> |
|||
<view class="detail_value">{{ childInfo.contact_phone }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="detail_section"> |
|||
<view class="section_title">校区信息</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">所属校区</view> |
|||
<view class="detail_value">{{ childInfo.campus_name || '未分配' }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">班级</view> |
|||
<view class="detail_value">{{ childInfo.class_name || '未分配' }}</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">教练</view> |
|||
<view class="detail_value">{{ childInfo.coach_name || '未分配' }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="detail_section"> |
|||
<view class="section_title">学习情况</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">总课程数</view> |
|||
<view class="detail_value">{{ childInfo.total_courses }}节</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">已完成</view> |
|||
<view class="detail_value">{{ childInfo.completed_courses }}节</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">剩余课时</view> |
|||
<view class="detail_value">{{ childInfo.remaining_courses }}节</view> |
|||
</view> |
|||
<view class="detail_item"> |
|||
<view class="detail_label">出勤率</view> |
|||
<view class="detail_value">{{ childInfo.attendance_rate }}%</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="detail_section" v-if="childInfo.note"> |
|||
<view class="section_title">备注信息</view> |
|||
<view class="note_content">{{ childInfo.note }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty_state" v-if="!loading && !childInfo"> |
|||
<image :src="$util.img('/static/icon-img/empty.png')" class="empty_icon"></image> |
|||
<view class="empty_text">暂无孩子信息</view> |
|||
</view> |
|||
|
|||
<!-- 加载状态 --> |
|||
<view class="loading_state" v-if="loading"> |
|||
<view class="loading_text">加载中...</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
childInfo: null, |
|||
loading: false, |
|||
childId: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad(options) { |
|||
this.childId = options.childId |
|||
this.loadChildInfo() |
|||
}, |
|||
methods: { |
|||
async loadChildInfo() { |
|||
if (!this.childId) { |
|||
// 如果没有传入childId,使用当前选中的孩子 |
|||
if (this.selectedChild) { |
|||
this.childInfo = this.selectedChild |
|||
} else { |
|||
uni.showToast({ |
|||
title: '缺少孩子ID参数', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
try { |
|||
const response = await apiRoute.parent_getChildInfo({ |
|||
child_id: this.childId |
|||
}) |
|||
|
|||
if (response.code === 1) { |
|||
this.childInfo = response.data |
|||
} else { |
|||
uni.showToast({ |
|||
title: response.msg || '获取孩子信息失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取孩子信息失败:', error) |
|||
uni.showToast({ |
|||
title: '获取孩子信息失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.child_info_card { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.child_header { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 24rpx; |
|||
padding-bottom: 32rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
margin-bottom: 32rpx; |
|||
|
|||
.child_avatar { |
|||
width: 120rpx; |
|||
height: 120rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.child_basic { |
|||
flex: 1; |
|||
|
|||
.child_name { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.child_tags { |
|||
display: flex; |
|||
gap: 12rpx; |
|||
flex-wrap: wrap; |
|||
|
|||
.tag { |
|||
font-size: 22rpx; |
|||
padding: 6rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
|
|||
&.gender { |
|||
background: rgba(41, 211, 180, 0.1); |
|||
color: #29d3b4; |
|||
} |
|||
|
|||
&.age { |
|||
background: rgba(52, 152, 219, 0.1); |
|||
color: #3498db; |
|||
} |
|||
|
|||
&.label { |
|||
background: rgba(230, 126, 34, 0.1); |
|||
color: #e67e22; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.child_details { |
|||
.detail_section { |
|||
margin-bottom: 32rpx; |
|||
|
|||
.section_title { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 20rpx; |
|||
padding-bottom: 12rpx; |
|||
border-bottom: 2rpx solid #29d3b4; |
|||
} |
|||
|
|||
.detail_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 16rpx 0; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
.detail_label { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
min-width: 160rpx; |
|||
} |
|||
|
|||
.detail_value { |
|||
font-size: 26rpx; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
} |
|||
|
|||
.note_content { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
line-height: 1.6; |
|||
padding: 16rpx; |
|||
background: #f8f9fa; |
|||
border-radius: 8rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.empty_state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
|
|||
.empty_icon { |
|||
width: 160rpx; |
|||
height: 160rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.empty_text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.loading_state { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.loading_text { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,592 @@ |
|||
<!--家长端用户信息管理页面--> |
|||
<template> |
|||
<view class="main_box"> |
|||
<view class="navbar_section"> |
|||
<view class="title">用户信息</view> |
|||
</view> |
|||
|
|||
<!-- 家长信息展示 --> |
|||
<view class="parent_info_section"> |
|||
<view class="parent_name">{{ parentInfo.name || '张家长' }}</view> |
|||
<view class="parent_phone">{{ parentInfo.phone_number || '13800138000' }}</view> |
|||
</view> |
|||
|
|||
|
|||
<!-- 选中孩子信息弹窗 --> |
|||
<view class="child_popup" v-if="showChildPopup" @click="closeChildPopup"> |
|||
<view class="popup_content" @click.stop> |
|||
<view class="popup_header"> |
|||
<view class="popup_title">选择孩子</view> |
|||
<view class="popup_close" @click="closeChildPopup">×</view> |
|||
</view> |
|||
<view class="popup_children_list"> |
|||
<view |
|||
v-for="child in childrenList" |
|||
:key="child.id" |
|||
:class="['popup_child_item', selectedChild && selectedChild.id === child.id ? 'selected' : '']" |
|||
@click="selectChildFromPopup(child)" |
|||
> |
|||
<view class="popup_child_info"> |
|||
<view class="popup_child_name">{{ child.name }}</view> |
|||
<view class="popup_child_details"> |
|||
<text class="popup_detail_tag">{{ child.gender === 1 ? '男' : '女' }}</text> |
|||
<text class="popup_detail_tag">{{ Math.floor(child.age) }}岁</text> |
|||
</view> |
|||
<view class="popup_child_campus">{{ child.campus_name || '未分配' }}</view> |
|||
<view class="popup_child_class">{{ child.class_name || '未分配' }}</view> |
|||
</view> |
|||
<view class="popup_child_courses">{{ child.remaining_courses || 0 }}课时</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 当前选中孩子的统计信息 --> |
|||
<view class="selected_child_section" v-if="selectedChild"> |
|||
<view class="selected_child_header"> |
|||
<view class="selected_child_info"> |
|||
<view class="selected_child_name">{{ selectedChild.name }}</view> |
|||
<view class="selected_child_details"> |
|||
<text class="selected_detail_tag">{{ selectedChild.gender === 1 ? '男' : '女' }}</text> |
|||
<text class="selected_detail_tag">{{ Math.floor(selectedChild.age) }}岁</text> |
|||
<text class="selected_detail_tag">{{ selectedChild.campus_name || '未分配校区' }}</text> |
|||
</view> |
|||
</view> |
|||
<view class="switch_button" @click="openChildPopup">切换</view> |
|||
</view> |
|||
|
|||
<view class="stats_grid"> |
|||
<view class="stat_item"> |
|||
<view class="stat_number">{{ selectedChild.total_courses || 0 }}</view> |
|||
<view class="stat_label">总课程</view> |
|||
</view> |
|||
<view class="stat_item"> |
|||
<view class="stat_number">{{ selectedChild.completed_courses || 0 }}</view> |
|||
<view class="stat_label">已完成</view> |
|||
</view> |
|||
<view class="stat_item"> |
|||
<view class="stat_number">{{ selectedChild.remaining_courses || 0 }}</view> |
|||
<view class="stat_label">剩余课时</view> |
|||
</view> |
|||
<view class="stat_item"> |
|||
<view class="stat_number">{{ selectedChild.attendance_rate || 0 }}%</view> |
|||
<view class="stat_label">出勤率</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 功能菜单 - 九宫格样式 --> |
|||
<view class="main_section"> |
|||
<view class="grid_container"> |
|||
<view class="grid_item" @click="viewChildDetail"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/tou.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">孩子详情</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewCourses"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/kkry.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">课程管理</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewMaterials"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/liu.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">教学资料</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewServices"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/notice.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">服务管理</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewOrders"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/used.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">订单管理</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewMessages"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/notice.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">消息管理</view> |
|||
</view> |
|||
|
|||
<view class="grid_item" @click="viewContracts"> |
|||
<view class="grid_icon"> |
|||
<image src="/static/icon-img/warn.png" class="icon_image"></image> |
|||
</view> |
|||
<view class="grid_text">合同管理</view> |
|||
</view> |
|||
|
|||
<!-- 空白占位,保持九宫格对齐 --> |
|||
<view class="grid_item empty_item"></view> |
|||
<view class="grid_item empty_item"></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState, mapMutations } from 'vuex' |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
parentInfo: {}, |
|||
childrenList: [], |
|||
showChildPopup: false, |
|||
loading: false |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['selectedChild']) |
|||
}, |
|||
onLoad() { |
|||
// 设置用户角色为家长 |
|||
this.setUserRole('parent') |
|||
this.loadChildrenList() |
|||
console.log('用户信息页面加载完成') |
|||
}, |
|||
|
|||
onShow() { |
|||
// 页面显示时刷新数据 |
|||
console.log('用户信息页面显示') |
|||
}, |
|||
methods: { |
|||
...mapMutations(['SET_USER_ROLE', 'SET_SELECTED_CHILD', 'SET_CHILDREN_LIST']), |
|||
|
|||
setUserRole(role) { |
|||
this.SET_USER_ROLE(role) |
|||
}, |
|||
|
|||
async loadChildrenList() { |
|||
this.loading = true |
|||
try { |
|||
console.log('开始加载孩子列表...') |
|||
const response = await apiRoute.parent_getChildrenList() |
|||
console.log('孩子列表响应:', response) |
|||
|
|||
if (response.code === 1) { |
|||
this.childrenList = response.data.data || [] |
|||
this.parentInfo = response.data.parent_info || {} |
|||
this.SET_CHILDREN_LIST(this.childrenList) |
|||
|
|||
console.log('加载到的孩子列表:', this.childrenList) |
|||
console.log('家长信息:', this.parentInfo) |
|||
|
|||
// 如果没有选中的孩子且有孩子列表,默认选中第一个 |
|||
if (!this.selectedChild && this.childrenList.length > 0) { |
|||
this.SET_SELECTED_CHILD(this.childrenList[0]) |
|||
} |
|||
} else { |
|||
console.error('获取孩子列表失败:', response) |
|||
uni.showToast({ |
|||
title: response.msg || '获取孩子列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取孩子列表失败:', error) |
|||
uni.showToast({ |
|||
title: '获取孩子列表失败', |
|||
icon: 'none' |
|||
}) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
|
|||
openChildPopup() { |
|||
this.showChildPopup = true |
|||
}, |
|||
|
|||
closeChildPopup() { |
|||
this.showChildPopup = false |
|||
}, |
|||
|
|||
selectChildFromPopup(child) { |
|||
this.SET_SELECTED_CHILD(child) |
|||
console.log('选中孩子:', child) |
|||
this.closeChildPopup() |
|||
}, |
|||
|
|||
viewChildDetail() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/user-info/child-detail?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewCourses() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/courses/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewMaterials() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/materials/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewServices() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/services/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewOrders() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/orders/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewMessages() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/messages/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
}, |
|||
|
|||
viewContracts() { |
|||
if (!this.selectedChild) { |
|||
uni.showToast({ |
|||
title: '请先选择孩子', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
uni.navigateTo({ |
|||
url: `/pages/parent/contracts/index?childId=${this.selectedChild.id}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.main_box { |
|||
background: #f8f9fa; |
|||
min-height: 100vh; |
|||
} |
|||
|
|||
// 自定义导航栏 |
|||
.navbar_section { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background: #29D3B4; |
|||
|
|||
.title { |
|||
padding: 40rpx 20rpx; |
|||
|
|||
// 小程序端样式 |
|||
// #ifdef MP-WEIXIN |
|||
padding: 80rpx 0 20rpx; |
|||
// #endif |
|||
|
|||
font-size: 30rpx; |
|||
color: #fff; |
|||
} |
|||
} |
|||
|
|||
// 家长信息 |
|||
.parent_info_section { |
|||
background: #fff; |
|||
padding: 32rpx; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
|
|||
.parent_name { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.parent_phone { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
|
|||
// 弹窗样式 |
|||
.child_popup { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
z-index: 1000; |
|||
|
|||
.popup_content { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
width: 80%; |
|||
max-height: 70vh; |
|||
overflow: hidden; |
|||
|
|||
.popup_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 32rpx; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.popup_title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.popup_close { |
|||
font-size: 48rpx; |
|||
color: #999; |
|||
font-weight: 300; |
|||
} |
|||
} |
|||
|
|||
.popup_children_list { |
|||
max-height: 60vh; |
|||
overflow-y: auto; |
|||
|
|||
.popup_child_item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 24rpx 32rpx; |
|||
border-bottom: 1px solid #f8f9fa; |
|||
|
|||
&.selected { |
|||
background: rgba(41, 211, 180, 0.1); |
|||
} |
|||
|
|||
.popup_child_info { |
|||
flex: 1; |
|||
|
|||
.popup_child_name { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.popup_child_details { |
|||
display: flex; |
|||
gap: 12rpx; |
|||
margin-bottom: 8rpx; |
|||
|
|||
.popup_detail_tag { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
background: #f0f0f0; |
|||
padding: 2rpx 8rpx; |
|||
border-radius: 8rpx; |
|||
} |
|||
} |
|||
|
|||
.popup_child_campus, .popup_child_class { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
margin-bottom: 4rpx; |
|||
} |
|||
} |
|||
|
|||
.popup_child_courses { |
|||
font-size: 24rpx; |
|||
color: #29d3b4; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 选中孩子信息 |
|||
.selected_child_section { |
|||
background: #fff; |
|||
margin: 20rpx; |
|||
border-radius: 16rpx; |
|||
padding: 32rpx; |
|||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|||
|
|||
.selected_child_header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 32rpx; |
|||
|
|||
.selected_child_info { |
|||
flex: 1; |
|||
|
|||
.selected_child_name { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.selected_child_details { |
|||
display: flex; |
|||
gap: 12rpx; |
|||
|
|||
.selected_detail_tag { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
background: #f0f0f0; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.switch_button { |
|||
background: #29d3b4; |
|||
color: #fff; |
|||
padding: 12rpx 24rpx; |
|||
border-radius: 20rpx; |
|||
font-size: 24rpx; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
|
|||
.stats_grid { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
|
|||
.stat_item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
|
|||
.stat_number { |
|||
color: #29D3B4; |
|||
font-size: 48rpx; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.stat_label { |
|||
color: #AAAAAA; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.main_section { |
|||
margin-top: 20rpx; |
|||
background: #f8f9fa; |
|||
padding: 0 24rpx; |
|||
padding-top: 40rpx; |
|||
padding-bottom: 150rpx; |
|||
|
|||
.grid_container { |
|||
background: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 40rpx 20rpx; |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
justify-content: space-between; |
|||
|
|||
.grid_item { |
|||
width: 30%; |
|||
margin-bottom: 40rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
&.empty_item { |
|||
// 空白占位项不显示任何内容 |
|||
} |
|||
|
|||
.grid_icon { |
|||
width: 88rpx; |
|||
height: 88rpx; |
|||
border-radius: 50%; |
|||
background: rgba(41, 211, 180, 0.1); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.icon_image { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
} |
|||
} |
|||
|
|||
.grid_text { |
|||
font-size: 26rpx; |
|||
color: #333; |
|||
text-align: center; |
|||
line-height: 1.2; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 箭头图标样式 |
|||
.arrow-icon { |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
opacity: 0.6; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue