Browse Source

临时提交

master
王泽彦 8 months ago
parent
commit
f580b148d2
  1. 24
      niucloud/app/api/controller/upload/Upload.php
  2. 2
      niucloud/app/api/route/file.php
  3. 13
      niucloud/app/service/api/upload/UploadService.php
  4. 13
      niucloud/core/upload/Tencent.php
  5. 253
      uniapp/pages.json
  6. 263
      uniapp/pages/academic/home/index.vue
  7. 638
      uniapp/pages/coach/class/info.vue
  8. 299
      uniapp/pages/coach/class/list.vue
  9. 234
      uniapp/pages/coach/course/info.vue
  10. 793
      uniapp/pages/coach/course/info_list.vue
  11. 648
      uniapp/pages/coach/course/list.vue
  12. 407
      uniapp/pages/coach/job/add.vue
  13. 836
      uniapp/pages/coach/job/list.vue
  14. 276
      uniapp/pages/coach/my/arrival_statistics.vue
  15. 431
      uniapp/pages/coach/my/index.vue
  16. 497
      uniapp/pages/coach/my/my_attendance.vue
  17. 870
      uniapp/pages/coach/my/service_detail.vue
  18. 565
      uniapp/pages/coach/my/service_list.vue
  19. 168
      uniapp/pages/coach/my/teaching_management_info.vue
  20. 481
      uniapp/pages/coach/schedule/schedule_detail.vue
  21. 599
      uniapp/pages/coach/student/info.vue
  22. 499
      uniapp/pages/coach/student/student_detail.vue
  23. 130
      uniapp/pages/coach/student/work_details.vue
  24. 765
      uniapp/pages/common/contract_list.vue
  25. 8
      uniapp/pages/common/personnel/add_personnel.vue
  26. 542
      uniapp/pages/demo/dict_optimization.vue
  27. 352
      uniapp/pages/demo/mock-demo.vue
  28. 633
      uniapp/pages/market/clue/class_arrangement_detail_bak.vue
  29. 149
      uniapp/pages/market/clue/clue_info.less
  30. 119
      uniapp/pages/market/clue/clue_info.vue
  31. 549
      uniapp/pages/market/clue/clue_info_refactored.vue
  32. 425
      uniapp/pages/market/clue/new_task.vue
  33. 1149
      uniapp/pages/market/clue/order_list.vue
  34. 1014
      uniapp/pages/market/clue/writing_followUp.vue
  35. 514
      uniapp/pages/market/course/course_detail.vue
  36. 836
      uniapp/pages/market/data/index.vue
  37. 390
      uniapp/pages/market/home/index.vue
  38. 6
      uniapp/pages/market/index/index.vue
  39. 207
      uniapp/pages/market/my/firm_info.vue
  40. 177
      uniapp/pages/parent/contracts/contract-detail.vue
  41. 187
      uniapp/pages/parent/courses/course-detail.vue
  42. 181
      uniapp/pages/parent/materials/material-detail.vue
  43. 174
      uniapp/pages/parent/messages/message-detail.vue
  44. 182
      uniapp/pages/parent/orders/order-detail.vue
  45. 159
      uniapp/pages/parent/services/service-detail.vue
  46. 1
      uniapp/pages/student/index/index.vue
  47. 272
      uniapp/pages/student/index/job_list.vue
  48. 114
      uniapp/pages/student/index/work_details.vue
  49. 379
      uniapp/pages/student/my/my.vue
  50. 607
      uniapp/pages/student/timetable/index.vue
  51. 254
      uniapp/pages/student/timetable/list.vue

24
niucloud/app/api/controller/upload/Upload.php

@ -91,4 +91,28 @@ class Upload extends BaseApiController
$base64_service = new Base64Service();
return success($base64_service->image($data['content']));
}
/**
* 头像上传(无token验证)
* @return Response
*/
public function avatar(Request $request){
$extraData = $request->all();
$data = $this->request->params([
['file', 'file'],
]);
$upload_service = new UploadService();
$res = $upload_service->avatar($data['file'],$extraData);
$res['ext'] = ''; // 初始化文件扩展名
$res['name'] = ''; // 初始化文件名称
if (isset($res['url'])) {
$res['ext'] = pathinfo($res['url'], PATHINFO_EXTENSION);
$res['name'] = basename($res['url']);
}
return success($res);
}
}

2
niucloud/app/api/route/file.php

@ -39,6 +39,8 @@ Route::group('file', function() {
//base64图片
Route::post('image/base64', 'upload.Upload/imageBase64');
//头像上传(无token验证)
Route::post('avatar', 'upload.Upload/avatar');
})->middleware(ApiChannel::class)
->middleware(ApiLog::class);

13
niucloud/app/service/api/upload/UploadService.php

@ -41,6 +41,19 @@ class UploadService extends BaseApiService
return $core_upload_service->image($file, $dir, $this->cate_id);
}
/**
* 头像上传(使用腾讯云存储)
* @param $file
* @return array
* @throws Exception
*/
public function avatar($file,array $extraData = [])
{
$dir = $this->root_path . '/' . 'avatar' . '/' . date('Ym') . '/' . date('d');//打印结果 例如=file/avatar/202505/29
$core_upload_service = new CoreUploadService();
return $core_upload_service->image($file, $dir, $this->cate_id);
}
/**
* 附件库上传视频
* @param $file

13
niucloud/core/upload/Tencent.php

@ -61,14 +61,27 @@ class Tencent extends BaseUpload
$this->validate();
$bucket = $this->config['bucket'];
try {
// 临时关闭PHP警告,避免deprecated警告被当作异常
$old_error_reporting = error_reporting(E_ERROR | E_PARSE);
$result = $this->client()->putObject(array(
'Bucket' => $bucket, //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.tencentcloud.com/cos5/bucket
'Key' => $this->getFullPath($dir),
'Body' => fopen($this->getRealPath(), 'rb'),
));
// 恢复错误报告级别
error_reporting($old_error_reporting);
// 请求成功
return true;
} catch ( Exception $e ) {
// 恢复错误报告级别
error_reporting($old_error_reporting);
// 输出详细错误信息用于调试
error_log("Tencent COS Upload Error: " . $e->getMessage());
error_log("Tencent COS Config: " . json_encode($this->config));
throw new UploadFileException($e->getMessage());
}
}

253
uniapp/pages.json

@ -18,24 +18,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/timetable/index",
"style": {
"navigationBarTitleText": "课表",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/my/my",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/my/my_coach",
"style": {
@ -45,16 +27,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/demo/mock-demo",
"style": {
"navigationBarTitleText": "Mock数据演示",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#1890ff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/login/forgot",
"style": {
@ -64,15 +36,6 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/student/index/work_details",
"style": {
"navigationBarTitleText": "作业详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/student/index/physical_examination",
"style": {
@ -91,15 +54,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/timetable/list",
"style": {
"navigationBarTitleText": "场馆",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/student/my/set_up",
"style": {
@ -145,36 +99,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/index/job_list",
"style": {
"navigationBarTitleText": "作业列表",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/home/index",
"style": {
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/course/course_detail",
"style": {
"navigationBarTitleText": "课程详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/common/privacy_agreement",
"style": {
@ -229,15 +153,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/contract_list",
"style": {
"navigationBarTitleText": "订单列表",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/my_attendance",
"style": {
@ -293,86 +208,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/job/add",
"style": {
"navigationBarTitleText": "发布作业",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/job/list",
"style": {
"navigationBarTitleText": "全部作业",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/course/list",
"style": {
"navigationBarTitleText": "课表",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/course/info_list",
"style": {
"navigationBarTitleText": "课时详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/course/info",
"style": {
"navigationBarTitleText": "课时详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/class/list",
"style": {
"navigationBarTitleText": "班级",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/class/info",
"style": {
"navigationBarTitleText": "班级详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/student/info",
"style": {
"navigationBarTitleText": "学员详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/student/work_details",
"style": {
"navigationBarTitleText": "作业任务",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/student/physical_examination",
"style": {
@ -390,31 +225,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/student/student_detail",
"style": {
"navigationBarTitleText": "学员详情",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/index",
"style": {
"navigationBarTitleText": "我的",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/arrival_statistics",
"style": {
"navigationBarTitleText": "到课统计",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/due_soon",
"style": {
@ -468,15 +278,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/teaching_management_info",
"style": {
"navigationBarTitleText": "文章详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/gotake_exam",
"style": {
@ -531,15 +332,6 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/order_list",
"style": {
"navigationBarTitleText": "订单列表",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/index",
"style": {
@ -552,7 +344,7 @@
{
"path": "pages/market/index/index",
"style": {
"navigationBarTitleText": "首页"
"navigationBarTitleText": "销售数据"
}
},
{
@ -627,24 +419,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/firm_info",
"style": {
"navigationBarTitleText": "企业信息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/data/index",
"style": {
"navigationBarTitleText": "数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/class_arrangement",
"style": {
@ -695,23 +469,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/service_detail",
"style": {
"navigationBarTitleText": "服务详情",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
}
},
{
"path": "pages/coach/my/service_list",
"style": {
"navigationBarTitleText": "服务列表",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/schedule_table",
"style": {
@ -745,14 +502,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/academic/home/index",
"style": {
"navigationBarTitleText": "教务首页",
"navigationBarBackgroundColor": "#007ACC",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/clue_table",
"style": {

263
uniapp/pages/academic/home/index.vue

@ -1,263 +0,0 @@
<template>
<view class="main_box">
<view class="main_section">
<!--教务待办事项-->
<view class="section_3">
<view class="title_box">
<view class="top_box">
<text>教务待办</text>
<view></view>
</view>
<view class="line"></view>
</view>
<view class="ul" v-if="infoData.todo_list && infoData.todo_list.length > 0">
<view class="li" v-for="(v,k) in infoData.todo_list" :key="k" @click="openViewTodoDetail(v)">
<view class="top_box">
<view class="title">任务{{ v.title }}</view>
<view class="title">创建时间{{ v.create_time }}</view>
<view class="title">截止时间{{ v.deadline }}</view>
</view>
<view class="botton_box">
<view class="box">
<view>优先级{{ v.priority_text }}</view>
<view>
查看
<fui-icon size="35" color="#fff" name="arrowright"></fui-icon>
</view>
</view>
</view>
<view
v-if="v.status == 'pending'"
class="tag"
style="background:#007ACC;">待处理
</view>
<view
v-if="v.status == 'processing'"
class="tag"
style="background:#fad24e;">处理中
</view>
<view
v-if="v.status == 'completed'"
class="tag"
style="background:#29d3b4;">已完成
</view>
</view>
</view>
<view v-else class="empty_box">
<image src="/static/images/empty.png" mode="aspectFit"></image>
<text>暂无待办事项</text>
</view>
</view>
<!--统计数据-->
<view class="section_3">
<view class="title_box">
<view class="top_box">
<text>数据统计</text>
<view></view>
</view>
<view class="line"></view>
</view>
<view class="stats_grid">
<view class="stat_item" @click="openViewStats('students')">
<view class="stat_number">{{ infoData.student_count || 0 }}</view>
<view class="stat_label">学员总数</view>
</view>
<view class="stat_item" @click="openViewStats('courses')">
<view class="stat_number">{{ infoData.course_count || 0 }}</view>
<view class="stat_label">课程总数</view>
</view>
<view class="stat_item" @click="openViewStats('teachers')">
<view class="stat_number">{{ infoData.teacher_count || 0 }}</view>
<view class="stat_label">教师总数</view>
</view>
<view class="stat_item" @click="openViewStats('classes')">
<view class="stat_number">{{ infoData.class_count || 0 }}</view>
<view class="stat_label">班级总数</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
infoData: {
todo_list: [],
student_count: 0,
course_count: 0,
teacher_count: 0,
class_count: 0
}
}
},
onShow() {
this.init()
},
methods: {
async init() {
try {
//
const res = await apiRoute.getAcademicHomeData();
if (res && res.code === 1) {
this.infoData = res.data;
}
} catch (error) {
console.error('获取教务数据失败:', error);
}
},
//
openViewTodoDetail(item) {
this.$navigateTo({
url: `/pages/academic/todo/detail?id=${item.id}`
});
},
//
openViewStats(type) {
this.$navigateTo({
url: `/pages/academic/stats/index?type=${type}`
});
}
}
}
</script>
<style lang="less" scoped>
.main_box {
width: 100%;
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 140rpx;
}
.main_section {
padding: 30rpx;
}
.section_3 {
margin-bottom: 30rpx;
}
.title_box {
.top_box {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
text {
font-size: 36rpx;
font-weight: bold;
color: #007ACC;
}
}
.line {
height: 4rpx;
background: linear-gradient(to right, #007ACC, #4DA6FF);
border-radius: 2rpx;
}
}
.ul {
margin-top: 30rpx;
}
.li {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 122, 204, 0.1);
position: relative;
.top_box {
.title {
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
line-height: 1.4;
}
}
.botton_box {
margin-top: 20rpx;
.box {
display: flex;
justify-content: space-between;
align-items: center;
background: #007ACC;
color: #fff;
padding: 20rpx 30rpx;
border-radius: 15rpx;
font-size: 26rpx;
margin-bottom: 10rpx;
}
}
.tag {
position: absolute;
top: 30rpx;
right: 30rpx;
padding: 10rpx 20rpx;
border-radius: 20rpx;
color: #fff;
font-size: 22rpx;
}
}
.empty_box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.5;
}
text {
color: #999;
font-size: 28rpx;
}
}
.stats_grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
margin-top: 30rpx;
}
.stat_item {
background: #fff;
border-radius: 20rpx;
padding: 40rpx 30rpx;
text-align: center;
box-shadow: 0 4rpx 20rpx rgba(0, 122, 204, 0.1);
.stat_number {
font-size: 48rpx;
font-weight: bold;
color: #007ACC;
margin-bottom: 10rpx;
}
.stat_label {
font-size: 26rpx;
color: #666;
}
}
</style>

638
uniapp/pages/coach/class/info.vue

@ -1,638 +0,0 @@
<!--班级-详情-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<!-- <view class="navbar_section">-->
<!-- <view class="title">班级详情</view>-->
<!-- </view>-->
<view class="main_section">
<view class="section_1">
<view class="left">
<image class="pic" :src="classInfo.head_coach_head_img"></image>
<view class="name">{{classInfo.head_coach_name}}</view>
</view>
<view class="right">
<view class="item">
班级{{classInfo.class_name}}
</view>
<view class="item">
校区{{classInfo.campus_name}}
</view>
<!-- <view class="item">
<!-- 课程篮球少儿课
<!-- </view>-->
<view class="item">
人数{{classInfo.classPersonnelRel.length}}
</view>
<!--
<view class="item">
时间{{classInfo.start_date}} - {{classInfo.end_date}}
</view> -->
</view>
</view>
<!-- <view class="section_2">-->
<!-- <view class="title_box">-->
<!-- <view>最近课程</view>-->
<!-- </view>-->
<!-- <view class="tag_list">-->
<!-- <view class="item" @click="openViewCourseInfo({id:1})">-->
<!-- <view class="title">-->
<!-- 篮球少儿课-->
<!-- </view>-->
<!-- <view>-->
<!-- 2020.05.30 15:30 - 17:30-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="item" @click="openViewCourseInfo({id:2})">-->
<!-- <view class="title">-->
<!-- 篮球少儿课-->
<!-- </view>-->
<!-- <view>-->
<!-- 2020.05.30 15:30 - 17:30-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
<view class="section_3">
<view class="btn_box">
<view :class="['btn', tabType=='1'?'select':'']" @click="tabChange(1)">
班级成员
</view>
<view :class="['btn', tabType=='2'?'select':'']" @click="tabChange(2)">
课程计划
</view>
</view>
</view>
<!-- 班级成员列表-->
<view class="section_4" v-if="tabType=='1'">
<view class="ul">
<view class="li" v-for="(v,k) in classInfo.classPersonnelRel" :key="k"
@click="openViewStudentInfo(v)">
<view class="left">
<view class="box_1">
<image class="pic" :src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="tag_box" v-if="v.expire">
即将到期
</view>
</view>
<view class="box_2">
<view class="name">{{ v.student.name }}</view>
<view class="date">课程截止时间{{ $util.formatToDateTime(v.end_date, 'Y-m-d') }}</view>
</view>
</view>
<view class="right">
<view class="item">
<view>{{ v.studentCoursesInfo.use_gift_hours + v.studentCoursesInfo.use_gift_hours }}
</view>
<view>已上课时</view>
</view>
<view class="item">
<view>
{{ (v.studentCoursesInfo.total_hours + v.studentCoursesInfo.gift_hours) - (v.studentCoursesInfo.use_gift_hours + v.studentCoursesInfo.use_gift_hours) }}
</view>
<view>剩余课时</view>
</view>
</view>
</view>
</view>
</view>
<!--课程计划-->
<scroll-view v-if="tabType=='2'" class="section_5" scroll-y="true" :lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData" style="height: 65vh;">
<view class="ul">
<view class="li" v-for="(v,k) in courseList" :key="k" @click="openViewCourseInfo(v)">
<view class="top_box">
<view class="title">
课程{{v.course.course_name}}
</view>
<view class="title">
时间{{v.course_date}}
</view>
<view class="title">
地点{{v.campus_name}}
</view>
</view>
<view class="bottom_box">
<view class="item">
<view class="left">应到学员{{v.student.length}}</view>
<view class="right">
查看
<fui-icon size="35" color="#fff" name="arrowright"></fui-icon>
</view>
</view>
<view class="item">
<view class="left">已签到学生{{v.student_courses.length}}/{{v.student.length}}</view>
<view class="right">
查看
<fui-icon size="35" color="#fff" name="arrowright"></fui-icon>
</view>
</view>
<!-- <view class="item">-->
<!-- <view class="left">作业完成率80%</view>-->
<!-- <view class="right">-->
<!-- 查看-->
<!-- <fui-icon size="35" color="#fff" name="arrowright"></fui-icon>-->
<!-- </view>-->
<!-- </view>-->
</view>
<view class="tag" v-if="isCourseFuture(v.student_courses[0].start_date)"
style="background-color:#20CAAF;">
未开始
</view>
<view class="tag" v-if="isNowBetween(v.student_courses[0].start_date, v.student_courses[0].end_date)"
style="background-color:#fad24e;">
上课中
</view>
<view class="tag" v-if="!isCourseFuture(v.student_courses[0].end_date)" style="background-color:#e2e2e2;">
已结束
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 底部导航-->
<!-- <AQTabber/>-->
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
class_id: '', //id
classInfo: {}, //
classMemberList: {}, //
tabType: '1', //1=,2=
loading: false, //
lowerThreshold: 100, //
isReachedBottom: false, //|true=|false=
//
filteredData: {
page: 1, //
limit: 10, //
total: 10, //
class_id: '', //id
},
courseList: [], //
}
},
onLoad(options) {
this.class_id = options.class_id //id
},
onShow() {
this.init()
},
//
async onPullDownRefresh() {
//
await this.loadData()
await this.getCourseList()
},
methods: {
async init() {
// member/course_list//
// member/class_info//+
this.getClassInfo() //
await this.getCourseList() //
},
isNowBetween(start_date, end_date) {
const now = new Date();
const start = new Date(start_date);
const end = new Date(end_date);
return now >= start && now <= end;
},
isCourseFuture(courseDate) {
const courseTime = new Date(courseDate).getTime();
const nowTime = new Date().getTime();
return courseTime > nowTime;
},
//-
async getClassInfo() {
let res = await apiRoute.jlClassInfo({
class_id: this.class_id
}) //
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log('获取班级列表', res.data)
this.classInfo = res.data
this.classMemberList = res.data.students_list
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true; //
this.getCourseList();
}
},
//
async loadData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1 //
this.filteredData.limit = 10 //
this.filteredData.total = 10 //
},
//-
async getCourseList() {
let data = {
...this.filteredData
}
data.class_id = this.class_id
console.log(12123, this.courseList)
if (data.page == 1) {
this.courseList = []
}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
let res = await apiRoute.classCourseList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.courseList = this.courseList.concat(res.data.data) // 使 concat
this.filteredData.total = res.data.total
console.log('获取课程列表', this.courseList)
this.filteredData.page++
},
//tab
tabChange(tabType) {
this.tabType = tabType
},
//
openViewCourseInfo(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/course/info_list?id=${id}`
})
},
//
openViewStudentInfo(item) {
let students_id = item.student.id
this.$navigateTo({
url: `/pages/coach/student/info?students_id=${students_id}`
})
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
}
//
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section {
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1 {
background: #333333;
border-radius: 16rpx;
padding: 34rpx 64rpx;
display: flex;
align-items: center;
gap: 38rpx;
.left {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
}
}
.right {
display: flex;
flex-direction: column;
gap: 12rpx;
.item {}
}
}
.section_2 {
margin-top: 42rpx;
.title_box {
font-size: 32rpx;
}
.tag_list {
margin-top: 26rpx;
display: flex;
align-items: center;
gap: 26rpx;
.item {
padding-left: 20rpx;
width: 330rpx;
height: 162rpx;
border: 1px solid #00e5bb;
border-radius: 10rpx;
display: flex;
flex-direction: column;
justify-content: center;
gap: 10rpx;
}
}
}
//
.section_3 {
margin-top: 54rpx;
display: flex;
justify-content: center;
align-items: center;
.btn_box {
width: 698rpx;
border-radius: 8rpx;
background-color: rgba(22, 132, 252, 1);
border: 1rpx solid rgba(22, 132, 252, 1);
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
.btn {
width: 100%;
height: 76rpx;
line-height: 76rpx;
text-align: center;
color: #fff;
font-size: 26rpx;
}
.select {
color: #1684fc;
background-color: #fff;
}
}
}
//
.section_4 {
margin-top: 40rpx;
.ul {
display: flex;
flex-direction: column;
gap: 10rpx;
.li {
padding: 20rpx 0;
padding-bottom: 40rpx;
border-bottom: 2px solid #D7D7D7;
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
gap: 30rpx;
.box_1 {
padding-left: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
.pic {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
}
.tag_box {
position: absolute;
bottom: -30rpx;
width: 120rpx;
height: 38rpx;
background-color: #F59A23;
border-radius: 4rpx;
line-height: 35rpx;
text-align: center;
font-size: 20rpx;
}
}
.box_2 {
display: flex;
flex-direction: column;
gap: 20rpx;
.name {
font-size: 28rpx;
}
.date {
font-size: 24rpx;
}
}
}
.right {
display: flex;
align-items: center;
gap: 14rpx;
.item {
border: 1px solid #00E5BB;
border-radius: 10rpx;
width: 102rpx;
display: flex;
flex-direction: column;
view {
text-align: center;
height: 50rpx;
line-height: 50rpx;
}
view:nth-child(1) {
font-size: 32rpx;
background-color: #fff;
color: #00e5bb;
}
view:nth-child(2) {
font-size: 20rpx;
background-color: #00e5bb;
}
}
}
}
}
}
//
.section_5 {
margin-top: 36rpx;
.ul {
display: flex;
flex-direction: column;
gap: 30rpx;
.li {
position: relative;
border-radius: 10rpx;
background-color: rgba(63, 63, 67, 1);
border: 2rpx solid rgba(0, 229, 187, 1);
padding: 20rpx 14rpx;
display: flex;
flex-direction: column;
.top_box {
display: flex;
flex-direction: column;
gap: 10rpx;
.title {
color: rgba(255, 255, 255, 1);
font-size: 24rpx;
}
}
.bottom_box {
padding-top: 10rpx;
margin-top: 20rpx;
border-top: 1px dashed #F2F2F2;
display: flex;
flex-direction: column;
gap: 10rpx;
.item {
display: flex;
align-items: center;
justify-content: space-between;
color: rgba(215, 215, 215, 1);
font-size: 24rpx;
}
}
.tag {
position: absolute;
right: 0rpx;
top: 0rpx;
width: 110rpx;
height: 60rpx;
line-height: 58rpx;
border-radius: 0rpx 8rpx 0rpx 24rpx;
background-color: rgba(32, 202, 175, 1);
color: rgba(255, 255, 255, 1);
font-size: 24rpx;
text-align: center;
border: 0rpx solid rgba(121, 121, 121, 1);
}
}
}
}
}
</style>

299
uniapp/pages/coach/class/list.vue

@ -1,299 +0,0 @@
<!--班级-列表-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="title">班级</view>
</view>
<view class="main_section">
<view class="section_1">
<fui-input class="input_item" borderTop clearable="true" placeholder="搜索" v-model="filteredData.name" @confirm="search" @input="search"></fui-input>
</view>
<view class="section_2">
<scroll-view
class="ul"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 65vh;"
>
<view
class="li"
v-for="(v,k) in tableList"
:key="k"
@click="openViewClassInfo(v)"
>
<view class="left">
<image class="pic" :src="v.head_coach_head_img"></image>
</view>
<view class="right">
<view class="box_1">
<view class="name">
{{v.class_name}}
</view>
<view class="btn_box">
<view v-if="v.end_count">{{v.end_count}}人即将到期</view>
</view>
</view>
<view class="box_2">
<view class="user_list" v-for="(v2,k2) in v.classPersonnelRel" :key="k2">
<image
:src="$util.img(v2.student.customerResources.member.headimg)"></image>
</view>
<view class="num">{{v.students_count}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
name: '',//
},
tableList:[],//
}
},
onLoad(options) {},
onShow(){
this.init()//
},
methods: {
//
async init(){
this.getList()
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getList(){
this.loading = true
let data = {...this.filteredData}
console.log(111,(this.filteredData.page * this.filteredData.limit) ,(this.filteredData.total))
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
if(data.page == 1){
this.tableList = []
}
let res = await apiRoute.jlClassList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
console.log('列表',this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
async search(e){
await this.resetFilteredData()
this.filteredData.name = e
await this.getList()
},
//
openViewClassInfo(item){
let class_id = item.id
this.$navigateTo({
url: `/pages/coach/class/info?class_id=${class_id}`
})
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section{
// min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1 {
border-radius: 10rpx;
background-color: #525252;
::v-deep .fui-input__wrap{
border-radius: 10rpx !important;
background-color: #525252 !important;
}
::v-deep .fui-input__background{
background-color: #525252 !important;
}
.input_item {
height: 60rpx;
::v-deep .uni-input-wrapper{
.uni-input-placeholder {
font-size: 28rpx !important;
}
}
}
::v-deep .uni-input-input {
color: #fff;
}
}
.section_2{
margin-top: 34rpx;
.ul{
display: flex;
flex-direction: column;
//gap: 24rpx;
.li{
margin-bottom: 24rpx;
background: #404045;
padding: 50rpx 36rpx 46rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
gap: 32rpx;
.left{
.pic{
border-radius: 50%;
width: 92rpx;
height: 92rpx;
}
}
.right{
display: flex;
flex-direction: column;
gap: 28rpx;
.box_1{
display: flex;
align-items: center;
gap: 36rpx;
.name{
font-size: 28rpx;
}
.btn_box{
view{
border: 1px solid #FAD04D;
border-radius: 10rpx;
width: 182rpx;
height: 48rpx;
line-height: 42rpx;
text-align: center;
font-size: 26rpx;
color: #FAD04D;
}
}
}
.box_2{
display: flex;
align-items: center;
gap: 44rpx;
.user_list{
display: flex;
align-items: center;
gap: 14rpx;
image{
border-radius: 50%;
width: 48rpx;
height: 48rpx;
}
}
.num{}
}
}
}
}
}
}
</style>

234
uniapp/pages/coach/course/info.vue

@ -1,234 +0,0 @@
<!--课程-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="title_box">青少儿篮球课</view>
<view class="ul">
<view class="li">
<view class="title">课程名称</view>
<view class="content">青少年篮球课</view>
</view>
<view class="li">
<view class="title">班级</view>
<view class="content">班级名称</view>
</view>
<view class="li">
<view class="title">上课时间</view>
<view class="content">2020.03.25 15:30-17:00</view>
</view>
<view class="li">
<view class="title">上课地址</view>
<view class="content">xxxx体育馆302室</view>
</view>
<view class="li">
<view class="title">课程名称</view>
<view class="content">青少年篮球课</view>
</view>
<view class="li">
<view class="title">教练</view>
<view class="content">包子皮</view>
</view>
<view class="li">
<view class="title">教练号码</view>
<view class="content">18888888888</view>
</view>
<view class="li">
<view class="title">扣除课时</view>
<view class="content">2个课时</view>
</view>
<view class="state_box">
<view>已上</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// import user from '@/api/user.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
formData:{},
//
show_course:false,//
//
course_name:'课程',//
options_course: [
{
text: '请选择课程',
value: '',
checked: true
}, {
text: '羽毛球课程1',
value: '1'
}, {
text: '篮球课程2',
value: '2'
}
],
//
show_classroom:false,//
//
classroom_name:'课室',//
options_classroom: [
{
text: '请选择课室',
value: '',
checked: true
}, {
text: '羽毛球201',
value: '1'
}, {
text: '篮球室101',
value: '2'
}
],
}
},
onLoad() {
},
methods: {
//
clickCourse(e){
console.log(e)
this.course_name = e.text
this.show_course = true
},
//
filterTapCourse() {
//
this.$refs.ref_course.show()
this.show_course = true;
},
//
clickClassroom(e){
console.log(e)
this.classroom_name = e.text
this.show_classroom = true
},
//
filterTapClassroom() {
//
this.$refs.ref_classroom.show()
this.show_classroom = true;
},
//
openViewCourseInfo(item){
this.$navigateTo({
url: '/pages/coach/course/info'
})
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section{
min-height: 100vh;
background: #292929 100%;
padding: 30rpx 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1{
border-radius: 22rpx;
padding: 20rpx 40rpx;
background: #333333 100%;
width: 100%;
.title_box{
padding: 10rpx;
padding-left: 30rpx;
font-size: 32rpx;
}
.ul{
padding: 40rpx 10rpx;
padding-bottom: 350rpx;
margin-top: 10rpx;
border-top: 1px solid #555555;
display: flex;
flex-direction: column;
gap: 40rpx;
position: relative;
.li{
display: flex;
gap: 72rpx;
.title{
width: 120rpx;
text-align: right;
}
.content{}
}
.state_box{
position: absolute;
bottom: 50rpx;
right: 10rpx;
view{
width: 154rpx;
height: 154rpx;
border-radius: 50%;
background-color: rgba(41,211,180,0.17);
color: rgba(41,211,180,1);
font-size: 26rpx;
text-align: center;
line-height: 150rpx;
transform: rotate(-35deg); /* 旋转 */
}
}
}
}
}
</style>

793
uniapp/pages/coach/course/info_list.vue

