Browse Source

Merge branch 'master' of http://gitlab.frkj.cc/php/zhjwxt

master
于宏哲PHP 9 months ago
parent
commit
65c37d38fb
  1. 3
      niucloud/app/api/controller/apiController/Contract.php
  2. 8
      niucloud/app/service/api/apiService/ContractService.php
  3. 5
      uniapp/api/apiRoute.js
  4. 586
      uniapp/components/schedule/ScheduleDetail.vue
  5. 27
      uniapp/pages.json
  6. 474
      uniapp/pages/coach/schedule/add_schedule.vue
  7. 497
      uniapp/pages/coach/schedule/adjust_course.vue
  8. 511
      uniapp/pages/coach/schedule/sign_in.vue
  9. 9
      uniapp/pages/coach/student/student_list.vue
  10. 2
      uniapp/pages/common/contract/contract_sign.vue
  11. 6
      uniapp/pages/common/contract/my_contract.vue

3
niucloud/app/api/controller/apiController/Contract.php

@ -66,8 +66,7 @@ class Contract extends BaseApiService
} }
$where = [ $where = [
'contract_id' => $contract_id, 'id' => $contract_id
'personnel_id' => $this->member_id
]; ];
try { try {

8
niucloud/app/service/api/apiService/ContractService.php

@ -101,10 +101,9 @@ class ContractService extends BaseApiService
]; ];
try { try {
$contract_id = $where['contract_id'] ?? 0; $id = $where['id'] ?? 0;
$personnel_id = $where['personnel_id'] ?? 0;
if (empty($contract_id) || empty($personnel_id)) { if (empty($id)) {
$res['msg'] = '参数错误'; $res['msg'] = '参数错误';
return $res; return $res;
} }
@ -112,8 +111,7 @@ class ContractService extends BaseApiService
// 查询合同签订记录,关联合同表 // 查询合同签订记录,关联合同表
$contractSign = ContractSign::alias('cs') $contractSign = ContractSign::alias('cs')
->join('school_contract c', 'cs.contract_id = c.id') ->join('school_contract c', 'cs.contract_id = c.id')
->where('cs.contract_id', $contract_id) ->where('cs.id', $id)
->where('cs.personnel_id', $personnel_id)
->where('cs.deleted_at', 0) ->where('cs.deleted_at', 0)
->where('c.deleted_at', 0) ->where('c.deleted_at', 0)
->field([ ->field([

5
uniapp/api/apiRoute.js

@ -455,4 +455,9 @@ export default {
async getCourseScheduleFilterOptions(data = {}) { async getCourseScheduleFilterOptions(data = {}) {
return await http.get('/courseSchedule/filterOptions', data); return await http.get('/courseSchedule/filterOptions', data);
}, },
// 提交课程点名
async submitScheduleSignIn(data = {}) {
return await http.post('/courseSchedule/signIn', data);
},
} }

586
uniapp/components/schedule/ScheduleDetail.vue

@ -1,104 +1,89 @@
<template> <template>
<view class="schedule-detail" v-if="visible"> <fui-modal
<!-- <view class="popup-wrapper">--> :show="visible"
<!-- <view class="popup-header">--> title="课程安排详情"
<!-- <text class="popup-title">课次详情</text>--> width="700"
<!-- <view class="close-btn" @click="closePopup">--> @cancel="closePopup"
<!-- <text class="close-icon">×</text>--> :buttons="[]"
<!-- </view>--> >
<!-- </view>--> <view class="schedule-detail" v-if="scheduleInfo">
<!-- 课程基本信息 -->
<!-- <view class="popup-content" v-if="loading">--> <view class="section basic-info">
<!-- <view class="loading">--> <view class="section-title">基本信息</view>
<!-- <fui-loading></fui-loading>--> <view class="info-item">
<!-- <text class="loading-text">加载中...</text>--> <text class="item-label">课程名称</text>
<!-- </view>--> <text class="item-value">{{ scheduleInfo.course_name }}</text>
<!-- </view>--> </view>
<view class="info-item">
<!-- <view class="popup-content" v-else-if="error">--> <text class="item-label">上课时间</text>
<!-- <view class="error-message">--> <text class="item-value">{{ scheduleInfo.course_date }} {{ scheduleInfo.time_slot }}</text>
<!-- <text>{{ errorMessage }}</text>--> </view>
<!-- <view class="retry-btn" @click="fetchScheduleDetail">--> <view class="info-item">
<!-- <text>重试</text>--> <text class="item-label">上课地点</text>
<!-- </view>--> <text class="item-value">{{ scheduleInfo.venue_name }}</text>
<!-- </view>--> </view>
<!-- </view>--> <view class="info-item">
<text class="item-label">授课教练</text>
<!-- <view class="popup-content" v-else>--> <text class="item-value">{{ scheduleInfo.coach_name }}</text>
<!-- <view class="course-title">--> </view>
<!-- <text>{{ scheduleDetail.title || '暂无课程名称' }}</text>--> <view class="info-item">
<!-- </view>--> <text class="item-label">课程状态</text>
<text class="item-value" :class="getStatusClass(scheduleInfo.status)">{{ scheduleInfo.status_text }}</text>
<!-- <view class="course-time">--> </view>
<!-- <text>{{ scheduleDetail.course_date || '' }} {{ scheduleDetail.time_slot || '' }}</text>--> <view class="info-item">
<!-- </view>--> <text class="item-label">班级</text>
<text class="item-value">{{ scheduleInfo.class_info ? scheduleInfo.class_info.class_name : '无班级' }}</text>
<!-- <view class="schedule-info">--> </view>
<!-- <view class="info-item">--> </view>
<!-- <text class="info-label">授课教师:</text>-->
<!-- <text class="info-value">{{ scheduleDetail.coach?.name || '未设置' }}</text>--> <!-- 学员信息 -->
<!-- </view>--> <view class="section students-info">
<view class="section-title">学员信息 ({{ scheduleInfo.students ? scheduleInfo.students.length : 0 }})</view>
<!-- <view class="info-item">--> <view class="student-list" v-if="scheduleInfo.students && scheduleInfo.students.length > 0">
<!-- <text class="info-label">教室:</text>--> <view class="student-item" v-for="(student, index) in scheduleInfo.students" :key="index">
<!-- <text class="info-value">{{ scheduleDetail.venue?.venue_name || '未设置' }}</text>--> <view class="student-avatar">
<!-- </view>--> <image :src="student.avatar || '/static/icon-img/avatar.png'" mode="aspectFill"></image>
</view>
<!-- <view class="info-item">--> <view class="student-detail">
<!-- <text class="info-label">当前人数:</text>--> <text class="student-name">{{ student.name }}</text>
<!-- <text class="info-value">{{ studentCount }}/{{ scheduleDetail.venue?.capacity || 0 }}</text>--> <text class="student-status" :class="getStudentStatusClass(student.status)">{{ student.status_text }}</text>
<!-- </view>--> </view>
</view>
<!-- <view class="info-item">--> </view>
<!-- <text class="info-label">课程内容:</text>--> <view class="empty-list" v-else>
<!-- <text class="info-value">{{ scheduleDetail.content || '未设置上课内容' }}</text>--> <text>暂无学员参与此课程</text>
<!-- </view>--> </view>
</view>
<!-- <view class="info-item" v-if="scheduleDetail.remark">-->
<!-- <text class="info-label">备注:</text>--> <!-- 操作按钮 -->
<!-- <text class="info-value">{{ scheduleDetail.remark }}</text>--> <view class="action-buttons">
<!-- </view>--> <fui-button type="primary" @click="handleSignIn" :disabled="scheduleInfo.status === 'completed'">课程点名</fui-button>
<fui-button type="default" @click="handleAdjustClass" :disabled="scheduleInfo.status === 'completed'">调整课程</fui-button>
<!-- &lt;!&ndash; 学员列表 &ndash;&gt;--> </view>
<!-- <view class="student-list-section">-->
<!-- <view class="section-title">--> <!-- 关闭按钮 -->
<!-- <text>学员列表</text>--> <view class="close-btn" @click="closePopup">
<!-- <text class="status-tag" :class="statusClass">{{ statusText }}</text>--> <fui-icon name="close" :size="40" color="#999"></fui-icon>
<!-- </view>--> </view>
<!-- <view class="student-list" v-if="scheduleDetail.student_courses && scheduleDetail.student_courses.length > 0">--> </view>
<!-- <view class="student-item" v-for="(student, index) in scheduleDetail.student_courses" :key="index">-->
<!-- <view class="student-avatar">--> <view class="loading" v-if="loading && !scheduleInfo">
<!-- <image :src="$util.img(student.avatar)" mode="aspectFill"></image>--> <fui-loading></fui-loading>
<!-- </view>--> <text class="loading-text">加载中...</text>
<!-- <view class="student-info">--> </view>
<!-- <text class="student-name">{{ student.name }}</text>-->
<!-- <text class="student-status" :class="{'signed': student.status === 'signed'}">--> <view class="error-message" v-if="error && !scheduleInfo">
<!-- {{ student.status === 'signed' ? '已签到' : '未签到' }}--> <text>{{ errorMessage }}</text>
<!-- </text>--> <view class="retry-btn" @click="fetchScheduleDetail">
<!-- </view>--> <text>重试</text>
<!-- </view>--> </view>
<!-- </view>--> </view>
<!-- <view class="empty-list" v-else>--> </fui-modal>
<!-- <text>暂无学员参与此课程</text>-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="popup-footer">-->
<!-- <view class="action-btn adjust-btn" @click="handleAdjustClass">-->
<!-- <text>调课</text>-->
<!-- </view>-->
<!-- <view class="action-btn sign-btn" @click="handleSignIn">-->
<!-- <text>点名</text>-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
</view> </view>
</template> </template>
<script> <script>
import apiRoute from '@/api/apiRoute.js'; import api from '@/api/apiRoute.js';
export default { export default {
name: 'ScheduleDetail', name: 'ScheduleDetail',
@ -109,7 +94,7 @@ export default {
}, },
scheduleId: { scheduleId: {
type: [String, Number], type: [String, Number],
default: '' default: null
} }
}, },
data() { data() {
@ -117,66 +102,23 @@ export default {
loading: false, loading: false,
error: false, error: false,
errorMessage: '加载失败,请重试', errorMessage: '加载失败,请重试',
scheduleDetail: {}, scheduleInfo: null
studentCount: 0
}
},
computed: {
//
statusText() {
if (!this.scheduleDetail.student_courses || !this.scheduleDetail.student_courses[0]) {
return '未开始';
}
const now = new Date();
const startDate = this.scheduleDetail.student_courses[0].start_date ?
new Date(this.scheduleDetail.student_courses[0].start_date) : null;
const endDate = this.scheduleDetail.student_courses[0].end_date ?
new Date(this.scheduleDetail.student_courses[0].end_date) : null;
if (startDate && endDate) {
if (now >= startDate && now <= endDate) {
return '上课中';
} else if (now > endDate) {
return '已结束';
} else if (now < startDate) {
return '未开始';
}
}
return '未开始';
},
statusClass() {
switch (this.statusText) {
case '上课中':
return 'status-in-progress';
case '已结束':
return 'status-ended';
case '未开始':
default:
return 'status-not-started';
}
} }
}, },
watch: { watch: {
// ID
scheduleId: {
immediate: true,
handler(newVal) {
if (newVal && this.visible) {
this.fetchScheduleDetail();
}
}
},
//
visible(newVal) { visible(newVal) {
if (newVal && this.scheduleId) { if (newVal && this.scheduleId) {
this.fetchScheduleDetail(); this.fetchScheduleDetail();
} }
},
scheduleId(newVal) {
if (newVal && this.visible) {
this.fetchScheduleDetail();
}
} }
}, },
methods: { methods: {
// //
async fetchScheduleDetail() { async fetchScheduleDetail() {
if (!this.scheduleId) { if (!this.scheduleId) {
this.error = true; this.error = true;
@ -188,34 +130,21 @@ export default {
this.error = false; this.error = false;
try { try {
// 使 const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId });
const res = await apiRoute.getCourseScheduleInfo({ if (res.code === 1) {
id: this.scheduleId this.scheduleInfo = res.data;
});
if (res.code === 1 && res.data) {
this.scheduleDetail = res.data;
//
this.studentCount = this.scheduleDetail.student_courses ?
this.scheduleDetail.student_courses.length : 0;
} else { } else {
// 使 uni.showToast({
const fallbackRes = await apiRoute.courseInfo({ title: res.msg || '获取课程安排详情失败',
id: this.scheduleId icon: 'none'
}); });
this.error = true;
if (fallbackRes.code === 1 && fallbackRes.data) { this.errorMessage = res.msg || '获取课程安排详情失败';
this.scheduleDetail = fallbackRes.data;
this.studentCount = this.scheduleDetail.student_courses ?
this.scheduleDetail.student_courses.length : 0;
} else {
throw new Error(res.msg || fallbackRes.msg || '获取课程详情失败');
}
} }
} catch (error) { } catch (error) {
console.error('获取课程详情失败:', error); console.error('获取课程安排详情失败:', error);
this.error = true; this.error = true;
this.errorMessage = error.message || '获取课程详情失败,请重试'; this.errorMessage = error.message || '获取课程安排详情失败,请重试';
} finally { } finally {
this.loading = false; this.loading = false;
} }
@ -226,241 +155,118 @@ export default {
this.$emit('update:visible', false); this.$emit('update:visible', false);
}, },
// //
handleSignIn() { handleSignIn() {
//
if (this.statusText === '已结束') {
uni.showToast({
title: '课程已结束,无法点名',
icon: 'none'
});
return;
}
//
if (!this.scheduleDetail.student_courses || this.scheduleDetail.student_courses.length === 0) {
uni.showToast({
title: '暂无学员,无法点名',
icon: 'none'
});
return;
}
//
this.$emit('sign-in', { this.$emit('sign-in', {
scheduleId: this.scheduleId, scheduleId: this.scheduleId,
scheduleDetail: this.scheduleDetail scheduleName: this.scheduleInfo?.course_name
}); });
this.closePopup();
}, },
// //
handleAdjustClass() { handleAdjustClass() {
//
if (this.statusText === '已结束') {
uni.showToast({
title: '课程已结束,无法调课',
icon: 'none'
});
return;
}
//
this.$emit('adjust-class', { this.$emit('adjust-class', {
scheduleId: this.scheduleId, scheduleId: this.scheduleId,
scheduleDetail: this.scheduleDetail scheduleName: this.scheduleInfo?.course_name
}); });
this.closePopup();
},
//
getStatusClass(status) {
const statusMap = {
'pending': 'status-pending',
'upcoming': 'status-upcoming',
'ongoing': 'status-ongoing',
'completed': 'status-completed'
};
return statusMap[status] || '';
},
//
getStudentStatusClass(status) {
const statusMap = {
0: 'status-pending', //
1: 'status-completed', //
2: 'status-leave' //
};
return statusMap[status] || '';
} }
} }
} }
</script> </script>
<style lang="less" scoped> <style lang="scss" scoped>
.schedule-detail { .schedule-detail {
position: fixed; padding: 20rpx;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.popup-wrapper {
width: 90%;
max-height: 80vh; max-height: 80vh;
background-color: #434544;
border-radius: 16rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.popup-header {
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #555;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.close-icon {
font-size: 40rpx;
color: #fff;
}
.popup-content {
flex: 1;
padding: 30rpx;
overflow-y: auto; overflow-y: auto;
position: relative;
} }
.loading, .error-message { .section {
height: 300rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #ccc;
}
.error-message {
color: #ff6b6b;
font-size: 28rpx;
text-align: center;
}
.retry-btn {
margin-top: 30rpx;
padding: 12rpx 30rpx;
background-color: #29d3b4;
border-radius: 8rpx;
color: #fff;
font-size: 24rpx;
}
.course-title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
margin-bottom: 16rpx;
}
.course-time {
font-size: 28rpx;
color: #FAD24E;
margin-bottom: 30rpx; margin-bottom: 30rpx;
} background-color: #2a2a2a;
.schedule-info {
background-color: #333;
border-radius: 12rpx; border-radius: 12rpx;
padding: 24rpx; padding: 20rpx;
}
.info-item {
display: flex;
margin-bottom: 20rpx;
}
.info-label {
width: 160rpx;
font-size: 26rpx;
color: #ccc;
}
.info-value {
flex: 1;
font-size: 26rpx;
color: #fff;
}
.student-list-section {
margin-top: 30rpx;
} }
.section-title { .section-title {
display: flex; font-size: 30rpx;
justify-content: space-between; font-weight: bold;
align-items: center; color: #29d3b4;
padding-bottom: 16rpx;
border-bottom: 1px solid #555;
margin-bottom: 20rpx; margin-bottom: 20rpx;
border-bottom: 1px solid #3a3a3a;
padding-bottom: 10rpx;
} }
.section-title text { .info-item {
display: flex;
margin-bottom: 16rpx;
font-size: 28rpx; font-size: 28rpx;
color: #fff;
} }
.status-tag { .item-label {
font-size: 24rpx; color: #999;
padding: 4rpx 16rpx; width: 160rpx;
border-radius: 30rpx; flex-shrink: 0;
}
.status-in-progress {
background-color: #FAD24E;
color: #333;
}
.status-ended {
background-color: #e2e2e2;
color: #333;
} }
.status-not-started { .item-value {
background-color: #1cd188;
color: #fff; color: #fff;
flex: 1;
} }
.student-list { .student-list {
display: flex; display: flex;
flex-direction: column; flex-wrap: wrap;
gap: 20rpx; gap: 20rpx;
} }
.student-item { .student-item {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #3a3a3a;
border-radius: 8rpx;
padding: 15rpx;
width: calc(50% - 10rpx);
} }
.student-avatar { .student-avatar {
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
border-radius: 50%; border-radius: 40rpx;
overflow: hidden; overflow: hidden;
background-color: #555; margin-right: 15rpx;
margin-right: 20rpx;
} image {
width: 100%;
.student-avatar image { height: 100%;
width: 100%; }
height: 100%;
} }
.student-info { .student-detail {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -469,49 +275,81 @@ export default {
.student-name { .student-name {
font-size: 28rpx; font-size: 28rpx;
color: #fff; color: #fff;
margin-bottom: 6rpx; margin-bottom: 8rpx;
} }
.student-status { .student-status {
font-size: 24rpx; font-size: 24rpx;
color: #ff6b6b;
} }
.student-status.signed { .status-pending {
color: #1cd188; color: #ff9500;
} }
.empty-list { .status-upcoming {
padding: 40rpx 0; color: #29d3b4;
text-align: center; }
color: #999;
font-size: 28rpx; .status-ongoing {
color: #007aff;
}
.status-completed {
color: #8e8e93;
} }
.popup-footer { .status-leave {
color: #ff3b30;
}
.action-buttons {
display: flex; display: flex;
padding: 30rpx; justify-content: space-around;
gap: 20rpx; margin-top: 30rpx;
border-top: 1px solid #555; gap: 30rpx;
} }
.action-btn { .close-btn {
flex: 1; position: absolute;
height: 80rpx; top: 20rpx;
right: 20rpx;
z-index: 10;
padding: 10rpx;
}
.loading, .error-message {
height: 300rpx;
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 8rpx; }
.loading-text {
margin-top: 20rpx;
font-size: 28rpx; font-size: 28rpx;
color: #ccc;
} }
.adjust-btn { .error-message {
background-color: #555; color: #ff6b6b;
color: #fff; font-size: 28rpx;
text-align: center;
} }
.sign-btn { .retry-btn {
margin-top: 30rpx;
padding: 12rpx 30rpx;
background-color: #29d3b4; background-color: #29d3b4;
border-radius: 8rpx;
color: #fff; color: #fff;
font-size: 24rpx;
}
.empty-list {
padding: 40rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
} }
</style> </style>

27
uniapp/pages.json

@ -674,6 +674,33 @@
"navigationBarTextStyle": "white" "navigationBarTextStyle": "white"
} }
}, },
{
"path": "pages/coach/schedule/add_schedule",
"style": {
"navigationBarTitleText": "添加课程安排",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/adjust_course",
"style": {
"navigationBarTitleText": "调整课程安排",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/sign_in",
"style": {
"navigationBarTitleText": "课程点名",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{ {
"path": "pages/academic/home/index", "path": "pages/academic/home/index",
"style": { "style": {

474
uniapp/pages/coach/schedule/add_schedule.vue

@ -0,0 +1,474 @@
<template>
<view class="add-schedule-container">
<uni-nav-bar
title="添加课程安排"
left-icon="left"
fixed="true"
background-color="#292929"
color="#FFFFFF"
@clickLeft="goBack"
></uni-nav-bar>
<view class="form-container">
<fui-form>
<!-- 课程选择 -->
<fui-form-item label="课程" required>
<view class="selector-input" @click="showCoursePicker = true">
<text>{{ selectedCourse ? selectedCourse.course_name : '请选择课程' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showCoursePicker"
:options="courseOptions"
valueKey="id"
textKey="course_name"
@confirm="onCourseSelect"
@cancel="showCoursePicker = false"
></fui-picker>
</fui-form-item>
<!-- 班级选择 -->
<fui-form-item label="班级">
<view class="selector-input" @click="showClassPicker = true">
<text>{{ selectedClass ? selectedClass.class_name : '请选择班级(可选)' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showClassPicker"
:options="classOptions"
valueKey="id"
textKey="class_name"
@confirm="onClassSelect"
@cancel="showClassPicker = false"
></fui-picker>
</fui-form-item>
<!-- 教练选择 -->
<fui-form-item label="授课教练" required>
<view class="selector-input" @click="showCoachPicker = true">
<text>{{ selectedCoach ? selectedCoach.name : '请选择教练' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showCoachPicker"
:options="coachOptions"
valueKey="id"
textKey="name"
@confirm="onCoachSelect"
@cancel="showCoachPicker = false"
></fui-picker>
</fui-form-item>
<!-- 场地选择 -->
<fui-form-item label="上课场地" required>
<view class="selector-input" @click="showVenuePicker = true">
<text>{{ selectedVenue ? selectedVenue.venue_name : '请选择场地' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showVenuePicker"
:options="venueOptions"
valueKey="id"
textKey="venue_name"
@confirm="onVenueSelect"
@cancel="showVenuePicker = false"
></fui-picker>
</fui-form-item>
<!-- 日期选择 -->
<fui-form-item label="上课日期" required>
<view class="selector-input" @click="showDatePicker = true">
<text>{{ formData.course_date || '请选择日期' }}</text>
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-date-picker
:show="showDatePicker"
@confirm="onDateSelect"
@cancel="showDatePicker = false"
:value="formData.course_date"
></fui-date-picker>
</fui-form-item>
<!-- 时间选择 -->
<fui-form-item label="上课时间" required>
<view class="selector-input" @click="showTimePicker = true">
<text>{{ formData.time_slot || '请选择时间段' }}</text>
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showTimePicker"
:options="timeSlotOptions"
valueKey="value"
textKey="text"
@confirm="onTimeSelect"
@cancel="showTimePicker = false"
></fui-picker>
</fui-form-item>
<!-- 容量设置 -->
<fui-form-item label="课程容量" required>
<fui-input
type="number"
:value="formData.available_capacity"
placeholder="请输入课程容量"
@input="formData.available_capacity = $event"
></fui-input>
</fui-form-item>
<!-- 备注信息 -->
<fui-form-item label="备注">
<fui-textarea
:value="formData.remark"
placeholder="请输入备注信息(可选)"
@input="formData.remark = $event"
maxlength="200"
></fui-textarea>
</fui-form-item>
</fui-form>
<!-- 提交按钮 -->
<view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">创建课程安排</fui-button>
</view>
</view>
</view>
</template>
<script>
import api from '@/api/apiRoute.js';
export default {
data() {
return {
//
formData: {
course_id: '',
class_id: '',
coach_id: '',
venue_id: '',
course_date: '',
time_slot: '',
available_capacity: '',
remark: ''
},
//
showCoursePicker: false,
showClassPicker: false,
showCoachPicker: false,
showVenuePicker: false,
showDatePicker: false,
showTimePicker: false,
//
courseOptions: [],
classOptions: [],
coachOptions: [],
venueOptions: [],
timeSlotOptions: [],
//
selectedCourse: null,
selectedClass: null,
selectedCoach: null,
selectedVenue: null,
//
submitting: false,
//
prefillDate: '',
prefillTime: '',
prefillTimeSlot: ''
};
},
onLoad(options) {
//
if (options.date) {
this.prefillDate = options.date;
this.formData.course_date = options.date;
}
if (options.time) {
this.prefillTime = options.time;
}
if (options.time_slot) {
this.prefillTimeSlot = options.time_slot;
this.formData.time_slot = options.time_slot;
}
//
this.loadFilterOptions();
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
async loadFilterOptions() {
uni.showLoading({
title: '加载数据中...'
});
try {
const res = await api.getCourseScheduleFilterOptions();
if (res.code === 1) {
//
this.courseOptions = res.data.courses || [];
//
this.classOptions = res.data.classes || [];
//
this.coachOptions = res.data.coaches || [];
//
this.venueOptions = res.data.venues || [];
//
this.generateTimeSlotOptions();
//
if (this.prefillTimeSlot) {
this.formData.time_slot = this.prefillTimeSlot;
}
} else {
uni.showToast({
title: res.msg || '加载筛选选项失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载筛选选项失败:', error);
uni.showToast({
title: '加载筛选选项失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
//
generateTimeSlotOptions() {
const timeSlots = [];
//
for (let hour = 8; hour < 12; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 12; hour < 18; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 18; hour < 22; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
this.timeSlotOptions = timeSlots;
},
//
onCourseSelect(e) {
const index = e.index;
if (index >= 0 && index < this.courseOptions.length) {
this.selectedCourse = this.courseOptions[index];
this.formData.course_id = this.selectedCourse.id;
}
this.showCoursePicker = false;
},
onClassSelect(e) {
const index = e.index;
if (index >= 0 && index < this.classOptions.length) {
this.selectedClass = this.classOptions[index];
this.formData.class_id = this.selectedClass.id;
} else {
this.selectedClass = null;
this.formData.class_id = '';
}
this.showClassPicker = false;
},
onCoachSelect(e) {
const index = e.index;
if (index >= 0 && index < this.coachOptions.length) {
this.selectedCoach = this.coachOptions[index];
this.formData.coach_id = this.selectedCoach.id;
}
this.showCoachPicker = false;
},
onVenueSelect(e) {
const index = e.index;
if (index >= 0 && index < this.venueOptions.length) {
this.selectedVenue = this.venueOptions[index];
this.formData.venue_id = this.selectedVenue.id;
//
if (this.selectedVenue.capacity) {
this.formData.available_capacity = this.selectedVenue.capacity;
}
}
this.showVenuePicker = false;
},
onDateSelect(e) {
this.formData.course_date = e.result;
this.showDatePicker = false;
},
onTimeSelect(e) {
const index = e.index;
if (index >= 0 && index < this.timeSlotOptions.length) {
this.formData.time_slot = this.timeSlotOptions[index].value;
}
this.showTimePicker = false;
},
//
validateForm() {
if (!this.formData.course_id) {
uni.showToast({
title: '请选择课程',
icon: 'none'
});
return false;
}
if (!this.formData.coach_id) {
uni.showToast({
title: '请选择授课教练',
icon: 'none'
});
return false;
}
if (!this.formData.venue_id) {
uni.showToast({
title: '请选择上课场地',
icon: 'none'
});
return false;
}
if (!this.formData.course_date) {
uni.showToast({
title: '请选择上课日期',
icon: 'none'
});
return false;
}
if (!this.formData.time_slot) {
uni.showToast({
title: '请选择上课时间',
icon: 'none'
});
return false;
}
if (!this.formData.available_capacity) {
uni.showToast({
title: '请输入课程容量',
icon: 'none'
});
return false;
}
return true;
},
//
async submitForm() {
if (!this.validateForm()) {
return;
}
this.submitting = true;
try {
const res = await api.createCourseSchedule(this.formData);
if (res.code === 1) {
uni.showToast({
title: '创建成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: res.msg || '创建失败',
icon: 'none'
});
}
} catch (error) {
console.error('创建课程安排失败:', error);
uni.showToast({
title: '创建失败,请重试',
icon: 'none'
});
} finally {
this.submitting = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.add-schedule-container {
min-height: 100vh;
background-color: #18181c;
padding-top: 88rpx;
}
.form-container {
padding: 30rpx;
}
.selector-input {
height: 80rpx;
background-color: #23232a;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
font-size: 28rpx;
color: #fff;
}
.btn-container {
margin-top: 60rpx;
padding: 0 30rpx;
}
</style>

497
uniapp/pages/coach/schedule/adjust_course.vue

@ -0,0 +1,497 @@
<template>
<view class="adjust-course-container">
<uni-nav-bar
title="调整课程安排"
left-icon="left"
fixed="true"
background-color="#292929"
color="#FFFFFF"
@clickLeft="goBack"
></uni-nav-bar>
<view class="form-container">
<view v-if="loading" class="loading-container">
<fui-loading></fui-loading>
<text class="loading-text">加载中...</text>
</view>
<fui-form v-else>
<!-- 课程信息 -->
<view class="section-title">当前课程信息</view>
<view class="course-info-card">
<view class="info-row">
<text class="info-label">课程名称</text>
<text class="info-value">{{ scheduleInfo.course_name }}</text>
</view>
<view class="info-row">
<text class="info-label">上课日期</text>
<text class="info-value">{{ scheduleInfo.course_date }}</text>
</view>
<view class="info-row">
<text class="info-label">上课时间</text>
<text class="info-value">{{ scheduleInfo.time_slot }}</text>
</view>
<view class="info-row">
<text class="info-label">授课教练</text>
<text class="info-value">{{ scheduleInfo.coach_name }}</text>
</view>
<view class="info-row">
<text class="info-label">上课场地</text>
<text class="info-value">{{ scheduleInfo.venue_name }}</text>
</view>
</view>
<view class="section-title">调整后信息</view>
<!-- 教练选择 -->
<fui-form-item label="授课教练">
<view class="selector-input" @click="showCoachPicker = true">
<text>{{ selectedCoach ? selectedCoach.name : scheduleInfo.coach_name }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showCoachPicker"
:options="coachOptions"
valueKey="id"
textKey="name"
@confirm="onCoachSelect"
@cancel="showCoachPicker = false"
></fui-picker>
</fui-form-item>
<!-- 场地选择 -->
<fui-form-item label="上课场地">
<view class="selector-input" @click="showVenuePicker = true">
<text>{{ selectedVenue ? selectedVenue.venue_name : scheduleInfo.venue_name }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showVenuePicker"
:options="venueOptions"
valueKey="id"
textKey="venue_name"
@confirm="onVenueSelect"
@cancel="showVenuePicker = false"
></fui-picker>
</fui-form-item>
<!-- 日期选择 -->
<fui-form-item label="上课日期">
<view class="selector-input" @click="showDatePicker = true">
<text>{{ formData.course_date || scheduleInfo.course_date }}</text>
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-date-picker
:show="showDatePicker"
@confirm="onDateSelect"
@cancel="showDatePicker = false"
:value="formData.course_date || scheduleInfo.course_date"
></fui-date-picker>
</fui-form-item>
<!-- 时间选择 -->
<fui-form-item label="上课时间">
<view class="selector-input" @click="showTimePicker = true">
<text>{{ formData.time_slot || scheduleInfo.time_slot }}</text>
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-picker
:show="showTimePicker"
:options="timeSlotOptions"
valueKey="value"
textKey="text"
@confirm="onTimeSelect"
@cancel="showTimePicker = false"
></fui-picker>
</fui-form-item>
<!-- 容量设置 -->
<fui-form-item label="课程容量">
<fui-input
type="number"
:value="formData.available_capacity || scheduleInfo.available_capacity"
placeholder="请输入课程容量"
@input="formData.available_capacity = $event"
></fui-input>
</fui-form-item>
<!-- 调整原因 -->
<fui-form-item label="调整原因" required>
<fui-textarea
:value="formData.adjust_reason"
placeholder="请输入调整原因"
@input="formData.adjust_reason = $event"
maxlength="200"
></fui-textarea>
</fui-form-item>
<!-- 提交按钮 -->
<view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button>
</view>
</fui-form>
</view>
</view>
</template>
<script>
import api from '@/api/apiRoute.js';
export default {
data() {
return {
//
loading: true,
submitting: false,
// ID
scheduleId: null,
//
scheduleInfo: {},
//
formData: {
schedule_id: '',
coach_id: '',
venue_id: '',
course_date: '',
time_slot: '',
available_capacity: '',
adjust_reason: ''
},
//
showCoachPicker: false,
showVenuePicker: false,
showDatePicker: false,
showTimePicker: false,
//
coachOptions: [],
venueOptions: [],
timeSlotOptions: [],
//
selectedCoach: null,
selectedVenue: null
};
},
onLoad(options) {
if (options.id) {
this.scheduleId = options.id;
this.formData.schedule_id = options.id;
this.loadScheduleInfo();
this.loadFilterOptions();
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
async loadScheduleInfo() {
try {
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId });
if (res.code === 1) {
this.scheduleInfo = res.data;
//
this.formData.coach_id = this.scheduleInfo.coach_id;
this.formData.venue_id = this.scheduleInfo.venue_id;
this.formData.course_date = this.scheduleInfo.course_date;
this.formData.time_slot = this.scheduleInfo.time_slot;
this.formData.available_capacity = this.scheduleInfo.available_capacity;
} else {
uni.showToast({
title: res.msg || '获取课程安排信息失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取课程安排信息失败:', error);
uni.showToast({
title: '获取课程安排信息失败',
icon: 'none'
});
}
},
//
async loadFilterOptions() {
try {
const res = await api.getCourseScheduleFilterOptions();
if (res.code === 1) {
//
this.coachOptions = res.data.coaches || [];
//
this.venueOptions = res.data.venues || [];
//
this.generateTimeSlotOptions();
//
this.findSelectedOptions();
} else {
uni.showToast({
title: res.msg || '加载筛选选项失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载筛选选项失败:', error);
uni.showToast({
title: '加载筛选选项失败',
icon: 'none'
});
} finally {
this.loading = false;
}
},
//
findSelectedOptions() {
//
if (this.scheduleInfo.coach_id) {
this.selectedCoach = this.coachOptions.find(coach => coach.id === this.scheduleInfo.coach_id);
}
//
if (this.scheduleInfo.venue_id) {
this.selectedVenue = this.venueOptions.find(venue => venue.id === this.scheduleInfo.venue_id);
}
},
//
generateTimeSlotOptions() {
const timeSlots = [];
//
for (let hour = 8; hour < 12; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 12; hour < 18; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 18; hour < 22; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
this.timeSlotOptions = timeSlots;
},
//
onCoachSelect(e) {
const index = e.index;
if (index >= 0 && index < this.coachOptions.length) {
this.selectedCoach = this.coachOptions[index];
this.formData.coach_id = this.selectedCoach.id;
}
this.showCoachPicker = false;
},
onVenueSelect(e) {
const index = e.index;
if (index >= 0 && index < this.venueOptions.length) {
this.selectedVenue = this.venueOptions[index];
this.formData.venue_id = this.selectedVenue.id;
//
if (this.selectedVenue.capacity && !this.formData.available_capacity) {
this.formData.available_capacity = this.selectedVenue.capacity;
}
}
this.showVenuePicker = false;
},
onDateSelect(e) {
this.formData.course_date = e.result;
this.showDatePicker = false;
},
onTimeSelect(e) {
const index = e.index;
if (index >= 0 && index < this.timeSlotOptions.length) {
this.formData.time_slot = this.timeSlotOptions[index].value;
}
this.showTimePicker = false;
},
//
validateForm() {
//
const hasChanges = this.formData.coach_id !== this.scheduleInfo.coach_id ||
this.formData.venue_id !== this.scheduleInfo.venue_id ||
this.formData.course_date !== this.scheduleInfo.course_date ||
this.formData.time_slot !== this.scheduleInfo.time_slot ||
this.formData.available_capacity !== this.scheduleInfo.available_capacity;
if (!hasChanges) {
uni.showToast({
title: '未进行任何修改',
icon: 'none'
});
return false;
}
if (!this.formData.adjust_reason) {
uni.showToast({
title: '请输入调整原因',
icon: 'none'
});
return false;
}
return true;
},
//
async submitForm() {
if (!this.validateForm()) {
return;
}
this.submitting = true;
try {
const res = await api.updateCourseSchedule(this.formData);
if (res.code === 1) {
uni.showToast({
title: '调整成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: res.msg || '调整失败',
icon: 'none'
});
}
} catch (error) {
console.error('调整课程安排失败:', error);
uni.showToast({
title: '调整失败,请重试',
icon: 'none'
});
} finally {
this.submitting = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.adjust-course-container {
min-height: 100vh;
background-color: #18181c;
padding-top: 88rpx;
}
.form-container {
padding: 30rpx;
}
.loading-container {
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #29d3b4;
margin: 30rpx 0 20rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #333;
}
.course-info-card {
background-color: #23232a;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
}
.info-row {
display: flex;
margin-bottom: 16rpx;
font-size: 28rpx;
}
.info-label {
color: #999;
width: 160rpx;
flex-shrink: 0;
}
.info-value {
color: #fff;
flex: 1;
}
.selector-input {
height: 80rpx;
background-color: #23232a;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
font-size: 28rpx;
color: #fff;
}
.btn-container {
margin-top: 60rpx;
padding: 0 30rpx;
}
</style>

511
uniapp/pages/coach/schedule/sign_in.vue

@ -0,0 +1,511 @@
<template>
<view class="sign-in-container">
<uni-nav-bar
title="课程点名"
left-icon="left"
fixed="true"
background-color="#292929"
color="#FFFFFF"
@clickLeft="goBack"
></uni-nav-bar>
<view class="content">
<!-- 课程信息 -->
<view class="course-info-card" v-if="scheduleInfo">
<view class="course-title">{{ scheduleInfo.course_name }}</view>
<view class="course-time">{{ scheduleInfo.course_date }} {{ scheduleInfo.time_slot }}</view>
<view class="course-detail">
<view class="detail-item">
<text class="detail-label">授课教练:</text>
<text class="detail-value">{{ scheduleInfo.coach_name }}</text>
</view>
<view class="detail-item">
<text class="detail-label">上课场地:</text>
<text class="detail-value">{{ scheduleInfo.venue_name }}</text>
</view>
<view class="detail-item">
<text class="detail-label">学员人数:</text>
<text class="detail-value">{{ studentList.length }}</text>
</view>
</view>
</view>
<!-- 学员列表 -->
<view class="student-section">
<view class="section-header">
<view class="section-title">学员点名</view>
<view class="action-buttons">
<fui-button type="primary" size="small" @click="checkAllStudents">全部签到</fui-button>
<fui-button type="danger" size="small" @click="uncheckAllStudents">全部取消</fui-button>
</view>
</view>
<view class="empty-list" v-if="studentList.length === 0">
<image src="/static/icon-img/empty.png" mode="aspectFit" class="empty-img"></image>
<text class="empty-text">暂无学员数据</text>
</view>
<view class="student-list" v-else>
<view
class="student-item"
v-for="(student, index) in studentList"
:key="index"
@click="toggleStudentStatus(index)"
>
<view class="student-avatar">
<image :src="student.avatar || '/static/icon-img/avatar.png'" mode="aspectFill"></image>
<view class="status-badge" :class="getStatusClass(student.status)"></view>
</view>
<view class="student-info">
<text class="student-name">{{ student.name }}</text>
<text class="student-phone">{{ student.phone_number || '无联系电话' }}</text>
</view>
<view class="status-container">
<view class="status-select">
<view
class="status-option"
:class="{ active: student.status === 1 }"
@click.stop="setStudentStatus(index, 1)"
>
已到
</view>
<view
class="status-option"
:class="{ active: student.status === 2 }"
@click.stop="setStudentStatus(index, 2)"
>
请假
</view>
<view
class="status-option"
:class="{ active: student.status === 0 }"
@click.stop="setStudentStatus(index, 0)"
>
未到
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 点名备注 -->
<view class="remark-section">
<view class="section-title">点名备注</view>
<fui-textarea
v-model="signInRemark"
placeholder="请输入点名备注(可选)"
maxlength="200"
></fui-textarea>
</view>
<!-- 提交按钮 -->
<view class="submit-btn">
<fui-button type="primary" @click="submitSignIn" :loading="submitting">提交点名</fui-button>
</view>
</view>
</view>
</template>
<script>
import api from '@/api/apiRoute.js';
export default {
data() {
return {
// ID
scheduleId: null,
//
scheduleInfo: null,
//
studentList: [],
//
signInRemark: '',
//
submitting: false
};
},
onLoad(options) {
if (options.id) {
this.scheduleId = options.id;
this.loadScheduleInfo();
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
async loadScheduleInfo() {
uni.showLoading({
title: '加载中...'
});
try {
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId });
if (res.code === 1) {
this.scheduleInfo = res.data;
//
if (this.scheduleInfo.students && this.scheduleInfo.students.length > 0) {
this.studentList = [...this.scheduleInfo.students];
}
} else {
uni.showToast({
title: res.msg || '获取课程安排信息失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取课程安排信息失败:', error);
uni.showToast({
title: '获取课程安排信息失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
//
getStatusClass(status) {
const statusMap = {
0: 'status-absent',
1: 'status-present',
2: 'status-leave'
};
return statusMap[status] || 'status-absent';
},
//
toggleStudentStatus(index) {
const student = this.studentList[index];
// -> -> ->
let newStatus = 0;
if (student.status === 0) {
newStatus = 1;
} else if (student.status === 1) {
newStatus = 2;
} else {
newStatus = 0;
}
this.setStudentStatus(index, newStatus);
},
//
setStudentStatus(index, status) {
if (index >= 0 && index < this.studentList.length) {
this.studentList[index].status = status;
//
const statusTextMap = {
0: '待上课',
1: '已上课',
2: '请假'
};
this.studentList[index].status_text = statusTextMap[status];
}
},
//
checkAllStudents() {
this.studentList.forEach((student, index) => {
this.setStudentStatus(index, 1);
});
},
//
uncheckAllStudents() {
this.studentList.forEach((student, index) => {
this.setStudentStatus(index, 0);
});
},
//
async submitSignIn() {
//
const studentData = this.studentList.map(student => ({
student_id: student.student_id,
resource_id: student.resource_id,
status: student.status
}));
const submitData = {
schedule_id: this.scheduleId,
students: studentData,
remark: this.signInRemark
};
this.submitting = true;
try {
// 使API
const res = await api.submitScheduleSignIn(submitData);
if (res.code === 1) {
uni.showToast({
title: '点名成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: res.msg || '点名失败',
icon: 'none'
});
}
} catch (error) {
console.error('点名失败:', error);
uni.showToast({
title: '点名失败,请重试',
icon: 'none'
});
} finally {
this.submitting = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.sign-in-container {
min-height: 100vh;
background-color: #18181c;
padding-top: 88rpx;
}
.content {
padding: 30rpx;
}
.course-info-card {
background-color: #23232a;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 30rpx;
}
.course-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
margin-bottom: 10rpx;
}
.course-time {
font-size: 26rpx;
color: #29d3b4;
margin-bottom: 20rpx;
}
.course-detail {
background-color: #2a2a2a;
border-radius: 8rpx;
padding: 16rpx;
}
.detail-item {
display: flex;
margin-bottom: 10rpx;
font-size: 26rpx;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
color: #999;
width: 140rpx;
}
.detail-value {
color: #fff;
flex: 1;
}
.student-section {
background-color: #23232a;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 30rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #fff;
}
.action-buttons {
display: flex;
gap: 16rpx;
}
.student-list {
max-height: 600rpx;
overflow-y: auto;
}
.student-item {
display: flex;
align-items: center;
background-color: #2a2a2a;
border-radius: 8rpx;
padding: 16rpx;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.student-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
overflow: hidden;
position: relative;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.status-badge {
position: absolute;
bottom: 0;
right: 0;
width: 24rpx;
height: 24rpx;
border-radius: 12rpx;
background-color: #999;
border: 2rpx solid #fff;
}
.status-absent {
background-color: #ff3b30;
}
.status-present {
background-color: #34c759;
}
.status-leave {
background-color: #ff9500;
}
.student-info {
flex: 1;
display: flex;
flex-direction: column;
}
.student-name {
font-size: 28rpx;
color: #fff;
margin-bottom: 6rpx;
}
.student-phone {
font-size: 24rpx;
color: #999;
}
.status-container {
margin-left: 16rpx;
}
.status-select {
display: flex;
gap: 10rpx;
}
.status-option {
padding: 8rpx 16rpx;
font-size: 24rpx;
border-radius: 30rpx;
background-color: #3a3a3a;
color: #fff;
&.active {
background-color: #29d3b4;
}
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
.empty-img {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.remark-section {
background-color: #23232a;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 40rpx;
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #fff;
margin-bottom: 20rpx;
}
}
.submit-btn {
margin-top: 40rpx;
padding-bottom: 40rpx;
}
</style>

9
uniapp/pages/coach/student/student_list.vue

@ -1,5 +1,5 @@
<template> <template>
<view class="container"> <view class="container safe-area">
<view class="search-bar" @click="showSearch = true"> <view class="search-bar" @click="showSearch = true">
<uni-icons type="search" size="22" color="#00d18c" /> <uni-icons type="search" size="22" color="#00d18c" />
<text class="search-placeholder">搜索学员...</text> <text class="search-placeholder">搜索学员...</text>
@ -218,6 +218,13 @@
.container { .container {
min-height: 100vh; min-height: 100vh;
background-color: #18181c; background-color: #18181c;
display: flex;
flex-direction: column;
}
.safe-area {
padding-top: var(--status-bar-height);
padding-bottom: 120rpx;
} }
.search-bar { .search-bar {

2
uniapp/pages/common/contract/contract_sign.vue

@ -338,7 +338,7 @@ export default {
// //
const response = await apiRoute.post('/member/contract_sign', { const response = await apiRoute.post('/member/contract_sign', {
contract_sign_id: this.contractId, contract_sign_id: this.contractId,
sign_file: uploadResult.url pic_file: uploadResult.url
}) })
if (response.code === 1) { if (response.code === 1) {

6
uniapp/pages/common/contract/my_contract.vue

@ -207,11 +207,7 @@ export default {
// //
needSignButton(contract) { needSignButton(contract) {
return contract.status === 1 && ( return contract.status == 1
contract.sign_file === null ||
contract.sign_file === '' ||
contract.sign_file === undefined
)
}, },

Loading…
Cancel
Save