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