@ -1,793 +0,0 @@
<!--课程-详情列表-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="title">日期{{courseInfo.course_date}}</view>
<view class="title">时间{{courseInfo.time_slot}}</view>
<view class="title">地点{{courseInfo.venue.venue_name}}</view>
<view class="title">课程{{courseInfo.course.course_name}}</view>
<view class="title">教练{{courseInfo.coach.name}}</view>
<view class="title">人数{{courseInfo.venue.capacity}}</view>
<view class="isbtn" @click="addStudent">
添加学员
</view>
<view v-if="courseInfo.student_courses[0] && isNowBetween(courseInfo.student_courses[0].start_date, courseInfo.student_courses[0].end_date)" class="tag" style="background-color: #FAD24E;">上课中
</view>
<view v-if="courseInfo.student_courses[0] && !isCourseFuture(courseInfo.student_courses[0].end_date)" class="tag" style="background-color: #e2e2e2;">已结束
</view>
<view v-if="courseInfo.student_courses[0] && isCourseFuture(courseInfo.student_courses[0].start_date)" class="tag" style="background-color: #1cd188;">未开始
</view>
</view>
<view class="section_2">
<view :class="['table', tableType==1 ? 'select':'']" @click="switchTag(1)">学员情况</view>
<view :class="['table', tableType==2 ? 'select':'']" @click="switchTag(2)">作业情况</view>
</view>
<!--签到情况-->
<view class="section_3" v-if="tableType == 1">
<view class="tip_title" v-if="courseInfo.student_courses.length == 0">暂无数据</view>
<view class="item" v-for="(v,k) in courseInfo.student_courses" :key="k">
<view class="left" @click="openViewCourseInfo(v)">
<image class="pic" model="aspectFit" :src="$util.img(v.avatar)"></image>
<view class="box">
<view class="title">{{v.name}}</view>
<view class="title">来源{{v.source}}</view>
<view class="title">一访情况{{v['school_six_speed'].first_visit_time}}</view>
<view class="title">一访时间{{v['school_six_speed'].first_visit_status}}</view>
<view class="title">课程截止时间{{v.end_date}}</view>
</view>
</view>
<view class="right" v-if="courseInfo.status == 'pending'">
<view class="tag leave-tag" @click="written(v.student_id)" v-if="!isWithinTimeSlot(courseInfo.time_slot) && v.status !== 'signed'">请假</view>
<view class="tag signin-tag" @click="signIn(v.student_id)" v-if="isWithinTimeSlot(courseInfo.time_slot) && v.status !== 'signed'">签到</view>
<view class="tag signed-tag" v-if="v.status === 'signed'">已签到</view>
</view>
</view>
</view>
<!--作业情况-->
<view class="section_4" v-if="tableType == 2">
<view class="item_box">
<!-- 待批改-->
<fui-collapse-item @change="changeCollapse(1)" open="true" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>待批改{{courseInfo.groupedByStatus1.length}}</text>
</view>
<template v-slot:content>
<view class="ul">
<view v-for="(v,k) in courseInfo.groupedByStatus1" :key="k" class="li"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<view>{{v.created_at}}</view>
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
<!-- 未提交-->
<view class="item_box">
<fui-collapse-item @change="changeCollapse(2)" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>未提交{{courseInfo.groupedByStatus2.length}}</text>
</view>
<template v-slot:content>
<view class="ul">
<view class="li" v-for="(v,k) in courseInfo.groupedByStatus2" :key="k"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<!-- <view>{{v.created_at}}</view> -->
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
<!-- 已提交-->
<view class="item_box">
<fui-collapse-item @change="changeCollapse(3)" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>已提交{{courseInfo.groupedByStatus3.length}}</text>
</view>
<template v-slot:content>
<view class="ul">
<view class="li" v-for="(v,k) in courseInfo.groupedByStatus3" :key="k"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<view>{{v.created_at}}</view>
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
</view>
</view>
<fui-modal :show="show" title="选择学员" @click="handleModalClick" :buttons="[]" @cancel="cancel">
<view class="student-list">
<view class="student-item" v-for="(item, index) in StudentList" :key="index" @click="change(item)">
<view class="student-info">
<view class="student-name">{{item.text}}</view>
<view class="student-details">
<text>年龄: {{item.age || '未知'}}</text>
<text>来源: {{item.source || '未知'}}</text>
<text>渠道: {{item.source_channel || '未知'}}</text>
</view>
</view>
</view>
<view class="empty-list" v-if="StudentList.length === 0">
<text>暂无可添加的学员</text>
</view>
</view>
</fui-modal>
<fui-picker :options="StudentList" :linkage="false" :show="false" :layer="1" @change="change"
@cancel="cancel"></fui-picker>
<fui-actionsheet :show="written_show" :tips="written_tips" :isCancel="isCancel"
:itemList="itemList" @cancel="written_cancel" @click="written_click"></fui-actionsheet>
<uni-popup ref="popup" type="center">
<view class="popup-content">{{ tipContent }}</view>
</uni-popup>
<AQTabber />
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
course_id: '', //id
courseInfo: {
sign_list: [], //
assignments: {
dpg_list: [], //
wtj_list: [], //
ypg_list: [] //
},
}, //
tableType: 1, //1=2=
show: false,
StudentList: [],
written_show: false,
written_tips: '确认请假',
isCancel: false,
itemList: ['确认', '取消'],
qingjia_student_id: 0
}
},
onLoad(options) {
this.course_id = options.id
},
onShow() {
this.init()
},
methods: {
//
async init() {
this.getCourseInfo()
},
isNowBetween(start_date, end_date) {
const now = new Date();
const start = new Date(start_date);
const end = new Date(end_date);
return now >= start && now <= end;
},
isCourseFuture(courseDate) {
const courseTime = new Date(courseDate).getTime();
const nowTime = new Date().getTime();
return courseTime > nowTime;
},
//-
async getCourseInfo() {
let res = await apiRoute.courseInfo({
id: this.course_id
})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log('课程详情', res.data)
this.courseInfo = res.data
// 使
// this.courseInfo = {
// id: this.course_id || '1',
// course_date: '2025-07-25',
// time_slot: '14:00-15:30',
// status: 'pending',
// venue: {
// venue_name: ' 305',
// capacity: 20
// },
// course: {
// course_name: ''
// },
// coach: {
// name: ''
// },
// student_courses: [
// {
// student_id: '1001',
// name: '',
// avatar: '',
// start_date: '2025-07-25',
// end_date: '2025-10-25',
// status: 'pending'
// },
// {
// student_id: '1002',
// name: '',
// avatar: '',
// start_date: '2025-07-25',
// end_date: '2025-10-25',
// status: 'pending'
// }
// ],
// groupedByStatus1: [], //
// groupedByStatus2: [], //
// groupedByStatus3: [] //
// };
// console.log('()', this.courseInfo);
},
//
switchTag(type) {
this.tableType = type
},
//
openViewCourseInfo(item) {
// this.$navigateTo({
// url: '/pages/coach/course/info'
// })
uni.navigateTo({
url: `/pages/market/clue/clue_info?resource_sharing_id=25`
});
},
//
changeCollapse(type) {},
//-
openViewWorkDetails(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/student/work_details?id=${id}`
})
},
//
async addStudent() {
let res = await apiRoute.addStudentList({
id: this.course_id
})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.StudentList = res.data
if (this.StudentList.length == 0) {
uni.showToast({
title: '暂无可填加的学员',
icon: 'none'
})
return
} else {
//
this.show = true
}
},
//
handleModalClick(e) {
//
console.log('模态框点击事件', e)
},
async change(student) {
this.show = false
// student_idresource_id
let studentId = student.value
let resourceId = student.resource_id
let res = await apiRoute.addStudent({
student_id: studentId,
schedule_id: this.course_id,
time_slot: this.courseInfo.time_slot,
course_date: this.courseInfo.course_date,
resource_id: resourceId // resource_id
})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: '添加学员成功',
icon: 'success'
})
//
this.init()
},
cancel() {
this.show = false
},
//
written(student_id) {
this.qingjia_student_id = student_id
this.written_show = true
},
written_cancel() {
this.written_show = false
},
async written_click(e) {
if(e.text == '取消'){
this.written_show = false
} else {
let res = await apiRoute.delStudentCourse({
student_id: this.qingjia_student_id,
course_id: this.course_id,
})
if (res.data) {
uni.showToast({
title: '请假成功',
icon: 'none'
})
this.init()
} else {
// uni.showToast({
// title: '',
// icon: 'none'
// })
}
this.written_show = false
}
},
isWithinTimeSlot(time_slot) {
const [startStr, endStr] = time_slot.split('-');
const [startHours, startMinutes] = startStr.split(':').map(Number);
const [endHours, endMinutes] = endStr.split(':').map(Number);
const now = new Date();
const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), startHours, startMinutes);
const endTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), endHours, endMinutes);
return now >= startTime && now < endTime;
},
signIn(student_id) {
uni.showToast({
title: '签到成功',
icon: 'success'
});
// API
//
// apiRoute.studentSignIn({
// student_id: student_id,
// course_id: this.course_id,
// }).then(res => {
// if (res.code == 1) {
// uni.showToast({
// title: '',
// icon: 'success'
// });
// this.init();
// } else {
// uni.showToast({
// title: res.msg,
// icon: 'none'
// });
// }
// });
//
const studentIndex = this.courseInfo.student_courses.findIndex(s => s.student_id === student_id);
if (studentIndex !== -1) {
this.courseInfo.student_courses[studentIndex].status = 'signed';
}
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
}
//
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
/* 学员选择列表样式 */
.student-list {
max-height: 600rpx;
overflow-y: auto;
}
.student-item {
padding: 20rpx 30rpx;
border-bottom: 1px solid #eee;
background-color: #f8f8f8;
margin-bottom: 10rpx;
border-radius: 8rpx;
}
.student-item:active {
background-color: #e0e0e0;
}
.student-info {
display: flex;
flex-direction: column;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.student-details {
display: flex;
flex-direction: column;
font-size: 24rpx;
color: #666;
}
.student-details text {
margin-bottom: 6rpx;
}
.empty-list {
padding: 40rpx;
text-align: center;
color: #999;
font-size: 28rpx;
}
.main_section {
min-height: 100vh;
background: #292929 100%;
padding: 30rpx 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1 {
position: relative;
padding: 18rpx 34rpx 50rpx;
display: flex;
flex-direction: column;
gap: 15rpx;
color: #fff;
font-size: 24rpx;
background-color: #434544;
border-radius: 22rpx;
.tag {
position: absolute;
top: 0;
right: 0;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
border-radius: 0rpx 24rpx 0rpx 24rpx;
color: #fff;
font-size: 24rpx;
text-align: center;
}
}
.section_2 {
margin-top: 44rpx;
color: #fff;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
.table {
width: 50%;
height: 64rpx;
background-color: #1684FCFF;
line-height: 62rpx;
text-align: center;
}
.table:nth-child(1) {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
.table:nth-child(2) {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.select {
background-color: #fff;
color: #1684FCFF;
}
}
.section_3 {
margin-top: 44rpx;
min-height: 20vh;
padding: 30rpx 0;
border-radius: 22rpx;
background-color: #434544;
display: flex;
flex-direction: column;
//
.tip_title {
text-align: center;
font-size: 30rpx;
color: #FFFFFF;
}
.item {
border-top: 1px solid #D7D7D7;
padding: 20rpx 33rpx 20rpx 14rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left {
width: 80%;
display: flex;
align-items: center;
gap: 26rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
background-color: #797979FF;
}
.box {
display: flex;
flex-direction: column;
gap: 10rpx;
.title {
color: #fff;
font-size: 24rpx;
}
}
}
.right {
.tag {
width: 93rpx;
height: 93rpx;
border-radius: 50%;
line-height: 90rpx;
font-size: 26rpx;
text-align: center;
border: 0rpx;
//45°
transform: rotate(-30deg);
}
.leave-tag {
background-color: rgba(254, 250, 131, 0.62);
color: rgba(255, 255, 255, 1);
}
.signin-tag {
background-color: #1cd188;
color: #FFFFFF;
}
.signed-tag {
background-color: #a4adb3;
color: #FFFFFF;
transform: none;
}
}
}
.item:nth-child(1) {
border-top: 0px;
}
}
.section_4 {
margin-top: 44rpx;
padding: 30rpx 0;
border-radius: 22rpx;
background-color: #434544;
display: flex;
flex-direction: column;
.item_box {
margin-bottom: 30rpx;
padding-left: 24rpx;
padding-right: 32rpx;
font-size: 28rpx;
color: #fff;
background-color: #434544;
::v-deep .fui-collapse-item__title {
background-color: #434544 !important;
}
::v-deep .fui-collapse__border-color {
border-top: none !important;
/* 取消上边框 */
background: #434544 !important;
}
.title_box {}
.ul {
background-color: #434544;
.li {
background-color: #434544;
border-top: 1px solid #D7D7D7;
padding: 20rpx 33rpx 20rpx 14rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left {
width: 80%;
display: flex;
align-items: center;
gap: 26rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
background-color: #797979FF;
}
.box {
display: flex;
flex-direction: column;
gap: 10rpx;
.title {
color: #fff;
font-size: 24rpx;
}
}
}
.right {
.btn {
background-color: #a4adb3;
color: #fff;
width: 160rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 8rpx;
background-color: rgba(164, 173, 179, 1);
color: rgba(255, 255, 255, 1);
font-size: 24rpx;
text-align: center;
}
}
}
.li:nth-child(1) {
border-top: none !important;
/* 取消上边框 */
}
}
}
}
}
.isbtn {
border: 1px solid #FAD04D;
border-radius: 10rpx;
background: #434544;
color: #FAD04D;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
text-align: center;
font-size: 24rpx;
position: absolute;
bottom: 20rpx;
right: 15rpx;
}
</style>

648
uniapp/pages/coach/course/list.vue

@ -1,648 +0,0 @@
<!--课程-列表-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="ul">
<view class="li" v-for="(v,k) in dates" :key="k" @click="selectDate(v.date)">
<text>{{v.weekday}}</text>
<text :class="[filteredData.schedule_date == v.date ? 'today':'']">{{today == v.date ? '今':v.dayOfMonth}}</text>
</view>
</view>
<view class="btn" @click="show_calendar=true">
查看更多 <fui-icon name="arrowdown" color="#A4ADB3" size="45"></fui-icon>
</view>
</view>
<!-- <view class="section_2">-->
<!-- <view class="item_box">-->
<!-- <fui-dropdown-menu :size="28" selectedColor="#465CFF" :options="options_course" @click="clickCourse" @close="show_course=false" ref="ref_course">-->
<!-- <view class="fui-filter__item" @tap="filterTapCourse">-->
<!-- <text>{{course_name}}</text>-->
<!-- <view class="fui-filter__icon" :class="{'fui-icon__ani':show_course}">-->
<!-- <fui-icon name="turningdown" :size="32" color="#FFF"></fui-icon>-->
<!-- </view>-->
<!-- </view>-->
<!-- </fui-dropdown-menu>-->
<!-- </view>-->
<!-- <view class="item_box">-->
<!-- <fui-dropdown-menu :size="28" selectedColor="#465CFF" :options="options_classroom" @click="clickClassroom" @close="show_classroom=false" ref="ref_classroom">-->
<!-- <view class="fui-filter__item" @tap="filterTapClassroom">-->
<!-- <text>{{classroom_name}}</text>-->
<!-- <view class="fui-filter__icon" :class="{'fui-icon__ani':show_classroom}">-->
<!-- <fui-icon name="turningdown" :size="32" color="#FFF"></fui-icon>-->
<!-- </view>-->
<!-- </view>-->
<!-- </fui-dropdown-menu>-->
<!-- </view>-->
<!-- </view>-->
<scroll-view class="section_3" scroll-y="true" :lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData" style="height: 100vh;">
<view class="ul">
<view class="li" v-for="(v,k) in tableList" :key="k" @click="openViewCourseInfoList(v)">
<view class="top_box">
<view class="center_box">
<view>时间{{v.course_date}}</view>
<view>课室{{v.venue.venue_name}}</view>
<view>课程{{v.course.course_name}}</view>
<view>人数{{v.venue.capacity}}</view>
</view>
<view class="right_box">
<!-- v.status|1=未开始,2=进行中,3=已结束-->
<!-- <view class="tag"
:style="{background: v.status == 'pending' ? '#1cd188' : v.status == 'ongoing' ? '#fad24e' : '#ff4d4f'}">
{{ v.status === 'pending' ? '未开始' : v.status === 'ongoing' ? '上课中' : '已结束' }}
</view> -->
<!-- <view class="tag" style="background:#1cd188;">待上课</view>-->
</view>
</view>
<!-- <view class="bottom_box" v-if="v.status !== 'pending'"> -->
<view class="bottom_box">
<view class="hint">
已签到学生 ({{v.student.length }}/{{v.venue.capacity}})
</view>
<view class="list_box">
<view class="list">
<view class="itme" v-for="(item,index) in v.student || 0" :key="index">
<image :src="$util.img(item.avatar)"></image>
</view>
</view>
<view class="btn">
详情
</view>
</view>
</view>
<!-- <view class="isbtn" v-if="v.status === 'pending'">
详情
</view> -->
</view>
</view>
</scroll-view>
</view>
<!-- 日历选择-->
<fui-bottom-popup :show="show_calendar" @close="show_calendar=false">
<view class="fui-custom__wrap">
<uni-calendar :insert="true" :lunar="false" :selected="calendarSelected" :startDate="startDate"
:endDate="endDate" @change="changeCalendar" />
</view>
</fui-bottom-popup>
<!-- 底部导航-->
<AQTabber />
</view>
</template>
<script>
// import user from '@/api/user.js';
import memberApi from '@/api/member.js';
import commonApi from '@/api/common.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
loading: false, //
lowerThreshold: 100, //
isReachedBottom: false, //|true=|false=
//
filteredData: {
page: 1, //
limit: 10, //
total: 10, //
schedule_date: '', //
venue_id: '', //id
},
tableList: [], //
venuesInfo: {}, //
formData: {},
//
show_course: false, //
//
course_name: '课程', //
options_course: [{
text: '请选择课程',
value: '',
checked: true
}, {
text: '羽毛球课程1',
value: '1'
}, {
text: '篮球课程2',
value: '2'
}],
//
show_classroom: false, //
//
classroom_name: '课室', //
options_classroom: [{
text: '请选择课室',
value: '',
checked: true
}, {
text: '羽毛球201',
value: '1'
}, {
text: '篮球室101',
value: '2'
}],
//
today: '',
dateList: [], //
//
show_calendar: false, //
startDate: '', //
endDate: '', //
calendarSelected: [], //
dates: {}
}
},
onLoad() {},
onShow() {
this.init() //
},
//
async onPullDownRefresh() {
//
this.getThisDate()
let schedule_date = this.filteredData.schedule_date
await this.loadData()
this.filteredData.schedule_date = schedule_date
await this.getList()
},
methods: {
//
async init() {
await this.getThisDate()
await this.getHeadDate()
await this.getList()
// this.getDateRange()
// this.setCalendarSelected()
},
//
clickCourse(e) {
console.log(e)
this.course_name = e.text
this.show_course = true
},
//
filterTapCourse() {
//
this.$refs.ref_course.show()
this.show_course = true;
},
//
clickClassroom(e) {
console.log(e)
this.classroom_name = e.text
this.show_classroom = true
},
//
filterTapClassroom() {
//
this.$refs.ref_classroom.show()
this.show_classroom = true;
},
//
openViewCourseInfoList(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/course/info_list?id=${id}`
})
},
getDatesAroundToday(offsetDays = 0) {
const date = new Date();
date.setDate(date.getDate() + offsetDays);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const weekDay = ['日', '一', '二', '三', '四', '五', '六'][date.getDay()];
return {
date: `${year}-${month}-${day}`,
weekday: `星期${weekDay}`,
dayOfMonth: day // 👈
};
},
//
async getHeadDate() {
// let res = await commonApi.getDate()
// if (res.code != 1) {
// //
// uni.showToast({
// title: res.msg,
// icon: 'none',
// })
// return
// }
// this.dateList = []
// res.data.forEach((v, k) => {
// let today = v.date.split("-")[2]; // "09"
// this.dateList.push({
// date: v.date,
// status: v.status, //1 2
// week: v.week,
// today: today,
// })
// })
this.dates = {};
for (let i = -3; i <= 3; i++) {
const key = i === 0 ? '今天' : `${Math.abs(i)}${i < 0 ? '前' : '后'}`;
this.dates[key] = this.getDatesAroundToday(i);
}
},
//
async getThisDate() {
let date = new Date();
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0'); //
let day = String(date.getDate()).padStart(2, '0'); //
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
let res = `${year}-${month}-${day}`; //
this.today = res;
this.filteredData.schedule_date = res;
},
//
async selectDate(date) {
this.loadData()
this.filteredData.schedule_date = date
this.getList()
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true; //
this.getList();
}
},
//
async loadData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1 //
this.filteredData.limit = 10 //
this.filteredData.total = 10 //
this.filteredData.schedule_date = ''
},
//
async getList() {
this.loading = true
let data = {
...this.filteredData
}
//
if (this.filteredData.page * this.filteredData.limit > this.filteredData.total || this.filteredData.limit > this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
let res = await apiRoute.courseList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = res.data.data
//
this.venuesInfo = res.data.venues_info
this.filteredData.total = res.data.total
this.filteredData.page++
},
//
//
getDateRange() {
const today = new Date(); //
const startDate = new Date(today); //
const endDate = new Date(today); //
// startDate 1
startDate.setMonth(today.getMonth() - 1);
// endDate 1
endDate.setMonth(today.getMonth() + 2);
// YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// data startDate endDate
this.startDate = formatDate(startDate);
this.endDate = formatDate(endDate);
console.log([this.startDate, this.endDate])
},
//
async setCalendarSelected() {
//
let month = new Date().getMonth() + 1;
let res = await commonApi.getMonthDate({
month: month
})
if (res.code != 1) {
//
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
})
return
}
this.calendarSelected = []
res.data.forEach((v, k) => {
this.calendarSelected.push({
date: v.date,
})
})
// this.calendarSelected = [
// {
// date: '2025-04-07',
// },
// {
// date: '2025-04-08',
// },
// {
// date: '2025-04-09',
// }
// ]
},
//
changeCalendar(e) {
console.log('日历', e)
this.show_calendar = false
this.loadData()
this.filteredData.schedule_date = e.fulldate
this.getList()
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
}
//
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section {
min-height: 100vh;
background: #292929 100%;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1 {
background: #333333 100%;
width: 100%;
padding: 30rpx 28rpx;
padding-bottom: 15rpx;
.ul {
display: flex;
justify-content: space-between;
align-items: center;
.li {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10rpx;
text {
font-size: 24rpx;
color: #FFFFFF;
text-align: center;
}
text:nth-child(2) {
width: 44rpx;
height: 44rpx;
}
text:nth-child(3) {
width: 8rpx;
height: 8rpx;
}
.today {
border-radius: 50%;
background: #29D3B4;
text-align: center;
line-height: 42rpx;
}
.select_plan {
background: #F59A23;
border-radius: 50%;
}
}
}
.btn {
margin-top: 20rpx;
display: flex;
justify-content: center;
align-items: center;
color: #A4ADB3;
}
}
.section_2 {
margin-top: 30rpx;
padding: 0 20rpx;
color: #fff;
display: flex;
align-items: center;
gap: 20rpx;
.item_box {
width: 45%;
.fui-filter__item {
display: flex;
}
}
}
.section_3 {
margin-top: 36rpx;
color: #fff;
font-size: 24rpx;
.ul {
padding: 0 26rpx;
display: flex;
flex-direction: column;
gap: 30rpx;
.li {
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 14rpx 0;
padding-bottom: 30rpx;
display: flex;
flex-direction: column;
.top_box {
padding: 20rpx 30rpx;
.center_box {
display: flex;
flex-direction: column;
gap: 10rpx;
view {}
}
.right_box {
.tag {
position: absolute;
top: 0rpx;
right: 0rpx;
padding: 10rpx;
width: 102rpx;
text-align: center;
font-size: 24rpx;
border-bottom-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
}
}
.bottom_box {
border-top: 1px dashed #F2F2F2;
padding: 26rpx 16rpx 0 26rpx;
.hint {
color: #D7D7D7;
}
.list_box {
margin-top: 22rpx;
display: flex;
justify-content: space-between;
align-items: center;
.list {
display: flex;
align-items: center;
gap: 14rpx;
.itme {
image {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
}
}
}
.btn {
border: 1px solid #FAD04D;
border-radius: 10rpx;
background: #434544;
color: #FAD04D;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
text-align: center;
font-size: 24rpx;
}
}
}
}
}
}
}
.isbtn {
border: 1px solid #FAD04D;
border-radius: 10rpx;
background: #434544;
color: #FAD04D;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
text-align: center;
font-size: 24rpx;
position: absolute;
bottom: 20rpx;
right: 15rpx;
}
</style>

407
uniapp/pages/coach/job/add.vue

@ -1,407 +0,0 @@
<!--发布作业-->
<template>
<view class="main_section">
<fui-form class="formData" ref="form">
<view class="radio_input">
<fui-form-item label="发布类型" asterisk>
<fui-radio-group name="radio" v-model="formData.type" @change="changeType">
<view class="fui-list__item">
<fui-label>
<view class="fui-align__center">
<fui-radio value="1" checked></fui-radio>
<text class="fui-text">班级作业</text>
</view>
</fui-label>
<fui-label :margin="['0','0','0','40rpx']">
<view class="fui-align__center">
<fui-radio value="2"></fui-radio>
<text class="fui-text">学员作业</text>
</view>
</fui-label>
</view>
</fui-radio-group>
</fui-form-item>
</view>
<view class="radio_input">
<fui-form-item label="作业类型" asterisk>
<fui-radio-group name="radio" v-model="formData.content_type" @change="changeContentType">
<view class="fui-list__item">
<fui-label>
<view class="fui-align__center">
<fui-radio value="1" checked></fui-radio>
<text class="fui-text">图片作业</text>
</view>
</fui-label>
<fui-label :margin="['0','0','0','40rpx']">
<view class="fui-align__center">
<fui-radio value="2"></fui-radio>
<text class="fui-text">视频作业</text>
</view>
</fui-label>
</view>
</fui-radio-group>
</fui-form-item>
</view>
<view v-if="formData.type == 1">
<fui-input required label="班级" borderTop placeholder="请选择班级" v-model="formData.classes_id_name"
@click="show_class=true"></fui-input>
<fui-picker layer="1" :linkage="true" :options="options_class_arr" :show="show_class" @change="changeClass"
@cancel="show_class=false"></fui-picker>
</view>
<view v-if="formData.type != 1">
<fui-input required label="学员" borderTop placeholder="请选择学员" v-model="formData.students_ids_name"
@click="show_student=true"></fui-input>
<!--下拉多选-->
<fui-select
:show="show_student"
:options="options_student_arr"
title="请选择学员"
multiple
isReverse
checkboxColor="#FFC529"
btnBackground="#FFC529"
btnColor="#1A1D26"
closeColor="#6D758A"
@confirm="changeStudent"
@close="show_student=false">
</fui-select>
</view>
<view>
<fui-input required label="课程" borderTop placeholder="请选择课程" v-model="formData.course_id_name"
@click="show_course=true"></fui-input>
<fui-picker
layer="1"
:linkage="true"
:options="options_course_arr" :show="show_course" @change="changeCourse"
@cancel="show_course=false"></fui-picker>
</view>
<view>
<fui-textarea required flexStart label="作业" placeholder="请输入内容" v-model="formData.description"></fui-textarea>
</view>
<view class="submet_btn" @click="submetForm">提交</view>
</fui-form>
<!--吸顶消息提示-->
<fui-message ref="msg" :background="`#ff2b2b`"></fui-message>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
//
const rules = [
// {
// name: "classes_id_name",
// rule: ["required"],
// msg: [""]
// },
{
name: "course_id_name",
rule: ["required"],
msg: ["请选择课程"]
},
{
name: "description",
rule: ["required"],
msg: ["请输入作业内容"]
},
];
export default {
components: {
AQTabber,
},
data() {
return {
show_class:false,
show_course:false,
show_student:false,
//
options_class_arr:[
// { value: 1, text: '1' },
],
//
options_course_arr:[
// { value: 1, text: '1' },
],
//
options_student_arr:[
// {
// text: '3',
// value: '3',
// checked: false,//
// }
],
//
formData: {
type: '1',//()|1,2
course_id: '',//id()
course_id_name: '',//id()
content_type: '1',//()|1,2
description: '',//
class_id: '',//id()
classes_id_name: '',//id()
student_id: '',//id
students_ids_name:'',//id()
}
}
},
onLoad() {
},
onShow() {
this.init()
},
methods: {
//
async init() {
// -()
this.getClassesList()
//
this.getCoursesList()
//
this.getStudentList()
},
//
async getClassesList(){
let res = await apiRoute.jlGetClassesList({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.options_class_arr = []
res.data.forEach((v,k)=>{
this.options_class_arr.push({
text: v.class_name,
value: v.id,
})
})
},
//
async getCoursesList(){
let res = await apiRoute.jlGetCoursesList({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.options_course_arr = []
res.data.forEach((v,k)=>{
this.options_course_arr.push({
text: v.course_name,
value: v.id,
})
})
},
//
async getStudentList() {
let res = await apiRoute.jlGetStudentList({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.options_student_arr = []
res.data.forEach((v, k) => {
this.options_student_arr.push({
text: v.name,
value: v.id,
checked: false,//
})
})
},
//-
changeClass(e) {
console.log('选择器-班级', e);
this.formData.class_id = e.value; // class_id
this.formData.classes_id_name = e.text; // class_name
this.show_class = false; //
},
//-
changeCourse(e) {
console.log('选择器-课程', e);
this.formData.course_id = e.value; // course_id
this.formData.course_id_name = e.text; // course_name
this.show_course = false; //
},
//-
changeStudent(e) {
console.log('选择器-学员', e);
let id_arr = []
let name_arr = []
e.options.forEach((v,k)=>{
id_arr.push(v.value)
name_arr.push(v.text)
})
//
this.formData.student_id = id_arr.join(',')
this.formData.students_ids_name = name_arr.join(',')
this.show_student = false; //
},
//-
changeType(e) {
console.log('选择器-作业类型', e);
this.formData.type = e.detail.value; // type
//1=
if(e.detail.value == 1){
//
this.formData.student_id = ''
this.formData.students_ids_name = ''
}else{
// 2=
this.formData.class_id = ''
this.formData.classes_id_name = ''
}
},
//-
changeContentType(e) {
console.log('选择器-作业类型', e);
this.formData.content_type = e.detail.value; // type
},
//
async validatorForm(data) {
let res = await this.$refs.form.validator(data, rules)
return res
},
//
showMsg(msg) {
let options = {}
//text
options.text = msg
this.$refs.tips.show(options)
},
//
async submetForm(){
let data = {...this.formData}
console.log('提交',data)
let vf = await this.validatorForm(data)//
if(data.type == 1){
//
if(!data.class_id){
this.showMsg('请选择班级')
return
}
}else{
//
if(!data.student_id){
this.showMsg('请选择学员')
return
}
}
if(!vf.isPassed){
console.log('验证',vf)
return
}
//
let res = await apiRoute.jlPublishJob(data)
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
//
uni.redirectTo({
url: '/pages/coach/home/index'
})
}, 1000)
},
}
}
</script>
<style lang="less" scoped>
.main_section{
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
.formData{
display: flex;
flex-direction: column;
gap: 40rpx;
.radio_input{
.fui-form__item-wrap{
border-radius: 8rpx !important;
}
.fui-list__item{
display: flex;
align-items: center;
}
}
.submet_btn{
margin: 0 auto;
margin-top: 40rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #29d3b4;
border-radius: 8rpx;
width: 648rpx;
height: 88rpx;
color: rgba(255,255,255,1);
font-size: 32rpx;
}
}
}
</style>

836
uniapp/pages/coach/job/list.vue

@ -1,836 +0,0 @@
<!--作业列表-->
<template>
<view class="main_section">
<scroll-view class="section_3" scroll-y="true" :lower-threshold="lowerThreshold" @scrolltolower="loadMoreData"
style="height: 80vh;">
<view class="ul">
<view class="li" v-for="(v,k) in tableList" :key="k" @click="openViewWorkDetails(v)">
<!-- <view class="left_box">
<view class="date_box">
<text>{{v.wc_count}}</text>
<text>/</text>
<text>{{v.student_count}}</text>
</view>
<view class="ratio">
完成率{{v.rate}}%
</view>
</view> -->
<view class="center_box">
<view>班级{{v.class_name}}</view>
<view>时间{{v.create_time}}</view>
<view>课程{{v.course_name}}
</view>
</view>
<view class="right_box">
<view class="tag" v-if="v.status == 1" style="background:#1cd188;">未提交</view>
<view class="tag" v-if="v.status == 2" style="background:#1cd188;">已提交</view>
<view class="tag" v-if="v.status == 3" style="background:#1cd188;">待批改</view>
<view class="tag" v-if="v.status == 4" style="background:#1cd188;">已批改</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部发布作业按钮 -->
<view class="publish-btn" @click="openPublishPopup">
<text class="btn-text">发布作业</text>
</view>
<!-- 底部导航-->
<AQTabber />
<!-- 发布作业弹窗 -->
<uni-popup ref="publishPopup" type="center">
<view class="popup-content">
<view class="popup-title">发布作业</view>
<!--
<view class="form-item">
<text class="form-label">类型</text>
<input class="form-input" type="number" v-model="formData.type" placeholder="请输入类型" />
</view>
-->
<view class="form-item">
<text class="form-label">班级</text>
<!-- <input class="form-input" type="number" v-model="formData.class_id" placeholder="请输入班级ID" /> -->
<picker mode="selector" :range="class_id_options" range-key="name" @change="handleClassChange"
class="form-picker">
<view v-if="selectedClassName" class="picker-text">{{ selectedClassName }}</view>
<view v-else class="picker-placeholder">请选择班级</view>
</picker>
</view>
<view class="form-item">
<text class="form-label">课程</text>
<!-- <input class="form-input" type="number" v-model="formData.course_id" placeholder="请输入课程" /> -->
<picker mode="selector" :range="course_list" range-key="name" @change="handleCourseChange"
class="form-picker">
<view v-if="selectedCourseName" class="picker-text">{{ selectedCourseName }}</view>
<view v-else class="picker-placeholder">请选择课程</view>
</picker>
</view>
<view class="form-item">
<text class="form-label">学生</text>
<!-- <input class="form-input" type="number" v-model="formData.student_id" placeholder="请输入学生ID" /> -->
<picker mode="selector" :range="student_list" range-key="name" @change="handleStudentChange"
class="form-picker">
<view v-if="selectedStudentName" class="picker-text">{{ selectedStudentName }}</view>
<view v-else class="picker-placeholder">请选择学生</view>
</picker>
</view>
<view class="form-item">
<text class="form-label">作业描述</text>
<textarea class="form-textarea" v-model="formData.description" placeholder="请输入作业描述" />
</view>
<!-- <view class="form-item">
<text class="form-label">内容类型</text>
<picker class="form-picker" :value="contentTypeIndex" :range="contentTypes"
@change="contentTypeChange">
<view class="picker-text">{{contentTypes[contentTypeIndex]}}</view>
</picker>
</view> -->
<!-- 根据内容类型显示不同的上传控件 -->
<!-- 图片类型 -->
<!-- <view class="form-item" v-if="formData.content_type === '1'">
<text class="form-label">上传图片</text>
<view class="upload-container">
<view class="image-preview" v-if="uploadedFiles.image">
<image class="preview-image" :src="uploadedFiles.image" mode="aspectFill"></image>
<view class="delete-btn" @click="deleteFile('image')">
<text class="delete-icon">×</text>
</view>
</view>
<view class="upload-btn" v-if="!uploadedFiles.image" @click="chooseFile('image')">
<text class="upload-icon">+</text>
<text class="upload-text">上传图片</text>
</view>
</view>
</view> -->
<!-- 视频类型 -->
<!-- <view class="form-item" v-if="formData.content_type === '2'">
<text class="form-label">上传视频</text>
<view class="upload-container">
<view class="video-preview" v-if="uploadedFiles.video">
<video class="preview-video" :src="uploadedFiles.video" controls></video>
<view class="delete-btn" @click="deleteFile('video')">
<text class="delete-icon">×</text>
</view>
</view>
<view class="upload-btn" v-if="!uploadedFiles.video" @click="chooseFile('video')">
<text class="upload-icon">+</text>
<text class="upload-text">上传视频</text>
</view>
</view>
</view> -->
<!-- 文本类型 -->
<!-- <view class="form-item" v-if="formData.content_type === '3'">
<text class="form-label">内容文本</text>
<textarea class="form-textarea" v-model="formData.content_text" placeholder="请输入内容文本" />
</view> -->
<view class="form-buttons">
<button class="btn-cancel" @click="closePublishPopup">取消</button>
<button class="btn-submit" @click="submitForm">提交</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
loading: false, //
lowerThreshold: 100, //
isReachedBottom: false, //|true=|false=
//
filteredData: {
page: 1, //
limit: 10, //
total: 10, //
},
tableList: [], //
//
formData: {
class_id: '',
course_id: '',
student_id: '',
description: '',
},
class_id_options: [
],
selectedClassName:'',
course_list:[],
selectedCourseName:'',
student_list:[],
selectedStudentName:'',
//
contentTypes: ['图片', '视频', '文本'],
contentTypeIndex: 0,
//
uploadedFiles: {
image: '',
video: ''
},
//
mockData: []
}
},
onLoad() {
},
onShow() {
this.init()
// this.getData()
},
methods: {
handleClassChange(e) {
const index = e.detail.value;
const selectedClass = this.class_id_options[index];
this.formData.class_id = selectedClass.id;
this.selectedClassName = selectedClass.name;
},
handleCourseChange(e) {
const index = e.detail.value;
const selectedCourse = this.course_list[index];
this.formData.course_id = selectedCourse.id;
this.selectedCourseName = selectedCourse.name;
},
handleStudentChange(e) {
const index = e.detail.value;
const selectedStudent = this.student_list[index];
this.formData.student_id = selectedStudent.id;
this.selectedStudentName = selectedStudent.name;
},
async getClassList() {
let params = {
status: 1, //(1 2)
}
let res = await apiRoute.common_getClassAll(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log('班级列表', res.data)
let arr = []
res.data.forEach((v, k) => {
arr.push({
name: `${v.campus_name}-${v.class_name}`,
id: v.id,
})
})
this.class_id_options = arr
this.getCourseList()
},
async getCourseList() {
let res = await apiRoute.common_getCourseAll({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let arr = []
res.data.forEach((v, k) => {
arr.push({
name: `${v.course_name}`,
id: v.id,
})
})
this.course_list = arr
this.getStudentList()
},
async getStudentList() {
let res = await apiRoute.jlGetStudentList({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let arr = []
res.data.forEach((v, k) => {
arr.push({
name: `${v.name}`,
id: v.id,
})
})
this.student_list = arr
},
async init() {
this.getList()
},
async getData() {
this.getClassList()
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true; //
this.getList();
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1 //
this.filteredData.limit = 10 //
this.filteredData.total = 10 //
},
//
async getList() {
this.loading = true
let data = {
...this.filteredData
}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
if (data.page == 1) {
this.tableList = []
}
let res = await memberApi.jsGetAssignmentsList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
console.log('列表', this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
this.getData()
},
//-
openViewWorkDetails(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/student/work_details?id=${id}`
})
},
//
openPublishPopup() {
this.$refs.publishPopup.open()
},
//
closePublishPopup() {
this.$refs.publishPopup.close()
},
//
contentTypeChange(e) {
this.contentTypeIndex = e.detail.value
this.formData.content_type = String(parseInt(e.detail.value) + 1)
},
//
chooseFile(type) {
if (type === 'image') {
//
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
//
uni.showLoading({
title: '上传中...'
})
setTimeout(() => {
uni.hideLoading()
this.uploadedFiles.image = res.tempFilePaths[0]
//
uni.showToast({
title: '图片上传成功',
icon: 'success'
})
//
this.formData.content_text = res.tempFilePaths[0]
}, 1000)
}
})
} else if (type === 'video') {
//
uni.chooseVideo({
count: 1,
sourceType: ['album', 'camera'],
maxDuration: 60,
camera: 'back',
success: (res) => {
//
uni.showLoading({
title: '上传中...'
})
setTimeout(() => {
uni.hideLoading()
this.uploadedFiles.video = res.tempFilePath
//
uni.showToast({
title: '视频上传成功',
icon: 'success'
})
//
this.formData.content_text = res.tempFilePath
}, 1500)
}
})
}
},
//
deleteFile(type) {
if (type === 'image') {
this.uploadedFiles.image = ''
this.formData.content_text = ''
} else if (type === 'video') {
this.uploadedFiles.video = ''
this.formData.content_text = ''
}
},
//
previewFile(type) {
if (type === 'image' && this.uploadedFiles.image) {
uni.previewImage({
urls: [this.uploadedFiles.image]
})
}
//
},
//
async submitForm() {
//
if (!this.formData.class_id) {
uni.showToast({
title: '请输入班级ID',
icon: 'none'
})
return
}
if (!this.formData.course_id) {
uni.showToast({
title: '请输入课程ID',
icon: 'none'
})
return
}
if (!this.formData.description) {
uni.showToast({
title: '请输入作业描述',
icon: 'none'
})
return
}
//
// if (this.formData.content_type === '1' && !this.uploadedFiles.image) {
// uni.showToast({
// title: '',
// icon: 'none'
// })
// return
// }
// if (this.formData.content_type === '2' && !this.uploadedFiles.video) {
// uni.showToast({
// title: '',
// icon: 'none'
// })
// return
// }
// if (this.formData.content_type === '3' && !this.formData.content_text) {
// uni.showToast({
// title: '',
// icon: 'none'
// })
// return
// }
let res = await apiRoute.jlPublishJob(this.formData)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: '发布成功',
icon: 'success'
})
//
this.closePublishPopup()
//
this.formData = {
class_id: '',
course_id: '',
student_id: '',
description: '',
}
this.init()
// this.contentTypeIndex = 0
// this.uploadedFiles = {
// image: '',
// video: ''
// }
}
}
}
</script>
<style lang="less" scoped>
.main_section {
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
.section_3 {
margin-top: 36rpx;
color: #fff;
font-size: 24rpx;
.title_box {
display: flex;
flex-direction: column;
.top_box {
display: flex;
justify-content: space-between;
align-items: center;
text {
font-size: 30rpx;
}
}
.line {
width: 90rpx;
height: 2px;
background: #29D3B4;
}
}
.ul {
margin-top: 30rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.li {
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 14rpx 0;
display: flex;
align-items: center;
.left_box {
margin-left: 28rpx;
width: 146rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
.date_box {
display: flex;
font-size: 48rpx;
text:nth-child(1) {
color: #29D3B4;
}
}
.ratio {
color: #AAAAAA;
}
}
.center_box {
margin-left: 52rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.right_box {
.tag {
position: absolute;
top: 0rpx;
right: 0rpx;
padding: 10rpx;
width: 102rpx;
text-align: center;
font-size: 24rpx;
border-bottom-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
}
}
}
}
//
.publish-btn {
position: fixed;
bottom: 220rpx;
right: 40rpx;
width: 120rpx;
height: 120rpx;
background: #29D3B4;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(41, 211, 180, 0.4);
z-index: 99;
.btn-text {
color: #fff;
font-size: 26rpx;
text-align: center;
}
}
//
.popup-content {
width: 650rpx;
background: #333;
border-radius: 20rpx;
padding: 30rpx;
max-height: 80vh;
overflow-y: auto;
.popup-title {
font-size: 36rpx;
color: #fff;
text-align: center;
margin-bottom: 30rpx;
font-weight: bold;
}
.form-item {
margin-bottom: 20rpx;
.form-label {
display: block;
color: #fff;
font-size: 28rpx;
margin-bottom: 10rpx;
}
.form-input,
.form-textarea,
.form-picker {
width: 100%;
background: #444;
border-radius: 10rpx;
padding: 16rpx;
color: #fff;
font-size: 28rpx;
box-sizing: border-box;
}
.form-textarea {
height: 150rpx;
}
.picker-text {
height: 60rpx;
line-height: 60rpx;
}
//
.upload-container {
margin-top: 10rpx;
}
.upload-btn {
width: 200rpx;
height: 200rpx;
background: #444;
border-radius: 10rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px dashed #666;
.upload-icon {
font-size: 60rpx;
color: #29D3B4;
line-height: 60rpx;
}
.upload-text {
font-size: 24rpx;
color: #aaa;
margin-top: 10rpx;
}
}
//
.image-preview {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
overflow: hidden;
.preview-image {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
.delete-icon {
color: #fff;
font-size: 28rpx;
}
}
}
//
.video-preview {
position: relative;
width: 100%;
height: 300rpx;
border-radius: 10rpx;
overflow: hidden;
.preview-video {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
.delete-icon {
color: #fff;
font-size: 28rpx;
}
}
}
}
.form-buttons {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
button {
width: 45%;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
}
.btn-cancel {
background: #555;
color: #fff;
}
.btn-submit {
background: #29D3B4;
color: #fff;
}
}
}
}
</style>

276
uniapp/pages/coach/my/arrival_statistics.vue

@ -1,276 +0,0 @@
<!--到课统计-详情-->
<template>
<view class="main_box">
<view class="main_section">
<scroll-view
class="section_3"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 90vh;"
>
<view class="ul">
<view class="li"
v-for="(v,k) in courseList"
:key="k"
@click="openViewCourseInfo(v)"
>
<view class="left_box">
<view class="date_box">
<text>{{v.has_sign_count}}</text>
<text>/</text>
<text>{{v.students_count}}</text>
</view>
<view class="ratio">
到课率{{v.attendance_rate}}%
</view>
</view>
<view class="center_box">
<view>班级{{v.classes_name}}</view>
<view>时间{{v.date_time}} {{v.time_slot ? v.time_slot.replace(",", " - ") : ""}}</view>
<view>课室{{v.address}}
</view>
<view>课程{{v.courses_name}}
</view>
</view>
<view class="right_box">
<view v-if="!(['1','2'].includes(String(v.status)))" class="tag" style="background:#20CAAF;">未开始</view>
<view v-if="v.status == 1" class="tag" style="background:#fad24e;">上课中</view>
<view v-if="v.status == 2" class="tag" style="background:#e2e2e2;">已结束</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
loading: false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData: {
page: 1,//
limit: 10,//
total: 10,//
class_id: '',//id
},
courseList:[],//
}
},
onLoad() {
},
onShow(){
this.init()
},
methods: {
//
async init(){
this.getCourseList()
},
// .courseList
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getCourseList();
}
},
//
async loadData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//-
async getCourseList(){
let data = {...this.filteredData}
data.class_id = this.class_id
console.log(12123,this.courseList)
if(data.page == 1){
this.courseList = []
}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
let res = await memberApi.courseList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let arr = []
res.data.list.data.forEach((v,k)=>{
let item = v
item.attendance_rate = (v.students_count && !isNaN(v.students_count) && v.students_count > 0) ?
parseFloat(((v.has_sign_count / v.students_count) * 100).toFixed(1)) : 0;
arr.push(item)
})
this.courseList = this.courseList.concat(res.data.list.data)// 使 concat
this.filteredData.total = res.data.list.total
console.log('获取课程列表',this.courseList)
this.filteredData.page++
},
//
openViewCourseInfo(item){
let id= item.id
this.$navigateTo({
url: `/pages/coach/course/info_list?id=${id}`
})
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
display: flex;
justify-content: center;
align-items: center;
background: #29d3b4;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #315d55;
}
}
.main_section{
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 32rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
.section_3{
color: #fff;
font-size: 24rpx;
.title_box{
display: flex;
flex-direction: column;
.top_box{
display: flex;
justify-content: space-between;
align-items: center;
text{
font-size: 30rpx;
}
}
.line{
width: 90rpx;
height: 2px;
background: #29D3B4;
}
}
.ul{
margin-top: 30rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.li{
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 14rpx 0;
display: flex;
align-items: center;
.left_box{
margin-left: 28rpx;
width: 175rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
.date_box{
display: flex;
font-size: 48rpx;
text:nth-child(1){
color: #29D3B4;
}
}
.ratio{
color: #AAAAAA;
}
}
.center_box{
margin-left: 22rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.right_box{
.tag{
position:absolute;
top: 0rpx;
right: 0rpx;
padding: 10rpx;
width: 102rpx;
text-align: center;
font-size: 24rpx;
border-bottom-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
}
}
}
}
}
</style>

431
uniapp/pages/coach/my/index.vue

@ -1,431 +0,0 @@
<!--我的-首页-->
<template>
<view class="main_box">
<view style="background:#29D3B4;">
<!--用户信息-->
<view class="user_section">
<view class="box">
<view class="left" @click="openViewMyInfo()">
<image class="pic" :src="$util.img(memberInfo.head_img)"></image>
<view class="name">{{memberInfo.name}}</view>
</view>
<view class="right">
<view class="btn"></view>
<!-- <view class="btn">切换身份</view>-->
<!-- <view class="btn" @click="openViewArrivalStatistics()">到课率统计</view> -->
<!-- <view class="btn">到课率统计</view> -->
<view class="btn"></view>
</view>
</view>
</view>
<!--统计信息-->
<view class="count_section">
<view class="main">
<view class="course_box">
<view class="top">
<view class="item">
<view class="num">{{statisticsInfo.courseNum}}</view>
<view class="intro">总授课数/</view>
</view>
<view class="item">
<view class="num">{{statisticsInfo.classNum}}</view>
<view class="intro">总授班级/</view>
</view>
<view class="item">
<view class="num">{{statisticsInfo.studentNum}}</view>
<view class="intro">总负责学员/
</view>
</view>
</view>
</view>
</view>
<view class="bg_box bg_top"></view>
<view class="bg_box bg_bottom"></view>
</view>
</view>
<view class="main_section">
<view class="section_box">
<view class="item" @click="openViewDueSoon()">
<view>即将到期</view>
<view></view>
</view>
<view class="item" @click="openViewSchoolingStatisticsReal()">
<view>授课统计</view>
<view></view>
</view>
<!-- <view class="item"> -->
<view class="item" @click="openViewMyAttendance()">
<view>我的考勤</view>
<view></view>
</view>
<!-- <view class="item"> -->
<view class="item" @click="openViewSchoolingStatistics()">
<view>我的消息</view>
<view></view>
</view>
<view class="item" @click="teachingResearchManagement()">
<view>教研管理</view>
<view></view>
</view>
<view class="item" @click="openServiceDetail()">
<view>服务详情</view>
<view></view>
</view>
<view class="item" @click="goCourseSchedule()">
<view>课程安排</view>
<view></view>
</view>
<view class="item" @click="my_contract()">
<view>我的合同</view>
<view></view>
</view>
</view>
<view class="section_box">
<!-- <view class="item" @click="openViewFeedback()">
<view>意见反馈</view>
<view></view>
</view> -->
<view class="item" @click="openViewSetUp()">
<view>设置</view>
<view></view>
</view>
</view>
</view>
<!-- 底部导航-->
<AQTabber />
</view>
</template>
<script>
// import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
memberInfo: {},
statisticsInfo: []
}
},
onLoad() {
},
onShow() {
this.init();
},
methods: {
async init() {
this.getStatistics()
},
my_contract(){
this.$navigateTo({
url: '/pages/common/contract/my_contract'
})
},
//()
async getMemberInfo() {
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.memberInfo = res.data
},
//
async getStatistics() {
let res = await apiRoute.getStatisticsInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.statisticsInfo = res.data
this.getMemberInfo()
},
//
openViewArrivalStatistics() {
this.$navigateTo({
url: '/pages/coach/my/arrival_statistics'
})
},
//
openViewDueSoon() {
this.$navigateTo({
url: '/pages/coach/my/due_soon'
})
},
//
openViewSchoolingStatistics() {
this.$navigateTo({
url: '/pages/common/my_message'
})
},
//
teachingResearchManagement() {
this.$navigateTo({
url: '/pages/coach/my/teaching_management'
})
},
//
openViewFeedback() {
this.$navigateTo({
url: '/pages/common/feedback'
})
},
//
openViewMyInfo() {
this.$navigateTo({
url: '/pages/coach/my/info'
})
},
//
openViewSetUp() {
this.$navigateTo({
url: '/pages/coach/my/set_up'
})
},
//-
openViewMyAttendance() {
this.$navigateTo({
url: `/pages/common/my_attendance`
})
},
//
openServiceDetail() {
this.$navigateTo({
url: '/pages/coach/my/service_list'
})
},
//
openViewSchoolingStatisticsReal() {
this.$navigateTo({
url: '/pages/coach/my/schooling_statistics'
})
},
goCourseSchedule(){
this.$navigateTo({
url: '/pages/coach/schedule/schedule_table'
})
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
min-height: 28vh;
}
//
.navbar_section {
border: 1px solid #29D3B4;
display: flex;
justify-content: center;
align-items: center;
background: #29D3B4;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
//
.user_section {
background-color: #29D3B4;
padding-top: 58rpx;
padding-bottom: 42rpx;
color: #fff;
font-size: 28rpx;
.box {
padding-left: 19rpx;
padding-right: 29rpx;
display: flex;
justify-content: space-between;
align-items: center;
gap: 15rpx;
.left {
display: flex;
align-items: center;
gap: 20rpx;
.pic {
width: 144rpx;
height: 144rpx;
border-radius: 50%;
}
.name {
font-size: 28rpx;
}
}
.right {
display: flex;
flex-direction: column;
gap: 20rpx;
.btn {
min-height: 28rpx;
font-size: 28rpx;
}
}
}
}
//
.count_section {
position: relative;
.main {
position: relative;
z-index: 2;
padding: 0rpx 24rpx;
display: flex;
justify-content: center;
.course_box {
padding: 42rpx 28rpx;
width: 692rpx;
border-radius: 20rpx;
background-color: #fff;
display: flex;
flex-direction: column;
gap: 32rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
.item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
.num {
color: #29D3B4;
font-size: 56rpx;
}
.intro {
color: #AAAAAA;
font-size: 24rpx;
}
}
}
.bottom {
font-size: 24rpx;
color: #333333;
text {
color: #29D3B4;
}
}
}
}
.bg_box {
z-index: 1;
width: 100%;
height: 150rpx;
}
.bg_top {
position: absolute;
top: 0;
background-color: #29D3B4;
}
.bg_bottom {
top: 50%;
position: absolute;
background-color: #292929;
}
}
.main_section {
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #333333;
display: flex;
flex-direction: column;
gap: 22rpx;
.section_box {
background: #fff;
border-radius: 16rpx;
padding: 6rpx 24rpx;
display: flex;
flex-direction: column;
.item {
padding: 24rpx 78rpx;
border-top: 1px solid #F2F2F2;
font-size: 28rpx;
display: flex;
justify-content: space-between;
}
.item:nth-child(1) {
border-top: 0;
}
}
}
</style>

497
uniapp/pages/coach/my/my_attendance.vue

@ -1,497 +0,0 @@
<!--我的考勤-详情-->
<template>
<view class="main_box">
<fui-segmented-control
:values="optionTable"
type="text"
activeColor="#29d3b4"
color="#fff"
@click="segmented">
</fui-segmented-control>
<view class="main_section">
<!--考勤-->
<scroll-view
class="section_1"
v-if="filteredData.type == '1'"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 100vh;"
>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
</view>
<view class="title_box">普通考勤</view>
<view class="subhead_box">请假</view>
<view class="subhead_box">2025-01-01 00:00:00 - 2505-01-01 00:00:00</view>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">迟到</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">早退</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通记录</view>
<view class="content">本周考勤情况周一到周五均按时打卡未请假综合表现良好
</view>
</view>
</view>
</view>
</scroll-view>
<!--请假-->
<scroll-view
class="section_1"
v-if="filteredData.type == '2'"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 100vh;"
>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
</view>
<view class="title_box">普通考勤</view>
<view class="subhead_box">请假</view>
<view class="subhead_box">2025-01-01 00:00:00 - 2505-01-01 00:00:00</view>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">迟到</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">早退</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通记录</view>
<view class="content">本周考勤情况周一到周五均按时打卡未请假综合表现良好
</view>
</view>
</view>
</view>
</scroll-view>
<!--异常-->
<scroll-view
class="section_1"
v-if="filteredData.type == '3'"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 100vh;"
>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">篮球课</view>
<view class="content">考勤正常</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
</view>
<view class="title_box">普通考勤</view>
<view class="subhead_box">请假</view>
<view class="subhead_box">2025-01-01 00:00:00 - 2505-01-01 00:00:00</view>
<view class="ul">
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">迟到</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通考勤</view>
<view class="content">早退</view>
<view class="content">2025-01-01 00:00:00 - 2025-01-01 00:00:00</view>
</view>
</view>
<view class="li">
<view class="left">
<image src="http://www.firstui.cn:4000/vipdoc/img/img_logo.png" model="aspectFill"></image>
</view>
<view class="right">
<view class="content">普通记录</view>
<view class="content">本周考勤情况周一到周五均按时打卡未请假综合表现良好
</view>
</view>
</view>
</view>
</scroll-view>
<view class="section_btn">
<view class="btn">请假</view>
</view>
</view>
</view>
</template>
<script>
import marketApi from '@/api/market.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
//tab
optionTable: [
{
id: 1,
name: '考勤'
},
{
id: 2,
name: '请假'
},
{
id: 3,
name: '异常'
}
],
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
type: '1',//1=,2=,3=
},
tableList:[],//
}
},
onLoad(options) {},
onShow(){
this.init()//
},
methods: {
//
async init(){
await this.getList();
},
//tag
async segmented(e) {
//
await this.resetFilteredData()
this.filteredData.type = e.id//1=,2=,3=
await this.getList()
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getList(){
this.loading = true
let data = {...this.filteredData}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
if(data.page == 1){
this.tableList = []
}
let res = await marketApi.myClient(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.list.data); // 使 concat
console.log('列表',this.tableList)
this.filteredData.total = res.data.list.total
this.filteredData.page++
this.countArr = {
type_0:res.data.count[0],
type_1:res.data.count[1],
type_2:res.data.count[2],
type_3:res.data.count[3],
max_count:res.data.gh.max_count,
lq_count:res.data.gh.lq_count,
}
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
display: flex;
justify-content: center;
align-items: center;
background: #29d3b4;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #315d55;
}
}
.main_section{
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 32rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
.section_1{
color: #fff;
font-size: 28rpx;
.ul{
margin-top: 23rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
.li{
display: flex;
align-items: center;
gap: 43rpx;
.left{
image{
width: 174rpx;
height: 174rpx;
border-radius: 24rpx;
background-color: #333333;
}
}
.right{
display: flex;
flex-direction: column;
gap: 18rpx;
.content{
font-size: 24rpx;
}
.content:nth-child(1){
font-size: 28rpx;
}
}
}
}
.title_box{
margin-top: 46rpx;
font-size: 28rpx;
}
.subhead_box{
margin-top: 22rpx;
font-size: 24rpx;
}
}
.section_btn{
display: flex;
justify-content: center;
.btn{
width: 722rpx;
height: 64rpx;
line-height: 64rpx;
border-radius: 8rpx;
background-color: rgba(32,202,175,1);
color: rgba(255,255,255,1);
font-size: 28rpx;
text-align: center;
}
}
}
</style>

870
uniapp/pages/coach/my/service_detail.vue

@ -1,870 +0,0 @@
<!--服务详情页面-->
<template>
<view class="container">
<view class="main-content">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" content-text="加载中..."></uni-load-more>
</view>
<!-- 服务记录列表 -->
<view v-else class="service-cards">
<view class="service-card" v-for="(service, index) in serviceList" :key="index" @click="viewServiceDetail(service)">
<!-- 服务预览图 -->
<view class="service-preview" v-if="service.preview_image_url">
<image :src="getImageUrl(service.preview_image_url)" class="preview-image" mode="aspectFill"></image>
</view>
<view class="card-content">
<!-- 服务名称和状态 -->
<view class="card-header">
<view class="service-name">{{ service.service_name }}</view>
<view class="service-status" :class="service.status === 1 ? 'status-active' : (service.status === 0 ? 'status-pending' : 'status-inactive')">
{{ service.status === 1 ? '已完成' : (service.status === 0 ? '待处理' : '未知状态') }}
</view>
</view>
<!-- 服务信息 -->
<view class="service-info">
<view class="info-item" v-if="service.service_type">
<text class="label">服务类型</text>
<text class="value">{{ service.service_type }}</text>
</view>
<view class="info-item" v-if="service.description">
<text class="label">服务描述</text>
<text class="value">{{ service.description }}</text>
</view>
<view class="info-item" v-if="service.service_remark || service.status !== 1">
<text class="label">服务结果</text>
<view class="value-content" v-if="service.service_remark">
<rich-text :nodes="formatRichText(service.service_remark)"></rich-text>
</view>
<text class="value placeholder" v-else-if="service.status !== 1">点击编辑服务结果</text>
</view>
<view class="info-item" v-if="service.feedback">
<text class="label">家长反馈</text>
<text class="value">{{ service.feedback }}</text>
</view>
<view class="info-item" v-if="service.score">
<text class="label">家长评分</text>
<text class="value score">{{ service.score }}</text>
</view>
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{ formatDate(service.created_at) }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-actions" v-if="service.status !== 1">
<button class="edit-btn" @click.stop="editServiceRemark(service)">
<text class="iconfont icon-edit"></text>
编辑服务结果
</button>
</view>
</view>
<!-- 箭头图标 -->
<view class="card-arrow">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="!loading && serviceList.length === 0">
<view class="empty-icon">📝</view>
<view class="empty-text">暂无服务记录</view>
</view>
<!-- 底部加载更多 -->
<view v-if="hasMore && !loading && serviceList.length > 0" class="load-more">
<uni-load-more :status="loadMoreStatus" @clickLoadMore="loadMore"></uni-load-more>
</view>
</view>
<!-- 编辑服务结果弹窗 -->
<view v-if="showEditModal" class="edit-modal" @click="closeEditModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">编辑服务结果</text>
<view class="modal-close" @click="closeEditModal">
<text class="iconfont icon-close"></text>
</view>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">服务结果</text>
<!-- 富文本工具栏 -->
<view class="editor-toolbar">
<view class="toolbar-group">
<view class="tool-btn" :class="{ active: bold }" @click="toggleBold">
<text class="tool-text">B</text>
</view>
<view class="tool-btn" :class="{ active: italic }" @click="toggleItalic">
<text class="tool-text">I</text>
</view>
<view class="tool-btn" @click="insertBulletList">
<text class="tool-text"></text>
</view>
<view class="tool-btn" @click="insertNumberList">
<text class="tool-text">1.</text>
</view>
</view>
</view>
<!-- 文本输入区域 -->
<textarea
class="form-textarea"
v-model="editForm.service_remark"
placeholder="请输入服务结果内容,支持简单的格式化文本"
maxlength="1000"
:show-confirm-bar="false"
@focus="onTextareaFocus"
@blur="onTextareaBlur">
</textarea>
<!-- 字数统计 -->
<view class="char-count">{{ editForm.service_remark.length }}/1000</view>
<!-- 提示信息 -->
<view class="editor-tips">
<text class="tip-text">支持基础格式粗体斜体列表等</text>
</view>
</view>
</view>
<view class="modal-footer">
<button class="modal-btn cancel" @click="closeEditModal">取消</button>
<button class="modal-btn confirm" @click="saveServiceRemark" :disabled="saving">
{{ saving ? '保存中...' : '保存' }}
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/common/axios.js';
export default {
data() {
return {
loading: true,
serviceList: [],
currentPage: 1,
pageSize: 10,
hasMore: true,
loadMoreStatus: 'more',
showEditModal: false,
saving: false,
editForm: {
id: 0,
service_remark: ''
},
//
bold: false,
italic: false,
textareaFocused: false
}
},
onLoad() {
this.init();
},
onPullDownRefresh() {
this.refreshServiceList();
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore();
}
},
methods: {
async init() {
this.getServiceList();
},
//
async getServiceList(refresh = false) {
if (refresh) {
this.currentPage = 1;
this.hasMore = true;
this.serviceList = [];
}
this.loading = true;
this.loadMoreStatus = 'loading';
try {
const response = await apiRoute.get('/personnel/myServiceLogs', {
page: this.currentPage,
limit: this.pageSize,
demo: 1 //
});
if (response.code === 1) {
const newServices = response.data.data || [];
if (refresh) {
this.serviceList = newServices;
} else {
this.serviceList = [...this.serviceList, ...newServices];
}
//
this.hasMore = newServices.length === this.pageSize;
this.loadMoreStatus = this.hasMore ? 'more' : 'noMore';
} else {
uni.showToast({
title: response.data.msg || '加载失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取服务记录失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
} finally {
this.loading = false;
if (refresh) {
uni.stopPullDownRefresh();
}
}
},
//
refreshServiceList() {
this.getServiceList(true);
},
//
loadMore() {
if (this.hasMore && !this.loading) {
this.currentPage++;
this.getServiceList();
}
},
//
async viewServiceDetail(service) {
try {
const response = await apiRoute.get('/personnel/serviceLogDetail', {
id: service.id
});
if (response.code === 1) {
const detail = response.data;
this.showServiceDetailModal(detail);
} else {
uni.showToast({
title: response.data.msg || '获取详情失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取服务详情失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
}
},
//
showServiceDetailModal(detail) {
let content = `服务名称:${detail.service_name || '-'}\n`;
content += `服务类型:${detail.service_type || '-'}\n`;
content += `状态:${detail.status === 1 ? '已完成' : (detail.status === 0 ? '待处理' : '未知状态')}\n`;
if (detail.description) {
content += `描述:${detail.description}\n`;
}
if (detail.service_remark) {
content += `服务结果:${detail.service_remark}\n`;
}
if (detail.feedback) {
content += `家长反馈:${detail.feedback}\n`;
}
if (detail.score) {
content += `家长评分:${detail.score}`;
}
uni.showModal({
title: '服务详情',
content: content,
showCancel: false
});
},
// URL
getImageUrl(url) {
if (!url) return '';
if (url.startsWith('http')) {
return url;
}
return this.$baseUrl + '/' + url;
},
//
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// HTML
stripHtml(html) {
if (!html) return '';
return html.replace(/<[^>]*>/g, '').trim();
},
//
formatRichText(text) {
if (!text) return '';
// markdown
let formatted = text
//
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
//
.replace(/\*(.*?)\*/g, '<em>$1</em>')
//
.replace(/^\u2022\s(.+)$/gm, '<li>$1</li>')
//
.replace(/^\d+\.\s(.+)$/gm, '<li>$1</li>')
//
.replace(/\n/g, '<br/>');
// ul
if (formatted.includes('<li>')) {
formatted = formatted.replace(/(<li>.*?<\/li>)/g, '<ul>$1</ul>');
}
return formatted;
},
//
editServiceRemark(service) {
//
if (service.status === 1) {
uni.showToast({
title: '服务已完成,无法修改',
icon: 'none'
});
return;
}
this.editForm.id = service.id;
this.editForm.service_remark = this.stripHtml(service.service_remark || '');
this.showEditModal = true;
},
//
closeEditModal() {
this.showEditModal = false;
this.editForm = {
id: 0,
service_remark: ''
};
},
//
async saveServiceRemark() {
if (!this.editForm.service_remark.trim()) {
uni.showToast({
title: '请输入服务结果内容',
icon: 'none'
});
return;
}
this.saving = true;
try {
const response = await apiRoute.post('/personnel/updateServiceRemark', {
id: this.editForm.id,
service_remark: this.editForm.service_remark
});
if (response.data.code === 1) {
uni.showToast({
title: '保存成功',
icon: 'success'
});
//
const index = this.serviceList.findIndex(item => item.id === this.editForm.id);
if (index !== -1) {
this.serviceList[index].service_remark = this.editForm.service_remark;
}
this.closeEditModal();
} else {
uni.showToast({
title: response.data.msg || '保存失败',
icon: 'none'
});
}
} catch (error) {
console.error('保存服务结果失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
} finally {
this.saving = false;
}
},
//
onTextareaFocus() {
this.textareaFocused = true;
},
onTextareaBlur() {
this.textareaFocused = false;
},
toggleBold() {
this.bold = !this.bold;
this.insertFormatText('**', '**');
},
toggleItalic() {
this.italic = !this.italic;
this.insertFormatText('*', '*');
},
insertBulletList() {
this.insertFormatText('\n• ', '');
},
insertNumberList() {
this.insertFormatText('\n1. ', '');
},
insertFormatText(before, after) {
const textarea = this.editForm.service_remark;
const newText = textarea + before + '请输入内容' + after;
this.editForm.service_remark = newText;
}
}
}
</script>
<style lang="scss" scoped>
.container {
background: #1a1a1a;
min-height: 100vh;
}
.main-content {
padding: 20rpx;
}
.service-cards {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.service-card {
background: #2a2a2a;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
border: 1rpx solid #444;
position: relative;
display: flex;
align-items: center;
padding: 24rpx;
}
.service-preview {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
overflow: hidden;
margin-right: 24rpx;
flex-shrink: 0;
.preview-image {
width: 100%;
height: 100%;
}
}
.card-content {
flex: 1;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.service-name {
font-size: 32rpx;
font-weight: 600;
color: #fff;
flex: 1;
margin-right: 16rpx;
}
.service-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-active {
background: rgba(41, 211, 180, 0.2);
color: #29d3b4;
border: 1rpx solid #29d3b4;
}
&.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #ffc107;
border: 1rpx solid #ffc107;
}
&.status-inactive {
background: rgba(220, 53, 69, 0.2);
color: #dc3545;
border: 1rpx solid #dc3545;
}
}
}
.service-info {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.info-item {
display: flex;
align-items: flex-start;
.label {
color: #999;
font-size: 26rpx;
min-width: 140rpx;
flex-shrink: 0;
}
.value {
color: #ccc;
font-size: 26rpx;
flex: 1;
word-break: break-all;
&.score {
color: #29d3b4;
font-weight: 600;
}
&.placeholder {
color: #666;
font-style: italic;
}
}
.value-content {
flex: 1;
color: #ccc;
font-size: 26rpx;
line-height: 1.6;
/* 富文本样式 */
:deep(strong) {
font-weight: 600;
color: #fff;
}
:deep(em) {
font-style: italic;
color: #29d3b4;
}
:deep(ul) {
margin: 8rpx 0;
padding-left: 24rpx;
}
:deep(li) {
margin: 4rpx 0;
list-style: disc;
}
}
}
.card-arrow {
margin-left: 16rpx;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.iconfont {
font-size: 24rpx;
color: #666;
}
}
.card-actions {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #444;
.edit-btn {
background: #29d3b4;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 12rpx 24rpx;
font-size: 26rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
&:active {
background: #22b39a;
}
.iconfont {
font-size: 24rpx;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
opacity: 0.3;
}
.empty-text {
color: #666;
font-size: 28rpx;
}
}
.loading-container {
padding: 120rpx 40rpx;
text-align: center;
}
.load-more {
padding: 40rpx 0;
}
/* 动画效果 */
.service-card {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 编辑弹窗样式 */
.edit-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
.modal-content {
background-color: #2a2a2a;
border-radius: 20rpx;
width: 90%;
max-height: 80%;
overflow: hidden;
border: 1rpx solid #444;
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1rpx solid #444;
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
.modal-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 32rpx;
color: #999;
}
}
}
.modal-body {
padding: 32rpx;
.form-item {
.form-label {
font-size: 28rpx;
color: #ccc;
margin-bottom: 16rpx;
display: block;
}
.editor-toolbar {
background-color: #1a1a1a;
border: 1rpx solid #444;
border-bottom: none;
border-radius: 12rpx 12rpx 0 0;
padding: 16rpx;
.toolbar-group {
display: flex;
gap: 16rpx;
.tool-btn {
width: 48rpx;
height: 48rpx;
background-color: #444;
border: 1rpx solid #666;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
&.active {
background-color: #29d3b4;
border-color: #29d3b4;
}
.tool-text {
font-size: 24rpx;
color: #fff;
font-weight: 600;
}
}
}
}
.form-textarea {
width: 100%;
min-height: 300rpx;
background-color: #1a1a1a;
border: 1rpx solid #444;
border-radius: 0 0 12rpx 12rpx;
border-top: none;
padding: 20rpx;
font-size: 28rpx;
color: #fff;
line-height: 1.6;
box-sizing: border-box;
&::placeholder {
color: #666;
}
}
.char-count {
text-align: right;
margin-top: 8rpx;
font-size: 24rpx;
color: #666;
}
.editor-tips {
margin-top: 12rpx;
.tip-text {
font-size: 22rpx;
color: #666;
line-height: 1.4;
}
}
}
}
.modal-footer {
display: flex;
gap: 24rpx;
padding: 32rpx;
border-top: 1rpx solid #444;
.modal-btn {
flex: 1;
height: 72rpx;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 600;
border: none;
display: flex;
align-items: center;
justify-content: center;
&.cancel {
background-color: #444;
color: #ccc;
&:active {
background-color: #555;
}
}
&.confirm {
background-color: #29d3b4;
color: #fff;
&:active:not(:disabled) {
background-color: #22b39a;
}
&:disabled {
background-color: #666;
color: #999;
}
}
}
}
}
}
</style>

565
uniapp/pages/coach/my/service_list.vue

@ -1,565 +0,0 @@
<!--服务列表页面-->
<template>
<view class="container dark-theme">
<view class="header">
<view class="search-bar">
<view class="search-input">
<input
placeholder="搜索服务..."
v-model="searchText"
@input="handleSearch"
/>
</view>
<view class="filter-btn" @click="showFilter">
<text>筛选</text>
</view>
</view>
</view>
<view class="service-list">
<view class="service-item"
v-for="(service, index) in filteredServiceList"
:key="index"
@click="goToDetail(service)">
<view class="service-header">
<view class="service-title">{{ service.name }}</view>
<view class="service-badge" :class="[
service.status === '正常' ? 'badge-success' : '',
service.status === '即将到期' ? 'badge-warning' : '',
service.status === '已过期' ? 'badge-danger' : '',
!['正常', '即将到期', '已过期'].includes(service.status) ? 'badge-default' : ''
]">
{{ service.status }}
</view>
</view>
<view class="service-content">
<view class="service-meta">
<view class="meta-item">
<text class="meta-label">类型</text>
<text class="meta-value">{{ service.type }}</text>
</view>
<view class="meta-item">
<text class="meta-label">时长</text>
<text class="meta-value">{{ service.duration }}</text>
</view>
</view>
<view class="service-desc">{{ service.description }}</view>
<view class="service-footer">
<view class="service-time">
{{ service.startTime }} - {{ service.endTime }}
</view>
<view class="service-action">
<text class="action-text">查看详情</text>
<text class="action-arrow">></text>
</view>
</view>
</view>
</view>
</view>
<view class="empty-state" v-if="filteredServiceList.length === 0">
<image class="empty-icon" src="/static/icon-img/empty.png"></image>
<view class="empty-text">{{ searchText ? '未找到相关服务' : '暂无服务记录' }}</view>
</view>
<!-- 筛选弹窗 -->
<fui-bottom-popup v-model="showFilterPopup" :zIndex="9999">
<view class="filter-popup">
<view class="popup-header">
<view class="popup-title">筛选条件</view>
<view class="popup-close" @click="showFilterPopup = false"></view>
</view>
<view class="filter-content">
<view class="filter-group">
<view class="filter-title">服务状态</view>
<view class="filter-options">
<view
class="filter-option"
:class="{ active: selectedStatus === '' }"
@click="selectedStatus = ''"
>
全部
</view>
<view
class="filter-option"
:class="{ active: selectedStatus === '正常' }"
@click="selectedStatus = '正常'"
>
正常
</view>
<view
class="filter-option"
:class="{ active: selectedStatus === '即将到期' }"
@click="selectedStatus = '即将到期'"
>
即将到期
</view>
<view
class="filter-option"
:class="{ active: selectedStatus === '已过期' }"
@click="selectedStatus = '已过期'"
>
已过期
</view>
</view>
</view>
</view>
<view class="popup-actions">
<view class="btn btn-reset" @click="resetFilter">重置</view>
<view class="btn btn-confirm" @click="applyFilter">确定</view>
</view>
</view>
</fui-bottom-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
serviceList: [],
filteredServiceList: [],
searchText: '',
showFilterPopup: false,
selectedStatus: ''
}
},
onLoad() {
this.init();
},
methods: {
async init() {
this.getServiceList();
},
//
async getServiceList() {
try {
// API
this.serviceList = [
{
id: 1,
name: '专业体能训练服务',
type: '体能训练',
status: '正常',
duration: '3个月',
startTime: '2024-01-01',
endTime: '2024-03-31',
description: '专业的体能训练指导,包含有氧运动、力量训练等综合项目'
},
{
id: 2,
name: '基础动作指导服务',
type: '技术指导',
status: '即将到期',
duration: '1个月',
startTime: '2024-06-01',
endTime: '2024-06-30',
description: '针对基础动作的专业指导和纠正'
},
{
id: 3,
name: '营养咨询服务',
type: '营养指导',
status: '正常',
duration: '6个月',
startTime: '2024-01-15',
endTime: '2024-07-15',
description: '专业营养师提供个性化营养方案和饮食建议'
},
{
id: 4,
name: '康复训练服务',
type: '康复指导',
status: '已过期',
duration: '2个月',
startTime: '2023-10-01',
endTime: '2023-11-30',
description: '运动损伤康复和预防性训练指导'
}
];
this.filteredServiceList = [...this.serviceList];
// API
// let res = await apiRoute.getServiceList({});
// if (res.code === 1) {
// this.serviceList = res.data;
// this.filteredServiceList = [...this.serviceList];
// }
} catch (error) {
console.error('获取服务列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
}
},
//
handleSearch() {
this.filterServices();
},
//
filterServices() {
let filtered = [...this.serviceList];
//
if (this.searchText) {
filtered = filtered.filter(service =>
service.name.includes(this.searchText) ||
service.type.includes(this.searchText) ||
service.description.includes(this.searchText)
);
}
//
if (this.selectedStatus) {
filtered = filtered.filter(service => service.status === this.selectedStatus);
}
this.filteredServiceList = filtered;
},
//
showFilter() {
this.showFilterPopup = true;
},
//
resetFilter() {
this.selectedStatus = '';
this.filterServices();
},
//
applyFilter() {
this.filterServices();
this.showFilterPopup = false;
},
//
goToDetail(service) {
this.$navigateTo({
url: `/pages/coach/my/service_detail?id=${service.id}`
});
}
}
}
</script>
<style lang="less" scoped>
//
.container {
min-height: 100vh;
}
//
.dark-theme {
background-color: #121212;
color: #ffffff;
.header {
background-color: #181818;
padding: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.search-input {
background-color: #1e1e1e;
border-radius: 25rpx;
padding: 15rpx 30rpx;
input {
width: 100%;
font-size: 28rpx;
color: #ffffff;
}
}
.filter-btn {
background-color: #00d18c;
color: #121212;
padding: 15rpx 30rpx;
border-radius: 25rpx;
font-size: 28rpx;
font-weight: bold;
}
.service-item {
background-color: #1e1e1e;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
margin-bottom: 20rpx;
}
.service-title {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
flex: 1;
margin-right: 20rpx;
}
.service-badge {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
&.badge-success {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid #52c41a;
}
&.badge-warning {
background-color: rgba(250, 140, 22, 0.2);
color: #fa8c16;
border: 1px solid #fa8c16;
}
&.badge-danger {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid #ff4d4f;
}
&.badge-default {
background-color: rgba(102, 102, 102, 0.2);
color: #999;
border: 1px solid #666;
}
}
.meta-item {
.meta-label {
color: #b0b0b0;
font-size: 26rpx;
}
.meta-value {
color: #ffffff;
font-size: 26rpx;
}
}
.service-desc {
color: #b0b0b0;
font-size: 26rpx;
line-height: 1.5;
}
.service-footer {
padding-top: 15rpx;
border-top: 1px solid #333333;
}
.service-time {
color: #999999;
font-size: 24rpx;
}
.service-action {
color: #00d18c;
font-size: 26rpx;
}
.empty-state {
.empty-icon {
opacity: 0.2;
}
.empty-text {
color: #b0b0b0;
}
}
//
.filter-popup {
background-color: #1e1e1e;
border-radius: 20rpx 20rpx 0 0;
}
.popup-header {
border-bottom: 1px solid #333333;
}
.popup-title {
color: #ffffff;
}
.popup-close {
color: #b0b0b0;
}
.filter-title {
color: #ffffff;
}
.filter-option {
background-color: #282828;
color: #b0b0b0;
&.active {
background-color: #00d18c;
color: #121212;
}
}
.popup-actions {
border-top: 1px solid #333333;
}
.btn {
&.btn-reset {
background-color: #333333;
color: #ffffff;
}
&.btn-confirm {
background-color: #00d18c;
color: #121212;
font-weight: bold;
}
}
}
//
.search-bar {
display: flex;
gap: 20rpx;
align-items: center;
}
.search-input {
flex: 1;
}
.service-list {
padding: 20rpx;
display: flex;
flex-direction: column;
}
.service-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.service-content {
display: flex;
flex-direction: column;
gap: 15rpx;
}
.service-meta {
display: flex;
gap: 30rpx;
}
.meta-item {
display: flex;
align-items: center;
}
.service-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.service-action {
display: flex;
align-items: center;
.action-arrow {
margin-left: 10rpx;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 28rpx;
}
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
}
.popup-close {
font-size: 36rpx;
}
.filter-content {
padding: 30rpx;
}
.filter-group {
margin-bottom: 40rpx;
}
.filter-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.filter-option {
padding: 15rpx 30rpx;
border-radius: 25rpx;
font-size: 26rpx;
}
.popup-actions {
display: flex;
gap: 20rpx;
padding: 30rpx;
}
.btn {
flex: 1;
padding: 20rpx;
border-radius: 12rpx;
text-align: center;
font-size: 28rpx;
}
</style>

168
uniapp/pages/coach/my/teaching_management_info.vue

@ -1,168 +0,0 @@
<template>
<view style="height: 100vh;background-color: #fff;">
<view class="title_style">
{{arrayInfo.title}}
</view>
<view class="date_style">
{{arrayInfo.update_time}}
</view>
<view class="url_style" v-if="arrayInfo.type == 1 && arrayInfo.url">
<video style="margin: auto;" :src="arrayInfo.url" :enable-progress-gesture="true"
:show-fullscreen-btn="true" :show-play-btn="true" @error="onVideoError" @play="onPlay"
@pause="onPause" />
</view>
<view class="url_image_style" v-if="arrayInfo.type == 3 && arrayInfo.url">
<image :src="arrayInfo.url" mode="aspectFill" @click="clickPreviewImage(arrayInfo.url)"
style="width: 80%;margin: auto;">
</image>
</view>
<view class="url_file_style" v-if="arrayInfo.type == 2 && arrayInfo.url">
<a style="cursor: pointer;color: blue;" @click="previewFile(arrayInfo.url)">素材文件</a>
</view>
<!-- <view class="con_style" v-html="arrayInfo.content"></view> -->
<jyf-parser class="con_style" :html="arrayInfo.content" :tag-style="tagStyle"></jyf-parser>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import jyfParser from '@/components/jyf-parser/jyf-parser.vue';
export default {
components: {
jyfParser
},
data() {
return {
articleId: undefined,
arrayInfo: [],
tagStyle: {
//
p: 'margin:0;padding:0;color:#333333;',
span: 'color:#333333;',
h1: 'color:#333333;',
h2: 'color:#333333;',
h3: 'color:#333333;',
h4: 'color:#333333;',
h5: 'color:#333333;',
h6: 'color:#333333;',
li: 'color:#333333;',
div: 'color:#333333;'
}
}
},
onLoad(options) {
this.articleId = options.id;
console.log(this.articleId, 888)
this.init()
},
methods: {
init() {
apiRoute.teachingResearchInfo(this.articleId).then(res => {
if (res.code == 1) {
this.arrayInfo = res.data
}
})
},
clickPreviewImage(url) {
uni.previewImage({
urls: [url], //
current: url, //
success: () => {
console.log('预览成功');
},
fail: (err) => {
console.error('预览失败', err);
}
});
},
async previewFile(url) {
console.log(url)
try {
// 1.
const {
tempFilePath
} = await this.downloadFile(this.$util.img('public/'+url));
console.log(tempFilePath)
// 2.
await uni.openDocument({
filePath: this.$util.img(tempFilePath),
showMenu: true,
success: () => {
console.log('打开文档成功');
}
});
} catch (err) {
uni.showToast({
title: '预览失败',
icon: 'none'
});
console.error('预览失败:', err);
}
},
downloadFile(url) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200) {
resolve(res);
} else {
reject(new Error('下载失败'));
}
},
fail: (err) => {
reject(err);
}
});
});
}
}
}
</script>
<style>
.title_style {
width: 100%;
text-align: center;
font-size: 40rpx;
font-weight: bold;
padding-top: 10px;
}
.date_style {
width: 100%;
text-align: right;
font-size: 20rpx;
color: #ccc;
padding-right: 16rpx;
margin-top: 10px;
}
.con_style {
font-size: 30rpx;
padding: 18rpx;
margin-top: 20px;
}
.url_style {
margin-top: 20px;
text-align: center;
}
.url_image_style {
padding: 20rpx;
text-align: center;
}
.url_file_style{
padding: 20rpx;
}
</style>

481
uniapp/pages/coach/schedule/schedule_detail.vue

@ -1,481 +0,0 @@
<template>
<view class="schedule-detail-container">
<!-- 页面加载状态 -->
<view class="loading-container" v-if="loading">
<fui-loading></fui-loading>
<text class="loading-text">加载中...</text>
</view>
<!-- 错误状态显示 -->
<view class="error-container" v-else-if="error">
<text class="error-text">{{ errorMessage }}</text>
<view class="retry-btn" @click="fetchScheduleDetail">
<text>重试</text>
</view>
</view>
<view class="schedule-content" v-else>
<!-- 课程基本信息 -->
<view class="schedule-header">
<view class="course-title">
<text>{{ scheduleDetail.title || '暂无课程名称' }}</text>
<text class="status-tag" :class="statusClass">{{ statusText }}</text>
</view>
<view class="course-time">{{ scheduleDetail.course_date || '' }} {{ scheduleDetail.time_slot || '' }}</view>
</view>
<!-- 课程详细信息 -->
<view class="info-card">
<view class="info-item">
<text class="info-label">授课教师:</text>
<text class="info-value">{{ scheduleDetail.coach?.name || '未设置' }}</text>
</view>
<view class="info-item">
<text class="info-label">教室:</text>
<text class="info-value">{{ scheduleDetail.venue?.venue_name || '未设置' }}</text>
</view>
<view class="info-item">
<text class="info-label">当前人数:</text>
<text class="info-value">{{ studentCount }}/{{ scheduleDetail.venue?.capacity || 0 }}</text>
</view>
<view class="info-item">
<text class="info-label">课程内容:</text>
<text class="info-value">{{ scheduleDetail.content || '未设置上课内容' }}</text>
</view>
<view class="info-item" v-if="scheduleDetail.remark">
<text class="info-label">备注:</text>
<text class="info-value">{{ scheduleDetail.remark }}</text>
</view>
</view>
<!-- 学员列表 -->
<view class="student-list-section">
<view class="section-title">
<text>学员列表</text>
</view>
<view class="student-list" v-if="scheduleDetail.student_courses && scheduleDetail.student_courses.length > 0">
<view class="student-item" v-for="(student, index) in scheduleDetail.student_courses" :key="index">
<view class="student-avatar">
<image :src="$util.img(student.avatar)" mode="aspectFill"></image>
</view>
<view class="student-info">
<text class="student-name">{{ student.name }}</text>
<text class="student-status" :class="{'signed': student.status === 'signed'}">
{{ student.status === 'signed' ? '已签到' : '未签到' }}
</text>
</view>
</view>
</view>
<view class="empty-list" v-else>
<text>暂无学员参与此课程</text>
</view>
</view>
<!-- 底部按钮区域 -->
<view class="footer-actions">
<view class="action-btn adjust-btn" @click="handleAdjustClass">
<text>调课</text>
</view>
<view class="action-btn sign-btn" @click="handleSignIn">
<text>点名</text>
</view>
</view>
</view>
<!-- 引入课程详情组件 -->
<schedule-detail
:visible="showDetailPopup"
:scheduleId="scheduleId"
@update:visible="showDetailPopup = $event"
@sign-in="onSignIn"
@adjust-class="onAdjustClass"
></schedule-detail>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
import ScheduleDetail from '@/components/schedule/ScheduleDetail.vue'
export default {
components: {
ScheduleDetail
},
data() {
return {
scheduleId: '', // ID
loading: false,
error: false,
errorMessage: '加载失败,请重试',
scheduleDetail: {},
studentCount: 0,
showDetailPopup: false
}
},
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';
}
}
},
onLoad(options) {
if (options.id) {
this.scheduleId = options.id;
this.fetchScheduleDetail();
} else {
this.error = true;
this.errorMessage = '未找到课程安排ID';
}
},
methods: {
//
async fetchScheduleDetail() {
if (!this.scheduleId) {
this.error = true;
this.errorMessage = '课程ID不能为空';
return;
}
this.loading = true;
this.error = false;
try {
// 使
const res = await apiRoute.getCourseScheduleInfo({
id: this.scheduleId
});
if (res.code === 1 && res.data) {
this.scheduleDetail = res.data;
//
this.studentCount = this.scheduleDetail.student_courses ?
this.scheduleDetail.student_courses.length : 0;
} else {
// 使
const fallbackRes = await apiRoute.courseInfo({
id: this.scheduleId
});
if (fallbackRes.code === 1 && fallbackRes.data) {
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) {
console.error('获取课程详情失败:', error);
this.error = true;
this.errorMessage = error.message || '获取课程详情失败,请重试';
} finally {
this.loading = false;
}
},
//
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;
}
//
uni.navigateTo({
url: `/pages/coach/schedule/sign_in?id=${this.scheduleId}`
});
},
//
handleAdjustClass() {
//
if (this.statusText === '已结束') {
uni.showToast({
title: '课程已结束,无法调课',
icon: 'none'
});
return;
}
//
uni.navigateTo({
url: `/pages/coach/schedule/adjust_course?id=${this.scheduleId}`
});
},
//
onSignIn(data) {
console.log('处理点名:', data);
uni.navigateTo({
url: `/pages/coach/schedule/sign_in?id=${data.scheduleId}`
});
},
//
onAdjustClass(data) {
console.log('处理调课:', data);
uni.navigateTo({
url: `/pages/coach/schedule/adjust_course?id=${data.scheduleId}`
});
},
//
showPopup() {
this.showDetailPopup = true;
}
}
}
</script>
<style lang="less" scoped>
.schedule-detail-container {
background-color: #292929;
min-height: 100vh;
padding-bottom: 140rpx; //
}
.loading-container, .error-container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-text, .error-text {
margin-top: 30rpx;
font-size: 28rpx;
color: #ccc;
}
.retry-btn {
margin-top: 30rpx;
padding: 12rpx 30rpx;
background-color: #29d3b4;
border-radius: 8rpx;
color: #fff;
font-size: 24rpx;
}
.schedule-content {
padding: 30rpx;
}
.schedule-header {
margin-bottom: 30rpx;
}
.course-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 36rpx;
font-weight: bold;
color: #fff;
margin-bottom: 16rpx;
}
.status-tag {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 30rpx;
}
.status-in-progress {
background-color: #FAD24E;
color: #333;
}
.status-ended {
background-color: #e2e2e2;
color: #333;
}
.status-not-started {
background-color: #1cd188;
color: #fff;
}
.course-time {
font-size: 28rpx;
color: #FAD24E;
margin-bottom: 30rpx;
}
.info-card {
background-color: #434544;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 30rpx;
}
.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 {
background-color: #434544;
border-radius: 16rpx;
padding: 24rpx;
}
.section-title {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16rpx;
border-bottom: 1px solid #555;
margin-bottom: 20rpx;
}
.section-title text {
font-size: 28rpx;
color: #fff;
}
.student-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.student-item {
display: flex;
align-items: center;
}
.student-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
overflow: hidden;
background-color: #555;
margin-right: 20rpx;
}
.student-avatar image {
width: 100%;
height: 100%;
}
.student-info {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.student-name {
font-size: 28rpx;
color: #fff;
}
.student-status {
font-size: 24rpx;
color: #ff6b6b;
background: rgba(255, 107, 107, 0.1);
padding: 4rpx 12rpx;
border-radius: 20rpx;
}
.student-status.signed {
color: #1cd188;
background: rgba(28, 209, 136, 0.1);
}
.empty-list {
padding: 40rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
.footer-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 30rpx;
gap: 20rpx;
background-color: #292929;
border-top: 1px solid #434544;
}
.action-btn {
flex: 1;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8rpx;
font-size: 28rpx;
}
.adjust-btn {
background-color: #555;
color: #fff;
}
.sign-btn {
background-color: #29d3b4;
color: #fff;
}
</style>

599
uniapp/pages/coach/student/info.vue

@ -1,599 +0,0 @@
<!--学员-详情-->
<template>
<view class="main_box">
<!--学员信息-->
<view class="user_section">
<view class="box">
<view class="left">
<image class="pic" :src="studentsInfo.customerResources.member.headimg"></image>
</view>
<view class="right">
<view class="item">
<view class="name">{{ studentsInfo.name }}</view>
<view class="age">
{{ studentsInfo.customerResources.age }}
</view>
</view>
<view class="item">
<view class="title">电话{{ studentsInfo.customerResources.phone_number }}</view>
</view>
</view>
</view>
</view>
<view class="main_section">
<view class="section_box">
<view class="tag_box">
<view :class="['item', tabType=='1' ? 'select':'']" @click="tabChange(1)">出勤记录</view>
<view :class="['item', tabType=='2' ? 'select':'']" @click="tabChange(2)">体侧报告</view>
</view>
<!--作业列表-->
<view v-if="tabType=='1'" class="section_1">
<view class="ul">
<view class="li"
v-for="(v,k) in assignmentsList"
:key="k"
@click="opebViewWorkDetails(v)">
<view class="left">
<view class="title">{{ v.courses_name }}</view>
<view class="date">上课时间{{ v.submit_time }}</view>
</view>
<view class="right">
<view v-if="v.status==1" class="btn" style="background-color: #e2e2e2;">作业未提交</view>
<view v-else-if="v.status==2" class="btn" style="background-color: #a4adb3;">待批改</view>
<view v-else-if="v.status==3" class="btn" style="background-color: #29d3b4;">作业已完成</view>
</view>
</view>
</view>
</view>
<!--评测报告-->
<view v-if="tabType=='2'" class="section_2">
<scroll-view
class="ul"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 65vh;"
>
<view
class="li"
v-for="(v,k) in surveyList"
:key="k" @click="openViewPhysicalExamination(v)"
>
<view class="top">
<view class="title">综合评分:{{ v.calculateChildHealthScore }}</view>
<!-- <view class="hint">打败了99%学员</view>-->
</view>
<view class="bottom">测试时间{{ $util.formatToDateTime(v.created_at, 'Y-m-d') }}</view>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- 底部导航-->
<!-- <AQTabber/>-->
</view>
</template>
<script>
import memberApi from '@/api/member.js'
import AQTabber from '@/components/AQ/AQTabber.vue'
import apiRoute from '@/api/apiRoute.js'
export default {
components: {
AQTabber,
},
data() {
return {
tabType: '1',//1=,2=
Atype: 1,//1=,2=
students_id: '',//id
studentsInfo: {},//
assignmentsList: [],//
loading: false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData: {
page: 1,//
limit: 10,//
total: 10,//
user_id: '',//id
},
surveyList: [],//
}
},
onLoad(options) {
this.students_id = options.students_id//id
},
onShow() {
this.init()//
},
methods: {
//
async init() {
//
await this.getStudentsInfo()
await this.getSurveyList()
},
formatAgeMonth(input) {
let str = String(input)
//
let [yearPart, monthPart] = str.split('.')
// 0
if (!monthPart) {
monthPart = '00'
}
// 00 0
monthPart = monthPart === '00' ? '0' : monthPart
return `${yearPart}${monthPart}`
},
//
async getStudentsInfo() {
let data = {
students_id: this.students_id,
}
let res = await apiRoute.jlStudentsInfo(data)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none',
})
return
}
this.studentsInfo = res.data//
this.assignmentsList = res.data.physical_test//
this.filteredData.user_id = res.data.user_id
},
// expire_time 5
checkExpireTime(expireTime = '') {
if (!expireTime) {
return false
}
const expireDate = new Date(expireTime)
const currentDate = new Date()
//
const timeDifference = expireDate - currentDate
const daysDifference = timeDifference / (1000 * 60 * 60 * 24)
if (daysDifference >= 5) {
return true
} else {
return false
}
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true//
this.getSurveyList()
}
},
//
async resetFilteredData() {
this.isReachedBottom = false // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getSurveyList() {
this.loading = true
let data = { ...this.filteredData }
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none',
})
return
}
if (data.page == 1) {
this.surveyList = []
}
//-
let res = await apiRoute.physicalTest(data)
this.loading = false
this.isReachedBottom = false
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none',
})
return
}
console.log(res, 111)
this.surveyList = this.surveyList.concat(res.data.physical_test.data) // 使 concat
console.log('列表', this.surveyList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//tab
tabChange(tabType) {
this.tabType = tabType
},
//
openViewCourseInfo(item) {
this.$navigateTo({
url: '/pages/coach/course/info',
})
},
//
openViewStudentInfo(item) {
this.$navigateTo({
url: '/pages/coach/student/info',
})
},
//
openViewPhysicalExamination(item) {
let survey_id = item.id
this.$navigateTo({
url: `/pages/coach/student/physical_examination?survey_id=${survey_id}`,
})
},
//
opebViewWorkDetails(item) {
this.$navigateTo({
url: '/pages/coach/student/work_details',
})
},
},
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
min-height: 100vh;
}
//
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
//
.user_section {
background-color: #29D3B4;
padding-top: 58rpx;
padding-bottom: 42rpx;
.box {
display: flex;
justify-content: center;
align-items: center;
gap: 15rpx;
.left {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 120rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
}
.btn_box {
position: absolute;
bottom: -18rpx;
.btn {
width: 120rpx;
height: 38rpx;
line-height: 40rpx;
border-radius: 4rpx;
background-color: rgba(245, 154, 35, 1);
color: rgba(255, 255, 255, 1);
font-size: 20rpx;
text-align: center;
border: 0rpx solid rgba(121, 121, 121, 1);
}
}
}
.right {
display: flex;
flex-direction: column;
gap: 18rpx;
.item {
color: #fff;
display: flex;
align-items: center;
.name {
font-size: 28rpx;
}
.age {
margin-left: 20rpx;
width: 128rpx;
height: 42rpx;
line-height: 42rpx;
border-radius: 34rpx;
background-color: rgba(255, 255, 255, 1);
color: rgba(51, 51, 51, 1);
font-size: 28rpx;
text-align: center;
}
}
}
}
}
//
.course_section {
position: relative;
.main {
position: relative;
z-index: 2;
padding: 0 24rpx;
display: flex;
justify-content: center;
.course_box {
padding: 42rpx 28rpx;
width: 692rpx;
border-radius: 20rpx;
background-color: #fff;
display: flex;
flex-direction: column;
gap: 20rpx;
position: relative;
.item {
display: flex;
align-items: center;
gap: 22rpx;
.title {
font-size: 28rpx;
color: #333333;
}
.pic {
width: 58rpx;
height: 58rpx;
border-radius: 50%;
}
.name {
color: #333333;
font-size: 24rpx;
}
.content {
color: #AAAAAA;
font-size: 24rpx;
}
}
.tag {
position: absolute;
right: 0;
top: 0;
width: 110rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 0rpx 24rpx 0rpx 24rpx;
background-color: rgba(236, 128, 141, 1);
color: rgba(255, 255, 255, 1);
font-size: 24rpx;
text-align: center;
}
.btn {
position: absolute;
right: 30rpx;
bottom: 50rpx;
width: 130rpx;
height: 48rpx;
line-height: 48rpx;
border-radius: 10rpx;
background-color: rgba(41, 211, 180, 0);
color: rgba(50, 219, 224, 1);
font-size: 24rpx;
text-align: center;
font-family: -regular;
border: 2rpx solid rgba(50, 219, 224, 1);
}
}
}
.bg_box {
z-index: 1;
width: 100%;
height: 150rpx;
}
.bg_top {
position: absolute;
top: 0;
background-color: #29D3B4;
}
.bg_bottom {
top: 50%;
position: absolute;
background-color: #292929;
}
}
.main_section {
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #333333;
.section_box {
background: #fff;
border-radius: 16rpx;
padding: 34rpx 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 38rpx;
.tag_box {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 112rpx;
.item {
width: 112rpx;
font-size: 28rpx;
}
.select {
color: #29D3B4;
}
}
//
.section_1 {
width: 100%;
.ul {
display: flex;
flex-direction: column;
gap: 12rpx;
.li {
padding: 30rpx 20rpx;
border: 1px solid #29D3B4;
border-radius: 18rpx;
background-color: rgba(41, 211, 180, 0.16);
font-size: 26rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
flex-direction: column;
gap: 15rpx;
}
.right {
.btn {
width: 110rpx;
height: 44rpx;
line-height: 44rpx;
border-radius: 8rpx;
background-color: rgba(41, 211, 180, 1);
color: rgba(255, 255, 255, 1);
font-size: 20rpx;
text-align: center;
}
}
}
}
}
//
.section_2 {
width: 100%;
.ul {
display: flex;
flex-direction: column;
.li {
margin-bottom: 12rpx;
padding: 30rpx 20rpx;
border: 1px solid #29D3B4;
border-radius: 18rpx;
background-color: rgba(41, 211, 180, 0.16);
font-size: 26rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.top {
display: flex;
align-items: center;
gap: 40rpx;
.title {
font-size: 34rpx;
}
.hint {
color: #F59A23;
font-size: 24rpx;
}
}
.bottom {
}
}
}
}
}
}
</style>

499
uniapp/pages/coach/student/student_detail.vue

@ -1,499 +0,0 @@
<template>
<view class="container">
<uni-nav-bar fixed status-bar left-icon="left" title="学员详情" @clickLeft="navigateBack"></uni-nav-bar>
<view class="content" v-if="studentInfo">
<view class="student-header">
<view class="avatar-box">
<image :src="studentInfo.avatar || '/static/icon-img/avatar.png'" mode="aspectFill" class="avatar-img"></image>
</view>
<view class="name-box">
<text class="name">{{studentInfo.name}}</text>
<text class="id">ID: {{studentInfo.id}}</text>
</view>
</view>
<view class="card-section">
<view class="section-title">基本信息</view>
<view class="info-card">
<view class="info-item">
<text class="item-label">所属校区</text>
<text class="item-value">{{studentInfo.campus}}</text>
</view>
<view class="info-item">
<text class="item-label">联系电话</text>
<text class="item-value">{{studentInfo.phone}}</text>
</view>
<view class="info-item">
<text class="item-label">年龄</text>
<text class="item-value">{{studentInfo.age}}</text>
</view>
<view class="info-item">
<text class="item-label">入学时间</text>
<text class="item-value">{{studentInfo.enrollmentDate}}</text>
</view>
</view>
</view>
<view class="card-section">
<view class="section-title">课程信息</view>
<view class="info-card">
<view class="info-item">
<text class="item-label">剩余课程</text>
<text class="item-value highlight">{{studentInfo.remainingCourses}}</text>
</view>
<view class="info-item">
<text class="item-label">课程到期时间</text>
<text class="item-value highlight">{{studentInfo.expiryDate}}</text>
</view>
<view class="info-item">
<text class="item-label">已消课程</text>
<text class="item-value">{{studentInfo.completedCourses}}</text>
</view>
<view class="info-item">
<text class="item-label">报名课程</text>
<text class="item-value">{{studentInfo.courseName}}</text>
</view>
</view>
</view>
<view class="card-section">
<view class="section-title">最近上课记录</view>
<view class="info-card" v-if="studentInfo.recentClasses && studentInfo.recentClasses.length > 0">
<view class="class-item" v-for="(item, index) in studentInfo.recentClasses" :key="index">
<view class="class-date">{{item.date}}</view>
<view class="class-info">
<text class="class-name">{{item.courseName}}</text>
<text class="class-time">{{item.timeSlot}}</text>
</view>
</view>
</view>
<view class="empty-class" v-else>
<text>暂无上课记录</text>
</view>
</view>
<view class="btn-group">
<button class="btn primary" @click="contactStudent">联系学员</button>
<button class="btn secondary" @click="viewSchedule">查看课表</button>
</view>
</view>
<view v-else class="loading-box">
<text>加载中...</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
id: null,
studentInfo: null
}
},
onLoad(options) {
if (options.id) {
this.id = options.id;
this.getStudentDetail();
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
this.navigateBack();
}, 1500);
}
},
methods: {
navigateBack() {
uni.navigateBack();
},
getStudentDetail() {
// API
// try {
// const res = await memberApi.getStudentDetail({id: this.id});
// if(res.code == 1) {
// this.studentInfo = res.data || null;
// } else {
// uni.showToast({
// title: res.msg || '',
// icon: 'none'
// });
// }
// } catch(error) {
// console.error(':', error);
// uni.showToast({
// title: '',
// icon: 'none'
// });
// }
// 使
setTimeout(() => {
//
const studentData = {
'1': {
id: '1',
name: '张三',
avatar: '/static/icon-img/avatar.png',
campus: '总部校区',
phone: '13812341000',
age: 11,
enrollmentDate: '2023-01-11',
remainingCourses: 10,
expiryDate: '2023-12-31',
completedCourses: 20,
courseName: '少儿英语基础班',
recentClasses: [
{
date: '2023-09-15',
courseName: '少儿英语基础班',
timeSlot: '15:30-17:00'
},
{
date: '2023-09-08',
courseName: '少儿英语基础班',
timeSlot: '15:30-17:00'
},
{
date: '2023-09-01',
courseName: '少儿英语基础班',
timeSlot: '15:30-17:00'
}
]
},
'2': {
id: '2',
name: '李四',
avatar: '/static/icon-img/avatar.png',
campus: '西区校区',
phone: '13812341001',
age: 12,
enrollmentDate: '2023-01-12',
remainingCourses: 5,
expiryDate: '2023-11-15',
completedCourses: 25,
courseName: '少儿英语进阶班',
recentClasses: [
{
date: '2023-09-14',
courseName: '少儿英语进阶班',
timeSlot: '14:00-15:30'
},
{
date: '2023-09-07',
courseName: '少儿英语进阶班',
timeSlot: '14:00-15:30'
}
]
},
'3': {
id: '3',
name: '王五',
avatar: '/static/icon-img/avatar.png',
campus: '东区校区',
phone: '13812341002',
age: 13,
enrollmentDate: '2023-01-13',
remainingCourses: 15,
expiryDate: '2024-01-20',
completedCourses: 15,
courseName: '少儿英语口语班',
recentClasses: [
{
date: '2023-09-16',
courseName: '少儿英语口语班',
timeSlot: '10:00-11:30'
},
{
date: '2023-09-09',
courseName: '少儿英语口语班',
timeSlot: '10:00-11:30'
},
{
date: '2023-09-02',
courseName: '少儿英语口语班',
timeSlot: '10:00-11:30'
}
]
},
'4': {
id: '4',
name: '赵六',
avatar: '/static/icon-img/avatar.png',
campus: '南区校区',
phone: '13812341003',
age: 10,
enrollmentDate: '2023-02-15',
remainingCourses: 8,
expiryDate: '2023-11-30',
completedCourses: 12,
courseName: '少儿英语基础班',
recentClasses: [
{
date: '2023-09-13',
courseName: '少儿英语基础班',
timeSlot: '16:00-17:30'
},
{
date: '2023-09-06',
courseName: '少儿英语基础班',
timeSlot: '16:00-17:30'
}
]
},
'5': {
id: '5',
name: '刘七',
avatar: '/static/icon-img/avatar.png',
campus: '北区校区',
phone: '13812341004',
age: 14,
enrollmentDate: '2023-03-20',
remainingCourses: 20,
expiryDate: '2024-02-15',
completedCourses: 10,
courseName: '少儿英语进阶班',
recentClasses: [
{
date: '2023-09-12',
courseName: '少儿英语进阶班',
timeSlot: '17:00-18:30'
},
{
date: '2023-09-05',
courseName: '少儿英语进阶班',
timeSlot: '17:00-18:30'
}
]
},
'6': {
id: '6',
name: '陈八',
avatar: '/static/icon-img/avatar.png',
campus: '总部校区',
phone: '13812341005',
age: 9,
enrollmentDate: '2023-04-05',
remainingCourses: 3,
expiryDate: '2023-10-30',
completedCourses: 27,
courseName: '少儿英语口语班',
recentClasses: [
{
date: '2023-09-11',
courseName: '少儿英语口语班',
timeSlot: '14:30-16:00'
},
{
date: '2023-09-04',
courseName: '少儿英语口语班',
timeSlot: '14:30-16:00'
}
]
}
};
// ID
this.studentInfo = studentData[this.id] || {
id: this.id,
name: '未知学员',
avatar: '/static/icon-img/avatar.png',
campus: '未知校区',
phone: '暂无',
age: '暂无',
enrollmentDate: '暂无',
remainingCourses: 0,
expiryDate: '暂无',
completedCourses: 0,
courseName: '暂无',
recentClasses: []
};
}, 500);
},
contactStudent() {
if (this.studentInfo && this.studentInfo.phone) {
uni.makePhoneCall({
phoneNumber: this.studentInfo.phone.replace(/\*/g, '0')
});
}
},
viewSchedule() {
this.$navigateToPage(`/pages/coach/student/timetable`, {
id: this.id
});
}
}
}
</script>
<style lang="scss">
.container {
min-height: 100vh;
background-color: #F5F5F5;
}
.content {
padding: 20rpx;
}
.loading-box {
display: flex;
justify-content: center;
align-items: center;
height: 80vh;
color: #999;
font-size: 28rpx;
}
.student-header {
display: flex;
align-items: center;
background-color: #FFFFFF;
border-radius: 12rpx;
padding: 40rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.avatar-box {
width: 150rpx;
height: 150rpx;
border-radius: 75rpx;
overflow: hidden;
margin-right: 30rpx;
.avatar-img {
width: 100%;
height: 100%;
}
}
.name-box {
display: flex;
flex-direction: column;
.name {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.id {
font-size: 24rpx;
color: #999;
}
}
}
.card-section {
margin-bottom: 20rpx;
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin: 30rpx 10rpx 20rpx;
}
.info-card {
background-color: #FFFFFF;
border-radius: 12rpx;
padding: 20rpx 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.info-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.item-label {
color: #666;
font-size: 28rpx;
}
.item-value {
color: #333;
font-size: 28rpx;
&.highlight {
color: #FF6600;
font-weight: bold;
}
}
}
.class-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.class-date {
width: 180rpx;
font-size: 28rpx;
color: #666;
}
.class-info {
flex: 1;
display: flex;
flex-direction: column;
.class-name {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.class-time {
font-size: 24rpx;
color: #999;
}
}
}
.empty-class {
padding: 40rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
}
.btn-group {
display: flex;
justify-content: space-between;
margin: 40rpx 0;
.btn {
width: 48%;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
&.primary {
background-color: #3B7CF9;
color: #FFFFFF;
}
&.secondary {
background-color: #FFFFFF;
color: #3B7CF9;
border: 1px solid #3B7CF9;
}
}
}
</style>

130
uniapp/pages/coach/student/work_details.vue

@ -1,130 +0,0 @@
<!--作业详情-->
<template>
<view class="dark-theme">
<!-- 作业展示-->
<view class="top-style" v-if="infoData.content_text">
<video v-if="infoData.content_type == 2" class="pic" style="width: 100%;border-radius: 15rpx;" :src="$util.img(infoData.content_text)"></video>
<image v-if="infoData.content_type == 1" style="width: 100%;border-radius: 15rpx;" :src="$util.img(infoData.content_text)" mode="aspectFit"></image>
<view v-if="infoData.content_type == 3" class="multi-line-ellipsis" v-html="infoData.content_text"></view>
</view>
<!-- 简练信息+作业描述-->
<view class="below-style">
<view class="head-img">
<!--教练头像-->
<fui-avatar width="80" :src="$util.img(infoData.coach_pic)"></fui-avatar>
<view class="head-text">{{infoData.coach_name}}</view>
</view>
<view class="multi-line-ellipsis" v-html="infoData.description"></view>
</view>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
export default {
data() {
return {
//
filteredData: {
id: '',//id
},
infoData: {},
}
},
onLoad(options) {
this.filteredData.id = options.id || '1' // ID1
// 使
// this.mockData()
},
onShow(){
this.init() // -
},
methods: {
//
async init(){
this.getAssignmentsInfo() //
},
//
mockData() {
this.infoData = {
student_file: '/static/icon-img/empty.png',
student_file_type: 1, // 1-2-
coach_pic: '/static/icon-img/default_avatar.png',
coach_name: '张教练',
content_text: '<p>这是一份作业的详细说明,包含了作业的要求和注意事项。</p><p>1. 请按照要求完成动作练习</p><p>2. 注意动作的标准性和连贯性</p><p>3. 完成后请上传视频或图片记录</p>'
}
},
// -
async getAssignmentsInfo() {
let params = {...this.filteredData}
let res = await memberApi.assignmentsInfo(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.infoData = res.data
},
}
}
</script>
<style lang="less" scoped>
.dark-theme {
background-color: #121212;
color: #ffffff;
min-height: 100vh;
padding-top: 0;
}
.top-style{
width: 92%;
height: 500rpx;
margin: auto;
margin-top: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: #1e1e1e;
border-radius: 15rpx;
}
.top-style-img{
width: 120rpx;
height: 120rpx;
}
.below-style{
width: 92%;
margin: auto;
background-color: #1e1e1e;
border-radius: 15rpx;
margin-top: 20rpx;
padding: 20rpx 0;
}
.head-img {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
}
.head-text {
color: #ffffff;
font-size: 35rpx;
padding-left: 20rpx;
}
.multi-line-ellipsis {
color: #e0e0e0;
font-size: 29rpx;
padding: 20rpx 30rpx;
line-height: 1.6;
}
</style>

765
uniapp/pages/common/contract_list.vue

@ -1,765 +0,0 @@
<!--订单列表-列表-->
<template>
<view class="main_box">
<view class="main_section">
<scroll-view
class="section_1"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 83vh;"
>
<view
class="item"
v-for="(v,k) in tableList"
:key="k"
>
<view class="top">
<view class="title">订单状态{{v.order_status == 'pending' ? '待支付':'已支付' }}</view>
<!-- <view class="btn" @click="downloadFile($util.img(v.file_data))">下载合同 <fui-icon name="arrowright" color="#A4ADB3" size="35"></fui-icon></view>-->
</view>
<view class="bottom">
<view class="box">
<view class="title">客户姓名</view>
<view class="content">{{ v.resource_id_name || ''}}</view>
</view>
<view class="box">
<view class="title">付款类型</view>
<view class="content">
{{ v.payment_type === 'cash' ? '现金支付' : v.payment_type === 'scan_code' ? '扫码支付' : '订阅支付' }}
</view>
</view>
<view class="box">
<view class="title">订单金额</view>
<view class="content">{{ v.order_amount || ''}}</view>
</view>
<view class="box">
<view class="title">课程</view>
<view class="content">{{ v.course_id_name || ''}}</view>
</view>
<view class="box">
<view class="title">班级</view>
<view class="content">{{ v.class_id_name }}</view>
</view>
<view class="box">
<view class="title">人员</view>
<view class="content">{{ v.staff_id_name || ''}}</view>
</view>
<view class="box">
<view class="title">支付时间</view>
<view class="content">{{ v.payment_time || '' }}</view>
</view>
</view>
</view>
</scroll-view>
<!-- <view class="btn_section">-->
<!-- <view class="btn" style="background-color:#29d3b4;" @click="openOrderShow()">创建订单</view>-->
<!-- </view>-->
</view>
<!--创建订单弹出层-->
<fui-modal class="order_modal" :buttons="[]" width="600" :show="order_show">
<text class="fui-title">创建订单</text>
<text class="fui-descr"></text>
<fui-form class="form-section" ref="form" top="0" :model="formData" :show="false">
<view class="input-style">
<!--客户名称-->
<fui-form-item
label="客户名称"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<view class="input-title" style="margin-right:14rpx;">
<fui-input
:disabled="true"
:borderBottom="false"
:padding="[0]"
placeholder="请输入客户名称"
v-model="formData.resource_id_name"
backgroundColor="#fff"
size="26"
color="#000"
></fui-input>
</view>
</fui-form-item>
<!--付款类型-->
<fui-form-item
label="选择付款类型"
asterisk
asteriskPosition="right"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<view class="input-title" style="margin-right:14rpx;">
<view
class="input-title"
style="margin-right:14rpx;"
@click="openPaymentType()">
{{ (formData.payment_type) ? formData.payment_type_name : '点击选择' }}
</view>
</view>
<fui-picker
:linkage='true'
:options="payment_type_options"
:layer="1"
:show="payment_type_show"
@change="changePaymentType"
@cancel="cancelPaymentType">
</fui-picker>
</fui-form-item>
<!--课程-->
<fui-form-item
label="选择课程"
asterisk
asteriskPosition="right"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<view class="input-title" style="margin-right:14rpx;">
<view
class="input-title"
style="margin-right:14rpx;"
@click="openCourseId()">
{{ (formData.course_id) ? formData.course_id_name : '点击选择' }}
</view>
</view>
<fui-picker
:linkage='true'
:options="course_id_options"
:layer="1"
:show="course_id_show"
@change="changeCourseId"
@cancel="cancelCourseId">
</fui-picker>
</fui-form-item>
<!--班级-->
<fui-form-item
label="选择班级"
asterisk
asteriskPosition="right"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<view class="input-title" style="margin-right:14rpx;">
<view
class="input-title"
style="margin-right:14rpx;"
@click="openClassId()">
{{ (formData.class_id) ? formData.class_id_name : '点击选择' }}
</view>
</view>
<fui-picker
:linkage='true'
:options="class_id_options"
:layer="1"
:show="class_id_show"
@change="changeClassId"
@cancel="cancelClassId">
</fui-picker>
</fui-form-item>
<!--订单金额-->
<fui-form-item
label="订单金额"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<view class="input-title" style="margin-right:14rpx;">
<fui-input
:disabled="true"
:borderBottom="false"
:padding="[0]"
placeholder="订单金额"
v-model="formData.money"
backgroundColor="#fff"
size="26"
color="#000"
></fui-input>
</view>
</fui-form-item>
</view>
<view class="button_box">
<fui-button background="#fff" color="#414141" borderColor="#465CFF" btnSize="small" @click="closeOrderShow">取消</fui-button>
<fui-button background="#fff" color="#465CFF" borderColor="#465CFF" btnSize="small" @click="clickOrder({index:1})">确定</fui-button>
</view>
</fui-form>
<view class="fui-icon__close" @tap="closeOrderShow">
<fui-icon name="close" color="#B2B2B2" :size="48"></fui-icon>
</view>
</fui-modal>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
},
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
resource_id:'',//id
},
tableList:[],//
//
formData:{
payment_type:'',//
payment_type_name:'',//
course_id:'',//ID
course_id_name:'',//ID
class_id:'',//ID
class_id_name:'',//ID
staff_id:'',//ID
staff_id_name:'',//ID
resource_id:'',//ID
resource_id_name:'',//ID
money:'',//|
},
order_show:false,//|true=,false=
//-
payment_type_options:[
// {
// text:'',
// value:'1'
// },
],//
payment_type_show:false,//
//-
course_id_options:[
// {
// text:'',
// value:'1',
// price:'1'//
// },
],//
course_id_show:false,//
//-
class_id_options:[
// {
// text:'',
// value:'1'
// },
],//
class_id_show:false,//
}
},
onLoad(options) {
this.filteredData.resource_id = options.resource_id//id
this.formData.resource_id = options.resource_id//id
this.formData.resource_id_name = options.resource_name//id
this.formData.staff_id = options.staff_id//id
this.formData.staff_id_name = options.staff_id_name//id
},
onShow(){
this.init()
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init(){
//
await this.getPaymentTypeList()
//
await this.getCourseList()
//
await this.getClassList()
//
await this.getList();
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getList(){
this.loading = true
let params = {...this.filteredData}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
if(params.page == 1){
this.tableList = []
}
let res = await apiRoute.xy_orderTableList(params)//
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
// this.tableList.unshift(...res.data.data); //
console.log('列表',this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//
async getPaymentTypeList(){
let res = await apiRoute.common_Dictionary({key:'payment_type'})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let dictionary = res.data.dictionary
let arr = []
dictionary.forEach((v,k)=>{
arr.push({
text: v.name,
value: v.value,
})
})
this.payment_type_options = arr
console.log('付款类型',this.payment_type_options)
},
//
async getCourseList(){
let params = {}
let res = await apiRoute.common_getCourseAll(params)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let arr = []
res.data.forEach((v,k)=>{
arr.push({
text: `${v.course_name}`,
value: v.id,
price: v.price,
})
})
this.course_id_options = arr
console.log('课程列表',this.course_id_options)
},
//
async getClassList(){
let params = {
status:1,//(1 2)
}
let res = await apiRoute.common_getClassAll(params)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log('班级列表',res.data)
let arr = []
res.data.forEach((v,k)=>{
arr.push({
text: `${v.campus_name}-${v.class_name}`,
value: v.id,
})
})
this.class_id_options = arr
},
//
//
openOrderShow(){
//
this.formData.payment_type = ''//
this.formData.payment_type_name = ''//
this.formData.course_id = ''//ID
this.formData.course_id_name = ''//ID
this.formData.class_id = ''//ID
this.formData.class_id_name = ''//ID
this.formData.money = ''//|
this.order_show = true//
},
//
closeOrderShow(){
this.order_show = false
},
//-
async clickOrder(e){
if(e.index == 0){
//
this.closeOrderShow()
}else{
console.log('提交',this.formData)
await this.submitFormData()
}
},
//
async submitFormData() {
let param = {...this.formData}
//
//...
if(!param.class_id){
uni.showToast({
title: '请选择班级',
icon: 'none'
})
return
}
if(!param.course_id){
uni.showToast({
title: '请选择课程',
icon: 'none'
})
return
}
if(!param.payment_type){
uni.showToast({
title: '请选择付款类型',
icon: 'none'
})
return
}
console.log('提交xxx',param)
this.closeOrderShow()
let res = await apiRoute.xy_orderTableAdd(param)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: '操作成功',
icon: 'success'
})
//1s
setTimeout(() => {
this.resetFilteredData()//
this.getList();//
}, 1500)
},
//-
//-
changePaymentType(e){
this.formData.payment_type = e.value
this.formData.payment_type_name = e.text
this.cancelPaymentType()
},
//-
openPaymentType(){
this.payment_type_show = true
},
//-
cancelPaymentType(){
this.payment_type_show = false
},
//-
//-
changeCourseId(e){
console.log('课程',e)
this.formData.course_id = e.value
this.formData.course_id_name = e.text
this.formData.money = this.course_id_options.find(v=>v.value == e.value).price
// console.log('formData',this.formData)
this.cancelCourseId()
},
//-
openCourseId(){
this.course_id_show = true
},
//-
cancelCourseId(){
this.course_id_show = false
},
//-
//-
changeClassId(e){
this.formData.class_id = e.value
this.formData.class_id_name = e.text
this.cancelClassId()
},
//-
openClassId(){
this.class_id_show = true
},
//-
cancelClassId(){
this.class_id_show = false
},
//
async downloadFile(fileUrl) {
if (!fileUrl) {
this.$util.showToast({
title: '暂无电子发票'
});
return false;
}
uni.downloadFile({
url: fileUrl,
success: function (res) {
console.log('下载成功');
// uni.openDocument({
// filePath: res.tempFilePath,
// fileType: 'pdf',
// success: function (res) {
// console.log('');
// }
// });
}
});
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
}
//
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #29d3b4;
.title {
padding: 20rpx 0;
font-size: 30rpx;
color: #315d55;
}
}
.main_section {
min-height: 100vh;
background: #292929 100%;
padding: 0 0rpx;
padding-top: 32rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.section {
background-color: #434544;
padding: 40rpx 40rpx;
}
.section_1{
padding: 0 24rpx;
display: flex;
flex-direction: column;
.item{
margin-bottom: 38rpx;
display: flex;
flex-direction: column;
padding: 32rpx 24rpx;
border-radius: 14rpx;
background-color: #434544;
color: #fff;
.top{
font-size: 28rpx;
display: flex;
justify-content: space-between;
.title{
font-size: 30rpx;
}
.btn{
display: flex;
align-items: center;
color: #29D3B4;
}
}
.bottom{
font-size: 26rpx;
margin-top: 25rpx;
display: flex;
flex-direction: column;
gap: 15rpx;
.box{
display: flex;
justify-content: space-between;
.title{
width: 180rpx;
}
.content{
width: 100%;
}
}
}
}
}
.btn_section{
display: flex;
justify-content: center;
align-items: center;
.btn{
border-radius: 10rpx;
padding: 15rpx 0;
width: 70%;
color: #fff;
font-size: 30rpx;
text-align: center;
}
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
//
.order_modal{
.fui-title {
font-size: 32rpx;
padding-top: 24rpx;
}
.fui-descr {
font-size: 24rpx;
color: #B2B2B2;
padding-top: 12rpx;
padding-bottom: 48rpx;
}
.fui-icon__close {
position: absolute;
right: 24rpx;
top: 20rpx;
}
.form-section{
.input-style {
text-align: right !important;
.input-title{}
}
.button_box{
margin-top: 30rpx;
padding: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
}
}
</style>

8
uniapp/pages/common/personnel/add_personnel.vue

@ -390,12 +390,9 @@ export default {
uni.showLoading({ title: '上传中...' })
uni.uploadFile({
url: this.$baseUrl + '/uploadImage',
url: this.$baseUrl + '/file/avatar',
filePath: filePath,
name: 'file',
header: {
'Authorization': uni.getStorageSync('token')
},
success: (res) => {
const data = JSON.parse(res.data)
if (data.code === 1) {
@ -405,7 +402,8 @@ export default {
uni.showToast({ title: data.msg || '上传失败', icon: 'none' })
}
},
fail: () => {
fail: (error) => {
console.error('头像上传失败:', error)
uni.showToast({ title: '上传失败', icon: 'none' })
},
complete: () => {

542
uniapp/pages/demo/dict_optimization.vue

@ -1,542 +0,0 @@
<template>
<view class="container">
<view class="header">
<text class="title">字典获取优化演示</text>
<text class="subtitle">对比原方式和批量获取的性能差异</text>
</view>
<!-- 性能对比 -->
<view class="performance-section">
<view class="section-title">性能对比</view>
<!-- 原有方式 -->
<view class="test-item">
<view class="test-header">
<text class="test-name">原方式单个获取</text>
<button
class="test-btn old-style"
@click="testOldMethod"
:disabled="oldTesting">
{{ oldTesting ? '测试中...' : '开始测试' }}
</button>
</view>
<view class="test-result" v-if="oldResult">
<text class="result-text">耗时: {{ oldResult.time }}ms</text>
<text class="result-text">请求次数: {{ oldResult.requests }}</text>
<text class="result-text">获取数据: {{ oldResult.count }}个字典</text>
</view>
</view>
<!-- 新方式 -->
<view class="test-item">
<view class="test-header">
<text class="test-name">新方式批量获取</text>
<button
class="test-btn new-style"
@click="testNewMethod"
:disabled="newTesting">
{{ newTesting ? '测试中...' : '开始测试' }}
</button>
</view>
<view class="test-result" v-if="newResult">
<text class="result-text">耗时: {{ newResult.time }}ms</text>
<text class="result-text">请求次数: {{ newResult.requests }}</text>
<text class="result-text">获取数据: {{ newResult.count }}个字典</text>
<text class="improvement">
性能提升: {{ newResult.improvement }}
</text>
</view>
</view>
</view>
<!-- 缓存演示 -->
<view class="cache-section">
<view class="section-title">缓存机制演示</view>
<view class="cache-controls">
<button class="cache-btn" @click="testCache">测试缓存效果</button>
<button class="cache-btn clear" @click="clearCache">清除缓存</button>
</view>
<view class="cache-result" v-if="cacheResult">
<text class="cache-text">第一次获取: {{ cacheResult.firstTime }}ms</text>
<text class="cache-text">缓存获取: {{ cacheResult.cacheTime }}ms</text>
<text class="cache-text">性能提升: {{ cacheResult.improvement }}</text>
</view>
</view>
<!-- 字典数据展示 -->
<view class="data-section">
<view class="section-title">字典数据展示</view>
<view class="dict-tabs">
<view
class="tab-item"
:class="{ active: activeTab === key }"
v-for="(data, key) in dictData"
:key="key"
@click="activeTab = key">
{{ getDictDisplayName(key) }}
</view>
</view>
<scroll-view class="dict-content" scroll-y>
<view class="dict-items" v-if="dictData[activeTab]">
<view
class="dict-item"
v-for="(item, index) in dictData[activeTab]"
:key="index">
<text class="item-name">{{ item.name || item.text || '-' }}</text>
<text class="item-value">{{ item.value || '-' }}</text>
</view>
</view>
<view class="empty-state" v-else>
<text>暂无数据</text>
</view>
</scroll-view>
</view>
<!-- 使用说明 -->
<view class="usage-section">
<view class="section-title">使用说明</view>
<view class="usage-content">
<text class="usage-text">1. 使用 dictUtil.getBatchDict() 批量获取字典</text>
<text class="usage-text">2. 支持自动缓存30分钟有效期</text>
<text class="usage-text">3. 支持业务场景批量获取</text>
<text class="usage-text">4. 向后兼容原有 util.getDict() 方法</text>
</view>
</view>
</view>
</template>
<script>
import dictUtil from '@/common/dictUtil.js'
import util from '@/common/util.js'
export default {
data() {
return {
//
oldTesting: false,
newTesting: false,
//
oldResult: null,
newResult: null,
cacheResult: null,
//
dictData: {},
activeTab: '',
// keys
testKeys: [
'SourceChannel',
'source',
'customer_purchasing_power',
'preliminarycustomerintention',
'cognitive_concept',
'kh_status',
'decision_maker',
'distance'
]
}
},
onLoad() {
//
this.loadInitialData()
},
methods: {
//
async loadInitialData() {
try {
const data = await dictUtil.getBatchDict(this.testKeys)
this.dictData = data
this.activeTab = Object.keys(data)[0] || ''
} catch (error) {
console.error('加载初始数据失败:', error)
}
},
//
async testOldMethod() {
this.oldTesting = true
this.oldResult = null
try {
const startTime = Date.now()
let successCount = 0
//
for (const key of this.testKeys) {
try {
await this.getOldDict(key)
successCount++
} catch (error) {
console.warn(`获取字典 ${key} 失败:`, error)
}
}
const endTime = Date.now()
const totalTime = endTime - startTime
this.oldResult = {
time: totalTime,
requests: this.testKeys.length,
count: successCount
}
uni.showToast({
title: `原方式完成,耗时 ${totalTime}ms`,
icon: 'none'
})
} catch (error) {
console.error('测试原方式失败:', error)
uni.showToast({
title: '测试失败',
icon: 'none'
})
} finally {
this.oldTesting = false
}
},
//
async testNewMethod() {
this.newTesting = true
this.newResult = null
try {
const startTime = Date.now()
// 使
const data = await dictUtil.getBatchDict(this.testKeys, false) // 使
const endTime = Date.now()
const totalTime = endTime - startTime
const successCount = Object.keys(data).length
this.newResult = {
time: totalTime,
requests: 1, // 1
count: successCount
}
//
if (this.oldResult) {
const improvement = Math.round(((this.oldResult.time - totalTime) / this.oldResult.time) * 100)
this.newResult.improvement = `${improvement}%`
}
//
this.dictData = data
uni.showToast({
title: `新方式完成,耗时 ${totalTime}ms`,
icon: 'none'
})
} catch (error) {
console.error('测试新方式失败:', error)
uni.showToast({
title: '测试失败',
icon: 'none'
})
} finally {
this.newTesting = false
}
},
//
async getOldDict(key) {
return new Promise((resolve, reject) => {
//
setTimeout(async () => {
try {
//
const result = await dictUtil.getDict(key, false)
resolve(result)
} catch (error) {
reject(error)
}
}, Math.random() * 200 + 100) // 100-300ms
})
},
//
async testCache() {
try {
//
dictUtil.clearCache()
//
const startTime1 = Date.now()
await dictUtil.getBatchDict(this.testKeys.slice(0, 3), true)
const firstTime = Date.now() - startTime1
//
const startTime2 = Date.now()
await dictUtil.getBatchDict(this.testKeys.slice(0, 3), true)
const cacheTime = Date.now() - startTime2
const improvement = Math.round(((firstTime - cacheTime) / firstTime) * 100)
this.cacheResult = {
firstTime,
cacheTime,
improvement: `${improvement}%`
}
uni.showToast({
title: '缓存测试完成',
icon: 'success'
})
} catch (error) {
console.error('缓存测试失败:', error)
uni.showToast({
title: '缓存测试失败',
icon: 'none'
})
}
},
//
clearCache() {
dictUtil.clearCache()
this.cacheResult = null
uni.showToast({
title: '缓存已清除',
icon: 'success'
})
},
//
getDictDisplayName(key) {
const nameMap = {
'SourceChannel': '来源渠道',
'source': '来源',
'customer_purchasing_power': '购买力',
'preliminarycustomerintention': '意向度',
'cognitive_concept': '认知理念',
'kh_status': '客户状态',
'decision_maker': '决策人',
'distance': '距离'
}
return nameMap[key] || key
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 20rpx;
background: #f5f5f5;
min-height: 100vh;
}
.header {
background: #fff;
padding: 30rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
text-align: center;
.title {
font-size: 36rpx;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.subtitle {
font-size: 26rpx;
color: #666;
display: block;
}
}
.performance-section, .cache-section, .data-section, .usage-section {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
border-left: 4rpx solid #29d3b4;
padding-left: 16rpx;
}
.test-item {
margin-bottom: 30rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
.test-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
.test-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.test-btn {
padding: 12rpx 24rpx;
border-radius: 6rpx;
border: none;
font-size: 24rpx;
color: #fff;
&.old-style {
background: #ff6b6b;
}
&.new-style {
background: #29d3b4;
}
&:disabled {
opacity: 0.6;
}
}
}
.test-result {
display: flex;
flex-direction: column;
gap: 8rpx;
.result-text {
font-size: 24rpx;
color: #666;
}
.improvement {
font-size: 26rpx;
color: #29d3b4;
font-weight: 600;
}
}
.cache-controls {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
.cache-btn {
flex: 1;
padding: 16rpx;
border-radius: 8rpx;
border: none;
font-size: 26rpx;
color: #fff;
background: #29d3b4;
&.clear {
background: #ff6b6b;
}
}
}
.cache-result {
display: flex;
flex-direction: column;
gap: 8rpx;
.cache-text {
font-size: 24rpx;
color: #666;
}
}
.dict-tabs {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-bottom: 20rpx;
.tab-item {
padding: 12rpx 20rpx;
background: #f0f0f0;
border-radius: 20rpx;
font-size: 24rpx;
color: #666;
&.active {
background: #29d3b4;
color: #fff;
}
}
}
.dict-content {
height: 400rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
}
.dict-items {
padding: 20rpx;
}
.dict-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.item-name {
font-size: 26rpx;
color: #333;
flex: 1;
}
.item-value {
font-size: 24rpx;
color: #666;
margin-left: 20rpx;
}
}
.empty-state {
padding: 60rpx;
text-align: center;
color: #999;
font-size: 26rpx;
}
.usage-content {
display: flex;
flex-direction: column;
gap: 12rpx;
.usage-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
</style>

352
uniapp/pages/demo/mock-demo.vue

@ -1,352 +0,0 @@
<template>
<view class="mock-demo">
<view class="header">
<text class="title">Mock数据演示</text>
<view class="env-info">
<text class="env-label">当前环境: {{ envInfo.env }}</text>
<text class="mock-status" :class="{ active: envInfo.mockEnabled }">
Mock状态: {{ envInfo.mockEnabled ? '已开启' : '已关闭' }}
</text>
</view>
</view>
<view class="content">
<!-- 用户信息展示 -->
<view class="section">
<view class="section-title">用户信息</view>
<view class="user-card" v-if="userInfo">
<image class="avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<view class="user-details">
<text class="username">{{ userInfo.username }}</text>
<text class="phone">{{ userInfo.phone }}</text>
<text class="email">{{ userInfo.email }}</text>
</view>
</view>
<view class="loading" v-else>
<text>加载中...</text>
</view>
</view>
<!-- 课程表展示 -->
<view class="section">
<view class="section-title">今日课程</view>
<view class="schedule-list">
<view class="schedule-item" v-for="item in scheduleList" :key="item.id">
<view class="time">{{ item.start_time }} - {{ item.end_time }}</view>
<view class="course-info">
<text class="course-name">{{ item.course_name }}</text>
<text class="teacher">{{ item.teacher_name }}</text>
<text class="classroom">{{ item.classroom }}</text>
</view>
<view class="status" :class="item.status">
{{ getStatusText(item.status) }}
</view>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="actions">
<button class="btn primary" @click="refreshData">刷新数据</button>
<button class="btn secondary" @click="toggleMock">
{{ envInfo.mockEnabled ? '关闭Mock' : '开启Mock' }}
</button>
</view>
</view>
</view>
</template>
<script>
import { Api_url, isMockEnabled, isDebug, env } from '@/common/config.js'
import http from '@/common/axios.js'
export default {
data() {
return {
userInfo: null,
scheduleList: [],
envInfo: {
env: env,
mockEnabled: isMockEnabled,
debug: isDebug
}
}
},
onLoad() {
this.loadData()
},
methods: {
async loadData() {
try {
//
await this.loadUserInfo()
//
await this.loadSchedule()
} catch (error) {
console.error('数据加载失败:', error)
uni.showToast({
title: '数据加载失败',
icon: 'none'
})
}
},
async loadUserInfo() {
try {
const response = await http.get('/user/info')
this.userInfo = response.data
} catch (error) {
console.error('用户信息加载失败:', error)
}
},
async loadSchedule() {
try {
const response = await http.get('/student/schedule')
this.scheduleList = response.data || []
} catch (error) {
console.error('课程表加载失败:', error)
}
},
async refreshData() {
uni.showLoading({
title: '刷新中...'
})
try {
await this.loadData()
uni.showToast({
title: '刷新成功',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '刷新失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
toggleMock() {
//
uni.showModal({
title: '提示',
content: 'Mock开关需要在环境变量中配置,请修改.env文件中的VUE_APP_MOCK_ENABLED参数',
showCancel: false
})
},
getStatusText(status) {
const statusMap = {
scheduled: '已安排',
completed: '已完成',
cancelled: '已取消'
}
return statusMap[status] || status
}
}
}
</script>
<style scoped>
.mock-demo {
padding: 20rpx;
background-color: #f8f9fa;
min-height: 100vh;
}
.header {
background: white;
padding: 30rpx;
border-radius: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 20rpx;
}
.env-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.env-label {
font-size: 28rpx;
color: #666;
}
.mock-status {
font-size: 28rpx;
color: #999;
padding: 10rpx 20rpx;
border-radius: 10rpx;
background: #f5f5f5;
}
.mock-status.active {
color: #52c41a;
background: #f6ffed;
}
.content {
flex: 1;
}
.section {
background: white;
padding: 30rpx;
border-radius: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.user-card {
display: flex;
align-items: center;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
}
.user-details {
flex: 1;
}
.username {
font-size: 32rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.phone, .email {
font-size: 28rpx;
color: #666;
display: block;
margin-bottom: 5rpx;
}
.schedule-list {
display: flex;
flex-direction: column;
}
.schedule-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.schedule-item:last-child {
border-bottom: none;
}
.time {
width: 200rpx;
font-size: 28rpx;
color: #666;
}
.course-info {
flex: 1;
margin-left: 20rpx;
}
.course-name {
font-size: 30rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.teacher, .classroom {
font-size: 26rpx;
color: #666;
display: block;
margin-bottom: 5rpx;
}
.status {
padding: 10rpx 20rpx;
border-radius: 10rpx;
font-size: 24rpx;
text-align: center;
min-width: 120rpx;
}
.status.scheduled {
background: #e6f7ff;
color: #1890ff;
}
.status.completed {
background: #f6ffed;
color: #52c41a;
}
.status.cancelled {
background: #fff2e8;
color: #fa8c16;
}
.loading {
text-align: center;
padding: 60rpx;
color: #666;
}
.actions {
display: flex;
gap: 20rpx;
padding: 30rpx;
}
.btn {
flex: 1;
padding: 24rpx;
border-radius: 12rpx;
font-size: 32rpx;
border: none;
cursor: pointer;
}
.btn.primary {
background: #1890ff;
color: white;
}
.btn.secondary {
background: #f5f5f5;
color: #666;
}
.btn:active {
opacity: 0.8;
}
</style>

633
uniapp/pages/market/clue/class_arrangement_detail_bak.vue

@ -1,633 +0,0 @@
<template>
<div class="course-schedule">
<!-- Header -->
<div class="header">
<div class="back-btn" @click="goBack">
<ChevronLeftIcon class="w-6 h-6" />
</div>
<h1 class="title">课程安排详情</h1>
</div>
<!-- Course Info -->
<div class="course-info">
<h2 class="course-title">课程安排详情</h2>
<p class="course-time">日期2025-07-24 08:30-09:30</p>
</div>
<!-- Formal Students Section -->
<div class="section">
<h3 class="section-title">正式学员</h3>
<div class="cards-grid">
<!-- Student Card with Data -->
<div class="student-card filled">
<div class="renewal-badge">待续费</div>
<div class="avatar"></div>
<div class="student-info">
<div class="student-name">张小明同学的名字很长需要省略</div>
<div class="student-age">年龄8</div>
<div class="course-status">课程状态正式课</div>
<div class="course-arrangement">课程安排固定课</div>
<div class="remaining-hours">剩余课时12</div>
<div class="expiry-date">到期时间2025-12-31</div>
</div>
</div>
<!-- Empty Slots -->
<div
v-for="n in 6"
:key="n"
class="student-card empty"
@click="openStudentModal('formal', n)"
>
<div class="add-icon">
<PlusIcon class="w-8 h-8" />
</div>
<div class="add-text">
<div class="slot-title">空位</div>
<div class="slot-subtitle">点击添加学员</div>
</div>
</div>
</div>
</div>
<!-- Waiting List Section -->
<div class="section">
<h3 class="section-title">等待位</h3>
<div class="cards-grid">
<div
v-for="n in 2"
:key="n"
class="student-card waiting"
@click="openStudentModal('waiting', n)"
>
<div class="add-icon waiting-icon">
<PlusIcon class="w-8 h-8" />
</div>
<div class="add-text">
<div class="slot-title">等待位</div>
<div class="slot-subtitle">点击添加学员</div>
</div>
</div>
</div>
</div>
<!-- Bottom Popup Modal -->
<div v-if="showModal" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>添加学员</h3>
</div>
<div class="modal-body">
<!-- Customer Selection -->
<div class="form-section">
<label class="form-label">客户选择</label>
<div class="search-tabs">
<button
:class="['tab-btn', { active: searchType === 'phone' }]"
@click="searchType = 'phone'"
>
手机号检索
</button>
<button
:class="['tab-btn', { active: searchType === 'name' }]"
@click="searchType = 'name'"
>
姓名检索
</button>
</div>
<input
v-model="searchQuery"
:placeholder="searchType === 'phone' ? '请输入手机号' : '请输入姓名'"
class="search-input"
@input="searchStudents"
/>
<!-- Search Results -->
<div v-if="searchResults.length > 0" class="search-results">
<div
v-for="student in searchResults"
:key="student.id"
:class="['student-item', { selected: selectedStudent?.id === student.id }]"
@click="selectStudent(student)"
>
<div class="student-avatar">{{ student.name.charAt(0) }}</div>
<div class="student-details">
<div class="student-name">{{ student.name }}</div>
<div class="student-phone">{{ student.phone }}</div>
</div>
<div v-if="selectedStudent?.id === student.id" class="check-icon">
<CheckIcon class="w-5 h-5" />
</div>
</div>
</div>
</div>
<!-- Course Arrangement -->
<div class="form-section">
<label class="form-label">课程安排</label>
<div class="radio-group">
<label class="radio-item">
<input
type="radio"
value="temporary"
v-model="courseArrangement"
/>
<span class="radio-text">临时课</span>
</label>
<label class="radio-item">
<input
type="radio"
value="fixed"
v-model="courseArrangement"
/>
<span class="radio-text">固定课</span>
</label>
</div>
</div>
<!-- Remarks -->
<div class="form-section">
<label class="form-label">备注</label>
<textarea
v-model="remarks"
placeholder="请输入备注信息"
class="remarks-textarea"
rows="3"
></textarea>
</div>
</div>
<!-- Modal Footer -->
<div class="modal-footer">
<button class="btn btn-cancel" @click="closeModal">取消</button>
<button class="btn btn-confirm" @click="confirmSelection">确定</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { ChevronLeftIcon, PlusIcon, CheckIcon } from 'lucide-vue-next'
export default {
name: 'CourseSchedule',
components: {
ChevronLeftIcon,
PlusIcon,
CheckIcon
},
data() {
return {
showModal: false,
searchType: 'phone',
searchQuery: '',
courseArrangement: 'temporary',
remarks: '',
selectedStudent: null,
currentSlot: null,
searchResults: [],
// Mock student data
allStudents: [
{ id: 1, name: '张小明', phone: '13800138001', age: 8 },
{ id: 2, name: '李小红', phone: '13800138002', age: 9 },
{ id: 3, name: '王小华', phone: '13800138003', age: 7 },
{ id: 4, name: '赵小强', phone: '13800138004', age: 10 }
]
}
},
methods: {
goBack() {
this.$router.go(-1)
},
openStudentModal(type, index) {
this.showModal = true
this.currentSlot = { type, index }
this.resetForm()
},
closeModal() {
this.showModal = false
this.resetForm()
},
resetForm() {
this.searchQuery = ''
this.searchResults = []
this.selectedStudent = null
this.courseArrangement = 'temporary'
this.remarks = ''
},
searchStudents() {
if (!this.searchQuery.trim()) {
this.searchResults = []
return
}
this.searchResults = this.allStudents.filter(student => {
if (this.searchType === 'phone') {
return student.phone.includes(this.searchQuery)
} else {
return student.name.includes(this.searchQuery)
}
})
},
selectStudent(student) {
this.selectedStudent = student
},
confirmSelection() {
if (!this.selectedStudent) {
alert('请选择学员')
return
}
// Here you would typically save the selection
console.log('Selected:', {
student: this.selectedStudent,
slot: this.currentSlot,
arrangement: this.courseArrangement,
remarks: this.remarks
})
this.closeModal()
}
}
}
</script>
<style scoped>
.course-schedule {
min-height: 100vh;
background: #1a1a1a;
color: white;
padding: 0;
}
.header {
display: flex;
align-items: center;
padding: 16px 20px;
background: #2a2a2a;
}
.back-btn {
margin-right: 16px;
cursor: pointer;
}
.title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.course-info {
padding: 24px 20px;
}
.course-title {
font-size: 24px;
font-weight: 600;
margin: 0 0 12px 0;
}
.course-time {
color: #4ade80;
font-size: 16px;
margin: 0;
}
.section {
margin: 24px 20px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #fbbf24;
margin: 0 0 16px 0;
}
.cards-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.student-card {
background: #2a2a2a;
border-radius: 12px;
padding: 16px;
min-height: 160px;
position: relative;
cursor: pointer;
transition: all 0.3s ease;
}
.student-card.empty {
border: 2px dashed #fbbf24;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.student-card.waiting {
border: 2px dashed #8b5cf6;
}
.student-card.filled {
border: 1px solid #374151;
}
.student-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.renewal-badge {
position: absolute;
top: 8px;
right: 8px;
background: #ef4444;
color: white;
font-size: 10px;
padding: 2px 6px;
border-radius: 8px;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: #4ade80;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-bottom: 8px;
}
.student-info {
font-size: 12px;
line-height: 1.4;
}
.student-name {
font-weight: 600;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.student-age,
.course-status,
.course-arrangement,
.remaining-hours,
.expiry-date {
color: #9ca3af;
margin-bottom: 2px;
}
.add-icon {
color: #fbbf24;
margin-bottom: 8px;
}
.waiting-icon {
color: #8b5cf6;
}
.add-text {
text-align: center;
}
.slot-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.slot-subtitle {
font-size: 12px;
color: #9ca3af;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 16px 16px 0 0;
width: 100%;
max-height: 80vh;
overflow-y: auto;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #e5e7eb;
text-align: center;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1f2937;
}
.modal-body {
padding: 20px;
color: #1f2937;
}
.form-section {
margin-bottom: 24px;
}
.form-label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: #374151;
}
.search-tabs {
display: flex;
margin-bottom: 12px;
background: #f3f4f6;
border-radius: 8px;
padding: 4px;
}
.tab-btn {
flex: 1;
padding: 8px 16px;
border: none;
background: transparent;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.tab-btn.active {
background: white;
color: #1f2937;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.search-input {
width: 100%;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
}
.search-results {
max-height: 200px;
overflow-y: auto;
border: 1px solid #e5e7eb;
border-radius: 8px;
margin-top: 8px;
}
.student-item {
display: flex;
align-items: center;
padding: 12px;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
transition: background 0.2s;
}
.student-item:hover {
background: #f9fafb;
}
.student-item.selected {
background: #eff6ff;
border-color: #3b82f6;
}
.student-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #3b82f6;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 12px;
}
.student-details {
flex: 1;
}
.student-details .student-name {
font-weight: 600;
margin-bottom: 4px;
}
.student-details .student-phone {
color: #6b7280;
font-size: 14px;
}
.check-icon {
color: #3b82f6;
}
.radio-group {
display: flex;
gap: 16px;
}
.radio-item {
display: flex;
align-items: center;
cursor: pointer;
}
.radio-item input[type="radio"] {
margin-right: 8px;
}
.radio-text {
font-size: 14px;
}
.remarks-textarea {
width: 100%;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
resize: vertical;
font-family: inherit;
}
.modal-footer {
padding: 20px;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 12px;
}
.btn {
flex: 1;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-cancel {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-cancel:hover {
background: #e5e7eb;
}
.btn-confirm {
background: #3b82f6;
color: white;
border: 1px solid #3b82f6;
}
.btn-confirm:hover {
background: #2563eb;
}
</style>

149
uniapp/pages/market/clue/clue_info.less

@ -2362,3 +2362,152 @@
font-size: 28rpx;
}
}
// 二维码支付弹窗样式
.qrcode-payment-modal {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 40rpx 40rpx 20rpx;
border-bottom: 1px solid #f0f0f0;
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f5f5f5;
color: #666;
font-size: 32rpx;
transition: all 0.3s ease;
&:active {
background: #e5e5e5;
transform: scale(0.95);
}
}
}
.order-info {
padding: 30rpx 40rpx;
.info-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 28rpx;
color: #666;
margin-right: 20rpx;
}
.value {
font-size: 28rpx;
color: #333;
flex: 1;
}
.amount {
font-size: 32rpx;
color: #ff4757;
font-weight: 600;
flex: 1;
}
}
}
.qrcode-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx;
background: #fafafa;
.qrcode-image {
width: 300rpx;
height: 300rpx;
border: 1px solid #e5e5e5;
border-radius: 12rpx;
background: #fff;
margin-bottom: 20rpx;
}
.qrcode-placeholder {
width: 300rpx;
height: 300rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #ccc;
border-radius: 12rpx;
background: #fff;
color: #999;
font-size: 24rpx;
margin-bottom: 20rpx;
}
.qrcode-tip {
font-size: 24rpx;
color: #666;
text-align: center;
line-height: 1.4;
}
}
.modal-buttons {
display: flex;
padding: 30rpx 40rpx 40rpx;
gap: 20rpx;
.btn {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s ease;
&.secondary {
background: #f5f5f5;
color: #666;
&:active {
background: #e5e5e5;
transform: scale(0.98);
}
}
&.primary {
background: #29d3b4;
color: #fff;
&:active {
background: #1ea08e;
transform: scale(0.98);
}
}
}
}
}

119
uniapp/pages/market/clue/clue_info.vue

@ -223,6 +223,49 @@
@confirm="handleOrderFormConfirm"
/>
</uni-popup>
<!-- 二维码支付弹窗 -->
<uni-popup ref="qrCodePopup" type="center" :show="showQRCodeModal" @close="closeQRCodeModal">
<view class="qrcode-payment-modal" v-if="qrCodePaymentData">
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">扫码支付</text>
<view class="close-btn" @click="closeQRCodeModal">
<text></text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-row">
<text class="label">订单号</text>
<text class="value">{{ qrCodePaymentData.order.order_no }}</text>
</view>
<view class="info-row">
<text class="label">支付金额</text>
<text class="amount">¥{{ qrCodePaymentData.order.total_amount }}</text>
</view>
</view>
<!-- 二维码区域 -->
<view class="qrcode-container">
<image
v-if="qrCodePaymentData.qrcodeImage"
:src="qrCodePaymentData.qrcodeImage"
class="qrcode-image"
mode="aspectFit"
/>
<text v-else class="qrcode-placeholder">二维码加载中...</text>
<text class="qrcode-tip">请使用微信扫码完成支付</text>
</view>
<!-- 操作按钮 -->
<view class="modal-buttons">
<view class="btn secondary" @click="closeQRCodeModal">取消支付</view>
<view class="btn primary" @click="confirmQRCodePayment">已完成支付</view>
</view>
</view>
</uni-popup>
</view>
</template>
@ -289,6 +332,10 @@ export default {
//
showOrderForm: false,
//
showQRCodeModal: false,
qrCodePaymentData: null,
//
remark_content: '',
currentRecord: null,
@ -1063,8 +1110,76 @@ export default {
},
//
showQRCodePayment(order) {
console.log('扫码支付:', order)
async showQRCodePayment(order) {
console.log('扫码支付:', order)
try {
uni.showLoading({ title: '生成支付二维码...' })
//
const res = await apiRoute.getOrderPayQrcode({
order_id: order._raw?.id || order.id
})
uni.hideLoading()
if (res.code === 1 && res.data) {
//
this.openQRCodeModal({
order: order,
qrcode: res.data.code_url,
qrcodeImage: res.data.qrcode_url
})
} else {
uni.showToast({
title: res.msg || '获取支付二维码失败',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('获取支付二维码失败:', error)
uni.showToast({
title: '获取支付二维码失败',
icon: 'none'
})
}
},
//
openQRCodeModal(paymentData) {
this.qrCodePaymentData = paymentData
this.showQRCodeModal = true
},
//
closeQRCodeModal() {
this.showQRCodeModal = false
this.qrCodePaymentData = null
},
//
async confirmQRCodePayment() {
if (!this.qrCodePaymentData?.order) return
const order = this.qrCodePaymentData.order
uni.showModal({
title: '支付确认',
content: '请确认是否已完成扫码支付?',
success: async (res) => {
if (res.confirm) {
try {
//
await this.updateOrderStatus(order, 'paid', `QR${Date.now()}`)
this.closeQRCodeModal()
} catch (error) {
console.error('支付确认失败:', error)
uni.showToast({ title: '支付确认失败', icon: 'none' })
}
}
}
})
},
//

549
uniapp/pages/market/clue/clue_info_refactored.vue

@ -1,549 +0,0 @@
<!--重构后的客户详情页面 - 使用组件化方式-->
<template>
<view class="assemble">
<view class="main_box">
<view style="height: 20rpx;background: #29D3B4;"></view>
<!-- 头部信息区域 -->
<view class="count_section">
<view class="main">
<view class="course_box">
<view class="course_box_top">
<view class="course_box_top_top">
<image class="pic" :src="$util.img('/uniapp_src/static/images/index/myk.png')"></image>
<view class="name">{{ $util.safeGet(clientInfo, 'customerResource.name', '未知客户') }}</view>
</view>
<view class="course_box_top_below">
<view class="course_box_top_below-right">
<!-- 操作按钮 -->
<view class="action-buttons">
<view class="btn-item" @click="handleMakeCall">
<image class="btn-icon" :src="$util.img('/uniapp_src/static/images/index/phone.png')"></image>
</view>
<view class="btn-item" @click="handleSendMessage" v-if="$util.safeGet(clientInfo, 'customerResource.member_id')">
<image class="btn-icon" :src="$util.img('/uniapp_src/static/images/index/message.png')"></image>
</view>
</view>
</view>
</view>
</view>
<!-- 标签切换组件 -->
<TabSwitcher
:tabs="tabs"
:active-tab-id="switch_tags_type"
@tab-change="handleTabChange"
/>
</view>
</view>
</view>
<view class="bg_box bg_top"></view>
<view class="bg_box bg_bottom"></view>
</view>
<!-- 基本资料 -->
<view class="content-section" v-if="switch_tags_type == 1">
<view class="integrated-info-section">
<view class="basic-message">
<view>客户和学生信息</view>
<view class="add-student-btn" @click="openAddStudentDialog">
<view class="add-icon">+</view>
<view class="add-text">添加学生</view>
</view>
</view>
<!-- 使用客户信息卡片组件 -->
<ClientInfoCard
:client-info="clientInfo"
@call="handleMakeCall"
/>
<!-- 学生信息列表 -->
<view class="student-list" v-if="studentList.length > 0">
<StudentInfoCard
v-for="student in studentList"
:key="student.id"
:student="student"
:actions="studentActions"
@toggle-actions="toggleStudentActions"
@action="handleStudentAction"
/>
</view>
</view>
</view>
<!-- 课程信息 -->
<view class="content-section" v-else-if="switch_tags_type == 2">
<view class="course-info-section">
<text>课程信息内容...</text>
</view>
</view>
<!-- 通话记录 -->
<view class="content-section" v-else-if="switch_tags_type == 3">
<view class="call-records-section">
<CallRecordCard
v-for="record in listCallUp"
:key="record.id"
:record="record"
/>
</view>
</view>
<!-- 体测记录 -->
<view class="content-section" v-else-if="switch_tags_type == 4">
<view class="fitness-records-section">
<FitnessRecordCard
v-for="record in fitnessRecords"
:key="record.id"
:record="record"
@file-click="handleFitnessFileClick"
/>
</view>
</view>
<!-- 学习计划 -->
<view class="content-section" v-else-if="switch_tags_type == 5">
<view class="study-plan-section">
<text>学习计划内容...</text>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
import marketApi from '@/api/marketApi.js'
//
import ClientInfoCard from '@/components/client-info-card/client-info-card.vue'
import StudentInfoCard from '@/components/student-info-card/student-info-card.vue'
import TabSwitcher from '@/components/tab-switcher/tab-switcher.vue'
import FitnessRecordCard from '@/components/fitness-record-card/fitness-record-card.vue'
import CallRecordCard from '@/components/call-record-card/call-record-card.vue'
export default {
name: 'ClueInfoRefactored',
components: {
ClientInfoCard,
StudentInfoCard,
TabSwitcher,
FitnessRecordCard,
CallRecordCard
},
data() {
return {
resource_sharing_id: '',
switch_tags_type: 1, //
//
tabs: [
{ id: 1, name: '基本资料' },
{ id: 2, name: '课程信息' },
{ id: 3, name: '通话记录' },
{ id: 4, name: '体测记录' },
{ id: 5, name: '学习计划' }
],
//
studentActions: [
{ key: 'edit', text: '编辑学生' },
{ key: 'order', text: '查看订单' },
{ key: 'course', text: '课程安排' },
{ key: 'fitness', text: '体测记录' }
],
//
clientInfo: {},
userInfo: {},
studentList: [],
listCallUp: [],
fitnessRecords: [],
followList: [],
courseInfo: [],
coachList: [],
educationList: [],
assistantList: []
}
},
onLoad(option) {
console.log('页面加载参数:', option)
this.resource_sharing_id = option.resource_sharing_id || ''
this.init()
},
methods: {
async init() {
console.log('开始初始化数据...')
try {
await this.getInfo()
await Promise.all([
this.getUserInfo(),
this.getListCallUp(),
this.getStudentList(),
this.getFitnessRecords()
])
console.log('数据初始化完成')
} catch (error) {
console.error('初始化失败:', error)
}
},
//
async getInfo() {
if (!this.resource_sharing_id) {
uni.showToast({ title: '缺少必要参数', icon: 'none' })
return false
}
try {
const res = await apiRoute.xs_resourceSharingInfo({
resource_sharing_id: this.resource_sharing_id
})
if (res.code === 1) {
this.clientInfo = res.data
return true
} else {
uni.showToast({ title: res.msg, icon: 'none' })
return false
}
} catch (error) {
console.error('获取客户详情失败:', error)
return false
}
},
//
async getUserInfo() {
try {
const res = await apiRoute.getPersonnelInfo({})
if (res.code === 1) {
this.userInfo = res.data
return true
}
return false
} catch (error) {
console.error('获取员工信息失败:', error)
return false
}
},
//
async getListCallUp() {
if (!this.clientInfo.resource_id) return false
try {
const res = await apiRoute.listCallUp({
resource_id: this.clientInfo.resource_id
})
if (res.code === 1) {
this.listCallUp = res.data || []
return true
}
return false
} catch (error) {
console.error('获取通话记录失败:', error)
return false
}
},
//
async getStudentList() {
try {
if (!this.clientInfo.resource_id) {
// 使Mock
this.studentList = this.getMockStudentList()
return true
}
const res = await apiRoute.xs_getStudentList({
parent_resource_id: this.clientInfo.resource_id
})
if (res.code === 1) {
this.studentList = res.data || []
} else {
this.studentList = this.getMockStudentList()
}
return true
} catch (error) {
console.error('获取学生列表失败:', error)
this.studentList = this.getMockStudentList()
return true
}
},
//
async getFitnessRecords() {
try {
// 使Mock
this.fitnessRecords = this.getMockFitnessRecords()
return true
} catch (error) {
console.error('获取体测记录失败:', error)
return false
}
},
// Mock
getMockStudentList() {
return [
{
id: 1,
name: '张小明',
gender: 1,
age: 9.05,
birthday: '2015-05-10',
emergency_contact: '张妈妈',
contact_phone: '13800138001',
member_label: '新学员',
note: '活泼好动,喜欢运动',
actionsExpanded: false
}
]
},
getMockFitnessRecords() {
return [
{
id: 1,
test_date: '2024-01-15',
height: '165',
weight: '55',
pdf_files: [
{
id: 1,
name: '体测报告_2024-01-15.pdf',
size: 1024000,
url: '/static/mock/fitness_report_1.pdf'
}
]
}
]
},
//
handleTabChange({ tabId }) {
this.switch_tags_type = tabId
},
handleMakeCall() {
const phoneNumber = this.$util.safeGet(this.clientInfo, 'customerResource.phone_number', '')
this.$util.makePhoneCall(phoneNumber)
},
handleSendMessage() {
uni.showToast({
title: '发送消息功能待实现',
icon: 'none'
})
},
toggleStudentActions(student) {
const index = this.studentList.findIndex(s => s.id === student.id)
if (index !== -1) {
this.$set(this.studentList[index], 'actionsExpanded', !student.actionsExpanded)
}
},
handleStudentAction({ action, student }) {
console.log('学生操作:', action, student)
switch (action.key) {
case 'edit':
this.editStudent(student)
break
case 'order':
this.viewStudentOrders(student)
break
case 'course':
this.viewStudentCourse(student)
break
case 'fitness':
this.viewStudentFitness(student)
break
}
},
handleFitnessFileClick({ file, record }) {
console.log('点击体测文件:', file, record)
//
},
openAddStudentDialog() {
console.log('打开添加学生对话框')
//
},
//
editStudent(student) {
this.$util.navigateToPage('/pages/student/edit', {
student_id: student.id
})
},
viewStudentOrders(student) {
this.$util.navigateToPage('/pages/market/clue/order_list', {
resource_id: this.clientInfo.resource_id,
student_id: student.id,
student_name: student.name
})
},
viewStudentCourse(student) {
this.$util.navigateToPage('/pages/market/clue/course_arrange', {
resource_id: this.clientInfo.resource_id,
student_id: student.id,
student_name: student.name
})
},
viewStudentFitness(student) {
this.$util.navigateToPage('/pages/fitness/records', {
student_id: student.id
})
}
}
}
</script>
<style lang="less" scoped>
.assemble {
width: 100%;
height: 100vh;
overflow: auto;
background-color: #292929;
}
.main_box {
background: #292929;
min-height: 20vh;
}
.action-buttons {
display: flex;
align-items: center;
.btn-item {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 20rpx;
.btn-icon {
width: 40rpx;
height: 40rpx;
}
}
}
.count_section {
width: 100%;
position: relative;
.main {
width: 100%;
position: absolute;
z-index: 2;
padding: 0rpx 24rpx;
display: flex;
justify-content: center;
.course_box {
padding: 26rpx 22rpx 0 22rpx;
width: 95%;
height: 250rpx;
border-radius: 20rpx;
background-color: #fff;
}
}
.bg_top {
height: 180rpx;
background-color: #29D3B4;
}
.bg_bottom {
height: 80rpx;
background-color: #292929;
}
}
.course_box_top {
display: flex;
justify-content: space-between;
align-items: center;
.course_box_top_top {
display: flex;
align-items: center;
.pic {
width: 60rpx;
height: 60rpx;
margin-right: 20rpx;
}
.name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
}
.content-section {
background-color: #292929;
min-height: 60vh;
padding: 20rpx;
}
.integrated-info-section {
.basic-message {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 20rpx;
color: white;
font-size: 28rpx;
font-weight: bold;
.add-student-btn {
display: flex;
align-items: center;
background-color: #29d3b4;
padding: 15rpx 25rpx;
border-radius: 25rpx;
.add-icon {
color: white;
font-size: 24rpx;
margin-right: 10rpx;
}
.add-text {
color: white;
font-size: 22rpx;
}
}
}
}
.student-list,
.call-records-section,
.fitness-records-section {
padding: 20rpx 0;
}
.course-info-section,
.study-plan-section {
padding: 40rpx 20rpx;
color: white;
text-align: center;
}
</style>

425
uniapp/pages/market/clue/new_task.vue

@ -1,425 +0,0 @@
<template>
<view class="assemble">
<view class="title">跟进任务</view>
<view class="form-style">
<fui-form class="input-style" ref="form" top="0" :model="formData" :show="false">
<!--基础表单-->
<!--下拉-->
<fui-form-item
asterisk
label="跟进类型"
asteriskPosition="right"
labelSize='26'
prop=""
background='#434544'
labelColor='#fff'
:bottomBorder='false'
>
<view class="input-title" style="margin-right:14rpx;">
<view v-if="!formData.entry_type" class="input-title" style="margin-right:14rpx;" @click="selectCon(`entry_type`)">点击选择
</view>
<view v-else class="input-title" style="margin-right:14rpx;" @click="selectCon(`entry_type`)">
{{ str_entry_type }}
</view>
</view>
</fui-form-item>
<!--下拉-->
<fui-form-item
asterisk
label="跟进人员"
asteriskPosition="right"
labelSize='26'
prop=""
background='#434544'
labelColor='#fff'
:bottomBorder='false'
>
<view class="input-title" style="margin-right:14rpx;">
<view v-if="!formData.follow_staff_id" class="input-title" style="margin-right:14rpx;" @click="selectCon(`follow_staff_id`)">点击选择
</view>
<view v-else class="input-title" style="margin-right:14rpx;" @click="selectCon(`follow_staff_id`)">
{{ str_follow_staff_id }}
</view>
</view>
</fui-form-item>
<!--下拉-->
<fui-form-item
asterisk
label="跟进时间"
asteriskPosition="right"
labelSize='26'
prop=""
background='#434544'
labelColor='#fff'
:bottomBorder='false'
>
<view class="input-title" style="margin-right:14rpx;">
<view v-if="!formData.reminder_time" class="input-title" style="margin-right:14rpx;" @click="selectCon(`reminder_time`)">点击选择
</view>
<view v-else class="input-title" style="margin-right:14rpx;" @click="selectCon(`reminder_time`)">
{{ formData.reminder_time }}
</view>
</view>
</fui-form-item>
<!--手写-->
<fui-form-item
label="备注"
asteriskPosition="right"
labelSize='26'
prop=""
background='#434544'
labelColor='#fff'
:bottomBorder='false'
>
<view class="input-title" style="margin-right:14rpx;">
<fui-input :borderBottom="false" :padding="[0]" placeholder="点击填写" v-model="formData.follow_content"
backgroundColor="#434544" size="26" color="#fff"></fui-input>
</view>
</fui-form-item>
</fui-form>
</view>
<view class="fui-btn__box">
<fui-button background="#434544" color="#24BA9F" borderColor="#24BA9F" @click="submit">保存</fui-button>
</view>
<!-- 年月日-选择时间 -->
<fui-date-picker :show="show_date" type="5" @change="change_date" @cancel="cancel_date"></fui-date-picker>
<!-- 下拉选择器 -->
<fui-picker :linkage='linkage' :options="options" :layer="1" :show="show" @change="changeOptions"
@cancel="cancel"></fui-picker>
</view>
</template>
<script>
import marketApi from '@/api/market.js';
import memberApi from '@/api/member.js';
const rules = [{
name: "mobile",
rule: ["required", "isMobile"],
msg: ["请输入手机号", "请输入正确的手机号"]
}];
export default {
data() {
return {
rules,
is_submit: true,//()|true=,false=
//
formData:{
//##### #####
entry_type:'',//(1=,2=)
follow_staff_id:'',//
reminder_time:'',//
follow_content:'',//
},
//
options_type: undefined,//
show: false,//
linkage: true,//
options: [
// {
// 'value': 1,
// 'text': '1'
// }
],//
//
show_date: false,//|true=,false=
//-
//-
options_entry_type:[
{
value: 1,
text: '市场人员'
},
{
value: 2,
text: '销售人员'
},
],
//-
str_entry_type:'',
//-
//-
options_follow_staff_id_sc:[],//(-)
options_follow_staff_id_xs:[],//(-)
//-
str_follow_staff_id:'',
}
},
onShow() {
this.init()
},
methods: {
//
async init() {
this.getUserInfo()
//-()
this.getDic_staff_id('5')
//-()
this.getDic_staff_id('6')
},
//
async getUserInfo(){
let res = await marketApi.member({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log(111,res.data)
this.formData.staff_id = res.data.staff_id//->
this.str_staff_id = res.data.name//->
},
//-()
// role_id|5=,6=
async getDic_staff_id(role_id){
let res = await memberApi.staffList({
type: 2,
role_id:role_id
})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let arr = []
res.data.forEach((v,k)=>{
arr.push({
text: v.name,
value: v.id,
})
})
if(role_id == 5){
this.options_follow_staff_id_sc = arr
console.log('市场',arr)
}else if(role_id == 6){
this.options_follow_staff_id_xs = arr
console.log('销售',arr)
}else{
//
this.options_staff_id = arr
console.log('全部',arr)
}
},
//
async validatorForm(data) {
//
if(!data.entry_type){
uni.showToast({
title: '跟进类型必填',
icon: 'none'
})
return false
}
//
if(!data.follow_staff_id){
uni.showToast({
title: '跟进人员必填',
icon: 'none'
})
return false
}
//
if(!data.reminder_time){
uni.showToast({
title: '跟进时间必填',
icon: 'none'
})
return false
}
return true
},
//
async submit() {
console.log('提交',this.formData)
let data = {...this.formData}
//
let validatorForm = await this.validatorForm(data)
console.log('验证结果',validatorForm)
if(!validatorForm){
return
}
//
if (!this.is_submit) {
return
}
this.is_submit = false
let res = await marketApi.createTask(data)//
this.is_submit = true
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
//-线
//
uni.redirectTo({
url: `/pages/market/clue/index`
})
}, 1000)
},
//
selectCon(type) {
this.options_type = type
switch (type) {
//
case 'entry_type':
this.options = this.options_entry_type
this.show = true
this.linkage = true
//->
this.formData.follow_staff_id = ''
break;
//->
case 'follow_staff_id':
if(!this.formData.entry_type){
uni.showToast({
title: '请先选择跟进类型',
icon: 'none'
})
return
}
if(this.formData.entry_type == 1){
//
this.options = this.options_follow_staff_id_sc
}else{
//
this.options = this.options_follow_staff_id_xs
}
this.show = true
this.linkage = true
break;
//
case 'reminder_time':
this.show_date = true
break;
}
},
//-
changeOptions(e) {
console.log('选择器选中',e)
this.show = false
let type = this.options_type
switch (type) {
//
case 'entry_type':
this.str_entry_type = e.text//text
this.formData.entry_type = e.value//value
break;
//
case 'follow_staff_id':
this.str_follow_staff_id = e.text//text
this.formData.follow_staff_id = e.value//value
break;
}
},
//
cancel() {
this.show = false
},
//-
change_date(e) {
this.show_date = false
//
let type = this.options_type
console.log('时间选择器',type,e)
let val = (e.result ?? '')
if(val){
val = val + ':00'
}
switch (type) {
//->
case 'reminder_time':
this.formData.reminder_time = val
break;
}
},
//
cancel_date() {
this.show_date = false
},
}
}
</script>
<style lang="less" scoped>
.assemble {
width: 100%;
height: 100vh;
background: #292929;
}
.title {
font-size: 26rpx;
color: #fff;
padding: 26rpx 0 26rpx 32rpx;
}
.input-title {
font-size: 26rpx;
color: #fff;
}
.form-style {
width: 100%;
background: #434544;
}
.form-style-vid {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
}
.input-style {
text-align: right !important;
}
.fui-btn__box {
margin: 20rpx auto;
width: 92%;
}
</style>

1149
uniapp/pages/market/clue/order_list.vue

File diff suppressed because it is too large

1014
uniapp/pages/market/clue/writing_followUp.vue

File diff suppressed because it is too large

514
uniapp/pages/market/course/course_detail.vue

@ -1,514 +0,0 @@
<template>
<view class="course-detail-container">
<!-- 课程基本信息 -->
<view class="course-info-card">
<view class="course-header">
<text class="course-title">{{ courseInfo.course_name || '课程详情' }}</text>
<view :class="['course-status',getStatusClass(courseInfo.status)]">
{{ getStatusText(courseInfo.status) }}
</view>
</view>
<view class="course-stats">
<view class="stat-item">
<text class="stat-label">总课时</text>
<text class="stat-value">{{ courseInfo.total_hours || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-label">赠送课时</text>
<text class="stat-value">{{ courseInfo.gift_hours || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-label">已用课时</text>
<text class="stat-value">{{ (courseInfo.use_total_hours || 0) + (courseInfo.use_gift_hours || 0) }}</text>
</view>
<view class="stat-item">
<text class="stat-label">剩余课时</text>
<text class="stat-value remaining">{{ getRemainingHours() }}</text>
</view>
</view>
<view class="course-dates">
<text class="date-item">开始日期{{ courseInfo.start_date || '--' }}</text>
<text class="date-item">结束日期{{ courseInfo.end_date || '--' }}</text>
</view>
</view>
<!-- 课程安排列表 -->
<view class="section-title">
<text class="title-text">课程安排</text>
<text class="title-count">({{ scheduleList.length }})</text>
</view>
<view class="schedule-list">
<view
v-for="(item, index) in scheduleList"
:key="item.id"
class="schedule-item"
@tap="showScheduleDetail(item)"
>
<view class="schedule-date">
<text class="date-text">{{ formatDate(item.course_date) }}</text>
<text class="time-text">{{ item.time_slot }}</text>
</view>
<view class="schedule-info">
<view class="schedule-type">
<text :class="['type-tag',getScheduleTypeClass(item.schedule_type)]">
{{ getScheduleTypeText(item.schedule_type) }}
</text>
<text :class="['type-tag',getCourseTypeClass(item.course_type)]">
{{ getCourseTypeText(item.course_type) }}
</text>
</view>
<view class="schedule-status">
<text :class="['status-tag',getScheduleStatusClass(item.status)]">
{{ getScheduleStatusText(item.status) }}
</text>
</view>
</view>
<view class="schedule-arrow">
<text class="arrow-icon">></text>
</view>
</view>
</view>
<!-- 课程使用记录 -->
<view class="section-title">
<text class="title-text">使用记录</text>
<text class="title-count">({{ usageList.length }})</text>
</view>
<view class="usage-list">
<view
v-for="(item, index) in usageList"
:key="item.id"
class="usage-item"
>
<view class="usage-date">
<text class="date-text">{{ formatDate(item.usage_date) }}</text>
<text class="time-text">{{ item.time_slot || '--' }}</text>
</view>
<view class="usage-info">
<text class="usage-hours">消耗课时{{ item.hours_used || 0 }}</text>
<text class="usage-type">{{ item.usage_type || '正常上课' }}</text>
</view>
<view class="usage-status">
<text class="status-text confirmed">已确认</text>
</view>
</view>
</view>
<!-- 无数据提示 -->
<view v-if="scheduleList.length === 0 && usageList.length === 0" class="no-data">
<text class="no-data-text">暂无课程安排和使用记录</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
courseId: '',
courseInfo: {},
scheduleList: [],
usageList: []
}
},
onLoad(options) {
this.courseId = options.courseId || ''
if (this.courseId) {
this.loadCourseDetail()
}
},
methods: {
//
async loadCourseDetail() {
try {
uni.showLoading({ title: '加载中...' })
const res = await this.$http.get('/xy/course/detail', {
course_id: this.courseId
})
if (res.data.code === 1) {
this.courseInfo = res.data.data.course_info || {}
this.scheduleList = res.data.data.schedule_list || []
this.usageList = res.data.data.usage_list || []
} else {
uni.showToast({
title: res.data.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
console.error('加载课程详情失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
//
getRemainingHours() {
const total = (this.courseInfo.total_hours || 0) + (this.courseInfo.gift_hours || 0)
const used = (this.courseInfo.use_total_hours || 0) + (this.courseInfo.use_gift_hours || 0)
return Math.max(0, total - used)
},
//
getStatusText(status) {
const statusMap = {
1: '有效',
2: '过期',
3: '等待期',
4: '延期'
}
return statusMap[status] || '未知'
},
//
getStatusClass(status) {
const classMap = {
1: 'status-active',
2: 'status-expired',
3: 'status-waiting',
4: 'status-delayed'
}
return classMap[status] || 'status-unknown'
},
//
getScheduleTypeText(type) {
const typeMap = {
1: '临时课',
2: '固定课'
}
return typeMap[type] || '未知'
},
//
getScheduleTypeClass(type) {
const classMap = {
1: 'type-temp',
2: 'type-fixed'
}
return classMap[type] || 'type-unknown'
},
//
getCourseTypeText(type) {
const typeMap = {
1: '加课',
2: '补课',
3: '等待位'
}
return typeMap[type] || '正常课'
},
//
getCourseTypeClass(type) {
const classMap = {
1: 'course-add',
2: 'course-makeup',
3: 'course-waiting'
}
return classMap[type] || 'course-normal'
},
//
getScheduleStatusText(status) {
const statusMap = {
0: '待上课',
1: '已上课',
2: '请假'
}
return statusMap[status] || '未知'
},
//
getScheduleStatusClass(status) {
const classMap = {
0: 'schedule-pending',
1: 'schedule-completed',
2: 'schedule-leave'
}
return classMap[status] || 'schedule-unknown'
},
//
formatDate(dateStr) {
if (!dateStr) return '--'
const date = new Date(dateStr)
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${month}-${day}`
},
//
showScheduleDetail(item) {
//
console.log('查看课程安排详情:', item)
}
}
}
</script>
<style lang="scss" scoped>
.course-detail-container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.course-info-card {
background: white;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.course-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.course-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.course-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-active {
background: #e8f5e8;
color: #52c41a;
}
&.status-expired {
background: #fff2f0;
color: #ff4d4f;
}
&.status-waiting {
background: #f6ffed;
color: #faad14;
}
&.status-delayed {
background: #f0f5ff;
color: #1890ff;
}
}
.course-stats {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.stat-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.stat-value {
font-size: 28rpx;
font-weight: 600;
color: #333;
&.remaining {
color: #1890ff;
}
}
.course-dates {
display: flex;
justify-content: space-between;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.date-item {
font-size: 24rpx;
color: #666;
}
.section-title {
display: flex;
align-items: center;
margin: 30rpx 0 16rpx;
}
.title-text {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.title-count {
font-size: 24rpx;
color: #666;
margin-left: 8rpx;
}
.schedule-list, .usage-list {
background: white;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.schedule-item, .usage-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.schedule-date, .usage-date {
display: flex;
flex-direction: column;
width: 140rpx;
margin-right: 20rpx;
}
.date-text {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 4rpx;
}
.time-text {
font-size: 24rpx;
color: #666;
}
.schedule-info, .usage-info {
flex: 1;
}
.schedule-type {
display: flex;
gap: 8rpx;
margin-bottom: 8rpx;
}
.type-tag, .course-type-tag {
padding: 4rpx 8rpx;
border-radius: 8rpx;
font-size: 20rpx;
&.type-temp {
background: #fff7e6;
color: #fa8c16;
}
&.type-fixed {
background: #f6ffed;
color: #52c41a;
}
&.course-add {
background: #e6f7ff;
color: #1890ff;
}
&.course-makeup {
background: #f9f0ff;
color: #722ed1;
}
&.course-waiting {
background: #fff2f0;
color: #ff4d4f;
}
}
.schedule-status, .usage-status {
display: flex;
align-items: center;
}
.status-text {
font-size: 24rpx;
font-weight: 500;
&.schedule-pending {
color: #faad14;
}
&.schedule-completed {
color: #52c41a;
}
&.schedule-leave {
color: #ff4d4f;
}
&.confirmed {
color: #52c41a;
}
}
.schedule-arrow {
margin-left: 16rpx;
}
.arrow-icon {
font-size: 24rpx;
color: #ccc;
}
.usage-hours {
font-size: 26rpx;
color: #333;
margin-bottom: 4rpx;
}
.usage-type {
font-size: 24rpx;
color: #666;
}
.no-data {
text-align: center;
padding: 100rpx 0;
}
.no-data-text {
font-size: 24rpx;
color: #999;
}
</style>

836
uniapp/pages/market/data/index.vue

@ -1,836 +0,0 @@
<!--数据-首页-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="title">数据</view>
</view>
<!-- 市场人员展示-->
<view v-if="infoData.role_type == 'market_type'">
<view class="count_section">
<view class="title_box">业绩统计</view>
<view class="box_1">
<view class="left">
<view class="charts-box">
<qiun-data-charts
type="ring"
:opts="opts"
:chartData="chartData"
/>
</view>
</view>
<view class="right">
<view class="title">本周已分配</view>
<view class="content">
<text class="strong">{{infoData.num_1}}</text>
</view>
<view class="title">本周未分配</view>
<view class="content">
<text class="strong">{{infoData.num_2}}</text>
<!-- <text>较上月</text>-->
</view>
<view class="legeng">
<view class="item">
<view class="piece" style="background-color: #45c59f;"></view>
<view class="lable">已分配</view>
<view class="item">
<view class="piece" style="background-color:#02a7f0;"></view>
<view class="lable">未分配</view>
</view>
</view>
</view>
</view>
</view>
<view class="title_box" v-show="box_2_show">新客签到</view>
<view class="box_2" v-show="box_2_show">
<view class="progress-container">
<view :style="{ width: progress + '%' }" class="progress-bar">
</view>
<view class="dian" :style="{ left: (progress - 2) + '%' }"></view>
</view>
<view class="progress-text">
<text>0</text>
<text>50</text>
<text>100</text>
</view>
</view>
</view>
<view class="main_section">
<view class="tag_section">
<view :class="['left',tagType=='1'?'select':'']" @click="changeTag('1')">统计分析</view>
<view :class="['right',tagType=='2'?'select':'']" @click="changeTag('2')">统计排名</view>
</view>
<!-- 销售分析-->
<view class="section_box_1" v-if="tagType=='1'">
<view class="left">
<qiun-data-charts
type="funnel"
:opts="opts_2"
:chartData="chartData_2"
/>
</view>
<view class="right">
<view class="item">
<view class="title" style="color: #12E7E8;">
已分配<text>({{infoData.num_1_rate}}%)</text>
</view>
<view class="title" style="color: #12E7E8;">
{{infoData.num_1}}<text></text>
</view>
</view>
<view class="item">
<view class="title" style="color: #4DA3FF;">
未分配<text>({{infoData.num_2_rate}}%)</text>
</view>
<view class="title" style="color: #4DA3FF;">
{{infoData.num_2}}<text></text>
</view>
</view>
<view class="item">
<view class="title" style="color: #FFCB31;">
本周拉新<text>({{infoData.num_3_rate}}%)</text>
</view>
<view class="title" style="color: #FFCB31;">
{{infoData.total_1}}<text></text>
</view>
</view>
</view>
</view>
<!-- 销售排名-->
<view class="section_box_2" v-else>
<view class="itme" v-for="(v,k) in infoData.staff_list" :key="k">
<view class="title">{{k+1}} {{v.name}}</view>
<view class="money">{{v.goal}}</view>
<view class="plan">
<fui-progress :percent="getPercent(v.wx_yj,v.goal)" height="15" radius="100" background="#e4e4e4" activeColor="#4bced0"></fui-progress>
</view>
</view>
</view>
</view>
</view>
<!-- 销售人员展示-->
<view v-else>
<view class="count_section">
<view class="title_box">业绩统计</view>
<view class="box_1">
<view class="left">
<view class="charts-box">
<qiun-data-charts
type="ring"
:opts="opts"
:chartData="chartData"
/>
</view>
</view>
<view class="right">
<view class="title">已成交</view>
<view class="content">
<text class="strong">{{infoData.num_1}}</text>
</view>
<view class="title">未成交</view>
<view class="content">
<text class="strong">{{infoData.num_2}}</text>
<!-- <text>较上月</text>-->
</view>
<view class="legeng">
<view class="item">
<view class="piece" style="background-color: #45c59f;"></view>
<view class="lable">已成交</view>
<view class="item">
<view class="piece" style="background-color:#02a7f0;"></view>
<view class="lable">未成交</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="main_section">
<view class="tag_section">
<view :class="['left',tagType=='1'?'select':'']" @click="changeTag('1')">统计分析</view>
<view :class="['right',tagType=='2'?'select':'']" @click="changeTag('2')">统计排名</view>
</view>
<!-- 销售分析-->
<view class="section_box_1" v-if="tagType=='1'">
<view class="left">
<qiun-data-charts
type="funnel"
:opts="opts_2"
:chartData="chartData_2"
/>
</view>
<view class="right">
<view class="item">
<view class="title" style="color: #12E7E8;">
已成交<text>({{infoData.num_1_rate}}%)</text>
</view>
<view class="title" style="color: #12E7E8;">
{{infoData.num_1}}<text></text>
</view>
</view>
<view class="item">
<view class="title" style="color: #4DA3FF;">
未成交<text>({{infoData.num_2_rate}}%)</text>
</view>
<view class="title" style="color: #4DA3FF;">
{{infoData.num_2}}<text></text>
</view>
</view>
<view class="item">
<view class="title" style="color: #FFCB31;">
本周分配<text>({{infoData.num_3_rate}}%)</text>
</view>
<view class="title" style="color: #FFCB31;">
{{infoData.total_1}}<text></text>
</view>
</view>
</view>
</view>
<!-- 销售排名-->
<view class="section_box_2" v-else>
<view class="itme" v-for="(v,k) in infoData.staff_list" :key="k">
<view class="title">{{k+1}} {{v.name}}</view>
<view class="money">{{v.goal}}</view>
<view class="plan">
<fui-progress :percent="getPercent(v.wx_yj,v.goal)" height="15" radius="100" background="#e4e4e4" activeColor="#4bced0"></fui-progress>
</view>
</view>
</view>
</view>
</view>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
// import marketApi from '@/api/market.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
infoData:{
staff_list:[],//
},//
//
chartData: {},
opts: {
rotate: false, //
rotateLock: false, //
color: [
"#e9e9e9", // 1
"#02a7f0", // 2
"#45c59f", // 3
],
padding: [0, 0, 0, 0], // [, , , ]
dataLabel: true, //
enableScroll: false, //
legend: {
show: false, //
// position: "right", //
// lineHeight: 25 //
},
title: {
name: "本周分析", //
fontSize: 16, //
color: "#666666" //
},
subtitle: {
name: "0%", //
fontSize: 18, //
color: "#7cb5ec" //
},
extra: {
ring: {
ringWidth: 17, //
activeOpacity: 0.5, //
activeRadius: 5, // TooltiplabelWidth
offsetAngle: 0, //
labelWidth: 1, //
border: true, //
customRadius:68,//
borderWidth: 2, // 线
borderColor: "#fff" //
}
}
},
//
progress: 50, // 50%
box_2_show: false,//|true=,false=
tagType:'1',//1=,2=
//
chartData_2: {},
opts_2: {
//
// color: ["#12E7E8","#4DA3FF","#FFCB31"],
//
padding: [0,0,0,0],
// false
enableScroll: false,
legend:{
show:true//
},
//
extra: {
//
funnel: {
//
activeOpacity: 0.3,
//
activeWidth: 10,
//
border: true,
//
borderWidth: 2,
//
borderColor: "#FFFFFF",
//
fillOpacity: 1,
// left
labelAlign: "right",
// pyramid
type: "pyramid"
}
}
},
userInfo: {},//
}
},
onLoad() {},
onShow() {
this.init()
},
methods: {
async init(){
await this.getUserInfo()
await this.getPerformance()
},
//
async getUserInfo(){
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.userInfo = res.data
},
//
async getPerformance(){
let role_key_arr = this.userInfo.role_key_arr.join(',')
let params = {
personnel_id:this.userInfo.id,//id
role_key_arr: role_key_arr, // key
}
let res= await apiRoute.xs_statisticsMarketData(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
});
return
}
// console.log('xx',res)
this.infoData = res.data
if(this.infoData.role_type == 'market_type'){
//
//
let chartData_1 = {
series: [
{
data: [
{
"name": "已分配",
"value": this.infoData.num_1_rate,
"labelShow": false
},
{
"name": "未分配",
"value": this.infoData.num_2_rate,
"labelShow": false
},
{
"name": "总人数",
"value": this.infoData.num_3_rate,
"labelShow": false
},
]
}
]
};
this.chartData = JSON.parse(JSON.stringify(chartData_1));
this.opts.subtitle = {
name: `${this.infoData.num_4_rate}%`, //
fontSize: 18, //
color: "#7cb5ec" //
}
//
let chartDataB = {
series: [
{
data: [
{
"name": "本周拉新", //
"centerText": this.infoData.total_1, //
"value": this.infoData.num_3_rate, // 50
// "labelText":''
"labelShow":false,
"color": "#FFCB31", //
},
{
"name": "未分配",
"centerText": this.infoData.num_2,
"value": this.infoData.num_2_rate,
// "labelText":""
"labelShow":false,
"color": "#4DA3FF", //
},
{
"name": "已分配",
"centerText": this.infoData.num_1,
"value": this.infoData.num_1_rate,
// "labelText":""
"labelShow":false,
"color": "#12E7E8", //
}
]
}
]
};
this.chartData_2 = JSON.parse(JSON.stringify(chartDataB));
}else{
//
let chartData_1 = {
series: [
{
data: [
{
"name": "已成交",
"value": this.infoData.num_1_rate,
"labelShow": false
},
{
"name": "未成交",
"value": this.infoData.num_2_rate,
"labelShow": false
},
{
"name": "总人数",
"value": this.infoData.num_3_rate,
"labelShow": false
},
]
}
]
};
this.chartData = JSON.parse(JSON.stringify(chartData_1));
this.opts.subtitle = {
name: `${this.infoData.num_4_rate}%`, //
fontSize: 18, //
color: "#7cb5ec" //
}
//
let chartDataB = {
series: [
{
data: [
{
"name": "本周成交", //
"centerText": this.infoData.total_1, //
"value": this.infoData.num_3_rate, // 50
// "labelText":''
"labelShow":false,
"color": "#FFCB31", //
},
{
"name": "未成交",
"centerText": this.infoData.num_2,
"value": this.infoData.num_2_rate,
// "labelText":""
"labelShow":false,
"color": "#4DA3FF", //
},
{
"name": "已成交",
"centerText": this.infoData.num_1,
"value": this.infoData.num_1_rate,
// "labelText":""
"labelShow":false,
"color": "#12E7E8", //
}
]
}
]
};
this.chartData_2 = JSON.parse(JSON.stringify(chartDataB));
}
},
//
getPercent(num1,total){
//
if(!total){
return 0
}
let num_percent = (num1 / total) * 100;
if(num_percent <= 0){
num_percent = 0
}else{
num_percent = Math.ceil((num1 / total) * 100);
}
// console.log('qqq',[num_percent,num1,total])
return num_percent
},
//tag
changeTag(type){
this.tagType = type
},
//
openViewArrivalStatistics(){
this.$navigateTo({
url: '/pages/market/my/arrival_statistics'
})
},
//
openViewDueSoon(){
this.$navigateTo({
url: '/pages/market/my/due_soon'
})
},
//
openViewSchoolingStatistics(){
this.$navigateTo({
url: '/pages/market/my/schooling_statistics'
})
},
//
openViewFeedback(){
this.$navigateTo({
url: '/pages/common/feedback'
})
},
//
openViewMyInfo(){
this.$navigateTo({
url: '/pages/market/my/info'
})
},
//
openViewFirmInfo(){
this.$navigateTo({
url: '/pages/market/my/firm_info'
})
},
//
openViewSetUp(){
this.$navigateTo({
url: '/pages/market/my/set_up'
})
}
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929;
min-height: 100%;
}
//
.navbar_section{
border: 1px solid #292929;
display: flex;
justify-content: center;
align-items: center;
background: #292929;
position: relative;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
.statistics-btn {
position: absolute;
right: 20rpx;
background-color: #29D3B4;
padding: 10rpx 20rpx;
border-radius: 30rpx;
font-size: 24rpx;
color: #fff;
}
}
//
.count_section{
padding: 20rpx 46rpx;
padding-bottom: 60rpx;
background-color: #434544;
color: #fff;
.title_box{
font-size: 26rpx;
}
.box_1{
margin-top: 25rpx;
display: flex;
justify-content: space-between;
align-items: center;
gap: 30rpx;
.left{
width: 280rpx; /* 设置宽度为 280rpx */
height: 280rpx; /* 设置高度为 280rpx */
.charts-box {
width: 280rpx; /* 设置宽度为 280rpx */
height: 280rpx; /* 设置高度为 280rpx */
}
}
.right{
font-size: 24rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.title{}
.content{
display: flex;
gap: 40rpx;
.strong{
font-size: 30rpx;
}
.text{
font-size: 24rpx;
}
}
.legeng{
display: flex;
align-items: center;
gap: 50rpx;
.item{
display: flex;
align-items: center;
gap: 20rpx;
.piece{
width: 20rpx;
height: 20rpx;
}
}
}
}
}
//
.box_2 {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20rpx;
.progress-container {
position: relative;
width: 80%;
height: 4rpx;
background-color: #ccc;
border-radius: 2rpx;
//overflow: hidden;
margin-bottom: 10rpx;
.progress-bar {
height: 100%;
background-color: #45c59f;
border-radius: 2rpx;
}
.dian{
position: absolute;
top: -9rpx;
left: 0rpx;
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background-color: #fff;
}
}
.progress-text {
margin-left: 5%;
width: 85%;
display: flex;
justify-content: space-between;
text {
font-size: 26rpx;
color: #fff;
}
}
}
}
.main_section{
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #333333;
display: flex;
flex-direction: column;
gap: 22rpx;
.tag_section{
display: flex;
justify-content: center;
align-items: center;
view{
border: 1px solid #969696;
width: 258rpx;
height: 72rpx;
line-height: 72rpx;
background-color: #292929;
text-align: center;
font-size: 26rpx;
color: #fff;
}
.left{
border-radius: 22rpx 0rpx 0rpx 22rpx;
}
.right{
border-radius: 0rpx 22rpx 22rpx 0rpx;
}
.select{
background-color: #fff;
color: #29D3B4;
}
}
.section_box_1{
margin-top: 38rpx;
padding: 66rpx 14rpx;
border-radius: 12rpx;
background-color: #434544FF;
color: #d9dada;
font-size: 26rpx;
text-align: center;
height: 600rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left{
width: 70%;
}
.right{
display: flex;
flex-direction: column;
gap: 20rpx;
.item{
display: flex;
flex-direction: column;
gap: 10rpx;
.title{
font-size: 26rpx;
text-align: left;
text{
margin-left: 5rpx;
color: #d9dada;
}
}
}
}
}
.section_box_2{
color: #fff;
margin-top: 38rpx;
padding: 20rpx 42rpx;
background-color: #434544;
border-radius: 12rpx;
font-size: 26rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.itme{
display: flex;
flex-direction: column;
gap: 15rpx;
.money{
display: flex;
justify-content: flex-end;
}
}
}
}
</style>

390
uniapp/pages/market/home/index.vue

@ -1,390 +0,0 @@
<template>
<view class="assemble">
<view style="height: 20rpx;"></view>
<!-- 市场人员展示-->
<view class="div-style">
<view style="height: 38vh;">
<view style="display: flex;align-items: center;padding: 20rpx 0 0 20rpx;">
<view>
<image :src="$util.img('/uniapp_src/static/images/index/danlan.png')" class="drop-image">
</image>
</view>
<view class="title">本月业绩</view>
</view>
<view class="coach-message">
<view class="left1">
<view style="padding: 20rpx 0;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/huang.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">资源总数</view>
</view>
<view class="title-x1">{{infoData.month.new_total}}</view>
</view>
<view>
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/lvs.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">已分配</view>
</view>
<view class="title-x1">{{infoData.month.new_total}}</view>
</view>
<view>
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/shenlan.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">昨日新增</view>
</view>
<view class="title-x1">{{infoData.month.yesterday_new}}</view>
</view>
<view>
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/lan.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">今日新增</view>
</view>
<view class="title-x1">{{infoData.month.today_new}}</view>
</view>
</view>
<!-- 统计图-->
<view class="right1">
<view style="text-align: center;">{{infoData.date_range}}</view>
<view class="statistics_box">
<view class="item">
<view class="box">
<view class="progress-bar"
:style="{ height: `${infoData.month.yesterday_new_rate}%`, background: '#f59a23' }">
</view>
<view class="ratio"
:style="{ color: infoData.month.yesterday_new_rate <= 0 ? '#333333' : '#000' }">
{{ infoData.month.yesterday_new_rate }}%
</view>
</view>
<view class="title">昨日</view>
</view>
<view class="item">
<view class="box">
<view class="progress-bar"
:style="{ height: `${infoData.month.assigned_sales_rate}%`, background: '#039f64' }">
</view>
<view class="ratio"
:style="{ color: infoData.month.assigned_sales_rate <= 0 ? '#333333' : '#000' }">
{{ infoData.month.assigned_sales_rate }}%
</view>
</view>
<view class="title">分配</view>
</view>
<view class="item">
<view class="box">
<view class="progress-bar"
:style="{ height: `${infoData.month.today_new_rate}%`, background: '#4066f2' }">
</view>
<view class="ratio"
:style="{ color: infoData.month.today_new_rate <= 0 ? '#333333' : '#000' }">
{{ infoData.month.today_new_rate }}%
</view>
</view>
<view class="title">今日</view>
</view>
</view>
</view>
</view>
</view>
<view style="width: 90%;background: #EFF3F8;height: 4rpx;margin: auto;"></view>
<view style="height: 38vh;">
<view style="display: flex;align-items: center;padding: 20rpx 0 0 20rpx;">
<view>
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')" class="drop-image"></image>
</view>
<view class="title">个人业绩</view>
</view>
<view class="coach-message">
<view class="this_month">
<view style="padding: 20rpx 0;display: flex;justify-content: space-between;">
<view style="width: 48%;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">今日新增资源</view>
</view>
<view class="title-x1">{{infoData.last_month.xzzy}}</view>
</view>
<view style="width: 48%;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">今日业绩收入</view>
</view>
<view class="title-x1">{{infoData.last_month.yjsr}}</view>
</view>
</view>
<view style="padding: 20rpx 0;display: flex;justify-content: space-between;">
<view style="width: 48%;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">历史关单数量</view>
</view>
<view class="title-x1">{{infoData.last_month.gdsl}}</view>
</view>
<view style="width: 48%;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">其他奖励</view>
</view>
<view class="title-x1">{{infoData.last_month.qtjl}}</view>
</view>
</view>
<view style="padding: 20rpx 0;display: flex;justify-content: space-between;">
<view style="width: 48%;">
<view style="display: flex;align-items: center;">
<view style="padding: 12rpx;">
<image :src="$util.img('/uniapp_src/static/images/index/danlv.png')"
class="drop-image-x"></image>
</view>
<view class="title-x">本月提成</view>
</view>
<view class="title-x1">{{infoData.last_month.bytc}}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<AQTabber />
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
infoData: {}, //
userInfo: {}, //
}
},
onShow() {
this.init()
},
methods: {
async init() {
await this.getUserInfo()
await this.getXsIndex()
},
//
async getUserInfo() {
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.userInfo = res.data
},
//
async getXsIndex() {
let role_key_arr = this.userInfo.role_key_arr.join(',')
let params = {
personnel_id: this.userInfo.id, //id
role_key_arr: role_key_arr, // key
}
let res = await apiRoute.xs_statisticsMarketHome(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.infoData = res.data
console.log('统计', this.infoData)
},
}
}
</script>
<style lang="less" scoped>
//
.navbar_section {
border: 1px solid #fff;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #858585;
}
}
.assemble {
width: 100%;
height: 100vh;
background: #292929;
}
.div-style {
width: 92%;
height: 85vh;
background: #fff;
border-radius: 16rpx;
margin: auto;
}
.coach-message {
width: 92%;
margin: 10rpx auto;
display: flex;
align-items: center;
padding-top: 20rpx;
}
.drop-image {
width: 50rpx;
height: 50rpx;
}
.title {
font-size: 30rpx;
color: #7F7F7F;
padding-left: 20rpx;
}
.left1 {
width: 48%;
height: 95%;
margin: auto;
}
.right1 {
width: 48%;
height: 95%;
margin: auto;
.statistics_box {
margin: auto;
margin-top: 10rpx;
display: flex;
justify-content: space-between;
.item {
width: 90rpx;
display: flex;
flex-direction: column;
align-items: center;
.box {
width: 100%;
height: 328rpx;
border: 1px solid #ddd;
border-radius: 6rpx;
background: #f5f5f5;
position: relative;
.progress-bar {
width: 100%;
height: 0;
transition: height 0.3s ease;
position: absolute;
bottom: 0;
}
.ratio {
width: 100%;
position: absolute;
bottom: -0rpx;
font-size: 26rpx;
text-align: center;
}
}
.title {
margin-top: 5rpx;
padding: 0;
font-size: 26rpx;
color: #999999;
;
text-align: center;
}
}
}
}
.this_month {
width: 100%;
height: 95%;
margin: auto;
}
.drop-image-x {
width: 20rpx;
height: 20rpx;
}
.title-x {
font-size: 28rpx;
color: #7F7F7F;
padding-left: 20rpx;
}
.title-x1 {
font-size: 28rpx;
color: #333333;
padding-left: 60rpx;
}
</style>

6
uniapp/pages/market/index/index.vue

@ -111,19 +111,13 @@
</view>
</view>
</view>
<AQTabber />
</view>
</template>
<script>
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js'
export default {
components: {
AQTabber,
},
data() {
return {
currentDate: this.formatDate(new Date()),

207
uniapp/pages/market/my/firm_info.vue

@ -1,207 +0,0 @@
<!--企业信息-->
<template>
<view class="assemble">
<view style="height: 30rpx;"></view>
<scroll-view
class="ul"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData_1"
style="height: 80vh;"
>
<view class="li" v-for="(v,k) in tableList" :key="k">
<!--企业图-->
<view class="section_1">
<image class="pic" v-if="v.campus_preview_image" :src="v.campus_preview_image" mode="aspectFit"></image>
</view>
<view class="title_section">基本信息</view>
<view class="section_2">
<view class="item">
<view class="title">企业名称</view>
<view class="content">{{v.campus_name}}</view>
</view>
<view class="item">
<view class="title">企业地址</view>
<view class="content">{{v.campus_address}}</view>
</view>
</view>
<view class="title_section">企业地图</view>
<view class="section_3">
<view class="map_box">
<map
:id="`shopMap_${k}`"
:latitude="v.campus_coordinates_arr.lat"
:longitude="v.campus_coordinates_arr.lng"
:markers="getMarkers(k,v)"
:scale="15"
:show-location="false"
style="width: 90%; height: 400rpx;"
></map>
</view>
</view>
<view class="title_section">企业介绍</view>
<view class="section_4">
<view class="html" v-html="v.
campus_introduction"></view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
dataInfo:{},//
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
personnel_id:'',
},
//
tableList:[],
//
//
mapControls:{
position:{
},
}
}
},
onShow(){
this.init()
},
methods: {
//
async init(){
await this.getUserInfo()
await this.getPersonnelCampus()
},
//
async getUserInfo(){
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.filteredData.personnel_id = res.data.id//id
},
//
async getPersonnelCampus(){
let params = {...this.filteredData}
let res = await apiRoute.common_getPersonnelCampus(params)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = res.data
console.log(123,this.tableList)
},
//
getMarkers(id,item) {
return [{
id: id,
latitude: item.campus_coordinates_arr.lat,
longitude: item.campus_coordinates_arr.lng,
name: item.campus_address,
iconPath: '/static/icon-img/ding_wei.png'
}];
},
}
}
</script>
<style lang="less" scoped>
.assemble{
width: 100%;
min-height: 100vh;
background: #333333;
color: #fff;
}
.ul{
display: flex;
flex-direction: column;
gap: 40rpx;
.li{
border-top: 1px solid #434544;
.section_1{
.pic{
width: 100%;
height: 300rpx;
}
}
.title_section{
padding: 20rpx 40rpx;
color: #fff;
font-size: 24rpx;
}
.section_2{
background-color: #434544;
display: flex;
flex-direction: column;
.item{
padding: 40rpx;
padding-left: 40rpx;
display: flex;
gap: 30rpx;
}
}
.section_3{
padding: 20rpx 22rpx;
.html{
color: #fff;
}
.map_box{
color: #fff;
display: flex;
justify-content: center;
}
}
.section_4{
padding: 20rpx 22rpx;
padding-bottom: 250rpx;
.html{
color: #fff;
}
}
}
.li:nth-child(1){
border: 0px solid #434544;
}
}
</style>

177
uniapp/pages/parent/contracts/contract-detail.vue

@ -1,177 +0,0 @@
<!--合同详情页面-->
<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>

187
uniapp/pages/parent/courses/course-detail.vue

@ -1,187 +0,0 @@
<!--课程详情页面-->
<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>

181
uniapp/pages/parent/materials/material-detail.vue

@ -1,181 +0,0 @@
<!--教学资料详情页面-->
<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>

174
uniapp/pages/parent/messages/message-detail.vue

@ -1,174 +0,0 @@
<!--消息详情页面-->
<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>

182
uniapp/pages/parent/orders/order-detail.vue

@ -1,182 +0,0 @@
<!--订单详情页面-->
<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>

159
uniapp/pages/parent/services/service-detail.vue

@ -1,159 +0,0 @@
<!--服务详情页面-->
<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>

1
uniapp/pages/student/index/index.vue

@ -161,7 +161,6 @@
</view>
<view style="height: 180rpx;width: 100%;"></view>
</view>
<AQTabber />
</view>
</template>

272
uniapp/pages/student/index/job_list.vue

@ -1,272 +0,0 @@
<template>
<view class="main_box">
<view class="after-class-title">
<view class="after-class-title-left">课后作业</view>
</view>
<scroll-view
class="table_list"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 100vh;"
>
<view class="con-list" v-for="(v,k) in tableList" :key="k" @click="openViewWorkDetails(v)">
<view class="con-list-img" v-if="v.content_text">
<video v-if="v.content_type == 2" class="pic" style="width: 100%;border-radius: 15rpx;" :src="$util.img(v.content_text)"></video>
<image v-else-if="v.content_type == 1" style="width: 100%;border-radius: 15rpx;" :src="$util.img(v.content_text)" mode="aspectFit"></image>
<view class="text" v-else>{{v.content_text}}</view>
</view>
<view class="date_box">
<view class="describe" style="margin-top: 20rpx;">
时间{{v.created_at}}
</view>
<!--是否已经完成作业-->
<view class="mark" v-if="v.status == 3">
<image class="check_mark" :src="$util.img('/uniapp_src/static/images/index/check_mark.png')"></image>
</view>
</view>
<!--作业描述-->
<view class="con" style="margin-bottom: 20rpx; color:#fff;" v-html="v.description"></view>
<view class="assignment">
<view>{{v.type == 1 ? '班级作业' : '个人作业'}}</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import memberApi from '@/api/member.js';
export default {
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
resources_id: '',//id
status: '',// 1 2 3
},
tableList:[],//
member_info:{},//
}
},
onShow(){
this.init()//
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init(){
await this.memberInit()
await this.getList();
},
//
async memberInit() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.member_info = res.data
this.filteredData.resources_id = this.member_info.id,//id
console.log('xxxx',this.member_info)
},
//()
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getList(){
this.loading = true
let data = {...this.filteredData}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
if(data.page == 1){
this.tableList = []
}
let res = await apiRoute.xy_assignment(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
console.log('列表',this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//-
openViewWorkDetails(item) {
let id = item.id
this.$navigateTo({
url: `/pages/student/index/work_details?id=${id}`
})
},
},
}
</script>
<style lang="less" scoped>
.main_box{
width: 100%;
height: 100vh;
background: #292929;
overflow: auto;
.table_list{
padding-bottom: 150rpx;
}
}
.after-class-title {
width: 92%;
display: flex;
justify-content: space-between;
margin: auto;
padding-top: 30rpx;
}
.after-class-title-left {
color: #fff;
font-size: 35rpx;
border-bottom: 4rpx #29d3b4 solid;
}
.con-list{
width: 100%;
padding: 30rpx;
margin-top: 20rpx;
border-bottom: 2rpx #fff solid;
.date_box{
padding-top: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
.describe{
width: 75%;
color: #fff;
}
}
}
.con-list-img{
width: 100%;
display: flex;
align-items: center;
justify-content: center;
video{
width: 100%;
height: 280rpx;
}
image{
width: 100%;
height: 280rpx;
}
.text{
width: 100%;
font-size: 28rpx;
color:#fff;
white-space: pre-wrap;
word-wrap: break-word;
}
}
.pic{
width: 85%;
height: 100%;
}
.con{
width: 75%;
color: #fff;
padding-top: 30rpx;
}
.mark{
width: 70rpx;
height: 70rpx;
border-radius: 100%;
background: #29d3b4;
display: flex;
align-items: center;
justify-content: center;
}
.check_mark{
width: 80%;
height: 80%;
}
.assignment{
display: flex;
justify-content: flex-end;
align-items: center;
view{
padding: 6rpx 12rpx;
color: #6b9d53;
border: 2rpx #6b9d53 solid;
border-radius: 10rpx;
width: 150rpx;
text-align: center;
}
}
</style>

114
uniapp/pages/student/index/work_details.vue

@ -1,114 +0,0 @@
<!--作业详情-->
<template>
<view class="main_box">
<!-- 作业展示-->
<view class="top-style" v-if="infoData.content_text">
<video v-if="infoData.content_type == 2" class="pic" style="width: 100%;border-radius: 15rpx;" :src="$util.img(infoData.content_text)"></video>
<image v-else-if="infoData.content_type == 1" style="width: 100%;border-radius: 15rpx;" :src="$util.img(infoData.content_text)" mode="aspectFit"></image>
</view>
<!-- 简练信息+作业描述-->
<view class="below-style">
<view class="head-img">
<!--学生头像-->
<fui-avatar width="80" :src="infoData.student.customerResources.member.headimg ? infoData.student.customerResources.member.headimg : $util.img('/uniapp_src/static/images/common/yong_hu.png')"></fui-avatar>
<view class="head-text">{{infoData .student.name}}</view>
</view>
<view class="multi-line-ellipsis">
状态{{ infoData.status == 1 ? '待批改' : infoData.status == 2 ? '未提交' : infoData.status == 3 ? '已提交' :''}} </view>
<view class="multi-line-ellipsis" v-html="infoData.description"></view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
//
filteredData: {
id: '',//id
},
infoData:{},
}
},
onLoad(options) {
this.filteredData.id = options.id//id
},
onShow(){
this.init()//
},
methods: {
//
async init(){
this.getAssignmentsInfo()
},
//
async getAssignmentsInfo() {
let params = {...this.filteredData}
let res = await apiRoute.xy_assignmentsInfo(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.infoData = res.data
},
}
}
</script>
<style lang="less" scoped>
.main_box{
width: 100%;
height: 100%;
background: #292929;
padding-top: 45rpx;
}
.top-style{
width: 92%;
height: 700rpx;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
.top-style-img{
width: 120rpx;
height: 120rpx;
}
.below-style{
width: 92%;
margin: auto;
.head-img {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
}
.head-text {
color: #fff;
font-size: 35rpx;
padding-left: 20rpx;
}
}
.multi-line-ellipsis {
margin-top: 20rpx;
color: #fff;
font-size: 29rpx;
padding: 5rpx 10rpx;
// display: -webkit-box;
// -webkit-box-orient: vertical;
// -webkit-line-clamp: 2;
// overflow: hidden;
// text-overflow: ellipsis;
}
</style>

379
uniapp/pages/student/my/my.vue

@ -1,379 +0,0 @@
<!--我的-首页-->
<template>
<view class="main_box">
<view class="navbar_section">
<view class="title">我的</view>
</view>
<view style="background:#29D3B4;">
<!--用户信息-->
<view class="user_section">
<view class="box">
<view class="left" @click="personal_data">
<image class="pic" :src="member_info.memberHasOne ? member_info.memberHasOne.headimg : $util.img('/uniapp_src/static/images/common/yong_hu.png')"></image>
<view class="name">{{member_info.name}}</view>
</view>
<view class="right" @click="setup">
<image :src="$util.img('/uniapp_src/static/images/index/setup.png')" style="width: 50rpx;height: 50rpx;"></image>
</view>
</view>
</view>
<!--统计信息-->
<view class="count_section">
<view class="main">
<view class="course_box">
<view class="top">
<view class="item">
<view class="num">{{member_info.classes_count}}</view>
<view class="intro">我的课程</view>
</view>
<view class="item">
<view class="num">{{member_info.sign_count}}</view>
<view class="intro">已上课时</view>
</view>
<view class="item">
<view class="num">{{member_info.stay_sign_count}}</view>
<view class="intro">待上课时
</view>
</view>
</view>
</view>
</view>
<view class="bottom"></view>
<view class="bg_box bg_top"></view>
<view class="bg_box bg_bottom"></view>
</view>
</view>
<view class="main_section">
<view class="section_box">
<view class="item" style="border-radius: 16rpx 16rpx 0 0;" @click="lesson_consumption">
<view>课时消耗</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
<view class="item" @click="openViewOrder()">
<view>我的订单</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
<view class="item" @click="navigateToTimetable()">
<view>我的课表</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
</view>
<view class="section_box">
<view class="item" @click="openViewMyCoach()">
<view>我的教练</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
<view class="item" @click="navigateToHomework()">
<view>作业管理</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
<view class="item" @click="feedback">
<view>意见反馈</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
<view class="item" @click="openViewMyMessage({user_id:1})">
<view>我的消息</view>
<image :src="$util.img('/uniapp_src/static/images/index/right_arrow.png')" class="arrow-icon"></image>
</view>
</view>
</view>
<!-- 底部导航-->
<AQTabber />
</view>
</template>
<script>
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
import member from '@/api/member.js';
export default {
components: {
AQTabber,
},
data() {
return {
member_info: [],
}
},
onLoad() {
this.member_init();
},
methods: {
//
async member_init() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.member_info = res.data
},
//
setup(item) {
this.$navigateTo({
url: '/pages/student/my/set_up'
})
},
//
feedback() {
this.$navigateTo({
url: '/pages/common/feedback'
})
},
//
lesson_consumption() {
this.$navigateTo({
url: '/pages/student/my/lesson_consumption'
})
},
//
my_members() {
this.$navigateTo({
url: '/pages/student/my/my_members'
})
},
//
personal_data() {
this.$navigateTo({
url: '/pages/student/my/personal_data'
})
},
//-
openViewOrder() {
let resource_id = this.member_info.id//id
let resource_name = this.member_info.name || ''//id
// let staff_id = this.userInfo.id//id
// let staff_id_name = this.userInfo.name || ''//
let staff_id = ''//id
let staff_id_name = ''//
this.$navigateTo({
url: `/pages/common/contract_list?resource_id=${resource_id}&resource_name=${resource_name}&staff_id=${staff_id}&staff_id_name=${staff_id_name}`
})
},
//-
openViewMyMessage(item) {
this.$navigateTo({
url: `/pages/common/my_message`
})
},
//-
openViewMyCoach(){
this.$navigateTo({
url: `/pages/student/my/my_coach`
})
},
//-
navigateToTimetable(){
this.$navigateTo({
url: `/pages/student/timetable/index`
})
},
//-
navigateToHomework(){
this.$navigateTo({
url: `/pages/student/index/job_list`
})
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
height: 100%;
}
//
.navbar_section {
border: 1px solid #29D3B4;
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;
}
}
//
.user_section {
background-color: #29D3B4;
padding-top: 58rpx;
padding-bottom: 42rpx;
color: #fff;
font-size: 28rpx;
.box {
padding-left: 19rpx;
padding-right: 29rpx;
display: flex;
justify-content: space-between;
align-items: center;
gap: 15rpx;
.left {
display: flex;
align-items: center;
gap: 20rpx;
.pic {
width: 144rpx;
height: 144rpx;
border-radius: 50%;
}
.name {
font-size: 28rpx;
}
}
.right {
display: flex;
flex-direction: column;
gap: 20rpx;
.btn {
min-height: 28rpx;
font-size: 28rpx;
}
}
}
}
//
.count_section {
position: relative;
.main {
position: relative;
z-index: 2;
padding: 0rpx 24rpx;
display: flex;
justify-content: center;
.course_box {
padding: 42rpx 28rpx;
width: 692rpx;
border-radius: 20rpx;
background-color: #fff;
display: flex;
flex-direction: column;
gap: 32rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
.item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
.num {
color: #29D3B4;
font-size: 56rpx;
}
.intro {
color: #AAAAAA;
font-size: 24rpx;
}
}
}
}
}
.bg_box {
z-index: 1;
width: 100%;
height: 150rpx;
}
.bg_top {
position: absolute;
top: 0;
background-color: #29D3B4;
}
.bg_bottom {
top: 50%;
position: absolute;
background-color: #292929;
}
}
.main_section {
margin-top: 20rpx;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #333333;
display: flex;
flex-direction: column;
gap: 22rpx;
.section_box {
margin-bottom: 10rpx;
background: #fff;
border-radius: 16rpx;
padding: 6rpx 24rpx;
display: flex;
flex-direction: column;
.item {
padding: 35rpx 78rpx;
border-top: 1px solid #F2F2F2;
font-size: 28rpx;
display: flex;
justify-content: space-between;
}
.item:nth-child(1) {
border-top: 0;
}
}
}
//
.arrow-icon {
width: 24rpx;
height: 24rpx;
opacity: 0.6;
}
</style>

607
uniapp/pages/student/timetable/index.vue

@ -1,607 +0,0 @@
<!--课程-列表-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="title">课表</view>
</view>
<view class="main_section">
<view class="section_1">
<view class="ul">
<view class="li" v-for="(v,k) in dateList" :key="k" @click="selectDate(v.date)">
<text>{{v.week}}</text>
<text :class="[filteredData.course_date == v.date ? 'today':'']">{{today == v.date ? '今':v.today}}</text>
<text :class="[v.is_sign == 1 ? 'select_plan':'']"></text>
</view>
</view>
<view class="btn" @click="show_calendar=true">
查看更多 <fui-icon name="arrowdown" color="#A4ADB3" size="45"></fui-icon>
</view>
</view>
<view class="section_2">
<view class="item_box">
<text v-if="(venuesInfo.id || '')">{{venuesInfo.venue_name}}</text>
</view>
<view class="item_box" style="text-align: right;color: #F59A23;" @click="more">
更多场馆
</view>
</view>
<scroll-view
class="section_3"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 100vh;"
>
<view class="ul">
<view v-for="(v,k) in tableList" :key="k" class="li" @click="openViewCourseInfo(v)">
<view class="top_box">
<view class="center_box">
<view>教练{{v.courseScheduleHasOne.coach.name}}</view>
<view>课程{{v.courseScheduleHasOne.course.course_name}}</view>
<view>时间{{v.course_date}}</view>
<view>课室{{v.courseScheduleHasOne.campus_name}} {{v.courseScheduleHasOne.venue.venue_name}}</view>
</view>
<view class="right_box">
<!-- v.status|1=未开始,2=进行中,3=已结束-->
<view class="tag"
v-if="v.status != null"
:style="{background: v.status == 1 ? '#1cd188' : v.status == 2 ? '#fad24e' : '#ff4d4f'}">
{{ v.status == 0 ? '待上课' : v.status == 1 ? '已上课' : '请假' }}
</view>
<!-- <view class="tag" style="background:#1cd188;">待上课</view>-->
</view>
</view>
<view class="bottom_box">
<view class="hint">
<!-- 已签到学生 ({{v.sign_list.length }}/{{v.max_students.split(',').length }})-->
</view>
<view class="list_box">
<!-- <view class="list">-->
<!-- <view class="itme" v-for="(item,index) in v.sign_list || 0" :key="index">-->
<!-- <image :src="$util.img(item.header)"></image>-->
<!-- </view>-->
<!-- </view>-->
<view class="btn">
详情
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 加载状态-->
<!-- <fui-loading :isFixed="true" srcCol="/static/icon-img/loading_white.png" text="正在加载..." v-if="loading"></fui-loading>-->
</view>
<!-- 日历选择-->
<fui-bottom-popup :show="show_calendar" @close="show_calendar=false">
<view class="fui-custom__wrap">
<uni-calendar
:insert="true"
:lunar="false"
:selected="calendarSelected"
:startDate="startDate"
:endDate="endDate"
@change="changeCalendar"
/>
</view>
</fui-bottom-popup>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import memberApi from '@/api/member.js';
import commonApi from '@/api/common.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
loading: false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
memberInfo:{},//
//
filteredData: {
page: 1,//
limit: 10,//
total: 10,//
resources_id:'',//id
course_date: '',//
venue_id: '',//id
},
tableList: [],//
venuesInfo: {id:''},//
//
today: '',
dateList: [],//
//
show_calendar:false,//
startDate:'',//
endDate:'',//
calendarSelected: [
// {
// date: '2025-04-07',//
// },
// {
// date: '2025-04-09',//
// },
],//()
}
},
onLoad(options) {
if (options.venue_id) {
this.filteredData.venue_id = options.venue_id
}
},
onShow() {
this.init()//
},
//
async onPullDownRefresh() {
//
let course_date = this.filteredData.course_date
await this.loadData()
this.filteredData.course_date = course_date
await this.getList()
},
methods: {
//
async init() {
await this.getMemberInfo()//
await this.getThisDate()//
await this.getList()//
await this.getHeadDate()//
this.getDateRange()//
this.setCalendarSelected()//
},
//
async getMemberInfo() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.memberInfo = res.data
this.filteredData.resources_id = res.data.id
},
//
async getHeadDate() {
// 2
let startDate = new Date();
startDate.setDate(startDate.getDate() - 2);
let start_date = startDate.toISOString().split('T')[0];
// 4
let endDate = new Date();
endDate.setDate(endDate.getDate() + 4);
let end_date = endDate.toISOString().split('T')[0];
let params = {
resources_id: this.memberInfo.id, // ID
start_date: start_date, // (Y-m-d)
end_date: end_date, // (Y-m-d)
}
let res = await apiRoute.xy_personCourseScheduleGetCalendar(params)
if (res.code != 1) {
//
uni.showToast({
title: res.msg,
icon: 'none',
})
return
}
this.dateList = []
res.data.forEach((v, k) => {
this.dateList.push({
date: v.date,
status: v.status,//1 2
week: v.week,
today: v.today,
is_sign: v.is_sign,
})
})
console.log('xxx', res)
},
//
async getThisDate() {
let date = new Date();
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0'); //
let day = String(date.getDate()).padStart(2, '0'); //
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
let res = `${year}-${month}-${day}`; //
this.today = res;
this.filteredData.course_date = res;
},
//()
async loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
await this.getList();
}
},
//
async loadData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
async getList() {
this.loading = true
let data = {...this.filteredData}
if(this.filteredData.page == 1){
this.tableList = []
}
//
if ((this.filteredData.page - 1) * this.filteredData.limit >= this.filteredData.total) {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
})
return
}
let res = await apiRoute.xy_personCourseSchedule(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
//
this.venuesInfo = res.data.venues_info
this.filteredData.total = res.data.total
this.filteredData.page++
},
//
async selectDate(date) {
this.loadData()
this.filteredData.course_date = date
this.getList()
},
//
//
getDateRange() {
const today = new Date(); //
const startDate = new Date(today); //
const endDate = new Date(today); //
// startDate 1
startDate.setMonth(today.getMonth() - 1);
// endDate 1
endDate.setMonth(today.getMonth() + 2);
// YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// data startDate endDate
this.startDate = formatDate(startDate);
this.endDate = formatDate(endDate);
console.log([this.startDate,this.endDate])
},
//
async setCalendarSelected(){
//
const today = new Date(); //
const year = today.getFullYear(); //
const month = today.getMonth(); // 0-11
//
const firstDay = new Date(year, month, 1);
// 1
const lastDay = new Date(year, month + 1, 0);
// YYYY-MM-DD
const formatDate = (date) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0'); //
const d = String(date.getDate()).padStart(2, '0'); //
return `${y}-${m}-${d}`;
};
let start_date = formatDate(firstDay); //
let end_date = formatDate(lastDay); //
let params = {
resources_id: this.memberInfo.id, // ID
start_date: start_date, // (Y-m-d)
end_date: end_date, // (Y-m-d)
}
let res = await apiRoute.xy_personCourseScheduleGetCalendar(params)
this.calendarSelected = []
res.data.forEach((v,k)=>{
if(v.is_sign == 1){
this.calendarSelected.push({
date: v.date,
})
}
})
// this.calendarSelected = [
// {
// date: '2025-04-07',
// },
// {
// date: '2025-04-08',
// },
// {
// date: '2025-04-09',
// }
// ]
},
//
changeCalendar(e){
console.log('日历',e)
this.show_calendar = false
console.log('日历',e)
this.show_calendar = false
this.loadData()
this.filteredData.course_date = e.fulldate
this.getList()
},
//
openViewCourseInfo(item) {
let person_course_schedule_id = item.id
this.$navigateTo({
url: `/pages/student/timetable/info?person_course_schedule_id=${person_course_schedule_id}`
})
},
//
more() {
let course_date = this.filteredData.course_date
let venue_id = this.venuesInfo.id || ''//id
this.$navigateTo({
url: `/pages/student/timetable/list?course_date=${course_date}&venue_id=${venue_id}`
})
}
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section{
min-height: 100vh;
background: #292929 100%;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1{
background: #333333 100%;
width: 100%;
padding: 30rpx 28rpx;
.ul{
display: flex;
justify-content: space-between;
align-items: center;
.li{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10rpx;
text{
font-size: 24rpx;
color: #FFFFFF;
text-align: center;
}
text:nth-child(2){
width: 44rpx;
height: 44rpx;
}
text:nth-child(3){
width: 8rpx;
height: 8rpx;
}
.today{
border-radius: 50%;
background: #29D3B4;
text-align: center;
line-height: 42rpx;
}
.select_plan{
background: #F59A23;
border-radius: 50%;
}
}
}
.btn{
margin-top: 20rpx;
display: flex;
justify-content: center;
align-items: center;
color: #A4ADB3;
}
}
.section_2{
margin-top: 30rpx;
padding: 0 20rpx ;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
.item_box {
width: 45%;
.fui-filter__item {
display: flex;
}
}
}
.section_3{
margin-top: 36rpx;
color: #fff;
font-size: 24rpx;
.ul{
padding: 0 26rpx;
display: flex;
flex-direction: column;
gap: 30rpx;
.li{
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 14rpx 0;
display: flex;
flex-direction: column;
.top_box{
padding: 20rpx 30rpx;
.center_box{
display: flex;
flex-direction: column;
gap: 10rpx;
view{}
}
.right_box{
.tag{
position:absolute;
top: 0rpx;
right: 0rpx;
padding: 10rpx;
width: 102rpx;
text-align: center;
font-size: 24rpx;
border-bottom-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
}
}
.bottom_box{
border-top: 1px dashed #F2F2F2;
padding: 26rpx 16rpx 0 26rpx;
.hint{
color:#D7D7D7;
}
.list_box{
margin-top: 22rpx;
display: flex;
justify-content: space-between;
align-items: center;
.list{
display: flex;
align-items: center;
gap: 14rpx;
.itme{
image{
width: 48rpx;
height: 48rpx;
border-radius: 50%;
}
}
}
.btn{
border: 1px solid #FAD04D;
border-radius: 10rpx;
background: #434544;
color: #FAD04D;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
text-align: center;
font-size: 24rpx;
}
}
}
}
}
}
}
</style>

254
uniapp/pages/student/timetable/list.vue

@ -1,254 +0,0 @@
<!--课程-详情-->
<template>
<view class="main_box">
<scroll-view scroll-y="true" :lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData" style="height: 100vh;">
<view class="data_hint" v-if="!this.tableList.length">暂无更多数据</view>
<view class="main_section" v-for="(v,k) in tableList" :key="k" @click="opebViewTimetable(v)">
<view class="title">{{v.campus.campus_name}} </view>
<view class="con">{{v.campus.campus_address}} {{v.venue_name}}</view>
<!-- <view class="con" v-if="v.distance === null ">无法获取定位</view>-->
<!-- <view class="con" v-else-if="v.distance">距您{{v.distance}}km</view>-->
<view class="current-venue" v-if="venue_id == v.id">
当前场馆
</view>
</view>
</scroll-view>
<!-- 加载状态-->
<!-- <fui-loading :isFixed="true" srcCol="/static/icon-img/loading_white.png" text="正在加载..." v-if="loading"></fui-loading>-->
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
memberInfo:{id:''},//
//
filteredData:{
// page:1,//
// limit:10,//
// total:10,//
course_date:'',//
resources_id:'',//ID
},
tableList:[],//
longitude:'',//
latitude:'',//
venue_id:'',//id
}
},
onLoad(options) {
this.filteredData.course_date = options.course_date//
//id
this.venue_id = options.venue_id || ''//ID
},
onShow() {
this.init()//
},
methods: {
//
async init() {
// await this.getUserLocation();
await this.getMemberInfo();
await this.getList();
},
//
async getMemberInfo() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.memberInfo = res.data
this.filteredData.resources_id = res.data.id
},
//
async getUserLocation() {
try {
const location = await this.getLocation();
this.longitude = location.longitude;
this.latitude = location.latitude;
} catch (error) {
console.log(error);
uni.showToast({
title: '获取位置失败',
icon: 'none'
});
// throw error; // 便 init
}
},
// uni.getLocation Promise
getLocation() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'wgs84',
success: (res) => {
console.log('当前位置的经度:' + res.longitude);
console.log('当前位置的纬度:' + res.latitude);
resolve(res);
},
fail: (err) => {
console.log(err);
reject(err);
}
});
});
},
//
getDistance(lat1, lng1, lat2, lng2) {
const R = 6371; //
const dLat = this.deg2rad(lat2 - lat1);
const dLon = this.deg2rad(lng2 - lng1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c; //
return distance.toFixed(2); //
},
//
deg2rad(deg) {
return deg * (Math.PI / 180);
},
//()
loadMoreData() {
return //
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
}
},
//
loadData() {
setTimeout(() => {
this.isReachedBottom = false; // 便
}, 1000);
},
//
async getList(){
this.loading = true
let data = {...this.filteredData}
//
// if(this.filteredData.page * this.filteredData.limit > this.total){
// this.loading = false
// uni.showToast({
// title: '',
// icon: 'none'
// })
// return
// }
let res = await apiRoute.xy_personCourseScheduleGetVenueListAll(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.tableList = res.data
this.tableList.forEach((v,k)=>{
if(this.longitude && this.latitude && (v.longitude || '') && (v.latitude || '')){
//
v.distance = this.getDistance(this.latitude, this.longitude, v.latitude, v.longitude)
}else{
v.distance = null
}
})
console.log('列表',this.tableList)
},
//-
opebViewTimetable(item) {
let venue_id = item.id
this.$navigateTo({
url: `/pages/student/timetable/index?venue_id=${venue_id}`
})
},
}
}
</script>
<style lang="less" scoped>
.main_box {
width: 100%;
height: 100vh;
overflow: auto;
background: #292929;
}
.data_hint{
margin-top: 100rpx;
font-size: 30rpx;
text-align: center;
color: #fff;
}
.main_section{
width: 92%;
border-radius: 15rpx;
background-color: #404045;
margin: 20rpx auto;
padding: 40rpx 0 40rpx 80rpx;
color: #fff;
position: relative;
}
.title{
font-size: 32rpx;
}
.con{
color: #D7D7D7;
font-size: 26rpx;
margin-top: 20rpx;
}
.current-venue{
border-radius: 8rpx;
border: 2rpx #F59A23 solid;
width: 120rpx;
text-align: center;
color: #F59A23;
position: absolute;
top: 10%;
right: 3%;
}
</style>
Loading…
Cancel
Save