Browse Source

分包迁移

master
王泽彦 8 months ago
parent
commit
9e1297c8ce
  1. 3
      uniapp/App.vue
  2. 16
      uniapp/common/config.js
  3. 3
      uniapp/components/schedule/ScheduleDetail.vue
  4. 755
      uniapp/pages-coach/coach/home/index.vue
  5. 227
      uniapp/pages-coach/coach/my/due_soon.vue
  6. 47
      uniapp/pages-coach/coach/my/exam_results.vue
  7. 185
      uniapp/pages-coach/coach/my/gotake_exam.vue
  8. 505
      uniapp/pages-coach/coach/my/info.vue
  9. 546
      uniapp/pages-coach/coach/my/salary.vue
  10. 241
      uniapp/pages-coach/coach/my/schooling_statistics.vue
  11. 58
      uniapp/pages-coach/coach/my/set_up.vue
  12. 247
      uniapp/pages-coach/coach/my/teaching_management.vue
  13. 229
      uniapp/pages-coach/coach/my/update_pass.vue
  14. 682
      uniapp/pages-coach/coach/schedule/add_schedule.vue
  15. 583
      uniapp/pages-coach/coach/schedule/adjust_course.vue
  16. 1767
      uniapp/pages-coach/coach/schedule/schedule_table.vue
  17. 521
      uniapp/pages-coach/coach/schedule/sign_in.vue
  18. 282
      uniapp/pages-coach/coach/student/physical_examination.vue
  19. 728
      uniapp/pages-coach/coach/student/student_list.vue
  20. 129
      uniapp/pages-common/article_info.vue
  21. 707
      uniapp/pages-common/contract/contract_detail.vue
  22. 733
      uniapp/pages-common/contract/contract_sign.vue
  23. 411
      uniapp/pages-common/contract/my_contract.vue
  24. 235
      uniapp/pages-common/feedback.vue
  25. 600
      uniapp/pages-common/im_chat_info.vue
  26. 1129
      uniapp/pages-common/my_attendance.vue
  27. 374
      uniapp/pages-common/my_message.vue
  28. 899
      uniapp/pages-common/personnel/add_personnel.vue
  29. 111
      uniapp/pages-common/privacy_agreement.vue
  30. 259
      uniapp/pages-common/profile/index.vue
  31. 1282
      uniapp/pages-common/profile/personal_info.vue
  32. 225
      uniapp/pages-common/sys_msg_list.vue
  33. 2123
      uniapp/pages-market/clue/add_clues.vue
  34. 631
      uniapp/pages-market/clue/class_arrangement.vue
  35. 1292
      uniapp/pages-market/clue/class_arrangement_detail.vue
  36. 431
      uniapp/pages-market/clue/clue_info.less
  37. 1470
      uniapp/pages-market/clue/clue_info.vue
  38. 164
      uniapp/pages-market/clue/clue_table.vue
  39. 1806
      uniapp/pages-market/clue/edit_clues.vue
  40. 365
      uniapp/pages-market/clue/edit_clues_log.vue
  41. 1760
      uniapp/pages-market/clue/index.vue
  42. 343
      uniapp/pages-market/data/statistics.vue
  43. 427
      uniapp/pages-market/index/index.vue
  44. 495
      uniapp/pages-market/my/campus_data.vue
  45. 445
      uniapp/pages-market/my/dept_data.vue
  46. 523
      uniapp/pages-market/my/index.vue
  47. 505
      uniapp/pages-market/my/info.vue
  48. 292
      uniapp/pages-market/my/my_data.vue
  49. 105
      uniapp/pages-market/my/set_up.vue
  50. 275
      uniapp/pages-market/my/signed_client_list.vue
  51. 229
      uniapp/pages-market/my/update_pass.vue
  52. 272
      uniapp/pages-market/reimbursement/add.vue
  53. 136
      uniapp/pages-market/reimbursement/detail.vue
  54. 144
      uniapp/pages-market/reimbursement/list.vue
  55. 590
      uniapp/pages-optimized.json
  56. 501
      uniapp/pages-student/child/add.vue
  57. 988
      uniapp/pages-student/contracts/index.vue
  58. 1023
      uniapp/pages-student/course-booking/index.vue
  59. 1304
      uniapp/pages-student/knowledge/index.vue
  60. 887
      uniapp/pages-student/messages/index.vue
  61. 195
      uniapp/pages-student/my/lesson_consumption.vue
  62. 211
      uniapp/pages-student/my/my_coach.vue
  63. 158
      uniapp/pages-student/my/my_members.vue
  64. 421
      uniapp/pages-student/my/personal_data.vue
  65. 57
      uniapp/pages-student/my/set_up.vue
  66. 125
      uniapp/pages-student/my/update_pass.vue
  67. 1024
      uniapp/pages-student/orders/index.vue
  68. 823
      uniapp/pages-student/physical-test/index.vue
  69. 605
      uniapp/pages-student/profile/index.vue
  70. 933
      uniapp/pages-student/schedule/index.vue
  71. 254
      uniapp/pages-student/settings/index.vue
  72. 1134
      uniapp/pages.json
  73. 601
      uniapp/pages.json.backup
  74. 32
      uniapp/pages/coach/home/index.vue
  75. 8
      uniapp/pages/coach/my/due_soon.vue
  76. 4
      uniapp/pages/coach/my/exam_results.vue
  77. 4
      uniapp/pages/coach/my/gotake_exam.vue
  78. 4
      uniapp/pages/coach/my/schooling_statistics.vue
  79. 8
      uniapp/pages/coach/my/set_up.vue
  80. 8
      uniapp/pages/coach/my/teaching_management.vue
  81. 4
      uniapp/pages/coach/my/update_pass.vue
  82. 16
      uniapp/pages/coach/schedule/schedule_table.vue
  83. 2
      uniapp/pages/coach/student/student_list.vue
  84. 2
      uniapp/pages/common/contract/contract_detail.vue
  85. 4
      uniapp/pages/common/contract/my_contract.vue
  86. 39
      uniapp/pages/common/home/index.vue
  87. 8
      uniapp/pages/common/my_message.vue
  88. 12
      uniapp/pages/common/profile/index.vue
  89. 4
      uniapp/pages/common/sys_msg_list.vue
  90. 4
      uniapp/pages/contract/detail.vue
  91. 2
      uniapp/pages/contract/fill.vue
  92. 2
      uniapp/pages/contract/list.vue
  93. 12
      uniapp/pages/market/clue/add_clues.vue
  94. 4
      uniapp/pages/market/clue/class_arrangement.vue
  95. 8
      uniapp/pages/market/clue/clue_info.vue
  96. 4
      uniapp/pages/market/clue/edit_clues.vue
  97. 8
      uniapp/pages/market/clue/index.vue
  98. 64
      uniapp/pages/market/my/index.vue
  99. 8
      uniapp/pages/market/my/set_up.vue
  100. 4
      uniapp/pages/market/my/signed_client_list.vue

3
uniapp/App.vue

@ -45,4 +45,7 @@
// 使
@import '@/static/styles/app.scss';
@import "@/uni.scss";
// FirstUI
@import "@/components/firstui/fui-theme/fui-theme.css";
</style>

16
uniapp/common/config.js

@ -1,11 +1,15 @@
// 环境变量配置
const env = process.env.VUE_APP_ENV || 'development'
const isMockEnabled = process.env.VUE_APP_MOCK_ENABLED === 'true' || false // 默认禁用Mock优先模式,仅作为回退
const isDebug = process.env.VUE_APP_DEBUG === 'true' || true // 默认启用调试模式
// const env = 'development'
const env = 'prod'
const isMockEnabled = false // 默认禁用Mock优先模式,仅作为回退
const isDebug = false // 默认启用调试模式
const devurl = 'http://localhost:20080/api'
const devimgurl = 'http://localhost:20080'
const produrl = 'https://api.hnhbty.cn/api'
const prodimgurl = 'https://api.hnhbty.cn'
// API配置 - 支持环境变量
const Api_url = process.env.VUE_APP_API_URL || 'http://localhost:20080/api'
const img_domian = process.env.VUE_APP_IMG_DOMAIN || 'http://localhost:20080/'
const Api_url = env === 'development' ? devurl : produrl
const img_domian = env === 'development' ? devimgurl : prodimgurl
// 备用API地址
const Api_url_B = 'https://zhifuguanli.zeyan.wang/api/hygl'

3
uniapp/components/schedule/ScheduleDetail.vue

@ -467,7 +467,8 @@
//
handleArrangeStudent() {
//
const url = `/pages/market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`;
const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`;
console.log('跳转到学员管理页面:', url);
uni.navigateTo({
url: url,
success: () => {

755
uniapp/pages-coach/coach/home/index.vue

@ -0,0 +1,755 @@
<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.course_list && infoData.course_list.length > 0">
<!-- 上课中-->
<view class="li" v-for="(v,k) in infoData.course_list" :key="k" @click="openViewCourseInfoList(v)">
<view class="top_box">
<view class="title">课程{{ v.course_name }}</view>
<view class="title">时间{{ v.course_date }} {{ v.time_slot }}
</view>
<view class="title">地点{{ v.address }}</view>
</view>
<view class="botton_box" v-if="v.status == 'ongoing'">
<view class="box">
<view>已签到学生{{ v.sign_count }}/{{ v.students_count }}</view>
<view>
查看
<fui-icon size="35" color="#fff" name="arrowright"></fui-icon>
</view>
</view>
<!-- <view class="box">-->
<!-- <view>尚未发布作业</view>-->
<!-- <view></view>-->
<!-- </view>-->
</view>
<view class="botton_box" v-else>
<view class="box">
<view>应到学生{{ v.students_count }}</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:#fad24e;">待开始
</view>
<view
v-if="v.status == 'upcoming'"
class="tag"
style="background:#1cd188;">待上课
</view>
<view
v-if="v.status == 'ongoing'"
class="tag"
style="background:#fad24e;">上课中
</view>
</view>
</view>
<!-- 没有数据时的占位区域 -->
<view class="empty-placeholder" v-else>
<image :src="$util.img('/static/icon-img/empty.png')" mode="aspectFit"></image>
<text>暂无课程数据</text>
</view>
</view>
<!--作业批改-->
<view class="section_4">
<view class="title_box">
<view class="top_box">
<text>作业批改</text>
<view @click="openObjListView()">全部</view>
</view>
<view class="line"></view>
</view>
<view class="ul" v-if="infoData.task_list && infoData.task_list.length > 0">
<view class="li" v-for="(v,k) in infoData.task_list" :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" style="background:#fad24e;">上课中</view>-->
<view class="tag" style="background:#1cd188;">待批改</view>
</view>
</view>
</view>
<!-- 没有数据时的占位区域 -->
<view class="empty-placeholder" v-else>
<image :src="$util.img('/static/icon-img/empty.png')" mode="aspectFit"></image>
<text>暂无作业数据</text>
</view>
</view>
<!--我的服务列表-->
<view class="section_6">
<view class="title_box">
<view class="top_box">
<text>我的服务</text>
<view @click="openServiceListView()">全部</view>
</view>
<view class="line"></view>
</view>
<view class="service-list" v-if="serviceList && serviceList.length > 0">
<view class="service-item" v-for="(item, index) in serviceList" :key="index" @click="viewServiceDetail(item)">
<view class="service-info">
<view class="service-name">{{item.service_name}}</view>
<view class="service-desc">{{item.description}}</view>
<view class="service-time">创建时间{{item.created_at}}</view>
</view>
<view class="service-status" :class="{'active': item.status === 1}">
{{item.status === 1 ? '进行中' : '已结束'}}
</view>
</view>
</view>
<!-- 没有数据时的占位区域 -->
<view class="empty-placeholder" v-else>
<image :src="$util.img('/static/icon-img/empty.png')" mode="aspectFit"></image>
<text>暂无服务数据</text>
</view>
</view>
</view>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
//
infoData:{
course_list:[],//
task_list:[],//
},
serviceList: [], //
}
},
onLoad() {
this.init() //
},
onShow() {
this.init() //
},
methods: {
//
async init(){
await this.getInfo()
// await this.getServiceList() //
},
//
async getInfo(){
// API使
let res = await memberApi.jlIndex({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let course_list = res.data.course_list
let task_list = res.data.task_list
this.serviceList = res.data.service_list
// 使
this.infoData = {
course_list: course_list,
task_list: task_list
}
},
//
async getServiceList() {
// API使
// try {
// // API
// let res = await memberApi.serviceList({})
// if(res.code == 1){
// this.serviceList = res.data || []
// } else {
// uni.showToast({
// title: res.msg || '',
// icon: 'none'
// })
// }
// } catch (error) {
// console.error(':', error)
// uni.showToast({
// title: '',
// icon: 'none'
// })
// }
// 使
// this.serviceList = [
// {
// id: 1,
// name: '',
// description: '',
// create_time: '2025-07-15',
// status: 1
// },
// {
// id: 2,
// name: '',
// description: '3-5',
// create_time: '2025-07-20',
// status: 0
// },
// {
// id: 3,
// name: '',
// description: '',
// create_time: '2025-07-01',
// status: 1
// }
// ]
},
//-
openObjAddView(){
uni.navigateTo({
url: '/pages-coach/coach/job/add'
})
},
//
openObjListView(){
uni.navigateTo({
url: '/pages-coach/coach/job/list'
})
},
//-
openViewCourseInfoList(item){
let id = item.id
uni.navigateTo({
url: `/pages-coach/coach/course/info_list?id=${id}`
})
},
//-
openViewWorkDetails(item){
let id = item.id
uni.navigateTo({
url: `/pages-coach/coach/student/work_details?id=${id}`
})
},
//
openServiceListView() {
uni.navigateTo({
url: '/pages-coach/coach/my/service_list'
})
},
//
viewServiceDetail(item) {
let id = item.id
uni.navigateTo({
url: `/pages-coach/coach/my/service_detail?id=${id}`
})
},
//
goToStudentList() {
uni.navigateTo({
url: '/pages-coach/coach/student/student_list'
})
},
//
goToClassList() {
uni.navigateTo({
url: '/pages-coach/coach/class/list'
})
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929 ;
}
//
.navbar_section{
padding: 0 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
background: #29d3b4;
view{
width: 30%;
text-align: center;
}
.left{
text-align: left;
}
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
.right{
padding: 20rpx 0;
/* H5端样式 */
// #ifdef H5
padding: 40rpx 0 20rpx;
// #endif
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0 20rpx;
// #endif
font-size: 26rpx;
color: #fff;
text-align: right;
}
}
.main_section{
min-height: 100vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
/* 空数据占位区域样式 */
.empty-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
color: #999;
font-size: 28rpx;
}
}
.section_1{
background: #FFFFFF 100%;
height: 264rpx;
border-radius: 14rpx;
margin: 28rpx auto;
padding: 28rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 28rpx;
.top{
display: flex;
justify-content: space-between;
align-items: center;
.user{
display: flex;
align-items: center;
gap: 20rpx;
.pic{
width: 144rpx;
height: 144rpx;
border-radius: 50%;
image{
width: 100%;
height: 100%;
}
}
}
.right{
display: flex;
flex-direction: column;
gap: 20rpx;
view{
display: flex;
gap: 10rpx;
.title{
width: 148rpx;
text-align: right;
}
}
}
}
.bottom{
padding: 0 44rpx;
}
}
.section_2{
border-radius: 60rpx;
padding: 28rpx 36rpx;
background: #29D3B4 100%;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
.left_box{
display: flex;
view{
display: flex;
flex-direction: column;
text{}
}
view:nth-child(1){
padding-right: 10rpx;
border-right: 2px solid #fff;
}
view:nth-child(2){
padding-left: 10rpx;
}
}
.rigth_box{
view{
width: 106rpx;
height: 50rpx;
line-height: 50rpx;
text-align: center;
border-radius: 25rpx;
background: #32baa1;
font-size: 24rpx;
}
}
}
.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{
border: 1px solid #00E5BB;
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 14rpx 20rpx;
padding-bottom: 44rpx;
display: flex;
flex-direction: column;
.top_box{
padding-bottom: 18rpx;
border-bottom: 1px dashed #F2F2F2;
display: flex;
flex-direction: column;
gap: 18rpx;
.title{}
}
.botton_box{
padding-top: 18rpx;
display: flex;
flex-direction: column;
gap: 18rpx;
color: #D7D7D7;
.box{
display: flex;
justify-content: space-between;
view{
display: flex;
align-items: center;
}
}
}
.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;
}
}
}
}
.section_4{
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: 32rpx 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;
}
}
}
}
}
.section_5{
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;
}
}
.quick-links {
margin-top: 30rpx;
display: flex;
justify-content: space-around;
.quick-link-item {
display: flex;
flex-direction: column;
align-items: center;
background: #434544;
border-radius: 20rpx;
padding: 30rpx 20rpx;
width: 30%;
.quick-link-icon {
margin-bottom: 20rpx;
}
.quick-link-text {
font-size: 28rpx;
color: #fff;
}
}
}
}
.section_6{
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;
}
}
.service-list{
margin-top: 30rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.service-item{
position: relative;
border-radius: 22rpx;
background: #434544 100%;
padding: 24rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4rpx solid #29D3B4;
.service-info{
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
.service-name{
font-size: 30rpx;
color: #fff;
font-weight: 500;
}
.service-desc{
font-size: 24rpx;
color: #D7D7D7;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.service-time{
font-size: 22rpx;
color: #AAAAAA;
margin-top: 6rpx;
}
}
.service-status{
padding: 8rpx 20rpx;
border-radius: 30rpx;
background: #666;
color: #fff;
font-size: 24rpx;
text-align: center;
&.active {
background: #29D3B4;
}
}
}
}
}
}
</style>

227
uniapp/pages-coach/coach/my/due_soon.vue

@ -0,0 +1,227 @@
<!--即将到期-->
<template>
<view class="main_box">
<view class="main_section">
<!-- 班级成员列表-->
<view class="section_4">
<view class="ul">
<!-- @click="openViewStudentInfo({id:1})" -->
<view class="li" v-for="(item, index) in studentList" :key="index">
<view class="left">
<view class="box_1">
<image :src="item.avatar || $util.img('/static/icon-img/avatar.png')" mode="aspectFill" class="pic"></image>
<view class="tag_box">
即将到期
</view>
</view>
<view class="box_2">
<view class="name">{{item.name}}</view>
<view class="date">课程截止时间{{item.end_date}}</view>
</view>
</view>
<view class="right">
<view class="item">
<view>{{
(item.use_total_hours + item.use_gift_hours)
}}</view>
<view>已上课时</view>
</view>
<view class="item">
<view>{{
(item.total_hours + item.gift_hours) - (item.use_total_hours + item.use_gift_hours)
}}</view>
<view>剩余课时</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部导航-->
<!-- <AQTabber/>-->
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
formData: {},
tabType: '1', //1=,2=
studentList: []
}
},
onLoad() {
this.getStudentList();
},
methods: {
async getStudentList() {
// API
const res = await memberApi.jlGetStudentList({
'type': 'daoqi'
});
if (res.code == 1) {
this.studentList = res.data || [];
} else {
uni.showToast({
title: res.msg || '获取学员列表失败',
icon: 'none'
});
}
},
//
openViewCourseInfo(item) {
uni.navigateTo({
url: '/pages-coach/coach/course/info'
})
},
//
openViewStudentInfo(item) {
uni.navigateTo({
url: '/pages-coach/coach/student/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: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
//
.section_4 {
.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;
}
}
}
}
}
}
}
</style>

47
uniapp/pages-coach/coach/my/exam_results.vue

@ -0,0 +1,47 @@
<template>
<view>
<view style="margin-top: 380rpx;text-align: center;font-size: 50rpx;font-weight: bold;">
答对{{success}},答错{{error}}
</view>
<view style="margin-top: 15rpx;text-align: center;font-size: 50rpx;font-weight: bold;">
总共得分{{num}}
</view>
<view @click="back" style="background-color: #00bfa5;margin: auto;text-align: center;margin-top: 30rpx;border-radius: 50rpx;font-size: 42rpx;padding: 20rpx;">
返回列表
</view>
</view>
</template>
<script>
export default {
data() {
return {
error: '',
success: '',
num: ''
}
},
onLoad(options) {
if(options.error){
this.error = options.error;
}
if(options.success){
this.success = options.success;
}
if(options.num){
this.num = options.num;
}
},
methods: {
back() {
uni.navigateTo({
url: '/pages-coach/coach/my/teaching_management'
})
}
}
}
</script>
<style>
</style>

185
uniapp/pages-coach/coach/my/gotake_exam.vue

@ -0,0 +1,185 @@
<template>
<view class="whole">
<view style="height: 36rpx;"></view>
<view class="topic" v-for="(item,index) in quantityArr" :key="index" v-show="quantity == index">
<view class="dis_style">
<view class="title_icon">{{index + 1}}</view>
<view class="title_con" v-if="item.question_content_type === 'text'">{{item.question_content}}?</view>
</view>
<view class="dis_style">
<view class="title_con" style="margin: auto;" v-if="item.question_content_type === 'image'">
<img style="width: 300rpx;height: 300rpx;" :src="item.question_content" mode="aspectFit">
</view>
</view>
<view style="line-height: 60rpx;margin-top: 16rpx;">
<fui-radio-group name="radio" v-model="optionList[index]">
<fui-checkbox-group name="checkbox" v-model="optionList[index]">
<view class="title_con title_font" style="display: flex;align-items: center;" v-for="(item_option,index_option) in item.option_json" :key="index_option">
<view style="display: flex;align-items: center;">
<fui-radio :value="item_option.option" v-if="item.question_type != 'multiple_choice'"></fui-radio>
<fui-checkbox :value="item_option.option" v-if="item.question_type == 'multiple_choice'"></fui-checkbox>
</view>
<view>{{item_option.option}}. {{item_option.option_content}}</view>
</view>
</fui-checkbox-group>
</fui-radio-group>
</view>
</view>
<view class="progress_bar">
<fui-progress :percent="number_progress" background="#ddd" activeColor="#00bfa5" height="10"></fui-progress>
</view>
<view class="switch_style">
<view class="dis_style" style="color: #3e94ed;font-size: 30rpx;">
<view @click="previous_question">上一题</view>
<view style="margin-left: 20rpx;" @click="next_question">下一题</view>
</view>
<view class="sub_style" @click="submit">
提交试卷
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
quantity: 0,
quantityArr: [1,2,3,4],
number_progress: 0,
testPaperId: 0,
optionList: [],
zid: 0
}
},
onLoad(options) {
if(options.id){
this.testPaperId = options.id;
}
if(options.zid){
this.zid = options.zid;
}
console.log(this.testPaperId, 999)
this.init()
},
methods: {
async init() {
const res = await apiRoute.getTeachingTestPaper({exam_papers_id: this.testPaperId});
if(res.code === 1) {
this.quantityArr = res.data.resText
this.number_progress = (Math.round(100 / res.data.count) * ( this.quantity + 1));
}
},
previous_question() {
if(this.quantity <= 0){
uni.showToast({
title: '已经是第一题了',
icon: 'none'
})
return
} else {
this.quantity--
}
this.number_progress = (Math.round(100 / this.quantityArr.length) * ( this.quantity + 1));
},
next_question() {
if((this.quantity + 1) >= this.quantityArr.length){
uni.showToast({
title: '已经是最后一题了',
icon: 'none'
})
return
} else {
this.quantity++
}
this.number_progress = (Math.round(100 / this.quantityArr.length) * ( this.quantity + 1));
},
async submit() {
this.optionList.forEach((item,index) => {
if (Array.isArray(item)) {
this.optionList[index] = item.join(',')
}
})
const res = await apiRoute.submitTestPaper({optionList: this.optionList,testPaperId: this.testPaperId, id: this.zid});
if(res.code === 1) {
uni.navigateTo({
url: '/pages-coach/coach/my/exam_results?error=' + res.data.error + '&success=' + res.data.success + '&num=' + res.data.num
})
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
}
}
}
</script>
<style>
.whole{
width: 100%;
height: 100vh;
background-color: #171717;
}
.topic{
width: 95%;
height: auto;
background-color: #2d2d2d;
border: #3e91e8 4rpx solid;
border-radius: 16rpx;
padding: 26rpx;
margin: auto;
}
.dis_style{
display: flex;
align-items: center;
}
.title_icon{
width: 60rpx;
height: 60rpx;
border-radius: 50rpx;
background-color: #00bfa5;
font-size: 26rpx;
color: #fff;
font-weight: bold;
text-align: center;
line-height: 60rpx;
}
.title_con{
margin-left: 16rpx;
font-size: 26rpx;
color: #fff;
}
.title_font{
font-size: 30rpx;
}
.progress_bar{
width: 95%;
margin: 36rpx auto;
}
.switch_style{
width: 95%;
height: auto;
padding: 26rpx;
background-color: #2d2d2d;
border-radius: 16rpx;
margin: auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.sub_style{
font-size: 32rpx;
padding: 15rpx 30rpx;
border-radius: 30rpx;
color: #000;
font-weight: bold;
text-align: center;
background-color: #35a3f0;
}
</style>

505
uniapp/pages-coach/coach/my/info.vue

@ -0,0 +1,505 @@
<!--销售-个人资料-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section">
<view class="item">
<image
@click="changeAvatar()"
class="pic"
:src="$util.img(formData.head_img)"
></image>
<view class="btn" @click="changeAvatar()">修改头像</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
姓名 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.name" placeholder="请输入姓名" />
</view>
</view>
<view class="item">
<view class="title">
账号 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.username" disabled placeholder="暂无" />
</view>
</view>
<view class="item">
<view class="title">
部门 <text class="required"></text>
</view>
<view class="input">
<!-- <input disabled :placeholder="formData.department_name_str" />-->
<view class="dept disabled">{{formData.department_name_str || '暂无'}}</view>
</view>
</view>
<view class="item">
<view class="title">
等级 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.member_level_name" disabled placeholder="暂无" />
</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
性别 <text class="required">*</text>
</view>
<view class="input">
<input placeholder="请选择性别" v-model="formData.gender_str" @click="picker_show_sex=true"/>
<fui-picker
layer="1"
:linkage="true"
:options="options_sex_arr"
:show="picker_show_sex"
@change="changePickerSex"
@cancel="picker_show_sex=false"
></fui-picker>
</view>
</view>
<view class="item">
<view class="title">
生日 <text class="required">*</text>
</view>
<view class="input">
<input placeholder="请选择生日" @click="picker_show_birthday=true" v-model="formData.birthday"/>
<fui-date-picker
:minDate="minDate"
:maxDate="maxDate"
:show="picker_show_birthday"
type="3"
@change="changePickerBirthday"
@cancel="picker_show_birthday=false"
></fui-date-picker>
</view>
</view>
<view class="item">
<view class="title">
邮箱 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.email" placeholder="请输入邮箱" />
</view>
</view>
<view class="item">
<view class="title">
手机 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.phone" placeholder="请输入手机" />
</view>
</view>
<view class="item">
<view class="title">
微信 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.wx" placeholder="请输入微信" />
</view>
</view>
</view>
<view class="submet_btn" @click="submit">提交</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import marketApi from '@/api/market.js';
import {
Api_url
} from "@/common/config.js";
import AQTabber from "@/components/AQ/AQTabber"
export default {
components: {
AQTabber,
},
data() {
return {
formData:{
head_img:'',//
name:'',//
username:'',//
address:'',//
gender:'',//|1,2
gender_str:'',
birthday:'',//
email:'',//
phone:'',//
wx:'',//
},
userInfo: {},
//APi
uploadUrl: `${Api_url}/uploadImage`,
//
picker_show_sex: false,
sex_name:'请选择',
options_sex_arr: [
{
value: 1,
text: '男'
},
{
value: 2,
text: '女'
},
],
//
minDate: '',
maxDate: '',
picker_show_birthday: false,
upload_type: 1,
uploadHeadimg: '',
editHeadimg: '',
}
},
onLoad() {
},
onShow() {
this.init()
},
methods: {
async init(){
// this.getBirthday()
this.setDateYear()
await this.getUserInfo()
},
//
setDateYear() {
let currentYear = new Date().getFullYear();
this.minDate = String(currentYear - 100);
this.maxDate = String(currentYear + 1);
},
//
async getUserInfo(){
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let gender_str = ''
if(res.data.gender == 1){
gender_str = '男'
}else if(res.data.gender == 2){
gender_str = '女'
}
//
this.formData = {
head_img: res.data.head_img,//
name: res.data.name,//
username: res.data.phone,//
address: res.data.address,//
gender: res.data.gender,//|1,2
gender_str:gender_str,
birthday: res.data.birthday,//
email: res.data.email || '',//
phone: res.data.phone,//
wx: res.data.wx || '',//
member_level_name: res.data.member_level_name || '',//
department_name_str:res.data.department_name_str || '暂无',//
}
},
//
changeAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
//
this.uploadFilePromise(tempFilePath)
}
})
},
uploadFilePromise(url) {
let token = uni.getStorageSync('token') || ''
let a = uni.uploadFile({
url: this.uploadUrl, //
filePath: url,
name: 'file',
header: {
'token': `${token}`, //token
},
success: (e) => {
let res = JSON.parse(e.data.replace(/\ufeff/g, "") || "{}")
console.log('上传成功2', res)
if (res.code == 1) {
this.upload_type = 2
this.formData.head_img = res.data.url
// this.editHeadimg = res.data.path
// this.uploadHeadimg = res.data.url
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
});
},
//
changePickerSex(e) {
console.log('监听选择', e)
this.formData.gender = e.value
this.formData.gender_str = e.text
this.picker_show_sex = false
},
//
//+30
getBirthday() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let year_30 = year - 30;
let month_30 = month;
let day_30 = day;
if (month_30 == 2 && day_30 > 28) {
month_30 = 3;
day_30 = 1;
}
if (month_30 == 4 && day_30 > 30) {
month_30 = 5;
day_30 = 1;
}
if (month_30 == 6 && day_30 > 30) {
month_30 = 7;
day_30 = 1;
}
if (month_30 == 9 && day_30 > 30) {
month_30 = 10;
day_30 = 1;
}
if (month_30 == 11 && day_30 > 30) {
month_30 = 12;
day_30 = 1;
}
if (month_30 > 12) {
month_30 = month_30 - 12;
year_30 = year_30 + 1;
}
let minDate = year_30 + "-" + month_30 + "-" + day_30
let maxDate = year + "-" + month + "-" + day
this.minDate = minDate
this.maxDate = maxDate
},
//
changePickerBirthday(e) {
console.log('监听生日选择', e)
this.formData.birthday = e.result
this.picker_show_birthday = false
},
//
async submit() {
let data = {...this.formData}
if(!data.head_img){
uni.showToast({
title: '请上传头像',
icon: 'none'
})
return
}
if(!data.name){
uni.showToast({
title: '请填写',
icon: 'none'
})
return
}
if(!data.gender){
uni.showToast({
title: '请选择性别',
icon: 'none'
})
return
}
if(!data.birthday){
uni.showToast({
title: '请选择生日',
icon: 'none'
})
return
}
if(!data.email){
uni.showToast({
title: '请填写邮箱',
icon: 'none'
})
return
}
if(!data.phone){
uni.showToast({
title: '请填写手机',
icon: 'none'
})
return
}
let res = await apiRoute.editPersonnelInfo(data)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
this.getUserInfo()
}, 1000)
},
}
}
</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 0rpx;
padding-top: 32rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
color: #fff;
display: flex;
flex-direction: column;
gap: 20rpx;
.section{
background-color: #434544;
.item{
padding: 20rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
.pic{
width: 82rpx;
height: 82rpx;
border-radius: 50%;
}
.btn{}
.title{
min-width: 100rpx;
display: flex;
align-items: center;
font-size: 26rpx;
color: #D7D7D7;
.required{
margin-left: 10rpx;
color: red;
}
}
.input{
display: flex;
justify-content: flex-end;
input{
text-align: right;
}
.dept{
width: 50%;
}
.disabled{
color: #808080;
//
cursor: not-allowed;
}
}
}
}
.submet_btn{
margin: 0 auto;
margin-top: 40rpx;
border: 2px solid #25a18b;
color: #25a18b;
width: 80%;
height: 80rpx;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

546
uniapp/pages-coach/coach/my/salary.vue

@ -0,0 +1,546 @@
<!--员工-我的工资-->
<template>
<view class="main_box">
<view class="main_section">
<!-- 筛选条件 -->
<view class="filter_section">
<view class="filter_item">
<picker mode="date" fields="month" :value="selectedMonth" @change="onMonthChange">
<view class="picker_display">
<text>{{ selectedMonth || '选择月份' }}</text>
<text class="picker_arrow">></text>
</view>
</picker>
</view>
<view class="filter_btn" @click="loadSalaryList">查询</view>
</view>
<!-- 工资条列表 -->
<view class="salary_list" v-if="salaryList.length > 0">
<view
class="salary_item"
v-for="(item, index) in salaryList"
:key="item.id"
@click="viewSalaryDetail(item)"
>
<view class="salary_header">
<view class="salary_month">{{ formatMonth(item.salary_month) }}</view>
<view :class="['salary_status',getStatusClass(item.status)]">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="salary_content">
<view class="salary_row">
<text class="label">基础工资:</text>
<text class="value">¥{{ formatMoney(item.base_salary) }}</text>
</view>
<view class="salary_row">
<text class="label">出勤工资:</text>
<text class="value">¥{{ formatMoney(item.work_salary) }}</text>
</view>
<view class="salary_row">
<text class="label">应发工资:</text>
<text class="value highlight">¥{{ formatMoney(item.gross_salary) }}</text>
</view>
<view class="salary_row">
<text class="label">实发工资:</text>
<text class="value net_salary">¥{{ formatMoney(item.net_salary) }}</text>
</view>
</view>
<view class="salary_footer">
<text class="view_detail">点击查看详情 ></text>
</view>
</view>
</view>
<!-- 暂无数据 -->
<view class="no_data" v-else-if="!loading">
<image class="no_data_icon" src="/static/images/no_data.png" mode="aspectFit"></image>
<text class="no_data_text">暂无工资数据</text>
</view>
<!-- 加载中 -->
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
</view>
<!-- 工资详情弹窗 -->
<view class="salary_modal" v-if="showDetail" @click="closeDetail">
<view class="modal_content" @click.stop>
<view class="modal_header">
<text class="modal_title">工资详情</text>
<text class="modal_close" @click="closeDetail">×</text>
</view>
<view class="modal_body" v-if="salaryDetail">
<view class="detail_section">
<view class="section_title">基础信息</view>
<view class="detail_row">
<text class="detail_label">工资月份:</text>
<text class="detail_value">{{ formatMonth(salaryDetail.salary_month) }}</text>
</view>
<view class="detail_row">
<text class="detail_label">基础工资:</text>
<text class="detail_value">¥{{ formatMoney(salaryDetail.base_salary) }}</text>
</view>
<view class="detail_row">
<text class="detail_label">满勤天数:</text>
<text class="detail_value">{{ salaryDetail.full_attendance_days }}</text>
</view>
<view class="detail_row">
<text class="detail_label">出勤天数:</text>
<text class="detail_value">{{ salaryDetail.attendance }}</text>
</view>
</view>
<view class="detail_section">
<view class="section_title">收入明细</view>
<view class="detail_row">
<text class="detail_label">出勤工资:</text>
<text class="detail_value">¥{{ formatMoney(salaryDetail.work_salary) }}</text>
</view>
<view class="detail_row" v-if="salaryDetail.mgr_performance > 0">
<text class="detail_label">管理绩效:</text>
<text class="detail_value">¥{{ formatMoney(salaryDetail.mgr_performance) }}</text>
</view>
<view class="detail_row" v-if="salaryDetail.performance_bonus > 0">
<text class="detail_label">销售提成:</text>
<text class="detail_value">¥{{ formatMoney(salaryDetail.performance_bonus) }}</text>
</view>
<view class="detail_row" v-if="salaryDetail.other_subsidies > 0">
<text class="detail_label">其他补贴:</text>
<text class="detail_value">¥{{ formatMoney(salaryDetail.other_subsidies) }}</text>
</view>
</view>
<view class="detail_section">
<view class="section_title">扣除明细</view>
<view class="detail_row" v-if="salaryDetail.deductions > 0">
<text class="detail_label">其他扣款:</text>
<text class="detail_value text_red">-¥{{ formatMoney(salaryDetail.deductions) }}</text>
</view>
<view class="detail_row" v-if="salaryDetail.social_security > 0">
<text class="detail_label">社保:</text>
<text class="detail_value text_red">-¥{{ formatMoney(salaryDetail.social_security) }}</text>
</view>
<view class="detail_row" v-if="salaryDetail.individual_income_tax > 0">
<text class="detail_label">个税:</text>
<text class="detail_value text_red">-¥{{ formatMoney(salaryDetail.individual_income_tax) }}</text>
</view>
</view>
<view class="detail_section">
<view class="section_title">工资汇总</view>
<view class="detail_row highlight_row">
<text class="detail_label">应发工资:</text>
<text class="detail_value highlight">¥{{ formatMoney(salaryDetail.gross_salary) }}</text>
</view>
<view class="detail_row net_salary_row">
<text class="detail_label">实发工资:</text>
<text class="detail_value net_salary">¥{{ formatMoney(salaryDetail.net_salary) }}</text>
</view>
</view>
<view class="detail_section" v-if="salaryDetail.remarks">
<view class="section_title">备注</view>
<view class="detail_remarks">{{ salaryDetail.remarks }}</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
export default {
data() {
return {
selectedMonth: '',
salaryList: [],
loading: false,
showDetail: false,
salaryDetail: null,
}
},
onLoad() {
//
const now = new Date();
this.selectedMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
this.loadSalaryList();
},
methods: {
//
onMonthChange(e) {
this.selectedMonth = e.detail.value;
},
//
async loadSalaryList() {
this.loading = true;
try {
const params = {};
if (this.selectedMonth) {
params.salary_month = this.selectedMonth;
}
const res = await memberApi.getSalaryList(params);
if (res.code === 1) {
this.salaryList = res.data.data || [];
} else {
uni.showToast({
title: res.msg || '获取数据失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取工资列表失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
} finally {
this.loading = false;
}
},
//
async viewSalaryDetail(item) {
try {
const res = await memberApi.getSalaryInfo({ id: item.id });
if (res.code === 1) {
this.salaryDetail = res.data;
this.showDetail = true;
} else {
uni.showToast({
title: res.msg || '获取详情失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取工资详情失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
}
},
//
closeDetail() {
this.showDetail = false;
this.salaryDetail = null;
},
//
formatMonth(month) {
if (!month) return '';
return month.substring(0, 7).replace('-', '年') + '月';
},
//
formatMoney(amount) {
if (!amount && amount !== 0) return '0.00';
return Number(amount).toFixed(2);
},
//
getStatusText(status) {
switch (status) {
case 1: return '未发放';
case 2: return '已发放';
default: return '未知';
}
},
//
getStatusClass(status) {
switch (status) {
case 1: return 'status_pending';
case 2: return 'status_paid';
default: return '';
}
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
min-height: 100vh;
}
.main_section {
padding: 20rpx;
padding-bottom: 150rpx;
}
/* 筛选条件 */
.filter_section {
display: flex;
align-items: center;
background: #434544;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.filter_item {
flex: 1;
.picker_display {
display: flex;
justify-content: space-between;
align-items: center;
color: #D7D7D7;
font-size: 28rpx;
.picker_arrow {
color: #25a18b;
font-size: 24rpx;
}
}
}
.filter_btn {
background: #25a18b;
color: #fff;
padding: 15rpx 30rpx;
border-radius: 8rpx;
font-size: 26rpx;
margin-left: 20rpx;
}
}
/* 工资条列表 */
.salary_list {
.salary_item {
background: #434544;
border-radius: 10rpx;
padding: 30rpx;
margin-bottom: 20rpx;
.salary_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #555;
.salary_month {
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
.salary_status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
&.status_pending {
background: rgba(255, 165, 0, 0.2);
color: #FFA500;
}
&.status_paid {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
}
}
}
.salary_content {
.salary_row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
.label {
color: #D7D7D7;
font-size: 26rpx;
}
.value {
color: #fff;
font-size: 28rpx;
&.highlight {
color: #25a18b;
font-weight: bold;
}
&.net_salary {
color: #22c55e;
font-weight: bold;
font-size: 30rpx;
}
}
}
}
.salary_footer {
text-align: center;
margin-top: 20rpx;
padding-top: 15rpx;
border-top: 1rpx solid #555;
.view_detail {
color: #25a18b;
font-size: 24rpx;
}
}
}
}
/* 暂无数据 */
.no_data {
text-align: center;
padding: 100rpx 0;
color: #888;
.no_data_icon {
width: 150rpx;
height: 150rpx;
margin-bottom: 30rpx;
}
.no_data_text {
font-size: 28rpx;
}
}
/* 加载中 */
.loading {
text-align: center;
padding: 50rpx 0;
color: #888;
font-size: 28rpx;
}
/* 工资详情弹窗 */
.salary_modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
.modal_content {
background: #434544;
border-radius: 15rpx;
width: 90%;
max-height: 80%;
overflow: hidden;
.modal_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #555;
.modal_title {
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
.modal_close {
color: #888;
font-size: 40rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.modal_body {
max-height: 60vh;
overflow-y: auto;
padding: 30rpx;
.detail_section {
margin-bottom: 40rpx;
.section_title {
color: #25a18b;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 1rpx solid #555;
}
.detail_row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
.detail_label {
color: #D7D7D7;
font-size: 26rpx;
}
.detail_value {
color: #fff;
font-size: 26rpx;
&.highlight {
color: #25a18b;
font-weight: bold;
}
&.net_salary {
color: #22c55e;
font-weight: bold;
}
&.text_red {
color: #f56565;
}
}
}
&.highlight_row {
background: rgba(37, 161, 139, 0.1);
padding: 15rpx;
border-radius: 8rpx;
}
&.net_salary_row {
background: rgba(34, 197, 94, 0.1);
padding: 15rpx;
border-radius: 8rpx;
}
.detail_remarks {
background: #555;
padding: 20rpx;
border-radius: 8rpx;
color: #D7D7D7;
font-size: 26rpx;
line-height: 1.6;
}
}
}
}
}
</style>

241
uniapp/pages-coach/coach/my/schooling_statistics.vue

@ -0,0 +1,241 @@
<!--授课统计-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="ul">
<view class="li" v-for="(v,k) in sktjlist" :key="k">
<view class="title">{{v.month_date}}</view>
<view class="box">
<view class="top">
<view class="top_item">
<view class="num">{{v.ysks}}</view>
<view class="explain">月授课数/</view>
</view>
<view class="top_item">
<view class="num">{{v.zsbj}}</view>
<view class="explain">总授班级/</view>
</view>
<view class="top_item">
<view class="num">{{v.yfzxy}}</view>
<view class="explain">月负责学员/</view>
</view>
</view>
<view class="bottom">
月到课率<text>{{v.ydkl}}%</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
sktjlist:[],//
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() {
},
onShow(){
this.init()
},
methods: {
//
async init(){
await this.getList()
},
//
async getList(){
let res = await memberApi.jlSktj({})
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.sktjlist = res.data//
},
//
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){
uni.navigateTo({
url: '/pages-coach/coach/course/info'
})
},
}
}
</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: 24rpx;
.ul{
display: flex;
flex-direction: column;
gap: 56rpx;
.li{
.title{
color: #fff;
font-size: 24rpx;
}
.box{
margin-top: 24rpx;
padding: 36rpx 32rpx 28rpx 44rpx;
width: 700rpx;
border-radius: 14rpx;
background-color: #fff;
color: #333333FF;
font-size: 26rpx;
.top{
display: flex;
justify-content: space-between;
align-items: center;
.top_item{
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
.num{
font-size: 56rpx;
color: #29D3B4;
}
.explain{
font-size: 24rpx;
color: #AAAAAA;
}
}
}
.bottom{
margin-top: 34rpx;
font-size: 24rpx;
text{
margin-left: 15rpx;
color: #29D3B4;
}
}
}
}
}
}
}
</style>

58
uniapp/pages-coach/coach/my/set_up.vue

@ -0,0 +1,58 @@
<!--设置页-->
<template>
<view class="assemble">
<view style="height: 30rpx;"></view>
<view class="option" @click="update_pass()">修改密码</view>
<view class="option" @click="privacy_agreement(1)">用户协议</view>
<view class="option" @click="privacy_agreement(2)">隐私策略</view>
<view class="option">清空缓存</view>
<view style="width:90%;margin: 60rpx auto;">
<fui-button background="#29d3b4" @click="loginOut()">退出账号</fui-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
//退
loginOut(){
this.$util.loginOut()
},
privacy_agreement(type){
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
})
},
update_pass(){
uni.navigateTo({
url: '/pages-coach/coach/my/update_pass'
})
}
}
}
</script>
<style lang="less" scoped>
.assemble{
width: 100%;
height: 100vh;
background: #333333;
}
.option{
margin-bottom: 20rpx;
background: #404045;
width: 100%;
font-size: 28rpx;
color: #fff;
line-height: 56rpx;
padding: 20rpx 0 20rpx 100rpx;
}
</style>

247
uniapp/pages-coach/coach/my/teaching_management.vue

@ -0,0 +1,247 @@
<template>
<view class="dark-theme">
<fui-tabs :tabs="tabsList" @change="change" isDot scroll alignLeft class="custom-tabs" :color="'#00d18c'" :selectedColor="'#00d18c'" :background="'#121212'" :itemBackground="'#121212'"></fui-tabs>
<scroll-view scroll-y :scroll-with-animation="true" @scrolltolower="onReachBottom">
<view class="dis_style div_style" v-for="(item,index) in arrayList" :key="index">
<view @click="info(item.id)">
<view class='color_style'>{{item.title}}</view>
<view class='color_type_style'>{{getTableType(item.table_type)}}</view>
</view>
<view>
<view class='color_date_style'>{{item.create_time}}</view>
<view class='color_date_style exam-btn' v-if="item.exam_papers_id != 0" @click="goTake(item.exam_papers_id,item.id)">去考试</view>
</view>
</view>
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="noMoreData" class="no-more">没有更多数据了</view>
</scroll-view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
arrayList: [],
tabsList: [],
tableTypeName: {
'1': "课程教学大纲",
'2': "跳绳教案库",
'3': "增高教案库",
'4': "篮球教案库",
'5': "强化教案库",
'6': "空中忍者教案库",
'7': "少儿安防教案库",
'8': "体能教案库",
'9': "热身动作库",
'10': "体能动作库",
'11': "趣味游戏库",
'12': "放松动作库",
'13': "训练内容",
'14': "训练视频",
'15': "课后作业",
'16': "优秀一堂课",
'17': "空中忍者",
'18': "篮球动作",
'19': "跳绳动作",
'20': "跑酷动作",
'21': "安防动作",
'22': "标准化动作",
'23': "3-6岁体测",
'24': "7+体测",
'25': "3-6岁体测讲解—解读",
'26': "7+岁体测讲解—解读",
'27': "互动游戏",
'28': "套圈游戏",
'29': "鼓励方式"
},
tabsListArr: [],
searchArr: {
table_type: undefined,
page: 1,
limit: 10
},
loading: false,
noMoreData: false,
initId: undefined
}
},
onLoad() {
this.getTabs()
},
methods: {
getTabs() {
this.tabsListArr = []
this.tabsList = []
apiRoute.teachingResearchLookType().then(res => {
if (res.code == 1) {
res.data.forEach(item => {
let arr = {
value: item,
lebal: this.tableTypeName[item]
}
this.tabsListArr.push(arr)
this.tabsList.push(this.tableTypeName[item])
})
if (this.tabsListArr.length > 0) {
this.initId = this.tabsListArr[0].value
this.init()
}
}
})
},
change(e) {
const item = this.tabsListArr.find(item => item.lebal == e.name);
const id = item ? item.value : 0
this.initId = id
this.arrayList = []
this.searchArr.page = 1
this.init()
},
getTableType(text) {
return this.tableTypeName[text]
},
info(id) {
uni.navigateTo({
url: '/pages-coach/coach/my/teaching_management_info?id=' + id
})
},
onReachBottom() {
if (this.noMoreData || this.loading) return;
this.searchArr.page += 1
this.init();
},
init(id) {
this.searchArr.table_type = this.initId
apiRoute.teachingResearchList(this.searchArr).then(res => {
this.loading = true
if (res.code == 1) {
this.arrayList.push(...res.data.data)
if(res.last_page <= this.searchArr.page){
this.noMoreData = true;
}
this.loading = false
}
})
},
goTake(id,zid) {
uni.navigateTo({
url: '/pages-coach/coach/my/gotake_exam?id=' + id + '&zid=' + zid
})
}
}
}
</script>
<style lang="scss">
.dark-theme {
background-color: #121212;
color: #ffffff;
min-height: 100vh;
}
.dark-theme .div_style {
padding: 18rpx 50rpx;
background-color: #1e1e1e;
margin-top: 10rpx;
line-height: 30px;
border-radius: 8rpx;
}
.dark-theme .color_style {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
}
.dark-theme .color_type_style {
font-size: 22rpx;
color: #b0b0b0;
}
.dark-theme .color_date_style {
font-size: 22rpx;
color: #b0b0b0;
}
.dark-theme .exam-btn {
color: #7cb9ff;
}
.dark-theme .loading,
.dark-theme .no-more {
color: #b0b0b0;
text-align: center;
padding: 20rpx 0;
}
/* 非暗黑模式样式 */
.div_style {
padding: 18rpx 50rpx;
background-color: #fff;
margin-top: 10rpx;
line-height: 30px;
}
.dis_style {
display: flex;
justify-content: space-between;
align-items: center;
}
.color_style {
font-size: 30rpx;
font-weight: bold;
}
.color_type_style {
font-size: 22rpx;
}
.color_date_style {
font-size: 22rpx;
}
.exam-btn {
color: blue;
}
/* 自定义 tab 栏样式 */
.custom-tabs {
/* 确保背景是黑色 */
background-color: #121212 !important;
.fui-tabs__item {
background-color: #121212 !important;
color: #00d18c !important;
}
.fui-tabs__item-active {
color: #00d18c !important;
}
.fui-tabs__line {
background-color: #00d18c !important;
}
}
/* 全局样式覆盖 - 使用dart-sass语法 */
:deep(.fui-tabs__wrap) {
background-color: #121212 !important;
}
:deep(.fui-tabs__item) {
background-color: #121212 !important;
color: #00d18c !important;
}
:deep(.fui-tabs__item-active) {
color: #00d18c !important;
}
:deep(.fui-tabs__line) {
background-color: #00d18c !important;
}
</style>

229
uniapp/pages-coach/coach/my/update_pass.vue

@ -0,0 +1,229 @@
<!--修改密码-->
<template>
<view>
<view class="title">
<view :class="{'green-text': tset_style === 1}">1.验证旧密码</view>
<view :class="{'green-text': tset_style === 2}">2.设置新密码</view>
</view>
<view :style="{'background-color':'#fff','width':'100%','height':'100vh' }">
<view style="width: 95%;height: 30rpx;"></view>
<view v-if="tset_style == 1">
<view class="describe">
为保障您的账号安全修改密码前请填写原密码
</view>
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请输入原登录密码" v-model="old_password"
backgroundColor="#f2f2f2"></fui-input>
</view>
</view>
<view v-if="tset_style == 2">
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请设置6-20位新的登录密码" v-model="formData.new_password" @input="input"
backgroundColor="#f2f2f2"></fui-input>
</view>
<view style="width: 95%;margin: auto;">
<fui-input borderTop :padding="['20rpx','32rpx']" v-model="formData.new_password_2" placeholder="请再次输入新的登录密码" @input="input"
backgroundColor="#f2f2f2">
</fui-input>
</view>
</view>
<view style="width: 95%;margin:60rpx auto;">
<view class="btn_box">
<fui-button
background="#465cff"
radius="5rpx"
@click="nextStep(1)"
v-if="tset_style != 1">上一步
</fui-button>
<fui-button background="#00be8c" radius="5rpx" @click="nextStep(2)" v-if="tset_style == 1">下一步</fui-button>
<fui-button background="#00be8c" radius="5rpx" @click="submit" v-if="tset_style == 2">提交</fui-button>
</view>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#fff" radius="5rpx" @click="forgot" color="#999999" v-if="tset_style == 1">忘记原密码</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
code: '',
user: '',
tset_style: 1,//tab|1=,2=
old_password:'',//
formData:{
phone:'',//
new_password:'',//
new_password_2:'',//
key_value:'',//key_value
},
}
},
onLoad() {},
onShow() {
this.init()//
},
methods: {
//
async init(){
await this.getUserInfo()
},
//
async getUserInfo() {
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData.phone = res.data.phone//
},
///
async nextStep(tset_style){
//tset_style|1=,2=
if(tset_style == 2){
if(!this.old_password){
uni.showToast({
title: '请输入原登录密码',
icon: 'none'
})
return
}
//
let params = {
old_password: this.old_password
}
//
let res = await apiRoute.common_personnelCheckOldPwd(params)
if(!res.code){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData.key_value = res.data.key_value
}
this.tset_style = Number(tset_style)
},
//
forgot() {
uni.navigateTo({
url: '/pages/student/login/forgot'
})
},
//
async submit() {
//
if (!this.formData.new_password) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
})
return
}
if (this.formData.new_password.length < 6 || this.formData.new_password.length > 20) {
uni.showToast({
title: '新密码长度为6-20位',
icon: 'none'
})
return
}
if (!this.formData.new_password) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
})
return
}
//
if (this.formData.new_password != this.formData.new_password_2) {
uni.showToast({
title: '两次密码不一致',
icon: 'none'
})
return
}
let res = await apiRoute.common_personnelEdidPassword(this.formData)
if(!res.code){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
//-
//
uni.redirectTo({
url: `/pages-coach/coach/my/index`
})
}, 1000)
},
}
}
</script>
<style lang="less" scoped>
page {
font-weight: normal;
}
.fui-section__title {
margin-left: 32rpx;
}
.fui-left__icon {
padding-right: 24rpx;
}
.title {
display: flex;
justify-content: space-around;
align-items: center;
height: 100rpx;
width: 100%;
background-color: #fff;
font-size: 26rpx;
border: 4rpx #f5f5f5 solid;
}
.green-text{
color: #36d6b9;
}
.describe{
color: #999999;
padding-left: 30rpx;
}
.btn_box{
display: flex;
flex-direction: column;
gap: 40rpx;
}
</style>

682
uniapp/pages-coach/coach/schedule/add_schedule.vue

@ -0,0 +1,682 @@
<template>
<view class="add-schedule-container">
<view class="form-container">
<fui-form>
<!-- 课程选择 -->
<fui-form-item label="课程" required>
<view class="selector-input" @click="showCoursePicker = true">
<text>{{ selectedCourse ? selectedCourse.course_name : '请选择课程' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<single-picker
:show="showCoursePicker"
:data="courseOptions"
valueKey="id"
textKey="course_name"
title="选择课程"
@change="onCourseSelect"
@cancel="showCoursePicker = false"
@update:show="showCoursePicker = $event"
></single-picker>
</fui-form-item>
<!-- 班级选择 -->
<fui-form-item label="班级">
<view class="selector-input" @click="showClassPicker = true">
<text>{{ selectedClass ? selectedClass.class_name : '请选择班级(可选)' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<single-picker
:show="showClassPicker"
:data="classOptions"
valueKey="id"
textKey="class_name"
title="选择班级"
@change="onClassSelect"
@cancel="showClassPicker = false"
@update:show="showClassPicker = $event"
></single-picker>
</fui-form-item>
<!-- 教练选择 -->
<fui-form-item label="授课教练" required>
<view class="selector-input" @click="showCoachPicker = true">
<text>{{ selectedCoach ? selectedCoach.name : '请选择教练' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<single-picker
:show="showCoachPicker"
:data="coachOptions"
valueKey="id"
textKey="name"
title="选择教练"
@change="onCoachSelect"
@cancel="showCoachPicker = false"
@update:show="showCoachPicker = $event"
></single-picker>
</fui-form-item>
<!-- 场地选择 -->
<fui-form-item label="上课场地" required>
<view class="selector-input" @click="showVenuePicker = true">
<text>{{ selectedVenue ? selectedVenue.venue_name : '请选择场地' }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
<single-picker
:show="showVenuePicker"
:data="venueOptions"
valueKey="id"
textKey="venue_name"
title="选择场地"
@change="onVenueSelect"
@cancel="showVenuePicker = false"
@update:show="showVenuePicker = $event"
></single-picker>
</fui-form-item>
<!-- 日期选择 -->
<fui-form-item label="上课日期" required>
<view class="selector-input" @click="showDatePicker = true">
<text>{{ formData.course_date || '请选择日期' }}</text>
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon>
</view>
<fui-date-picker
:show="showDatePicker"
@confirm="onDateSelect"
@cancel="showDatePicker = false"
:value="formData.course_date"
></fui-date-picker>
</fui-form-item>
<!-- 时间选择 -->
<fui-form-item label="上课时间" required>
<view class="selector-input" @click="showTimePicker = true">
<text>{{ formData.time_slot || '请选择时间段' }}</text>
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon>
</view>
<single-picker
:show="showTimePicker"
:data="timeSlotOptions"
valueKey="value"
textKey="text"
title="选择时间"
@change="onTimeSelect"
@cancel="showTimePicker = false"
@update:show="showTimePicker = $event"
></single-picker>
</fui-form-item>
<!-- 容量设置 -->
<fui-form-item label="课程容量" required>
<view class="capacity-container">
<text class="capacity-text">{{ formData.available_capacity || '0' }}</text>
<text class="capacity-hint">(根据场地自动设置)</text>
</view>
</fui-form-item>
<!-- 备注信息 -->
<fui-form-item label="备注">
<fui-textarea
:value="formData.remark"
placeholder="请输入备注信息(可选)"
@input="formData.remark = $event"
maxlength="200"
></fui-textarea>
</fui-form-item>
</fui-form>
<!-- 提交按钮 -->
<view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">创建课程安排</fui-button>
</view>
</view>
</view>
</template>
<script>
import api from '@/api/apiRoute.js';
import SinglePicker from '@/components/custom-picker/single-picker.vue';
export default {
components: {
SinglePicker
},
data() {
return {
//
formData: {
course_id: '',
class_id: '',
coach_id: '',
venue_id: '',
course_date: '',
time_slot: '',
available_capacity: '',
remark: ''
},
//
showCoursePicker: false,
showClassPicker: false,
showCoachPicker: false,
showVenuePicker: false,
showDatePicker: false,
showTimePicker: false,
//
courseOptions: [],
classOptions: [],
coachOptions: [],
venueOptions: [],
timeSlotOptions: [],
//
selectedCourse: null,
selectedClass: null,
selectedCoach: null,
selectedVenue: null,
//
submitting: false,
//
prefillDate: '',
prefillTime: '',
prefillTimeSlot: ''
};
},
onLoad(options) {
//
if (options.date) {
this.prefillDate = options.date;
this.formData.course_date = options.date;
}
if (options.time) {
this.prefillTime = options.time;
}
if (options.time_slot) {
this.prefillTimeSlot = options.time_slot;
this.formData.time_slot = options.time_slot;
}
//
this.loadFilterOptions();
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
async loadFilterOptions() {
uni.showLoading({
title: '加载数据中...'
});
try {
//
const [courseRes, classRes, coachRes, venueRes] = await Promise.all([
api.getCourseListForSchedule(),
api.getClassListForSchedule(),
api.getCoachListForSchedule(),
api.getVenueListForSchedule()
]);
//
if (courseRes.code === 1) {
this.courseOptions = courseRes.data || [];
}
//
if (classRes.code === 1) {
this.classOptions = classRes.data || [];
}
//
if (coachRes.code === 1) {
this.coachOptions = coachRes.data || [];
}
//
if (venueRes.code === 1) {
this.venueOptions = venueRes.data || [];
}
//
if (this.prefillTimeSlot) {
this.formData.time_slot = this.prefillTimeSlot;
}
console.log('加载的数据:', {
courses: this.courseOptions.length,
classes: this.classOptions.length,
coaches: this.coachOptions.length,
venues: this.venueOptions.length
});
//
if (this.courseOptions.length === 0) {
uni.showToast({
title: '暂无可用课程',
icon: 'none'
});
}
if (this.coachOptions.length === 0) {
uni.showToast({
title: '暂无可用教练',
icon: 'none'
});
}
if (this.venueOptions.length === 0) {
uni.showToast({
title: '暂无可用场地',
icon: 'none'
});
}
} catch (error) {
console.error('加载筛选选项失败:', error);
//
let errorMsg = '加载数据失败';
if (error.response) {
errorMsg = `服务器错误: ${error.response.status}`;
} else if (error.request) {
errorMsg = '网络连接失败,请检查网络';
} else {
errorMsg = error.message || '未知错误';
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
});
} finally {
uni.hideLoading();
}
},
//
generateTimeSlotOptions() {
const timeSlots = [];
//
for (let hour = 8; hour < 12; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 12; hour < 18; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
//
for (let hour = 18; hour < 22; hour++) {
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
timeSlots.push({
value: `${startHour}:00-${endHour}:00`,
text: `${startHour}:00-${endHour}:00`
});
}
this.timeSlotOptions = timeSlots;
},
//
async loadTimeSlots() {
if (!this.formData.venue_id || !this.formData.course_date) {
return;
}
try {
const res = await api.getVenueTimeSlots({
venue_id: this.formData.venue_id,
date: this.formData.course_date
});
if (res.code === 1) {
// API
this.timeSlotOptions = res.data.map(slot => ({
value: slot.time_slot,
text: slot.time_slot
}));
console.log('可用时间段:', this.timeSlotOptions);
} else {
// API使
this.generateTimeSlotOptions();
uni.showToast({
title: res.msg || '获取可用时间段失败,使用默认时间段',
icon: 'none'
});
}
} catch (error) {
console.error('加载时间段失败:', error);
// API使
this.generateTimeSlotOptions();
let errorMsg = '获取可用时间段失败,使用默认时间段';
if (error.response && error.response.status === 404) {
errorMsg = '该场地暂无可用时间段';
} else if (error.request) {
errorMsg = '网络连接失败,使用默认时间段';
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
});
}
},
//
onCourseSelect(e) {
console.log('onCourseSelect', e);
if (e && e.item) {
this.selectedCourse = e.item;
this.formData.course_id = e.value;
}
},
onClassSelect(e) {
console.log('onClassSelect', e);
if (e && e.item) {
this.selectedClass = e.item;
this.formData.class_id = e.value;
} else {
this.selectedClass = null;
this.formData.class_id = '';
}
},
onCoachSelect(e) {
console.log('onCoachSelect', e);
if (e && e.item) {
this.selectedCoach = e.item;
this.formData.coach_id = e.value;
}
},
onVenueSelect(e) {
console.log('onVenueSelect', e);
if (e && e.item) {
this.selectedVenue = e.item;
this.formData.venue_id = e.value;
// 0
this.formData.available_capacity = this.selectedVenue.capacity || 0;
//
if (this.formData.course_date) {
this.loadTimeSlots();
}
} else {
// 0
this.formData.available_capacity = 0;
}
},
onDateSelect(e) {
this.formData.course_date = e.result;
this.showDatePicker = false;
//
if (this.formData.venue_id) {
this.loadTimeSlots();
}
},
onTimeSelect(e) {
console.log('onTimeSelect', e);
if (e && e.item) {
this.formData.time_slot = e.value;
}
},
//
async checkCoachTimeConflict() {
if (!this.formData.coach_id || !this.formData.course_date || !this.formData.time_slot) {
return true; //
}
try {
const res = await api.checkCoachConflict({
coach_id: this.formData.coach_id,
date: this.formData.course_date,
time_slot: this.formData.time_slot,
exclude_schedule_id: this.formData.id || 0 //
});
if (res.code === 1) {
if (res.data.has_conflict) {
uni.showModal({
title: '时间冲突提示',
content: `教练“${this.selectedCoach.name}”在选择的时间段已有其他课程安排,是否仍要继续?`,
confirmText: '继续添加',
cancelText: '重新选择',
success: (modalRes) => {
if (modalRes.confirm) {
this.submitForm(true); //
}
}
});
return false;
}
}
return true;
} catch (error) {
console.error('检查教练时间冲突失败:', error);
return true; //
}
},
//
validateForm() {
if (!this.formData.course_id) {
uni.showToast({
title: '请选择课程',
icon: 'none'
});
return false;
}
if (!this.formData.coach_id) {
uni.showToast({
title: '请选择授课教练',
icon: 'none'
});
return false;
}
if (!this.formData.venue_id) {
uni.showToast({
title: '请选择上课场地',
icon: 'none'
});
return false;
}
if (!this.formData.course_date) {
uni.showToast({
title: '请选择上课日期',
icon: 'none'
});
return false;
}
if (!this.formData.time_slot) {
uni.showToast({
title: '请选择上课时间',
icon: 'none'
});
return false;
}
return true;
},
//
async submitForm(ignoreConflict = false) {
if (!this.validateForm()) {
return;
}
//
if (!ignoreConflict) {
const noConflict = await this.checkCoachTimeConflict();
if (!noConflict) {
return; //
}
}
this.submitting = true;
try {
// API
const submitData = {
campus_id: 1, // ID
venue_id: this.formData.venue_id,
course_date: this.formData.course_date,
time_slot: this.formData.time_slot,
course_id: this.formData.course_id,
coach_id: this.formData.coach_id,
available_capacity: parseInt(this.formData.available_capacity),
class_id: this.formData.class_id || 0, //
remarks: this.formData.remark || '', //
created_by: 'manual'
};
console.log('提交数据:', submitData);
const res = await api.createCourseSchedule(submitData);
if (res.code === 1) {
uni.showToast({
title: '创建成功',
icon: 'success'
});
//
setTimeout(() => {
//
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.$vm && prevPage.$vm.loadScheduleList) {
//
uni.navigateBack({
success: function() {
prevPage.$vm.loadScheduleList();
}
});
} else {
uni.navigateBack();
}
}, 1500);
} else {
uni.showToast({
title: res.msg || '创建失败',
icon: 'none'
});
}
} catch (error) {
console.error('创建课程安排失败:', error);
//
let errorMsg = '创建失败,请重试';
if (error.response) {
if (error.response.status === 401) {
errorMsg = '登录已过期,请重新登录';
} else if (error.response.status === 403) {
errorMsg = '没有权限执行此操作';
} else if (error.response.status >= 500) {
errorMsg = '服务器内部错误,请联系管理员';
} else {
errorMsg = `请求失败: ${error.response.status}`;
}
} else if (error.request) {
errorMsg = '网络连接失败,请检查网络后重试';
} else {
errorMsg = error.message || '创建失败,请重试';
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
});
} finally {
this.submitting = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.add-schedule-container {
min-height: 100vh;
background-color: #18181c;
}
.form-container {
padding: 30rpx;
}
.selector-input {
height: 80rpx;
background-color: #23232a;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
font-size: 28rpx;
color: #fff;
}
.capacity-container {
height: 80rpx;
background-color: #23232a;
border-radius: 8rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
font-size: 28rpx;
}
.capacity-text {
color: #fff;
font-size: 28rpx;
}
.capacity-hint {
color: #999999;
font-size: 24rpx;
margin-left: 16rpx;
}
.btn-container {
margin-top: 60rpx;
padding: 0 30rpx;
}
</style>

583
uniapp/pages-coach/coach/schedule/adjust_course.vue

@ -0,0 +1,583 @@
<template>
<view class="adjust-course-container">
<view class="form-container">
<view v-if="loading" class="loading-container">
<fui-loading></fui-loading>
<text class="loading-text">加载中...</text>
</view>
<fui-form v-else>
<!-- 课程信息 -->
<view class="section-title">当前课程信息</view>
<view class="course-info-card">
<view class="info-row">
<text class="info-label">课程名称</text>
<text class="info-value">{{ scheduleInfo.course_name }}</text>
</view>
<view class="info-row">
<text class="info-label">上课日期</text>
<text class="info-value">{{ scheduleInfo.course_date }}</text>
</view>
<view class="info-row">
<text class="info-label">上课时间</text>
<text class="info-value">{{ scheduleInfo.time_slot }}</text>
</view>
<view class="info-row">
<text class="info-label">授课教练</text>
<text class="info-value">{{ scheduleInfo.coach_name }}</text>
</view>
<view class="info-row">
<text class="info-label">上课场地</text>
<text class="info-value">{{ scheduleInfo.venue_name }}</text>
</view>
</view>
<view class="section-title">调整后信息</view>
<!-- 教练选择 -->
<fui-form-item label="授课教练">
<picker
:value="coachPickerIndex"
:range="coachOptions"
:range-key="'name'"
@change="onCoachSelect"
>
<view class="selector-input">
<text>{{ selectedCoach ? selectedCoach.name : scheduleInfo.coach_name }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
</picker>
</fui-form-item>
<!-- 场地选择 -->
<fui-form-item label="上课场地">
<picker
:value="venuePickerIndex"
:range="venueOptions"
:range-key="'venue_name'"
@change="onVenueSelect"
>
<view class="selector-input">
<text>{{ selectedVenue ? selectedVenue.venue_name : scheduleInfo.venue_name }}</text>
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon>
</view>
</picker>
</fui-form-item>
<!-- 日期选择 -->
<fui-form-item label="上课日期">
<picker
mode="date"
:value="formData.course_date || scheduleInfo.course_date || getCurrentDate()"
:start="getMinDate()"
:end="getMaxDate()"
@change="onDateSelect"
>
<view class="selector-input">
<text>{{ formData.course_date || scheduleInfo.course_date }}</text>
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon>
</view>
</picker>
</fui-form-item>
<!-- 时间选择 -->
<fui-form-item label="上课时间">
<picker
:value="timePickerIndex"
:range="timeSlotOptions"
:range-key="'text'"
@change="onTimeSelect"
>
<view class="selector-input">
<text>{{ formData.time_slot || scheduleInfo.time_slot }}</text>
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon>
</view>
</picker>
</fui-form-item>
<!-- 容量设置 -->
<fui-form-item label="课程容量">
<fui-input
type="number"
:value="formData.available_capacity || scheduleInfo.available_capacity"
placeholder="请输入课程容量"
@input="formData.available_capacity = $event"
></fui-input>
</fui-form-item>
<!-- 提交按钮 -->
<view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button>
</view>
</fui-form>
</view>
</view>
</template>
<script>
import api from '@/api/apiRoute.js';
export default {
data() {
return {
//
loading: true,
submitting: false,
// ID
scheduleId: null,
//
scheduleInfo: {},
//
formData: {
schedule_id: '',
coach_id: '',
venue_id: '',
course_date: '',
time_slot: '',
available_capacity: ''
},
// showDatePicker
//
coachOptions: [],
venueOptions: [],
timeSlotOptions: [],
//
selectedCoach: null,
selectedVenue: null,
// picker
coachPickerIndex: 0,
venuePickerIndex: 0,
timePickerIndex: 0
};
},
onLoad(options) {
if (options.id) {
this.scheduleId = options.id;
this.formData.schedule_id = options.id;
this.loadScheduleInfo();
this.loadFilterOptions();
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
async loadScheduleInfo() {
try {
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId });
if (res.code === 1) {
this.scheduleInfo = res.data;
//
this.formData.coach_id = this.scheduleInfo.coach_id;
this.formData.venue_id = this.scheduleInfo.venue_id;
this.formData.course_date = this.scheduleInfo.course_date;
this.formData.time_slot = this.scheduleInfo.time_slot;
this.formData.available_capacity = this.scheduleInfo.available_capacity;
} else {
uni.showToast({
title: res.msg || '获取课程安排信息失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取课程安排信息失败:', error);
uni.showToast({
title: '获取课程安排信息失败',
icon: 'none'
});
}
},
//
async loadFilterOptions() {
try {
const res = await api.getCourseScheduleFilterOptions();
if (res.code === 1) {
//
this.coachOptions = res.data.coaches || [];
//
this.venueOptions = res.data.venues || [];
//
this.generateTimeSlotOptions();
//
this.findSelectedOptions();
} else {
uni.showToast({
title: res.msg || '加载筛选选项失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载筛选选项失败:', error);
uni.showToast({
title: '加载筛选选项失败',
icon: 'none'
});
} finally {
this.loading = false;
}
},
//
findSelectedOptions() {
//
if (this.scheduleInfo.coach_id) {
this.selectedCoach = this.coachOptions.find(coach => coach.id === this.scheduleInfo.coach_id);
this.coachPickerIndex = this.coachOptions.findIndex(coach => coach.id === this.scheduleInfo.coach_id);
if (this.coachPickerIndex === -1) this.coachPickerIndex = 0;
}
//
if (this.scheduleInfo.venue_id) {
this.selectedVenue = this.venueOptions.find(venue => venue.id === this.scheduleInfo.venue_id);
this.venuePickerIndex = this.venueOptions.findIndex(venue => venue.id === this.scheduleInfo.venue_id);
if (this.venuePickerIndex === -1) this.venuePickerIndex = 0;
}
//
if (this.scheduleInfo.time_slot && this.timeSlotOptions.length > 0) {
this.timePickerIndex = this.timeSlotOptions.findIndex(time => time.value === this.scheduleInfo.time_slot);
if (this.timePickerIndex === -1) this.timePickerIndex = 0;
}
},
//
generateTimeSlotOptions() {
// 使
this.generateDefaultTimeOptions();
},
//
onCoachSelect(e) {
const index = e.detail.value;
this.coachPickerIndex = index;
if (index >= 0 && index < this.coachOptions.length) {
this.selectedCoach = this.coachOptions[index];
this.formData.coach_id = this.selectedCoach.id;
}
},
onVenueSelect(e) {
const index = e.detail.value;
this.venuePickerIndex = index;
if (index >= 0 && index < this.venueOptions.length) {
this.selectedVenue = this.venueOptions[index];
this.formData.venue_id = this.selectedVenue.id;
//
if (this.selectedVenue.capacity) {
this.formData.available_capacity = this.selectedVenue.capacity;
}
//
this.loadVenueTimeOptions(this.selectedVenue.id);
}
},
onDateSelect(e) {
this.formData.course_date = e.detail.value;
},
onTimeSelect(e) {
const index = e.detail.value;
this.timePickerIndex = index;
if (index >= 0 && index < this.timeSlotOptions.length) {
this.formData.time_slot = this.timeSlotOptions[index].value;
}
},
//
validateForm() {
//
const hasChanges = this.formData.coach_id !== this.scheduleInfo.coach_id ||
this.formData.venue_id !== this.scheduleInfo.venue_id ||
this.formData.course_date !== this.scheduleInfo.course_date ||
this.formData.time_slot !== this.scheduleInfo.time_slot ||
this.formData.available_capacity !== this.scheduleInfo.available_capacity;
if (!hasChanges) {
uni.showToast({
title: '未进行任何修改',
icon: 'none'
});
return false;
}
return true;
},
//
async loadVenueTimeOptions(venueId) {
if (!venueId) {
// 使
this.generateDefaultTimeOptions();
return;
}
try {
const res = await api.getVenueTimeOptions({ venue_id: venueId });
if (res.code === 1) {
this.timeSlotOptions = res.data.time_options || [];
// picker
this.updateTimePickerIndex();
} else {
console.error('获取场地时间选项失败:', res.msg);
// 使
this.generateDefaultTimeOptions();
this.updateTimePickerIndex();
}
} catch (error) {
console.error('获取场地时间选项失败:', error);
// 使
this.generateDefaultTimeOptions();
this.updateTimePickerIndex();
}
},
// 8:30
generateDefaultTimeOptions() {
const timeSlots = [];
for (let hour = 8; hour < 22; hour++) {
const minute = (hour === 8) ? '30' : '00'; // 8:30
const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0');
const startTime = `${startHour}:${minute}`;
const endTime = `${endHour}:${minute}`;
timeSlots.push({
value: `${startTime}-${endTime}`,
text: `${startTime}-${endTime}`
});
}
this.timeSlotOptions = timeSlots;
},
// picker
updateTimePickerIndex() {
if (this.formData.time_slot && this.timeSlotOptions.length > 0) {
this.timePickerIndex = this.timeSlotOptions.findIndex(time => time.value === this.formData.time_slot);
if (this.timePickerIndex === -1) this.timePickerIndex = 0;
}
},
//
getMinDate() {
const today = new Date();
const year = today.getFullYear();
const month = (today.getMonth() + 1).toString().padStart(2, '0');
const day = today.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
},
//
getMaxDate() {
const nextYear = new Date();
nextYear.setFullYear(nextYear.getFullYear() + 1);
const year = nextYear.getFullYear();
const month = (nextYear.getMonth() + 1).toString().padStart(2, '0');
const day = nextYear.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
},
//
getCurrentDate() {
const today = new Date();
const year = today.getFullYear();
const month = (today.getMonth() + 1).toString().padStart(2, '0');
const day = today.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
},
//
async submitForm() {
if (!this.validateForm()) {
return;
}
this.submitting = true;
try {
const res = await api.updateCourseSchedule(this.formData);
if (res.code === 1) {
uni.showToast({
title: '调整成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: res.msg || '调整失败',
icon: 'none'
});
}
} catch (error) {
console.error('调整课程安排失败:', error);
uni.showToast({
title: '调整失败,请重试',
icon: 'none'
});
} finally {
this.submitting = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.adjust-course-container {
min-height: 100vh;
background-color: #18181c;
}
.form-container {
padding: 30rpx;
}
.loading-container {
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #29d3b4;
margin: 30rpx 0 20rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #333;
}
.course-info-card {
background-color: #23232a;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
}
.info-row {
display: flex;
margin-bottom: 16rpx;
font-size: 28rpx;
}
.info-label {
color: #999;
width: 160rpx;
flex-shrink: 0;
}
.info-value {
color: #fff;
flex: 1;
}
.selector-input {
height: 80rpx;
background-color: #23232a;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
font-size: 28rpx;
color: #fff;
}
.btn-container {
margin-top: 60rpx;
padding: 0 30rpx;
}
/* Picker样式 */
.picker-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: flex-end;
}
.picker-content {
width: 100%;
background-color: #23232a;
border-radius: 20rpx 20rpx 0 0;
max-height: 80vh;
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1px solid #333;
}
.picker-cancel, .picker-confirm {
font-size: 28rpx;
color: #29d3b4;
}
.picker-title {
font-size: 32rpx;
color: #fff;
font-weight: bold;
}
.picker-item {
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 28rpx;
color: #fff;
}
</style>

1767
uniapp/pages-coach/coach/schedule/schedule_table.vue

File diff suppressed because it is too large

521
uniapp/pages-coach/coach/schedule/sign_in.vue

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

282
uniapp/pages-coach/coach/student/physical_examination.vue

@ -0,0 +1,282 @@
<!--体测数据-详情页-->
<template>
<view class="overall">
<view class="date">{{$util.formatToDateTime(surveyInfo.created_at,'Y-m-d')}}</view>
<view class="content">
<view class="circle-container">
<view class="card-con-txt1-left">
<image :src="$util.img('/uniapp_src/static/images/index/score.png')" class="overlay-image"></image>
</view>
<view class="card-con-txt1-left-txt">{{surveyInfo.calculateChildHealthScore}}</view>
<view class="card-con-txt1-left-txt top1">综合评分</view>
</view>
<view style="height: 170rpx;"></view>
<view style="display: flex;justify-content: space-around;">
<view style="text-align: center;">
<view style="color: #AAAAAA;font-size: 30rpx;padding: 15rpx 0;">身高 (CM)</view>
<view style="font-size: 55rpx;color: #29d3b4;">{{(surveyInfo.height)}}</view>
</view>
<view style="text-align: center;">
<view style="color: #AAAAAA;font-size: 30rpx;padding: 15rpx 0;">体重 (KG)</view>
<view style="font-size: 55rpx;color: #29d3b4;">{{surveyInfo.weight}}</view>
</view>
</view>
<!-- <view class="coach-message">-->
<!-- <view>-->
<!-- <image :src="$util.img('/uniapp_src/static/images/index/lv.png')" class="drop-image"></image>-->
<!-- </view>-->
<!-- <view style="padding: 15rpx 0 0 5rpx;line-height: 1.6;font-size: 30rpx;color: #7F7F7F;">{{v.content}}</view>-->
<!-- </view>-->
<view class="list_box">
<!-- <view class="ul">
<view class="li">
<view class="li_title">坐位体前屈</view>
<view class="li_content">测试结果{{surveyInfo.seated_forward_bend}}</view>
</view>
<view class="li">
<view class="li_title">仰卧卷腹</view>
<view class="li_content">测试结果{{surveyInfo.sit_ups}}</view>
</view>
<view class="li">
<view class="li_title">九十度仰卧撑</view>
<view class="li_content">测试结果{{surveyInfo.push_ups}}</view>
</view>
<view class="li">
<view class="li_title">火烈鸟平衡测试</view>
<view class="li_content">测试结果{{surveyInfo.flamingo_balance}}</view>
</view>
<view class="li">
<view class="li_title">三十秒双脚连续跳</view>
<view class="li_content">测试结果{{surveyInfo.thirty_sec_jump}}</view>
</view>
<view class="li">
<view class="li_title">立定跳远</view>
<view class="li_content">测试结果{{surveyInfo.standing_long_jump}}</view>
</view>
<view class="li">
<view class="li_title">4乘10m灵敏折返跑</view>
<view class="li_content">测试结果{{surveyInfo.agility_run}}</view>
</view>
<view class="li">
<view class="li_title">走平衡木</view>
<view class="li_content">测试结果{{surveyInfo.balance_beam}}</view>
</view>
<view class="li">
<view class="li_title">网球掷远</view>
<view class="li_content">测试结果{{surveyInfo.tennis_throw}}</view>
</view>
<view class="li">
<view class="li_title">十米往返跑</view>
<view class="li_content">测试结果{{surveyInfo.ten_meter_shuttle_run}}</view>
</view>
</view> -->
<view v-for="(item,index) in surveyInfo.physical_test_report">
<view style="color: blue;" @click="previewFile(item)">{{surveyInfo.created_at}}体测报告{{index}}</view>
</view>
</view>
</view>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
survey_id: '', //id
surveyInfo: {}, //
}
},
onLoad(options) {
this.survey_id = options.survey_id //id
},
onShow() {
this.init()
},
methods: {
//
async init() {
this.getInfo()
},
//
async getInfo() {
let data = {
survey_id: this.survey_id
}
let res = await apiRoute.physicalTestInfo(data)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.surveyInfo = res.data
},
async previewFile(url) {
console.log(url)
try {
// 1.
const {
tempFilePath
} = await this.downloadFile(url);
// 2.
await uni.openDocument({
filePath: 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 lang="less" scoped>
.overall {
width: 100%;
height: 100vh;
background-color: #29d3b4;
}
.date {
color: #fff;
width: 92%;
margin: auto;
text-align: left;
font-size: 30rpx;
padding: 20rpx 0;
}
.content {
width: 92%;
height: 70vh;
background-color: #fff;
border-radius: 15rpx;
margin: 150rpx auto 0;
position: relative;
}
.circle-container::before {
content: '';
width: 200px;
height: 100px;
background-color: #fff;
border-radius: 100px 100px 0 0;
display: inline-block;
transform: translate(-50%, 0) rotate(0deg);
transform-origin: center top;
position: absolute;
top: -12%;
left: 50%;
transform: translate(-50%, -0%);
}
.card-con-txt1-left {
width: 100%;
height: 100%;
position: relative;
}
.overlay-image {
width: 300rpx;
height: 200rpx;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
}
.card-con-txt1-left-txt {
font-size: 32rpx;
color: #29d3b4;
position: absolute;
left: 50%;
transform: translate(-50%, -0%);
}
.top1 {
top: 5%;
}
.coach-message {
width: 92%;
margin: 10rpx auto;
display: flex;
}
.drop-image {
width: 60rpx;
height: 60rpx;
align-items: center;
}
.list_box{
font-size: 30rpx;
// text-align: center;
margin-left: 50rpx;
margin-top: 20rpx;
.ul{
padding: 0 20rpx;
display: flex;
flex-wrap: wrap; /* 允许换行 */
justify-content: space-between;
align-items: center;
gap: 20rpx;
.li{
width: 48%; /* 每个列表项占宽度的48%,留出一些间距 */
display: flex;
flex-direction: column;
gap: 5rpx;
.li_title{
text-align: left;
}
.li_content{
text-align: left;
}
}
}
}
</style>

728
uniapp/pages-coach/coach/student/student_list.vue

@ -0,0 +1,728 @@
<template>
<view class="container safe-area">
<view class="search-bar" @click="showSearch = true">
<uni-icons type="search" size="22" color="#00d18c" />
<text class="search-placeholder">搜索学员...</text>
</view>
<view class="content">
<view v-if="studentList.length === 0" class="empty-box">
<image :src="$util.img('/static/icon-img/empty.png')" mode="aspectFit" class="empty-img"></image>
<text class="empty-text">暂无学员数据</text>
</view>
<view v-else class="student-list">
<view v-for="(item, index) in studentList" :key="index" class="student-item" @click="goToDetail(item)">
<view class="student-card">
<view class="student-avatar">
<image :src="item.avatar || $util.img('/static/icon-img/avatar.png')" mode="aspectFill" class="avatar-img"></image>
</view>
<view class="student-info">
<view class="student-name">{{item.name}}</view>
<view class="info-row">
<text class="info-label">所属校区</text>
<text class="info-value">{{item.campus}}</text>
</view>
<view class="info-row">
<text class="info-label">剩余课程</text>
<text class="info-value">{{ getRemainingCourses(item) }}</text>
</view>
<view class="info-row">
<text class="info-label">到期时间</text>
<text class="info-value">{{item.end_date}}</text>
</view>
</view>
<view class="arrow-right">
<uni-icons type="right" size="16" color="#CCCCCC"></uni-icons>
</view>
</view>
</view>
</view>
</view>
<!-- 搜索弹窗 -->
<view v-if="showSearch" class="search_popup_mask" @tap="showSearch=false">
<view class="search_popup_content" @tap.stop>
<view class="popup_search_content">
<view class="popup_header">
<view class="popup_title">学员筛选</view>
<view class="popup_close" @tap="showSearch=false">
<text class="close_text"></text>
</view>
</view>
<scroll-view :scroll-y="true" class="popup_scroll_view">
<!-- 第一筛选区域 -->
<view class="popup_filter_section">
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">学生姓名</text>
<input class="popup_filter_input" placeholder="请输入学生姓名" v-model="searchForm.name" />
</view>
<view class="popup_filter_item">
<text class="popup_filter_label">联系电话</text>
<input class="popup_filter_input" placeholder="请输入联系电话" v-model="searchForm.phone" />
</view>
</view>
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">课时数量</text>
<input class="popup_filter_input" placeholder="请输入课时数量" v-model="searchForm.lessonCount" />
</view>
<view class="popup_filter_item">
<text class="popup_filter_label">请假次数</text>
<input class="popup_filter_input" placeholder="请输入请假次数" v-model="searchForm.leaveCount" />
</view>
</view>
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">课程名称</text>
<view class="popup_filter_picker" @click="showCoursePicker = true">
{{ selectedCourseName || '请选择课程' }}
</view>
</view>
<view class="popup_filter_item">
<text class="popup_filter_label">班级</text>
<view class="popup_filter_picker" @click="showClassPicker = true">
{{ selectedClassName || '请选择班级' }}
</view>
</view>
</view>
</view>
</scroll-view>
<view class="popup_filter_buttons">
<view class="popup_filter_btn reset_btn" @click="resetSearch">重置</view>
<view class="popup_filter_btn search_btn" @click="doSearchAndClose">搜索</view>
<view class="popup_filter_btn close_btn" @click="closeSearch">关闭</view>
</view>
</view>
</view>
</view>
<!-- 选择器组件 -->
<single-picker
:show.sync="showCoursePicker"
:data="courseList"
valueKey="id"
textKey="course_name"
title="选择课程"
@change="onCourseChange"
@cancel="showCoursePicker = false"
></single-picker>
<single-picker
:show.sync="showClassPicker"
:data="classList"
valueKey="id"
:textKey="['campus_name', 'class_name']"
textSeparator="-"
title="选择班级"
@change="onClassChange"
@cancel="showClassPicker = false"
></single-picker>
</view>
</template>
<script>
import memberApi from '@/api/member.js'
import apiRoute from '@/api/apiRoute.js';
import SinglePicker from "@/components/custom-picker/single-picker.vue"
export default {
components: {
SinglePicker
},
data() {
return {
studentList: [],
showSearch: false,
showCoursePicker: false,
showClassPicker: false,
searchForm: {
name: '',
phone: '',
lessonCount: '',
leaveCount: '',
courseId: null,
classId: null,
},
selectedCourseName: '',
selectedClassName: '',
courseList: [],
classList: [],
}
},
onLoad() {
this.getStudentList();
this.getClassesList();
},
methods: {
navigateBack() {
uni.navigateBack();
},
async getStudentList() {
try {
//
const params = { type: 'all' };
const res = await apiRoute.xs_getStudentList(Object.assign(params, this.searchForm));
console.log('获取学员列表响应:', res);
if(res.code == 1) {
this.studentList = res.data || [];
//
if (this.studentList.length === 0) {
this.studentList = [
{
id: 1,
name: '于支付',
avatar: '',
campus: '测试校区',
total_hours: 20,
gift_hours: 5,
use_total_hours: 8,
use_gift_hours: 2,
end_date: '2025-08-31',
resource_sharing_id: 1
},
{
id: 2,
name: '测试学员',
avatar: '',
campus: '测试校区',
total_hours: 15,
gift_hours: 3,
use_total_hours: 5,
use_gift_hours: 1,
end_date: '2025-08-15',
resource_sharing_id: 5
}
];
}
console.log('学员列表更新成功:', this.studentList);
} else {
console.error('API返回错误:', res);
uni.showToast({
title: res.msg || '获取学员列表失败',
icon: 'none'
});
}
}catch ( error) {
console.error('获取学员列表错误', error);
uni.showToast({
title: '获取学员列表失败',
icon: 'none'
});
return;
}
},
goToDetail(student) {
this.$navigateToPage(`/pages-market/clue/clue_info`, {
resource_sharing_id: student.resource_sharing_id
});
},
getRemainingCourses(item) {
const totalHours = (item.total_hours || 0)
+ (item.gift_hours || 0);
const usedHours = (item.use_total_hours ||
0) + (item.use_gift_hours || 0);
return totalHours - usedHours;
},
//
onNameInput(e) {
this.searchForm.name = e;
},
onPhoneInput(e) {
this.searchForm.phone = e;
},
onLessonCountInput(e) {
this.searchForm.lessonCount = e;
},
onLeaveCountInput(e) {
this.searchForm.leaveCount = e;
},
//
onCourseChange(e) {
this.searchForm.courseId = e.value;
this.selectedCourseName = e.text;
},
onClassChange(e) {
this.searchForm.classId = e.value;
this.selectedClassName = e.text;
},
//
clearCourseSelection(e) {
e.stopPropagation(); //
this.searchForm.courseId = null;
this.selectedCourseName = '';
},
clearClassSelection(e) {
e.stopPropagation(); //
this.searchForm.classId = null;
this.selectedClassName = '';
},
//
closeSearch() {
this.showSearch = false;
},
//
onMaskClick() {
this.showSearch = false;
},
//
async getClassesList() {
try {
const res = await apiRoute.jlGetClassesList();
if (res.code == 1) {
// API
this.classList = res.data.classes || [];
this.courseList = res.data.course || [];
} else {
uni.showToast({
title: res.msg || '获取班级列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取班级列表错误', error);
uni.showToast({
title: '获取班级列表失败',
icon: 'none'
});
}
},
doSearch() {
// searchForm
this.showSearch = false;
this.getStudentList()
},
doSearchAndClose() {
this.doSearch();
},
resetSearch() {
this.searchForm = {
name: '',
phone: '',
lessonCount: '',
leaveCount: '',
courseId: null,
classId: null,
};
this.selectedCourseName = '';
this.selectedClassName = '';
this.getStudentList();
}
}
}
</script>
<style lang="scss">
.container {
background-color: #18181c;
display: flex;
flex-direction: column;
}
.safe-area {
padding-top: var(--status-bar-height);
padding-bottom: 120rpx;
}
.search-bar {
display: flex;
align-items: center;
background: #23232a;
border-radius: 12rpx;
padding: 18rpx 24rpx;
margin: 24rpx 24rpx 0 24rpx;
color: #bdbdbd;
font-size: 28rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.08);
&:active {
background: #2a2a31;
}
}
.search-placeholder {
margin-left: 12rpx;
color: #bdbdbd;
flex: 1;
}
.content {
padding: 20rpx;
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 200rpx;
.empty-img {
width: 200rpx;
height: 200rpx;
}
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
}
.student-list {
.student-item {
margin-bottom: 20rpx;
}
.student-card {
display: flex;
align-items: center;
background-color: #23232a;
border-radius: 12rpx;
padding: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.12);
transition: box-shadow 0.2s;
&:active {
box-shadow: 0 4rpx 20rpx rgba(0,255,128,0.12);
}
}
.student-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
overflow: hidden;
margin-right: 30rpx;
.avatar-img {
width: 100%;
height: 100%;
}
}
.student-info {
flex: 1;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
color: #fff;
}
.info-row {
display: flex;
font-size: 26rpx;
margin-top: 8rpx;
.info-label {
color: #bdbdbd;
}
.info-value {
color: #fff;
}
}
.arrow-right {
padding-left: 20rpx;
}
}
.popup-content {
background: #23232a;
color: #fff;
padding: 32rpx 24rpx;
border-radius: 16rpx;
min-width: 300rpx;
text-align: left;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #00d18c;
margin-bottom: 24rpx;
text-align: center;
}
.picker-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx 0;
border-bottom: 1px solid #333;
.picker-label {
color: #bdbdbd;
font-size: 28rpx;
}
.picker-value {
color: #fff;
font-size: 28rpx;
}
}
.search-btn {
width: 100%;
background: #00d18c;
color: #fff;
border: none;
border-radius: 12rpx;
font-size: 30rpx;
padding: 20rpx 0;
margin-top: 32rpx;
margin-bottom: 8rpx;
}
.fui-picker__input {
display: flex;
justify-content: space-between;
align-items: center;
height: 72rpx;
padding: 0 24rpx;
background-color: #2c2c34;
border-radius: 8rpx;
text {
font-size: 28rpx;
color: #fff;
}
}
.fui-btn__box {
margin-top: 50rpx;
padding: 0 30rpx;
}
.fui-page__bd {
padding: 30rpx;
padding-top: 70rpx;
background-color: #23232a;
position: relative;
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
}
.fui-section__title {
font-size: 36rpx;
color: #00d18c;
font-weight: bold;
margin-top: 30rpx;
text-align: center;
}
.search-close-icon {
position: absolute;
top: 24rpx;
right: 24rpx;
z-index: 10;
padding: 10rpx;
}
.custom-picker-input {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-radius: 8rpx;
border-bottom: 2rpx solid #00d18c;
}
.picker-actions {
display: flex;
align-items: center;
gap: 10rpx;
}
// - market/clue
.search_popup_mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 999;
display: flex;
flex-direction: column;
}
.search_popup_content {
background: #fff;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
animation: slideDown 0.3s ease-out;
width: 100%;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
@keyframes slideDown {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
//
.popup_search_content {
padding: 0;
background: #fff;
min-height: 60vh;
max-height: 80vh;
display: flex;
flex-direction: column;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
overflow: hidden;
}
.popup_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
}
.popup_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup_close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.close_text {
font-size: 32rpx;
color: #999;
}
}
.popup_scroll_view {
flex: 1;
padding: 32rpx;
overflow-y: auto;
}
.popup_filter_section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
}
.popup_filter_row {
display: flex;
gap: 20rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
}
.popup_filter_item {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
&.full_width {
flex: 1;
}
.popup_filter_label {
font-size: 26rpx;
color: #666;
font-weight: 500;
}
.popup_filter_input {
height: 72rpx;
line-height: 72rpx;
padding: 0 16rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
background: #fff;
&::placeholder {
color: #999;
}
}
.popup_filter_picker {
height: 72rpx;
line-height: 72rpx;
padding: 0 16rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
background: #fff;
position: relative;
&::after {
content: '▼';
position: absolute;
right: 16rpx;
font-size: 20rpx;
color: #999;
}
}
}
.popup_filter_buttons {
display: flex;
gap: 20rpx;
padding: 32rpx;
margin-top: auto;
border-top: 1px solid #f0f0f0;
background: #fff;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
}
.popup_filter_btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 600;
&.search_btn {
background: #00d18c;
color: #fff;
}
&.reset_btn {
background: #f5f5f5;
color: #666;
border: 1px solid #ddd;
}
&.close_btn {
background: #666;
color: #fff;
}
}
</style>

129
uniapp/pages-common/article_info.vue

@ -0,0 +1,129 @@
<!--文章-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="titile">{{infoData.title}}</view>
<view class="content" v-html="infoData.content"></view>
</view>
</view>
</view>
</template>
<script>
import commonApi from '@/api/common.js';
export default {
components: {
},
data() {
return {
//
filteredData:{
id: '1',
},
infoData: {
title: '文章标题xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx111111',//
content: `
<p>aspectFit保持纵横比缩放图片使图片的长边能完全显示出来</p>
<p><img src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/cat-2.png" alt="uniapp" loading="lazy"></p>
`,//文章内容
},//
}
},
onLoad(options) {
this.filteredData.id = options.id//id
},
onShow(){
this.init()
},
//
async onPullDownRefresh() {
await this.getInfo()
},
methods: {
//
async init(){
await this.getInfo();
},
//
//
async getInfo(){
let res = await memberApi.courseInfo({
id: this.filteredData.id,
})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.infoData = res.data
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #fff;
word-wrap: break-word; /* 允许长单词或 URL 换行 */
word-break: break-all; /* 强制所有字符换行 */
}
//
.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: #fff;
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;
gap: 30rpx;
.titile{
font-size: 40rpx;
}
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
</style>

707
uniapp/pages-common/contract/contract_detail.vue

@ -0,0 +1,707 @@
<template>
<view class="contract-detail-container">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" content-text="加载中..."></uni-load-more>
</view>
<!-- 合同内容 -->
<view v-else class="contract-content">
<!-- 合同状态卡片 -->
<view class="status-card">
<view class="status-header">
<view class="status-info">
<text class="contract-name">{{contractData.contract_name}}</text>
<view class="status-badge" :class="needSignButton() ? 'status-unsigned' : 'status-signed'">
{{needSignButton() ? '待签订' : '已签订'}}
</view>
</view>
<view class="status-icon">
<text v-if="needSignButton()" class="status-icon-text"></text>
<text v-else class="status-icon-text"></text>
</view>
</view>
<view class="status-desc">
<text v-if="needSignButton()" class="desc-text">请仔细阅读合同内容确认无误后进行签订</text>
<text v-else class="desc-text">合同已签订完成您可以查看或下载合同文件</text>
</view>
</view>
<!-- 合同基本信息 -->
<view class="info-section">
<view class="section-title">基本信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">合同名称</text>
<text class="info-value">{{contractData.contract_name}}</text>
</view>
<view class="info-item">
<text class="info-label">合同类型</text>
<text class="info-value">{{contractData.contract_type}}</text>
</view>
<view class="info-item">
<text class="info-label">合同状态</text>
<text class="info-value">{{contractData.contract_status}}</text>
</view>
<view class="info-item">
<text class="info-label">创建时间</text>
<text class="info-value">{{formatDate(contractData.created_at)}}</text>
</view>
<view class="info-item" v-if="contractData.sign_time">
<text class="info-label">签订时间</text>
<text class="info-value">{{formatDate(contractData.sign_time)}}</text>
</view>
<view class="info-item" v-if="contractData.remarks">
<text class="info-label">备注</text>
<text class="info-value">{{contractData.remarks}}</text>
</view>
</view>
</view>
<!-- 合同模板预览 -->
<view class="template-section" v-if="contractData.contract_template">
<view class="section-title">合同模板</view>
<view class="template-preview" @click="previewTemplate">
<view class="template-icon">
<text class="iconfont icon-file"></text>
</view>
<view class="template-info">
<text class="template-name">{{getTemplateName()}}</text>
<text class="template-desc">点击预览合同模板</text>
</view>
<view class="template-arrow">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 签名文件 -->
<view class="signature-section" v-if="contractData.sign_file">
<view class="section-title">签名文件</view>
<view class="signature-preview" @click="previewSignature">
<image :src="getSignatureUrl()" class="signature-image" mode="aspectFit"></image>
<view class="signature-overlay">
<text class="signature-text">点击查看完整签名</text>
</view>
</view>
</view>
<!-- 操作历史 -->
<view class="history-section">
<view class="section-title">操作历史</view>
<view class="history-list">
<view class="history-item">
<view class="history-dot"></view>
<view class="history-content">
<text class="history-title">合同创建</text>
<text class="history-time">{{formatDate(contractData.created_at)}}</text>
</view>
</view>
<view class="history-item" v-if="contractData.sign_time">
<view class="history-dot active"></view>
<view class="history-content">
<text class="history-title">合同签订</text>
<text class="history-time">{{formatDate(contractData.sign_time)}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="footer-actions" v-if="!loading">
<button v-if="needSignButton()"
class="action-btn primary"
@click="goToSign">
立即签订
</button>
<button v-else
class="action-btn secondary"
@click="downloadContract">
下载合同
</button>
</view>
<!-- 图片预览模态框 -->
<view v-if="showImagePreview" class="image-preview-modal" @click="closeImagePreview">
<view class="preview-content" @click.stop>
<image :src="previewImageUrl" class="preview-image" mode="aspectFit"></image>
<view class="preview-close" @click="closeImagePreview">
<text class="iconfont icon-close"></text>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/common/axios.js'
export default {
data() {
return {
contractId: 0,
contractData: {},
loading: true,
showImagePreview: false,
previewImageUrl: ''
}
},
onLoad(options) {
if (options.id) {
this.contractId = parseInt(options.id)
this.loadContractDetail()
} else {
uni.showToast({
title: '合同ID不能为空',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
methods: {
//
async loadContractDetail() {
this.loading = true
try {
const response = await apiRoute.get('/contract/detail', {
id: this.contractId
})
if (response.code === 1) {
this.contractData = response.data.data || {}
} else {
uni.showToast({
title: response.data.msg || '加载失败',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
} catch (error) {
console.error('加载合同详情失败:', error)
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
//
needSignButton() {
return this.contractData.status === 1 && (
this.contractData.sign_file === null ||
this.contractData.sign_file === '' ||
this.contractData.sign_file === undefined
)
},
//
goToSign() {
uni.navigateTo({
url: `/pages-common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent(this.contractData.contract_name)}`
})
},
//
previewTemplate() {
if (this.contractData.contract_template) {
const templateUrl = this.$baseUrl + '/' + this.contractData.contract_template
// #ifdef APP-PLUS
plus.runtime.openURL(templateUrl)
// #endif
// #ifdef H5
window.open(templateUrl, '_blank')
// #endif
// #ifdef MP-WEIXIN
uni.downloadFile({
url: templateUrl,
success: (res) => {
uni.openDocument({
filePath: res.tempFilePath,
fileType: this.getFileType(this.contractData.contract_template)
})
}
})
// #endif
}
},
//
previewSignature() {
if (this.contractData.sign_file) {
this.previewImageUrl = this.getSignatureUrl()
this.showImagePreview = true
}
},
//
closeImagePreview() {
this.showImagePreview = false
this.previewImageUrl = ''
},
// URL
getSignatureUrl() {
if (this.contractData.sign_file) {
if (this.contractData.sign_file.startsWith('http')) {
return this.contractData.sign_file
} else {
return this.$baseUrl + '/' + this.contractData.sign_file
}
}
return ''
},
//
getTemplateName() {
if (this.contractData.contract_template) {
const parts = this.contractData.contract_template.split('/')
return parts[parts.length - 1]
}
return '合同模板'
},
//
getFileType(fileName) {
const ext = fileName.split('.').pop().toLowerCase()
const typeMap = {
'pdf': 'pdf',
'doc': 'doc',
'docx': 'doc',
'xls': 'xls',
'xlsx': 'xls'
}
return typeMap[ext] || 'doc'
},
//
downloadContract() {
if (this.contractData.sign_file) {
const signUrl = this.getSignatureUrl()
uni.downloadFile({
url: signUrl,
success: (res) => {
uni.showToast({
title: '下载成功',
icon: 'success'
})
},
fail: () => {
uni.showToast({
title: '下载失败',
icon: 'none'
})
}
})
}
},
//
shareContract() {
uni.showActionSheet({
itemList: ['分享给好友', '保存到相册'],
success: (res) => {
if (res.tapIndex === 0) {
//
this.shareToFriend()
} else if (res.tapIndex === 1) {
//
this.saveToAlbum()
}
}
})
},
//
shareToFriend() {
uni.showToast({
title: '分享功能开发中',
icon: 'none'
})
},
//
saveToAlbum() {
if (this.contractData.sign_file) {
uni.saveImageToPhotosAlbum({
filePath: this.getSignatureUrl(),
success: () => {
uni.showToast({
title: '保存成功',
icon: 'success'
})
},
fail: () => {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
},
//
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')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
}
}
</script>
<style lang="scss" scoped>
.contract-detail-container {
min-height: 100vh;
background-color: #1a1a1a;
padding-bottom: 120rpx;
}
/* 合同内容 */
.contract-content {
padding: 20rpx;
}
/* 状态卡片 */
.status-card {
background: linear-gradient(135deg, #007ACC 0%, #0056b3 100%);
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 24rpx;
color: #fff;
.status-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 24rpx;
.status-info {
flex: 1;
.contract-name {
font-size: 36rpx;
font-weight: 600;
display: block;
margin-bottom: 16rpx;
}
.status-badge {
display: inline-block;
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
background-color: rgba(255, 255, 255, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
}
}
.status-icon {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.status-icon-text {
font-size: 48rpx;
}
}
}
.status-desc {
.desc-text {
font-size: 28rpx;
opacity: 0.9;
line-height: 1.5;
}
}
}
/* 信息区块 */
.info-section, .template-section, .signature-section, .history-section {
background-color: #2a2a2a;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
border: 1rpx solid #444;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #fff;
margin-bottom: 24rpx;
border-left: 6rpx solid #29d3b4;
padding-left: 16rpx;
}
}
.info-list {
.info-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 28rpx;
color: #999;
width: 140rpx;
flex-shrink: 0;
}
.info-value {
font-size: 28rpx;
color: #ccc;
flex: 1;
}
}
}
/* 模板预览 */
.template-preview {
display: flex;
align-items: center;
padding: 24rpx;
background-color: #1a1a1a;
border-radius: 12rpx;
cursor: pointer;
border: 1rpx solid #444;
.template-icon {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #29d3b4;
border-radius: 12rpx;
margin-right: 20rpx;
.iconfont {
font-size: 32rpx;
color: #fff;
}
}
.template-info {
flex: 1;
.template-name {
font-size: 28rpx;
color: #fff;
font-weight: 500;
display: block;
margin-bottom: 8rpx;
}
.template-desc {
font-size: 24rpx;
color: #999;
}
}
.template-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 24rpx;
color: #666;
}
}
}
/* 签名预览 */
.signature-preview {
position: relative;
width: 100%;
height: 300rpx;
border-radius: 12rpx;
overflow: hidden;
cursor: pointer;
.signature-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.signature-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
padding: 20rpx;
display: flex;
align-items: center;
justify-content: center;
.signature-text {
color: #fff;
font-size: 24rpx;
}
}
}
/* 操作历史 */
.history-list {
.history-item {
display: flex;
align-items: flex-start;
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.history-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #e5e5e5;
margin-right: 20rpx;
margin-top: 8rpx;
flex-shrink: 0;
&.active {
background-color: #29d3b4;
}
}
.history-content {
flex: 1;
.history-title {
font-size: 28rpx;
color: #fff;
font-weight: 500;
display: block;
margin-bottom: 8rpx;
}
.history-time {
font-size: 24rpx;
color: #999;
}
}
}
}
/* 底部操作按钮 */
.footer-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #2a2a2a;
padding: 24rpx 32rpx;
border-top: 1rpx solid #444;
z-index: 100;
.action-btn {
width: 100%;
height: 88rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
display: flex;
align-items: center;
justify-content: center;
&.primary {
background-color: #29d3b4;
color: #fff;
&:active {
background-color: #22b39a;
}
}
&.secondary {
background-color: #f5f5f5;
color: #666;
&:active {
background-color: #e5e5e5;
}
}
}
}
/* 加载状态 */
.loading-container {
padding: 120rpx 40rpx;
text-align: center;
}
/* 图片预览模态框 */
.image-preview-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
.preview-content {
position: relative;
width: 90%;
height: 70%;
.preview-image {
width: 100%;
height: 100%;
}
.preview-close {
position: absolute;
top: -60rpx;
right: 0;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 36rpx;
color: #fff;
}
}
}
}
</style>

733
uniapp/pages-common/contract/contract_sign.vue

@ -0,0 +1,733 @@
<template>
<view class="contract-sign-container">
<!-- 顶部操作栏 -->
<view class="top-actions">
<view class="clear-btn" @click="clearSignature">
<text class="clear-text">清除签名</text>
</view>
</view>
<!-- 合同信息 -->
<view class="contract-info">
<view class="info-card">
<text class="contract-name">{{contractName}}</text>
<text class="sign-tip">请在下方签名区域进行手写签名</text>
</view>
</view>
<!-- 签名区域 -->
<view class="signature-section">
<view class="signature-title">
<text class="title-text">请在此区域签名</text>
<text class="tip-text">支持手指或触控笔签名</text>
</view>
<!-- Canvas 签名板 -->
<view class="signature-canvas-container">
<canvas
class="signature-canvas"
canvas-id="signatureCanvas"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
disable-scroll="true">
</canvas>
<!-- 签名提示 -->
<view v-if="!hasSigned" class="signature-placeholder">
<text class="placeholder-icon"></text>
<text class="placeholder-text">请在此处签名</text>
</view>
</view>
<!-- 签名工具栏 -->
<view class="signature-tools">
<view class="tool-group">
<text class="tool-label">笔迹颜色</text>
<view class="color-picker">
<view v-for="color in penColors"
:key="color"
class="color-item"
:class="currentColor === color ? 'active' : ''"
:style="{ backgroundColor: color }"
@click="setPenColor(color)">
</view>
</view>
</view>
<view class="tool-group">
<text class="tool-label">笔迹粗细</text>
<view class="width-picker">
<view v-for="width in penWidths"
:key="width"
class="width-item"
:class="currentWidth === width ? 'active' : ''"
@click="setPenWidth(width)">
<view class="width-preview" :style="{ width: width + 'rpx', height: width + 'rpx' }"></view>
</view>
</view>
</view>
</view>
</view>
<!-- 签名预览 -->
<view v-if="signatureImageUrl" class="preview-section">
<view class="preview-title">签名预览</view>
<view class="preview-image-container">
<image :src="signatureImageUrl" class="preview-image" mode="aspectFit"></image>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="footer-actions">
<button class="action-btn secondary" @click="previewSignature">预览签名</button>
<button class="action-btn primary"
@click="submitSignature"
:disabled="!hasSigned || submitting"
:loading="submitting">
{{submitting ? '提交中...' : '确认签订'}}
</button>
</view>
<!-- 签名预览弹窗 -->
<view v-if="showPreview" class="preview-modal" @click="closePreview">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">签名预览</text>
<view class="modal-close" @click="closePreview">
<text class="iconfont icon-close"></text>
</view>
</view>
<view class="modal-body">
<image v-if="signatureImageUrl" :src="signatureImageUrl" class="modal-image" mode="aspectFit"></image>
<text v-else class="no-signature">暂无签名</text>
</view>
<view class="modal-footer">
<button class="modal-btn secondary" @click="closePreview">取消</button>
<button class="modal-btn primary" @click="confirmSignature">确认签订</button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/common/axios.js'
import { uploadFile } from '@/common/util.js';
export default {
data() {
return {
contractId: 0,
contractName: '',
canvas: null,
ctx: null,
isDrawing: false,
lastPoint: null,
hasSigned: false,
signatureImageUrl: '',
submitting: false,
showPreview: false,
//
currentColor: '#000000',
currentWidth: 6,
penColors: ['#000000', '#FF0000', '#0000FF', '#008000', '#800080'],
penWidths: [3, 6, 9, 12],
// Canvas
canvasWidth: 0,
canvasHeight: 0,
pixelRatio: 1
}
},
onLoad(options) {
if (options.id) {
this.contractId = parseInt(options.id)
}
if (options.contractName) {
this.contractName = decodeURIComponent(options.contractName)
}
this.$nextTick(() => {
this.initCanvas()
})
},
onReady() {
this.initCanvas()
},
methods: {
// Canvas
initCanvas() {
const query = uni.createSelectorQuery().in(this)
query.select('.signature-canvas').boundingClientRect((rect) => {
if (rect) {
this.canvasWidth = rect.width
this.canvasHeight = rect.height
//
const systemInfo = uni.getSystemInfoSync()
this.pixelRatio = systemInfo.pixelRatio || 1
// Canvas
this.canvas = uni.createCanvasContext('signatureCanvas', this)
this.ctx = this.canvas
// Canvas
this.ctx.scale(this.pixelRatio, this.pixelRatio)
//
this.ctx.lineWidth = this.currentWidth
this.ctx.strokeStyle = this.currentColor
this.ctx.lineCap = 'round'
this.ctx.lineJoin = 'round'
// Canvas
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
this.ctx.draw()
}
}).exec()
},
//
touchStart(e) {
if (!this.ctx) return
this.isDrawing = true
const touch = e.touches[0]
this.lastPoint = {
x: touch.x,
y: touch.y
}
this.ctx.beginPath()
this.ctx.moveTo(touch.x, touch.y)
},
//
touchMove(e) {
if (!this.ctx || !this.isDrawing) return
const touch = e.touches[0]
const currentPoint = {
x: touch.x,
y: touch.y
}
this.ctx.lineTo(currentPoint.x, currentPoint.y)
this.ctx.stroke()
this.ctx.draw(true)
this.lastPoint = currentPoint
this.hasSigned = true
},
//
touchEnd(e) {
this.isDrawing = false
this.lastPoint = null
},
//
setPenColor(color) {
this.currentColor = color
if (this.ctx) {
this.ctx.strokeStyle = color
}
},
//
setPenWidth(width) {
this.currentWidth = width
if (this.ctx) {
this.ctx.lineWidth = width
}
},
//
clearSignature() {
if (this.ctx) {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
this.ctx.draw()
this.hasSigned = false
this.signatureImageUrl = ''
}
},
//
previewSignature() {
if (!this.hasSigned) {
uni.showToast({
title: '请先进行签名',
icon: 'none'
})
return
}
this.generateSignatureImage(() => {
this.showPreview = true
})
},
//
generateSignatureImage(callback) {
if (!this.canvas) return
uni.canvasToTempFilePath({
canvasId: 'signatureCanvas',
success: (res) => {
this.signatureImageUrl = res.tempFilePath
if (callback) callback()
},
fail: (err) => {
console.error('生成签名图片失败:', err)
uni.showToast({
title: '生成签名失败',
icon: 'none'
})
}
}, this)
},
//
closePreview() {
this.showPreview = false
},
//
confirmSignature() {
this.closePreview()
this.submitSignature()
},
//
submitSignature() {
if (!this.hasSigned) {
uni.showToast({
title: '请先进行签名',
icon: 'none'
})
return
}
this.generateSignatureImage(() => {
this.uploadSignature()
})
},
//
async uploadSignature() {
if (!this.signatureImageUrl) {
uni.showToast({
title: '签名生成失败,请重试',
icon: 'none'
})
return
}
this.submitting = true
try {
//
const uploadResult = await this.uploadSignatureFile()
if (!uploadResult.success) {
throw new Error(uploadResult.message || '上传签名文件失败')
}
//
const response = await apiRoute.post('/member/contract_sign', {
contract_sign_id: this.contractId,
pic_file: uploadResult.url
})
if (response.code === 1) {
uni.showToast({
title: '签名提交成功',
icon: 'success',
duration: 2000
})
setTimeout(() => {
//
uni.navigateBack({
delta: 1
})
}, 2000)
} else {
throw new Error(response.data.msg || '提交签名失败')
}
} catch (error) {
console.error('提交签名失败:', error)
uni.showToast({
title: error.message || '网络错误,请稍后重试',
icon: 'none'
})
} finally {
this.submitting = false
}
},
//
async uploadSignatureFile() {
return new Promise((resolve, reject) => {
uploadFile(
this.signatureImageUrl,
(fileData) => {
resolve({ success: true, url: fileData.url });
},
(err) => {
resolve({ success: false, message: '上传失败' });
}
);
});
},
}
}
</script>
<style lang="scss" scoped>
.contract-sign-container {
min-height: 100vh;
background-color: #1a1a1a;
padding-bottom: 120rpx;
}
/* 顶部操作栏 */
.top-actions {
display: flex;
justify-content: flex-end;
padding: 20rpx 32rpx;
.clear-btn {
padding: 12rpx 24rpx;
background-color: #29d3b4;
border-radius: 8rpx;
.clear-text {
font-size: 28rpx;
color: #fff;
}
}
}
/* 合同信息 */
.contract-info {
padding: 20rpx;
.info-card {
background-color: #2a2a2a;
border-radius: 16rpx;
padding: 32rpx;
text-align: center;
border: 1rpx solid #444;
.contract-name {
font-size: 32rpx;
font-weight: 600;
color: #fff;
display: block;
margin-bottom: 16rpx;
}
.sign-tip {
font-size: 26rpx;
color: #999;
}
}
}
/* 签名区域 */
.signature-section {
padding: 0 20rpx;
margin-bottom: 40rpx;
.signature-title {
text-align: center;
margin-bottom: 24rpx;
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #fff;
display: block;
margin-bottom: 8rpx;
}
.tip-text {
font-size: 24rpx;
color: #999;
}
}
}
.signature-canvas-container {
position: relative;
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 32rpx;
overflow: hidden;
border: 2rpx dashed #29d3b4;
.signature-canvas {
width: 100%;
height: 400rpx;
display: block;
}
.signature-placeholder {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: none;
.placeholder-icon {
font-size: 60rpx;
display: block;
margin-bottom: 16rpx;
}
.placeholder-text {
font-size: 28rpx;
color: #999;
}
}
}
/* 签名工具栏 */
.signature-tools {
background-color: #2a2a2a;
border-radius: 16rpx;
padding: 24rpx;
border: 1rpx solid #444;
.tool-group {
display: flex;
align-items: center;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.tool-label {
font-size: 26rpx;
color: #ccc;
margin-right: 20rpx;
min-width: 120rpx;
}
}
.color-picker {
display: flex;
gap: 16rpx;
.color-item {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 3rpx solid transparent;
cursor: pointer;
&.active {
border-color: #29d3b4;
}
}
}
.width-picker {
display: flex;
gap: 20rpx;
.width-item {
display: flex;
align-items: center;
justify-content: center;
width: 50rpx;
height: 50rpx;
border-radius: 8rpx;
border: 2rpx solid #e5e5e5;
cursor: pointer;
&.active {
border-color: #29d3b4;
background-color: rgba(41, 211, 180, 0.1);
}
.width-preview {
background-color: #333;
border-radius: 50%;
}
}
}
}
/* 签名预览 */
.preview-section {
padding: 0 20rpx;
margin-bottom: 40rpx;
.preview-title {
font-size: 30rpx;
font-weight: 600;
color: #fff;
text-align: center;
margin-bottom: 24rpx;
}
.preview-image-container {
background-color: #2a2a2a;
border-radius: 16rpx;
padding: 32rpx;
border: 1rpx solid #444;
.preview-image {
width: 100%;
height: 200rpx;
}
}
}
/* 底部操作按钮 */
.footer-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #2a2a2a;
padding: 24rpx 32rpx;
border-top: 1rpx solid #444;
display: flex;
gap: 24rpx;
z-index: 100;
.action-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
display: flex;
align-items: center;
justify-content: center;
&.primary {
background-color: #29d3b4;
color: #fff;
&:active:not(:disabled) {
background-color: #22b39a;
}
&:disabled {
background-color: #666;
color: #999;
}
}
&.secondary {
background-color: #444;
color: #ccc;
&:active {
background-color: #555;
}
}
}
}
/* 预览弹窗 */
.preview-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: #fff;
border-radius: 20rpx;
width: 80%;
max-height: 70%;
overflow: hidden;
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1rpx solid #e5e5e5;
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.modal-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 32rpx;
color: #999;
}
}
}
.modal-body {
padding: 32rpx;
text-align: center;
.modal-image {
width: 100%;
height: 300rpx;
}
.no-signature {
font-size: 28rpx;
color: #999;
padding: 60rpx 0;
}
}
.modal-footer {
display: flex;
gap: 24rpx;
padding: 32rpx;
border-top: 1rpx solid #e5e5e5;
.modal-btn {
flex: 1;
height: 72rpx;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 600;
border: none;
&.primary {
background-color: #29d3b4;
color: #fff;
}
&.secondary {
background-color: #f5f5f5;
color: #666;
}
}
}
}
}
</style>

411
uniapp/pages-common/contract/my_contract.vue

@ -0,0 +1,411 @@
<template>
<view class="contract-container">
<!-- 筛选栏 -->
<view class="filter-bar">
<view class="filter-item"
:class="filterStatus === '' ? 'active' : ''"
@click="filterContract('')">
全部
</view>
<view class="filter-item"
:class="filterStatus === 'unsigned' ? 'active' : ''"
@click="filterContract('unsigned')">
待签订
</view>
<view class="filter-item"
:class="filterStatus === 'signed' ? 'active' : ''"
@click="filterContract('signed')">
已签订
</view>
</view>
<!-- 合同列表 -->
<view class="contract-list">
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" content-text="加载中..."></uni-load-more>
</view>
<view v-else-if="filteredContracts.length === 0" class="empty-container">
<view class="empty-icon">📄</view>
<view class="empty-text">暂无合同数据</view>
</view>
<view v-else>
<view v-for="contract in filteredContracts"
:key="contract.id"
class="contract-item"
@click="goToDetail(contract)">
<!-- 合同卡片 -->
<view class="contract-card">
<!-- 合同标题和状态 -->
<view class="contract-header">
<view class="contract-title">
<text class="title-text">{{contract.contract_name}}</text>
<view class="contract-status" :class="needSignButton(contract) ? 'status-unsigned' : 'status-signed'">
{{needSignButton(contract) ? '待签订' : '已签订'}}
</view>
</view>
<view class="contract-arrow">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
<!-- 合同信息 -->
<view class="contract-info">
<view class="info-row">
<text class="info-label">合同类型</text>
<text class="info-value">{{contract.contract_type}}</text>
</view>
<view class="info-row">
<text class="info-label">创建时间</text>
<text class="info-value">{{formatDate(contract.created_at)}}</text>
</view>
<view class="info-row" v-if="contract.sign_time">
<text class="info-label">签订时间</text>
<text class="info-value">{{formatDate(contract.sign_time)}}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="contract-actions" v-if="needSignButton(contract)">
<button class="sign-btn" @click.stop="goToSign(contract)">
立即签订
</button>
</view>
</view>
</view>
</view>
</view>
<!-- 底部加载更多 -->
<view v-if="hasMore && !loading && filteredContracts.length > 0" class="load-more">
<uni-load-more :status="loadMoreStatus" @clickLoadMore="loadMore"></uni-load-more>
</view>
</view>
</template>
<script>
import apiRoute from '@/common/axios.js'
export default {
data() {
return {
loading: true,
contracts: [],
filterStatus: '', // '', 'unsigned', 'signed'
currentPage: 1,
pageSize: 10,
hasMore: true,
loadMoreStatus: 'more'
}
},
computed: {
filteredContracts() {
if (this.filterStatus === '') {
return this.contracts
} else if (this.filterStatus === 'unsigned') {
return this.contracts.filter(contract => this.needSignButton(contract))
} else if (this.filterStatus === 'signed') {
return this.contracts.filter(contract => !this.needSignButton(contract))
}
return this.contracts
}
},
onLoad() {
this.loadContracts()
},
onPullDownRefresh() {
this.refreshContracts()
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore()
}
},
methods: {
//
async loadContracts(refresh = false) {
if (refresh) {
this.currentPage = 1
this.hasMore = true
this.contracts = []
}
this.loading = true
this.loadMoreStatus = 'loading'
try {
const response = await apiRoute.get('/contract/myContracts', {
page: this.currentPage,
limit: this.pageSize
})
if (response.code === 1) {
const newContracts = response.data.data || []
if (refresh) {
this.contracts = newContracts
} else {
this.contracts = [...this.contracts, ...newContracts]
}
//
this.hasMore = newContracts.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()
}
}
},
//
refreshContracts() {
this.loadContracts(true)
},
//
loadMore() {
if (this.hasMore && !this.loading) {
this.currentPage++
this.loadContracts()
}
},
//
filterContract(status) {
this.filterStatus = status
},
//
goToDetail(contract) {
uni.navigateTo({
url: `/pages-common/contract/contract_detail?id=${contract.id}`
})
},
//
goToSign(contract) {
uni.navigateTo({
url: `/pages-common/contract/contract_sign?id=${contract.id}&contractName=${encodeURIComponent(contract.contract_name)}`
})
},
//
needSignButton(contract) {
return contract.status == 1
},
//
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}`
}
}
}
</script>
<style lang="scss" scoped>
.contract-container {
min-height: 100vh;
background-color: #1a1a1a;
}
/* 筛选栏 */
.filter-bar {
display: flex;
background-color: #2a2a2a;
padding: 24rpx 32rpx;
margin-bottom: 20rpx;
border-bottom: 1rpx solid #444;
.filter-item {
flex: 1;
text-align: center;
padding: 16rpx 24rpx;
font-size: 28rpx;
color: #ccc;
border-radius: 8rpx;
transition: all 0.3s;
&.active {
background-color: #29d3b4;
color: #fff;
}
}
}
/* 合同列表 */
.contract-list {
padding: 0 20rpx;
}
.contract-item {
margin-bottom: 24rpx;
}
.contract-card {
background-color: #2a2a2a;
border-radius: 16rpx;
padding: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
border: 1rpx solid #444;
}
.contract-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 24rpx;
.contract-title {
flex: 1;
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #fff;
display: block;
margin-bottom: 12rpx;
}
.contract-status {
display: inline-block;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-unsigned {
background-color: #fff3cd;
color: #856404;
border: 1rpx solid #ffeaa7;
}
&.status-signed {
background-color: #d4edda;
color: #155724;
border: 1rpx solid #c3e6cb;
}
}
}
.contract-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 24rpx;
color: #666;
}
}
}
.contract-info {
.info-row {
display: flex;
margin-bottom: 16rpx;
.info-label {
font-size: 26rpx;
color: #999;
width: 140rpx;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: #ccc;
flex: 1;
}
}
}
.contract-actions {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
.sign-btn {
width: 100%;
height: 72rpx;
background-color: #29d3b4;
color: #fff;
border: none;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
&:active {
background-color: #22b39a;
}
}
}
/* 加载状态 */
.loading-container {
padding: 60rpx 0;
text-align: center;
}
.empty-container {
text-align: center;
padding: 120rpx 40rpx;
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 28rpx;
color: #666;
}
}
.load-more {
padding: 40rpx 0;
}
/* 动画效果 */
.contract-item {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

235
uniapp/pages-common/feedback.vue

@ -0,0 +1,235 @@
<!--授课统计-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section">
<view class="text_input">
<fui-textarea placeholder="请输入反馈内容" v-model="formData.feedback_text"></fui-textarea>
</view>
</view>
<view class="section">
<view class="upload_box">
<view>上传图片</view>
<AQUplodeImgMulti :inputName="`attachment_url_arr`" :inputValue="formData.attachment_url_arr || []" :uploadApiUrl="uploadApiUrl" :maxFileNum="1"
@AQUploadSuccess="AQUploadSuccess" />
</view>
</view>
<!-- <view class="section">-->
<!-- <view class="input_box">-->
<!-- <fui-input label="邮箱方式" borderTop placeholder="请输入邮箱" v-model="formData.mailbox"></fui-input>-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="describe">-->
<!-- 反馈的相关问题会第一时间通过邮箱解答-->
<!-- </view>-->
<view class="btn" @click="submitForm()">提交</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import memberApi from '@/api/member.js';
import AQUplodeImgMulti from '@/components/AQ/AQUplodeImgMulti';
import AQTabber from "@/components/AQ/AQTabber"
import {
Api_url
} from "@/common/config.js";
export default {
components: {
AQTabber,
AQUplodeImgMulti,
},
data() {
return {
uploadApiUrl:`${Api_url}/memberUploadImage`,//url
formData: {
attachment_url_arr: [],
attachment_url:undefined,//,使,
feedback_text:'',//
user_id:'',//ID|school_customer_resourcesid
},
}
},
onLoad() {},
onShow(){
this.init()
},
methods: {
async init(){
//
await this.getMemberInfo()
},
//
async getMemberInfo() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData.user_id = res.data.id
console.log('xxxx',res.data)
},
//######AQ######
//
AQUploadSuccess(res) {
console.log('接收AQ上传回调xxx1', res)
// 使 split
let _inputValue = []
if (res.filePathArr.length) {
_inputValue = res.filePathArr
}
this.formData[res.inputName] = _inputValue
// console.log('AQxxx1',res)
// console.log('AQxxx2',this.formData[res.inputName])
},
async submitForm() {
let data = {...this.formData}
if(data.attachment_url_arr.length){
data.attachment_url = data.attachment_url_arr.join(',')
}
if(!data.feedback_text){
//
uni.showToast({
title:'反馈内容为必填项',
icon:'none'
})
return
}
let res = await apiRoute.xy_userFeedbackAdd(data)
if(res.code != 1){
uni.showToast({
title:res.msg,
icon:'none'
})
}else{
uni.showToast({
title:'提交成功',
icon:'none'
})
}
},
}
}
</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;
color: #fff;
display: flex;
flex-direction: column;
gap: 20rpx;
.section {
background-color: #434544;
padding: 40rpx 40rpx;
.text_input {
border: 1px solid #434544;
background-color: #434544 !important;
::v-deep .fui-textarea__wrap {
border: 1px solid #797979;
background-color: #434544 !important;
}
::v-deep textarea {
color: #fff !important;
}
::v-deep .fui-textarea__background {
border: 0;
background-color: #434544 !important;
}
}
.upload_box {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.input_box {
padding: 0;
color: #fff;
::v-deep .fui-input__wrap {
background: #434544 !important;
padding-left: 0 !important;
}
::v-deep .fui-input__label {
span {
color: #fff !important;
}
}
::v-deep .uni-input-input {
color: #fff;
}
::v-deep .fui-input__background {
background: #434544 !important;
}
}
}
.btn {
margin: 0 auto;
margin-top: 40rpx;
border: 1px solid #25a18b;
color: #25a18b;
width: 80%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
</style>

600
uniapp/pages-common/im_chat_info.vue

@ -0,0 +1,600 @@
<!--聊天记录-列表-->
<template>
<view class="main_box">
<view class="main_section">
<scroll-view
class="section_1"
scroll-y="true"
:scroll-top="scrollTop"
:lower-threshold="lowerThreshold"
style="height: 78vh;"
>
<view class="ul">
<view class="item_box" v-for="(v,k) in tableList" :key="k" :id="'item_' + v.id">
<view class="time_section" v-if="v.created_at">{{v.created_at}}</view>
<view class="li" v-if="v.direction == `left`">
<view class="item left_item" :style="{ backgroundColor: v.message_type === 'text' ? '#f4f6f9' : '' }">
<!--文本内容-->
<view class="text_box" v-if="v.message_type == 'text'">{{v.content}}</view>
<!-- 图片内容 -->
<view class="img_box" v-if="v.message_type == 'img'" @click="previewImage(v.content)">
<image class="chat_img" :src="v.content" mode="aspectFill"></image>
</view>
</view>
<view class="item"></view>
</view>
<view class="li" v-if="v.direction == `right`">
<view class="item"></view>
<view class="item right_item" :style="{ backgroundColor: v.message_type === 'text' ? '#1684fc' : '' }">
<view class="text_box" v-if="v.message_type == 'text'">{{v.content}}</view>
<!-- 图片内容 -->
<view class="img_box" v-if="v.message_type == 'img'" @click="previewImage(v.content)">
<image class="chat_img" :src="v.content" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="input_section">
<view class="left_box">
<input class="input" v-model="formData.content" type="text" placeholder="请输入">
</view>
<view class="right_box">
<!--发送-->
<view class="img_box">
<image @click="submitTextForm()" class="send_img" :src="$util.img('/uniapp_src/static/images/common/fa_song.png')"></image>
</view>
<!--更多选择-->
<view class="img_box">
<image @click="openMore()" class="send_img" :src="$util.img('/uniapp_src/static/images/common/jia_hao.png')"></image>
</view>
</view>
</view>
<!--更多选择-->
<fui-bottom-popup :show="moreShow" @close="closeMore">
<view class="more_section">
<view class="fui-scroll__wrap">
<scroll-view scroll-y class="fui-scroll__view">
<view class="ul">
<view class="li" @click="">
<AQUplodeImage
:uploadUrl=uploadUrl
:extraData="{ input_name: 'img_uplode', formData:{} }"
@uplodeImageRes="uplodeImageRes"
>
<view class="icon_box">
<fui-icon name="picture-fill" :size="80"></fui-icon>
</view>
<view class="title">相册</view>
</AQUplodeImage>
</view>
</view>
</scroll-view>
</view>
</view>
</fui-bottom-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import {
Api_url
} from "@/common/config.js";
import AQUplodeImage from '@/components/AQ/AQUplodeImage';//
import AQTabber from "@/components/AQ/AQTabber"
export default {
components: {
AQTabber,
AQUplodeImage,
},
data() {
return {
uploadUrl: `${Api_url}/uploadImage`,//|
scrollTop:'',//,
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
friend_id: '',//chat_friendsid
},
tableList:[],//
from_id:'',//ID
to_id:'',//ID
from_type:'',//|personnel=,customer=()
//
chatFriendsInfoParams:{
personnel_id:'',
customer_resources_id:'',
},
chatFriendsInfo:{},//
//
formData: {
from_type:'',//|personnel=,customer=()
from_id:'',//ID
to_id:'',//ID
friend_id:'',//chat_friendsid
message_type: 'text',//|text=,img=
content: '',//JSON ,=,=
},
//
moreShow:false,
}
},
onLoad(options) {
this.from_id = options.from_id//id
this.to_id = options.to_id//id
},
onShow(){
this.init()
},
//
async onPullDownRefresh() {
//()
await this.loadMoreData()
},
methods: {
//
async init(){
//
let userType = uni.getStorageSync('userType')
//1=,2=,3=
if (['1', '2'].includes(String(userType))) {
//
this.from_type = 'personnel'//
this.chatFriendsInfoParams.personnel_id = this.from_id//id
this.chatFriendsInfoParams.customer_resources_id = this.to_id//id
this.formData.from_type = 'personnel'//
this.formData.from_id = this.from_id//id()
this.formData.to_id = this.to_id//id()
} else {
//
this.from_type = 'customer'//
this.chatFriendsInfoParams.personnel_id = this.to_id//id
this.chatFriendsInfoParams.customer_resources_id = this.from_id//id
this.formData.from_type = 'customer'//
this.formData.from_id = this.to_id//id()
this.formData.to_id = this.from_id//id()
this.uploadUrl = `${Api_url}/memberUploadImage`//|
}
//
await this.getChatFriendsInfo()
//
await this.getList();
//
if (this.filteredData.page == 2) {
this.scrollToBottom()
}
},
//
async getChatFriendsInfo(){
let params = {
personnel_id: this.chatFriendsInfoParams.personnel_id,//ID
customer_resources_id: this.chatFriendsInfoParams.customer_resources_id,//ID
}
let res
if( this.from_type == 'personnel'){
//
res = await apiRoute.xs_chatGetChatFriendsInfo(params)
}else{
//
res = await apiRoute.xy_chatGetChatFriendsInfo(params)
}
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.chatFriendsInfo = res.data
this.filteredData.friend_id = res.data.id
this.formData.friend_id = res.data.id
},
//()
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
if( this.from_type == 'personnel'){
//
res = await apiRoute.xs_chatGetChatMessagesList(data)//
}else{
//
res = await apiRoute.xy_chatGetChatMessagesList(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
let list = res.data.data
list.forEach((v, k) => {
if (this.from_type == 'personnel') {
//
if (v.from_type == 'personnel') {
v.direction = 'right'//
} else {
v.direction = 'left'//
}
} else if (this.from_type == 'customer') {
//
if (v.from_type == 'customer') {
v.direction = 'right'//
} else {
v.direction = 'left'//
}
}
})
list.reverse()
this.tableList.unshift(...list); //
console.log('列表',res.data.total)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//######AQ######
//
AQUploadSuccess(res) {
console.log('接收AQ上传回调xxx1', res)
// 使 split
let _inputValue = []
if (res.filePathArr.length) {
_inputValue = res.filePathArr
}
this.formData[res.inputName] = _inputValue
// console.log('AQxxx1',res)
// console.log('AQxxx2',this.formData.member_store_certification_arr)
},
//
submitTextForm(){
this.formData.message_type = 'text'
this.submitForm()
},
//
async submitForm() {
let data = {...this.formData}
if (!data.content) {
//
uni.showToast({
title: '请输入内容',
icon: 'none'
})
return
}
let res
if( this.from_type == 'personnel'){
//
res = await apiRoute.xs_chatSendChatMessages(data)
}else{
//
res = await apiRoute.xy_chatSendChatMessages(data)
}
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
} else {
let content = this.formData.content
this.formData.content = ''//
let msgData = {
id:'',
from_type:data.from_type,//|personnel=,customer=()
from_id:data.from_id,//ID
to_id:data.to_id,//ID
friend_id:data.friend_id,//chat_friendsid
message_type: data.message_type,//|text=,img=
content: data.content,//JSON ,=,=
direction:'right'
}
//
this.tableList = this.tableList.concat(msgData);
}
},
//
scrollToBottom() {
this.$nextTick(() => {
this.scrollTop = 999999 //
})
},
//
openMore(){
this.moreShow = true
},
//
closeMore(){
this.moreShow = false
},
//
uplodeImageRes(resData,extraData){
console.log('上传成功回调',resData,extraData)
//
if(extraData.input_name == 'img_uplode'){
this.closeMore()//
this.formData.content = resData.url
this.formData.message_type = 'img'
this.submitForm()
}
},
//
previewImage(url){
uni.previewImage({
current: url, //
urls: [url] //
});
},
}
}
</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: 120rpx;
padding-bottom: 120rpx;
font-size: 28rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.section {
background-color: #434544;
padding: 40rpx 40rpx;
}
.section_1{
color: #FFFFFF;
font-size: 28rpx;
padding: 0 24rpx;
display: flex;
flex-direction: column;
gap: 40rpx;
.ul{
.time_section{
text-align: center;
font-size: 28rpx;
color: #989898;
}
.li{
margin: 40rpx 0;
display: flex;
justify-content: space-between;
.item{
max-width: 70%;
padding: 32rpx;
border-radius: 32rpx;
word-wrap: break-word; /* 允许长单词或 URL 换行 */
word-break: break-all; /* 强制所有字符换行 */
.text_box{}
.img_box {
.chat_img {
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
}
}
}
.left_item{
//background-color: #f4f6f9;
color: #343434;
}
.right_item{
//background-color: #1684fc;
color: #fff;
}
}
}
}
}
//
.input_section{
width: 100%;
position: fixed;
bottom: 0;
padding: 30rpx;
padding-bottom: 50rpx;
display: flex;
justify-content:space-between;
align-items: center;
gap: 20rpx;
.left_box{
.input{
background-color: #f4f6f9;
height: 88rpx;
padding: 28rpx;
font-size: 28rpx;
border-radius: 32rpx;
width: 480rpx;
color: #292929;
font-size: 28rpx;
}
}
.right_box{
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.img_box{
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background-color: #a2cefe;
display: flex;
justify-content: center;
align-items: center;
.send_img{
width: 36rpx;
height: 36rpx;
}
}
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
/* 更多选项相关 自定义内容区样式需自行控制 */
.more_section{
.fui-scroll__wrap {
padding-top: 30rpx;
position: relative;
}
.fui-title {
font-size: 30rpx;
font-weight: bold;
text-align: center;
padding-bottom: 24rpx;
}
.fui-icon__close {
position: absolute;
top: 24rpx;
left: 24rpx;
}
.fui-scroll__view {
width: 100%;
height: 350rpx;
}
.ul{
padding: 10rpx 40rpx;
display: flex;
.li{
display: flex;
flex-direction: column;
align-items: center;
.icon_box{
}
.title{
font-size: 28rpx;
text-align: center;
}
}
}
}
</style>

1129
uniapp/pages-common/my_attendance.vue

File diff suppressed because it is too large

374
uniapp/pages-common/my_message.vue

@ -0,0 +1,374 @@
<!--我的消息-列表-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<!-- <view class="item" @click="openViewSysMsgList()">-->
<!-- <view class="left">-->
<!-- <image class="pic" :src="$util.img('/uniapp_src/static/images/common/xi_tong_xiao_xi.png')"></image>-->
<!-- <view>系统消息</view>-->
<!-- </view>-->
<!-- <view class="right">-->
<!-- <view>99</view>-->
<!-- <fui-icon name="arrowright" :size="60"></fui-icon>-->
<!-- </view>-->
<!-- </view>-->
<view
v-for="(v,k) in contactList"
:key="k"
class="item"
@click="openViewImChatInfo(v)"
>
<view class="left">
<image
v-if="(['1','2','3'].includes(String(userType)))"
class="pic"
:src="$util.img('/uniapp_src/static/images/common/yong_hu.png')"
model="aspectFit"
></image>
<image v-else class="pic" :src="$util.img('/uniapp_src/static/images/common/xi_tong_xiao_xi.png')"></image>
<view>{{v.name}}</view>
</view>
<view class="right">
<view>{{v.count >= 99 ? '99':v.count}}</view>
<fui-icon name="arrowright" :size="60"></fui-icon>
</view>
</view>
</view>
</view>
</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"
export default {
components: {
AQTabber,
},
data() {
return {
//
filteredData:{
page:1,//
limit:10,//
total:10,//
personnel_id:'',//id(21)
customer_resources_id:'',//id(21)
},
formData: {
images_arr: [],
images: '',
content: '',
mailbox: '',
},
//
contactList:[],
userType:'',//|1=,2=,3=
from_type: '',//|personnel=,customer=()
}
},
onLoad() {
},
onShow() {
this.init();
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init(){
//
this.userType = uni.getStorageSync('userType')
//1=,2=,3=
if (['1', '2'].includes(String(this.userType))) {
this.from_type = 'personnel'//
await this.getPersonnelUserInfo()
}else{
//
this.from_type = 'customer'//
await this.getMemberInit()
}
this.getContactList()
},
//
async getPersonnelUserInfo(){
let data = {}
let res = await apiRoute.getPersonnelInfo(data);
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
res.data.cameus_dept_arr.forEach((v,k)=>{
let d_arr = []
v.dept_arr.forEach((dv,dk)=>{
d_arr.push(dv.dept_name)
})
//
v.dept_name_str = d_arr.join(',')
})
this.filteredData.personnel_id = res.data.id
},
//
async getMemberInit() {
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.customer_resources_id = res.data.id//id
},
//
async getContactList(){
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.contactList = []
}
let res
//1=,2=,3=
if (['1', '2'].includes(String(this.userType))) {
//
res = await apiRoute.xs_chatGetChatFriendsList(params)
}else{
//
res = await apiRoute.xy_chatGetChatFriendsList(params)
}
this.loading = false
this.isReachedBottom = false;
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
res.data.data.forEach((v,k)=>{
v.name = ''
v.count = '0'
if(this.from_type == 'personnel'){
v.name = v.personnel_id_name
v.count = v.unread_count_personnel
}else{
v.name = v.customer_resources_id_name
v.count = v.unread_count_customer_resources
}
})
console.log('列表',res.data.data)
this.contactList = this.contactList.concat(res.data.data); // 使 concat
this.filteredData.total = res.data.total
this.filteredData.page++
},
//######AQ######
//
AQUploadSuccess(res) {
console.log('接收AQ上传回调xxx1', res)
// 使 split
let _inputValue = []
if (res.filePathArr.length) {
_inputValue = res.filePathArr
}
this.formData[res.inputName] = _inputValue
// console.log('AQxxx1',res)
// console.log('AQxxx2',this.formData.member_store_certification_arr)
},
async submitForm() {
let data = {...this.formData}
data.images = data.images_arr.join(',')
if (!data.content) {
//
uni.showToast({
title: '反馈内容为必填项',
icon: 'none'
})
return
}
let res = await memberApi.setFeedback(data)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
} else {
uni.showToast({
title: '提交成功',
icon: 'none'
})
}
},
//-
openViewSysMsgList(e){
let hair_staff_id = e.hair_staff_id//id
uni.navigateTo({
url: `/pages-common/sys_msg_list?hair_staff_id=${hair_staff_id}`
})
},
//-
openViewImChatInfo(e){
if(e.type == 1){
//
this.openViewSysMsgList(e)
}else{
let from_id = ''//id
let to_id = ''//ID
//
if (this.from_type == 'personnel') {
from_id = e.personnel_id
to_id = e.customer_resources_id
} else {
from_id = e.customer_resources_id
to_id = e.personnel_id
}
uni.navigateTo({
url: `/pages-common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
})
}
},
}
}
</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;
gap: 38rpx;
.item{
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 18rpx 36rpx;
border-radius: 14rpx;
background-color: rgba(255,255,255,1);
border: 2rpx solid rgba(187,187,187,1);
.left{
display: flex;
align-items: center;
gap: 16rpx;
.pic{
width: 68rpx;
height: 68rpx;
border-radius: 50%;
}
view{
color: #D9001B;
font-size: 32rpx;
}
}
.right{
display: flex;
align-items: center;
view{
display: flex;
justify-content: center;
align-items: center;
width: 50rpx;
height: 50rpx;
padding: 5rpx;
border: 1px solid #BBBBBB;
border-radius: 50%;
color: #D9001B;
}
}
}
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
</style>

899
uniapp/pages-common/personnel/add_personnel.vue

@ -0,0 +1,899 @@
<template>
<view class="personnel-form-container">
<!-- 自定义导航栏 -->
<view class="custom-nav">
<view class="nav-left" @click="goBack">
<text class="iconfont icon-arrow-left"></text>
</view>
<view class="nav-title">新员工信息填写</view>
<view class="nav-right"></view>
</view>
<!-- 进度条 -->
<view class="progress-container">
<view class="progress-bar">
<view class="progress-step" :class="currentStep >= 1 ? 'active' : ''">
<text class="step-number">1</text>
<text class="step-text">基本信息</text>
</view>
<view class="progress-line" :class="currentStep >= 2 ? 'active' : ''"></view>
<view class="progress-step" :class="currentStep >= 2 ? 'active' : ''">
<text class="step-number">2</text>
<text class="step-text">详细信息</text>
</view>
<view class="progress-line" :class="currentStep >= 3 ? 'active' : ''"></view>
<view class="progress-step" :class="currentStep >= 3 ? 'active' : ''">
<text class="step-number">3</text>
<text class="step-text">确认提交</text>
</view>
</view>
</view>
<!-- 表单内容 -->
<view class="form-content">
<!-- 第一步基本信息 -->
<view v-if="currentStep === 1" class="step-content">
<view class="section-title">基本信息</view>
<!-- 头像上传 -->
<view class="form-item">
<view class="label">头像</view>
<view class="avatar-upload" @click="chooseAvatar">
<image v-if="formData.head_img" :src="formData.head_img" class="avatar-preview"></image>
<view v-else class="avatar-placeholder">
<text class="iconfont icon-camera"></text>
<text class="placeholder-text">点击上传头像</text>
</view>
</view>
</view>
<!-- 姓名 -->
<view class="form-item required">
<view class="label">姓名</view>
<input class="form-input" v-model="formData.name" placeholder="请输入姓名" />
</view>
<!-- 性别 -->
<view class="form-item required">
<view class="label">性别</view>
<view class="radio-group">
<label class="radio-item" @click="formData.gender = 1">
<view class="radio" :class="formData.gender === 1 ? 'checked' : ''"></view>
<text></text>
</label>
<label class="radio-item" @click="formData.gender = 0">
<view class="radio" :class="formData.gender === 0 ? 'checked' : ''"></view>
<text></text>
</label>
</view>
</view>
<!-- 生日 -->
<view class="form-item">
<view class="label">出生日期</view>
<picker mode="date" :value="formData.birthday" @change="onBirthdayChange">
<view class="picker-input">
{{formData.birthday || '请选择出生日期'}}
</view>
</picker>
</view>
<!-- 手机号 -->
<view class="form-item required">
<view class="label">手机号码</view>
<input class="form-input" v-model="formData.phone" placeholder="请输入手机号码" type="number" />
</view>
<!-- 邮箱 -->
<view class="form-item">
<view class="label">邮箱</view>
<input class="form-input" v-model="formData.email" placeholder="请输入邮箱地址" />
</view>
<!-- 微信号 -->
<view class="form-item">
<view class="label">微信号</view>
<input class="form-input" v-model="formData.wx" placeholder="请输入微信号" />
</view>
<!-- 账号类型 -->
<view class="form-item required">
<view class="label">职位类型</view>
<view class="radio-group">
<label class="radio-item" @click="formData.account_type = 'teacher'">
<view class="radio" :class="formData.account_type === 'teacher' ? 'checked' : ''"></view>
<text>教师</text>
</label>
<label class="radio-item" @click="formData.account_type = 'market'">
<view class="radio" :class="formData.account_type === 'market' ? 'checked' : ''"></view>
<text>市场</text>
</label>
</view>
</view>
<!-- 入职时间 -->
<view class="form-item">
<view class="label">入职时间</view>
<picker mode="date" :value="formData.join_time" @change="onJoinTimeChange">
<view class="picker-input">
{{formData.join_time || '请选择入职时间'}}
</view>
</picker>
</view>
</view>
<!-- 第二步详细信息 -->
<view v-if="currentStep === 2" class="step-content">
<view class="section-title">详细信息</view>
<!-- 民族 -->
<view class="form-item">
<view class="label">民族</view>
<input class="form-input" v-model="detailData.ethnicity" placeholder="请输入民族" />
</view>
<!-- 年龄 -->
<view class="form-item">
<view class="label">年龄</view>
<input class="form-input" v-model.number="detailData.age" placeholder="请输入年龄" type="number" />
</view>
<!-- 政治面貌 -->
<view class="form-item">
<view class="label">政治面貌</view>
<picker :range="politicsOptions" @change="onPoliticsChange">
<view class="picker-input">
{{detailData.politics || '请选择政治面貌'}}
</view>
</picker>
</view>
<!-- 毕业院校 -->
<view class="form-item">
<view class="label">毕业院校</view>
<input class="form-input" v-model="detailData.university" placeholder="请输入毕业院校" />
</view>
<!-- 学历 -->
<view class="form-item">
<view class="label">学历</view>
<picker :range="educationOptions" @change="onEducationChange">
<view class="picker-input">
{{detailData.education || '请选择学历'}}
</view>
</picker>
</view>
<!-- 专业 -->
<view class="form-item">
<view class="label">专业</view>
<input class="form-input" v-model="detailData.major" placeholder="请输入专业" />
</view>
<!-- 毕业日期 -->
<view class="form-item">
<view class="label">毕业日期</view>
<picker mode="date" :value="detailData.graduation_date" @change="onGraduationDateChange">
<view class="picker-input">
{{detailData.graduation_date || '请选择毕业日期'}}
</view>
</picker>
</view>
<!-- 籍贯 -->
<view class="form-item">
<view class="label">籍贯</view>
<input class="form-input" v-model="detailData.native_place" placeholder="请输入籍贯" />
</view>
<!-- 户口所在地 -->
<view class="form-item">
<view class="label">户口所在地</view>
<input class="form-input" v-model="detailData.household_place" placeholder="请输入户口所在地" />
</view>
<!-- 户口性质 -->
<view class="form-item">
<view class="label">户口性质</view>
<picker :range="householdTypeOptions" @change="onHouseholdTypeChange">
<view class="picker-input">
{{detailData.household_type || '请选择户口性质'}}
</view>
</picker>
</view>
<!-- 户籍地址 -->
<view class="form-item">
<view class="label">户籍地址</view>
<textarea class="form-textarea" v-model="detailData.household_address" placeholder="请输入户籍地址"></textarea>
</view>
<!-- 现居地址 -->
<view class="form-item">
<view class="label">现居地址</view>
<textarea class="form-textarea" v-model="detailData.current_address" placeholder="请输入现居地址"></textarea>
</view>
<!-- 紧急联系人 -->
<view class="form-item">
<view class="label">紧急联系人</view>
<input class="form-input" v-model="detailData.emergency_contact" placeholder="请输入紧急联系人姓名" />
</view>
<!-- 紧急联系电话 -->
<view class="form-item">
<view class="label">紧急联系电话</view>
<input class="form-input" v-model="detailData.emergency_phone" placeholder="请输入紧急联系电话" type="number" />
</view>
<!-- 婚姻状况 -->
<view class="form-item">
<view class="label">婚姻状况</view>
<picker :range="maritalStatusOptions" @change="onMaritalStatusChange">
<view class="picker-input">
{{detailData.marital_status || '请选择婚姻状况'}}
</view>
</picker>
</view>
<!-- 银行卡号 -->
<view class="form-item">
<view class="label">银行卡号</view>
<input class="form-input" v-model="detailData.bank_card" placeholder="请输入银行卡号" type="number" />
</view>
<!-- 开户银行 -->
<view class="form-item">
<view class="label">开户银行</view>
<input class="form-input" v-model="detailData.bank_name" placeholder="请输入开户银行" />
</view>
<!-- 备注 -->
<view class="form-item">
<view class="label">备注</view>
<textarea class="form-textarea" v-model="detailData.remark" placeholder="请输入备注信息"></textarea>
</view>
</view>
<!-- 第三步确认信息 -->
<view v-if="currentStep === 3" class="step-content">
<view class="section-title">确认信息</view>
<view class="confirm-info">
<view class="info-section">
<view class="info-title">基本信息</view>
<view class="info-item">
<text class="info-label">姓名</text>
<text class="info-value">{{formData.name}}</text>
</view>
<view class="info-item">
<text class="info-label">性别</text>
<text class="info-value">{{formData.gender === 1 ? '男' : '女'}}</text>
</view>
<view class="info-item">
<text class="info-label">手机</text>
<text class="info-value">{{formData.phone}}</text>
</view>
<view class="info-item">
<text class="info-label">职位</text>
<text class="info-value">{{formData.account_type === 'teacher' ? '教师' : '市场'}}</text>
</view>
</view>
<view class="info-section">
<view class="info-title">详细信息</view>
<view class="info-item" v-if="detailData.education">
<text class="info-label">学历</text>
<text class="info-value">{{detailData.education}}</text>
</view>
<view class="info-item" v-if="detailData.university">
<text class="info-label">毕业院校</text>
<text class="info-value">{{detailData.university}}</text>
</view>
<view class="info-item" v-if="detailData.emergency_contact">
<text class="info-label">紧急联系人</text>
<text class="info-value">{{detailData.emergency_contact}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="footer-buttons">
<button v-if="currentStep > 1" class="btn btn-secondary" @click="prevStep">上一步</button>
<button v-if="currentStep < 3" class="btn btn-primary" @click="nextStep">下一步</button>
<button v-if="currentStep === 3" class="btn btn-primary" @click="submitForm" :loading="submitting">提交</button>
</view>
<!-- 加载提示 -->
<uni-load-more v-if="submitting" status="loading" content-text="正在提交..."></uni-load-more>
</view>
</template>
<script>
import apiRoute from '@/common/axios.js'
export default {
data() {
return {
currentStep: 1,
submitting: false,
//
formData: {
name: '',
head_img: '',
gender: null,
birthday: '',
phone: '',
email: '',
wx: '',
account_type: '',
join_time: ''
},
//
detailData: {
ethnicity: '',
age: null,
politics: '',
university: '',
education: '',
major: '',
graduation_date: '',
native_place: '',
household_place: '',
household_type: '',
household_address: '',
current_address: '',
emergency_contact: '',
emergency_phone: '',
marital_status: '',
bank_card: '',
bank_name: '',
remark: ''
},
//
politicsOptions: ['群众', '共青团员', '中共党员', '民主党派', '无党派人士'],
educationOptions: ['高中', '中专', '大专', '本科', '硕士', '博士'],
householdTypeOptions: ['城镇', '农村'],
maritalStatusOptions: ['未婚', '已婚', '离异', '丧偶']
}
},
onLoad() {
//
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
this.formData.join_time = `${year}-${month}-${day}`
},
methods: {
//
goBack() {
uni.navigateBack()
},
//
chooseAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
this.uploadAvatar(tempFilePath)
}
})
},
//
uploadAvatar(filePath) {
uni.showLoading({ title: '上传中...' })
uni.uploadFile({
url: this.$baseUrl + '/file/avatar',
filePath: filePath,
name: 'file',
success: (res) => {
const data = JSON.parse(res.data)
if (data.code === 1) {
this.formData.head_img = data.data.url
uni.showToast({ title: '头像上传成功', icon: 'success' })
} else {
uni.showToast({ title: data.msg || '上传失败', icon: 'none' })
}
},
fail: (error) => {
console.error('头像上传失败:', error)
uni.showToast({ title: '上传失败', icon: 'none' })
},
complete: () => {
uni.hideLoading()
}
})
},
//
onBirthdayChange(e) {
this.formData.birthday = e.detail.value
//
this.calculateAge()
},
onJoinTimeChange(e) {
this.formData.join_time = e.detail.value
},
onGraduationDateChange(e) {
this.detailData.graduation_date = e.detail.value
},
//
onPoliticsChange(e) {
this.detailData.politics = this.politicsOptions[e.detail.value]
},
onEducationChange(e) {
this.detailData.education = this.educationOptions[e.detail.value]
},
onHouseholdTypeChange(e) {
this.detailData.household_type = this.householdTypeOptions[e.detail.value]
},
onMaritalStatusChange(e) {
this.detailData.marital_status = this.maritalStatusOptions[e.detail.value]
},
//
calculateAge() {
if (this.formData.birthday) {
const birthDate = new Date(this.formData.birthday)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
this.detailData.age = age
}
},
//
nextStep() {
if (this.validateCurrentStep()) {
this.currentStep++
}
},
prevStep() {
this.currentStep--
},
//
validateCurrentStep() {
if (this.currentStep === 1) {
if (!this.formData.name) {
uni.showToast({ title: '请输入姓名', icon: 'none' })
return false
}
if (this.formData.gender === null) {
uni.showToast({ title: '请选择性别', icon: 'none' })
return false
}
if (!this.formData.phone) {
uni.showToast({ title: '请输入手机号码', icon: 'none' })
return false
}
if (!/^1[3-9]\d{9}$/.test(this.formData.phone)) {
uni.showToast({ title: '请输入正确的手机号码', icon: 'none' })
return false
}
if (!this.formData.account_type) {
uni.showToast({ title: '请选择职位类型', icon: 'none' })
return false
}
}
return true
},
//
async submitForm() {
if (!this.validateCurrentStep()) {
return
}
this.submitting = true
try {
const submitData = {
...this.formData,
...this.detailData
}
const response = await apiRoute.post('personnel/add', submitData)
if (response.data.code === 1) {
uni.showToast({
title: '员工信息提交成功',
icon: 'success',
duration: 2000
})
setTimeout(() => {
uni.navigateBack()
}, 2000)
} else {
uni.showToast({
title: response.data.msg || '提交失败',
icon: 'none'
})
}
} catch (error) {
console.error('提交员工信息失败:', error)
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
})
} finally {
this.submitting = false
}
}
}
}
</script>
<style lang="scss" scoped>
.personnel-form-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
/* 自定义导航栏 */
.custom-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 32rpx;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
position: sticky;
top: 0;
z-index: 100;
.nav-left {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 36rpx;
color: #333;
}
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.nav-right {
width: 80rpx;
}
}
/* 进度条 */
.progress-container {
background-color: #fff;
padding: 40rpx 32rpx;
margin-bottom: 20rpx;
}
.progress-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
.step-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #e5e5e5;
color: #999;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
margin-bottom: 12rpx;
}
.step-text {
font-size: 24rpx;
color: #999;
}
&.active {
.step-number {
background-color: #007ACC;
color: #fff;
}
.step-text {
color: #007ACC;
}
}
}
.progress-line {
flex: 1;
height: 4rpx;
background-color: #e5e5e5;
margin: 0 20rpx;
margin-top: -30rpx;
&.active {
background-color: #007ACC;
}
}
/* 表单内容 */
.form-content {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
padding: 32rpx;
}
.section-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 40rpx;
border-left: 8rpx solid #007ACC;
padding-left: 20rpx;
}
.form-item {
margin-bottom: 40rpx;
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
.label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.form-input,
.picker-input {
width: 100%;
height: 88rpx;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
background-color: #fff;
box-sizing: border-box;
display: flex;
align-items: center;
&:focus {
border-color: #007ACC;
}
}
.form-textarea {
width: 100%;
min-height: 120rpx;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 20rpx 24rpx;
font-size: 28rpx;
color: #333;
background-color: #fff;
box-sizing: border-box;
resize: none;
&:focus {
border-color: #007ACC;
}
}
.picker-input {
color: #999;
cursor: pointer;
}
}
/* 头像上传 */
.avatar-upload {
width: 120rpx;
height: 120rpx;
border: 2rpx solid #e5e5e5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
cursor: pointer;
.avatar-preview {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
.iconfont {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.placeholder-text {
font-size: 20rpx;
}
}
}
/* 单选按钮组 */
.radio-group {
display: flex;
gap: 60rpx;
}
.radio-item {
display: flex;
align-items: center;
cursor: pointer;
.radio {
width: 32rpx;
height: 32rpx;
border: 2rpx solid #e5e5e5;
border-radius: 50%;
margin-right: 16rpx;
position: relative;
&.checked {
border-color: #007ACC;
&::after {
content: '';
width: 16rpx;
height: 16rpx;
background-color: #007ACC;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
text {
font-size: 28rpx;
color: #333;
}
}
/* 确认信息 */
.confirm-info {
.info-section {
margin-bottom: 40rpx;
.info-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
border-left: 6rpx solid #007ACC;
padding-left: 16rpx;
}
.info-item {
display: flex;
margin-bottom: 16rpx;
.info-label {
font-size: 28rpx;
color: #666;
width: 120rpx;
flex-shrink: 0;
}
.info-value {
font-size: 28rpx;
color: #333;
flex: 1;
}
}
}
}
/* 底部按钮 */
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 24rpx 32rpx 24rpx;
border-top: 1rpx solid #e5e5e5;
display: flex;
gap: 24rpx;
z-index: 100;
.btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
display: flex;
align-items: center;
justify-content: center;
&.btn-primary {
background-color: #007ACC;
color: #fff;
&:active {
background-color: #0056b3;
}
}
&.btn-secondary {
background-color: #f5f5f5;
color: #666;
&:active {
background-color: #e5e5e5;
}
}
}
}
/* 步骤内容动画 */
.step-content {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
</style>

111
uniapp/pages-common/privacy_agreement.vue

@ -0,0 +1,111 @@
<template>
<view class="assemble">
<view class="html-style" v-html="text"></view>
</view>
</template>
<script>
import agreement from '@/api/agreement.js';
export default {
data() {
return {
type: 1, // 1,2
text: '',
}
},
onLoad(item) {
console.log(item.type, '类型')
this.init(item.type)
},
methods: {
//
init(type) {
if (type == 1) {
//
agreement.userAgreement('service').then(res => {
if(res.code == 1){
this.text = res.data.content
}else{
this.text = ''
}
})
uni.setNavigationBarTitle({
title: '用户协议'
});
} else if (type == 2) {
//
agreement.userAgreement('privacy').then(res => {
if(res.code == 1){
this.text = res.data.content
}else{
this.text = ''
}
})
uni.setNavigationBarTitle({
title: '隐私策略'
});
}
}
}
}
</script>
<style lang="scss" scoped>
.assemble {
width: 100%;
height: 100vh;
overflow: auto;
background-color: $bg-color-white;
}
.html-style {
padding: 30rpx 40rpx;
line-height: 1.8;
font-size: $font-size-base;
color: $text-color-base;
//
::v-deep p {
margin-bottom: 20rpx;
line-height: 1.6;
}
::v-deep h1, ::v-deep h2, ::v-deep h3 {
color: $color-primary;
margin: 30rpx 0 20rpx 0;
font-weight: bold;
}
::v-deep h1 {
font-size: $font-size-extra-lg;
}
::v-deep h2 {
font-size: $font-size-lg;
}
::v-deep h3 {
font-size: $font-size-medium;
}
::v-deep ul, ::v-deep ol {
margin: 20rpx 0;
padding-left: 40rpx;
}
::v-deep li {
margin-bottom: 10rpx;
line-height: 1.6;
}
::v-deep strong {
color: $text-color-base;
font-weight: bold;
}
::v-deep a {
color: $color-primary;
text-decoration: underline;
}
}
</style>

259
uniapp/pages-common/profile/index.vue

@ -0,0 +1,259 @@
<template>
<view class="profile-container">
<!-- 自定义导航栏 -->
<uni-nav-bar
:statusBar="true"
backgroundColor="#181A20"
color="#fff"
title="我的"
/>
<!-- 用户头像和基本信息 -->
<view class="profile-header">
<view class="avatar-section">
<image :src="userInfo.avatar || $util.img('/static/icon-img/tou.png')" mode="aspectFill"></image>
</view>
<view class="user-info">
<text class="user-name">{{ userInfo.name || '员工姓名' }}</text>
<text class="user-role">{{ (userInfo.role_info && userInfo.role_info.role_name) || '员工角色' }}</text>
<text class="user-phone">{{ userInfo.phone || '手机号码' }}</text>
</view>
</view>
<!-- 我的功能九宫格 -->
<view class="grid-container">
<view class="grid-title">个人中心</view>
<view class="grid-content">
<view
class="grid-item"
v-for="(item, index) in profileItems"
:key="index"
@click="handleProfileClick(item)"
>
<view class="grid-icon">
<uni-icons :type="item.icon" size="32" color="#29d3b4"></uni-icons>
</view>
<text class="grid-text">{{ item.title }}</text>
<text class="grid-desc" v-if="item.desc">{{ item.desc }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
userInfo: {},
profileItems: [
{
title: '我的资料',
icon: 'contact',
desc: '查看编辑个人信息',
action: 'viewProfile'
},
{
title: '我的合同',
icon: 'compose',
desc: '查看签署合同',
path: '/pages-common/contract/my_contract'
},
{
title: '我的工资',
icon: 'wallet',
desc: '查看工资明细',
action: 'viewSalary'
},
{
title: '我的考勤',
icon: 'calendar',
path: '/pages-common/my_attendance'
},
{
title: '系统设置',
icon: 'settings',
path: '/pages-market/my/set_up'
}
]
}
},
onLoad() {
this.loadUserInfo();
},
onShow() {
this.loadUserInfo();
},
methods: {
loadUserInfo() {
//
const userInfo = uni.getStorageSync('userInfo');
if (userInfo) {
this.userInfo = userInfo;
}
},
handleProfileClick(item) {
if (item.action) {
//
switch (item.action) {
case 'viewProfile':
this.viewPersonalProfile();
break;
case 'viewSalary':
this.viewSalaryInfo();
break;
}
} else if (item.path) {
//
console.log('跳转到页面:', item.path);
uni.navigateTo({
url: item.path,
fail: (err) => {
console.error('页面跳转失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
}
},
viewPersonalProfile() {
//
console.log('跳转到个人资料页面');
uni.navigateTo({
url: '/pages-common/profile/personal_info',
fail: (err) => {
console.error('跳转到个人资料页面失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
},
viewSalaryInfo() {
//
console.log('跳转到工资页面');
uni.navigateTo({
url: '/pages-coach/coach/my/salary',
fail: (err) => {
console.error('跳转到工资页面失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
}
}
}
</script>
<style scoped>
.profile-container {
background-color: #181A20;
min-height: 100vh;
color: #fff;
}
.profile-header {
display: flex;
align-items: center;
padding: 30px 20px;
background: linear-gradient(135deg, #29d3b4 0%, #1a9b7c 100%);
margin: 20px;
border-radius: 12px;
}
.avatar-section {
width: 80px;
height: 80px;
margin-right: 20px;
}
.avatar-section image {
width: 100%;
height: 100%;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.3);
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
}
.user-name {
font-size: 20px;
font-weight: bold;
margin-bottom: 8px;
color: #fff;
}
.user-role {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 5px;
}
.user-phone {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.grid-container {
margin: 20px;
}
.grid-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #fff;
}
.grid-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.grid-item {
background-color: #292929;
border-radius: 8px;
padding: 20px 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 90px;
transition: all 0.3s ease;
}
.grid-item:active {
background-color: #3a3a3a;
transform: scale(0.95);
}
.grid-icon {
margin-bottom: 8px;
}
.grid-text {
font-size: 12px;
color: #fff;
text-align: center;
line-height: 1.2;
margin-bottom: 3px;
font-weight: bold;
}
.grid-desc {
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
text-align: center;
line-height: 1.1;
}
</style>

1282
uniapp/pages-common/profile/personal_info.vue

File diff suppressed because it is too large

225
uniapp/pages-common/sys_msg_list.vue

@ -0,0 +1,225 @@
<!--系统消息-列表-->
<template>
<view class="main_box">
<view class="main_section">
<scroll-view
class="section_1"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 90vh;"
>
<view
class="item"
v-for="(v,k) in tableList"
:key="k"
@click="openViewArticleInfo(v)"
>
<view class="title">{{v.title}}</view>
<!-- <image-->
<!-- class="img_box"-->
<!-- :src="$util.img('/upload/attachment/image/202504/02/1743562333d1bb6666f969da1b7170381d0845153e_local.png')"-->
<!-- model="aspectFit"-->
<!-- ></image>-->
<view class="content" v-html="v.content"></view>
<view class="time">{{v.show_time}}</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import commonApi from '@/api/common.js';
export default {
components: {
},
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
hair_staff_id: '',//id
},
tableList:[],//
}
},
onLoad(options) {
this.filteredData.hair_staff_id = options.hair_staff_id//id
},
onShow(){
this.init()
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init(){
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 commonApi.getContactMessage(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.tableList.unshift(...res.data.data); //
console.log('列表',this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//
openViewArticleInfo(item) {
let id = item.id
let redirect = item.redirect//
uni.navigateTo({
url: `/pages-common/article_info?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: 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: rgba(255,255,255,1);
border: 2rpx solid rgba(187,187,187,1);
color: #4F4F4F;
font-size: 32rpx;
.title{
}
.img_box{
margin-top: 30rpx;
width: 100%;
}
.content{
margin-top: 30rpx;
}
.time{
display: flex;
justify-content: flex-end;
margin-top: 36rpx;
}
}
}
}
.describe {
color: #999999;
padding-left: 30rpx;
}
</style>

2123
uniapp/pages-market/clue/add_clues.vue

File diff suppressed because it is too large

631
uniapp/pages-market/clue/class_arrangement.vue

@ -0,0 +1,631 @@
<template>
<view class="class-arrange-root">
<!-- 顶部日期选择 -->
<view class="date-bar">
<view class="date-item" v-for="(item, idx) in weekList" :key="idx" :class="{active: item.status}" @click="getDate(item.date,item.day)">
<view class="week">{{ item.week }}</view>
<view class="day">{{ item.day }}</view>
</view>
</view>
<!-- "查询"按钮移到日期条下方 -->
<view class="more-bar-wrapper">
<view class="more-bar" @click="openSearchPopup">
<text>查询</text>
<uni-icons type="arrowdown" size="18" color="#bdbdbd" />
</view>
</view>
<!-- 日历底部弹窗 -->
<uni-popup ref="calendarPopup" type="bottom">
<uni-calendar @change="onCalendarChange" />
</uni-popup>
<!-- 查询弹窗 -->
<view v-if="showSearchPopup" class="search_popup_mask" @tap="showSearchPopup=false">
<view class="search_popup_content" @tap.stop>
<view class="popup_search_content">
<view class="popup_header">
<view class="popup_title">筛选</view>
<view class="popup_close" @tap="showSearchPopup=false">
<text class="close_text"></text>
</view>
</view>
<scroll-view :scroll-y="true" class="popup_scroll_view">
<!-- 筛选区域 -->
<view class="popup_filter_section">
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">时间查询</text>
<picker :value="timeIndex" :range="timeOptions" @change="onTimeChange">
<view class="popup_filter_picker">{{ timeOptions[timeIndex] }}</view>
</picker>
</view>
<view class="popup_filter_item">
<text class="popup_filter_label">班主任筛选</text>
<input class="popup_filter_input" placeholder="人员名称筛选" v-model="searchForm.teacher_name" />
</view>
</view>
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">开始日期</text>
<picker mode="date" :value="searchForm.start_date" @change="onStartDateChange">
<view class="popup_filter_input">
{{ searchForm.start_date || '选择开始日期' }}
</view>
</picker>
</view>
<view class="popup_filter_item">
<text class="popup_filter_label">结束日期</text>
<picker mode="date" :value="searchForm.end_date" @change="onEndDateChange">
<view class="popup_filter_input">
{{ searchForm.end_date || '选择结束日期' }}
</view>
</picker>
</view>
</view>
<view class="popup_filter_row">
<view class="popup_filter_item">
<text class="popup_filter_label">固定位筛选</text>
<input class="popup_filter_input" placeholder="输入固定位编号" v-model="searchForm.venue_number" type="number" />
</view>
</view>
</view>
</scroll-view>
<view class="popup_filter_buttons">
<view class="popup_filter_btn reset_btn" @click="resetSearchOnly">重置</view>
<view class="popup_filter_btn search_btn" @click="searchDataAndClose">搜索</view>
<view class="popup_filter_btn close_btn" @click="closeSearchPopup">关闭</view>
</view>
</view>
</view>
</view>
<!-- 课程卡片列表 -->
<view class="course-list">
<view class="course-card" v-for="(course, idx) in courseList" :key="idx">
<view class="card-header">
<view class="status-end">{{ getStatusText(course.status) }}</view>
</view>
<view class="card-body">
<view class="row">时间{{ course.course_date || '未设置' }}</view>
<view class="row">校区{{ course.campus_name || '未设置' }}</view>
<view class="row">教室{{ course.venue ? course.venue.venue_name : '未设置' }}</view>
<view class="row">课程{{ course.course ? course.course.course_name : '未设置' }}</view>
<view class="row">人数{{ course.available_capacity || 0 }}</view>
<view class="row">安排情况{{ course.student ? course.student.length : 0 }}/{{course.max_students ? course.max_students : '不限'}}</view>
</view>
<view class="card-footer">
<button class="detail-btn" @click="viewDetail(course)">详情</button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
weekList: [],
selectedDayIndex: 4,
date: '',
courseList: [],
resource_id: '',
student_id: '',
//
showSearchPopup: false,
searchForm: {
time_hour: '',
start_date: '',
end_date: '',
teacher_name: '',
venue_number: ''
},
//
timeIndex: 0,
timeOptions: ['全部时间', '上午(8:00-12:00)', '下午(12:00-18:00)', '晚上(18:00-22:00)']
};
},
onLoad(options) {
this.resource_id = options.resource_id || '';
this.student_id = options.student_id || '';
this.getDate();
},
methods: {
async getDate(date = '', day = '') {
try {
let res = await apiRoute.getDate({
'date': date,
'day': day
})
this.weekList = res.data.dates
this.date = res.data.date
let data = await apiRoute.courseAllList({
'schedule_date': this.date
})
// courseList
this.courseList = Array.isArray(data.data) ? data.data : []
} catch (error) {
console.error('获取信息失败:', error);
// 使
this.courseList = [];
uni.showToast({
title: '获取课程数据失败',
icon: 'none'
});
}
},
openCalendar() {
this.$refs.calendarPopup.open();
},
closeCalendar() {
console.log(123123)
this.$refs.calendarPopup.close();
},
viewDetail(course) {
//
const resourceId = this.resource_id || '';
const studentId = this.student_id || '';
console.log('跳转到课程详情页:', course.id, resourceId, studentId);
uni.navigateTo({
url: '/pages-market/clue/class_arrangement_detail?schedule_id=' + course.id + '&resource_id=' + resourceId + '&student_id=' + studentId,
fail: (err) => {
console.error('跳转到课程详情页失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
},
onCalendarConfirm(e) {
// e.fulldate
uni.showToast({
title: '选择日期:' + e.fulldate,
icon: 'none'
});
this.closeCalendar();
},
onCalendarChange(e) {
this.getDate(e.fulldate, e.date);
this.closeCalendar();
},
getStatusText(status) {
const statusMap = {
pending: '待开始',
upcoming: '即将开始',
ongoing: '进行中',
completed: '已结束'
};
return statusMap[status] || status;
},
//
openSearchPopup() {
this.showSearchPopup = true;
},
closeSearchPopup() {
this.showSearchPopup = false;
},
//
onTimeChange(e) {
this.timeIndex = e.detail.value;
this.searchForm.time_hour = this.timeOptions[this.timeIndex];
},
//
onStartDateChange(e) {
this.searchForm.start_date = e.detail.value;
console.log('开始日期选择:', e.detail.value);
},
//
onEndDateChange(e) {
this.searchForm.end_date = e.detail.value;
console.log('结束日期选择:', e.detail.value);
},
//
searchDataAndClose() {
console.log('执行搜索,表单数据:', this.searchForm);
this.searchCourseData();
this.showSearchPopup = false;
},
//
async searchCourseData() {
try {
let searchParams = {
schedule_date: this.date
};
//
if (this.searchForm.time_hour && this.searchForm.time_hour !== '全部时间') {
searchParams.time_hour = this.searchForm.time_hour;
}
if (this.searchForm.start_date) {
searchParams.start_date = this.searchForm.start_date;
}
if (this.searchForm.end_date) {
searchParams.end_date = this.searchForm.end_date;
}
if (this.searchForm.teacher_name) {
searchParams.teacher_name = this.searchForm.teacher_name;
}
if (this.searchForm.venue_number) {
searchParams.venue_number = this.searchForm.venue_number;
}
console.log('搜索参数:', searchParams);
let data = await apiRoute.courseAllList(searchParams);
// courseList
this.courseList = Array.isArray(data.data) ? data.data : [];
uni.showToast({
title: '查询完成',
icon: 'success'
});
} catch (error) {
console.error('搜索失败:', error);
uni.showToast({
title: '搜索失败',
icon: 'none'
});
}
},
//
resetSearchOnly() {
this.searchForm = {
time_hour: '',
start_date: '',
end_date: '',
teacher_name: '',
venue_number: ''
};
this.timeIndex = 0;
//
this.getDate();
},
// - 访
safeGet(obj, path, defaultValue = '') {
if (!obj) return defaultValue;
const keys = path.split('.');
let result = obj;
for (let key of keys) {
if (result === null || result === undefined || !result.hasOwnProperty(key)) {
return defaultValue;
}
result = result[key];
}
return result || defaultValue;
}
},
};
</script>
<style lang="less" scoped>
.class-arrange-root {
background: #232323;
min-height: 100vh;
padding-bottom: 30rpx;
}
.date-bar {
display: flex;
align-items: center;
background: #232323;
padding: 0 0 10rpx 0;
overflow-x: auto;
border-bottom: 1px solid #333;
.date-item {
flex: 1;
text-align: center;
color: #bdbdbd;
padding: 16rpx 0 0 0;
.week {
font-size: 22rpx;
}
.day {
font-size: 28rpx;
margin-top: 4rpx;
}
&.active {
color: #29d3b4;
.day {
border-radius: 50%;
background: #333;
color: #29d3b4;
padding: 2rpx 10rpx;
}
}
}
}
.more-bar-wrapper {
width: 100%;
display: flex;
justify-content: center;
margin: 10rpx 0 0 0;
}
.more-bar {
display: flex;
align-items: center;
color: #bdbdbd;
font-size: 22rpx;
cursor: pointer;
background: #333;
border-radius: 20rpx;
padding: 8rpx 24rpx;
}
.course-list {
margin-top: 20rpx;
.course-card {
background: #434544;
border-radius: 10rpx;
margin: 0 0 20rpx 0;
padding: 0 0 20rpx 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
.card-header {
display: flex;
justify-content: flex-end;
padding: 10rpx 20rpx 0 0;
.status-end {
background: #e95c6b;
color: #fff;
border-radius: 10rpx;
padding: 4rpx 18rpx;
font-size: 22rpx;
}
}
.card-body {
padding: 0 20rpx;
.row {
color: #fff;
font-size: 24rpx;
margin: 8rpx 0;
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
.sign-info {
color: #bdbdbd;
font-size: 22rpx;
}
.detail-btn {
background: transparent;
border: 2rpx solid #ffd86b;
color: #ffd86b;
border-radius: 8rpx;
padding: 6rpx 24rpx;
font-size: 24rpx;
width: 100%;
margin-top: 40rpx;
}
}
}
}
//
.search_popup_mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 999;
display: flex;
flex-direction: column;
}
.search_popup_content {
background: #fff;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
animation: slideDown 0.3s ease-out;
width: 100%;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
@keyframes slideDown {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
//
.popup_search_content {
padding: 0;
background: #fff;
min-height: 60vh;
max-height: 80vh;
display: flex;
flex-direction: column;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
overflow: hidden;
}
.popup_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
}
.popup_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup_close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.close_text {
font-size: 32rpx;
color: #999;
}
}
.popup_scroll_view {
flex: 1;
padding: 32rpx;
overflow-y: auto;
}
.popup_filter_section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
}
.popup_filter_row {
display: flex;
gap: 20rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
}
.popup_filter_item {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
&.full_width {
flex: 1;
}
.popup_filter_label {
font-size: 26rpx;
color: #666;
font-weight: 500;
}
.popup_filter_input {
height: 72rpx;
line-height: 72rpx;
padding: 0 16rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
background: #fff;
&::placeholder {
color: #999;
}
}
.popup_filter_picker {
height: 72rpx;
line-height: 72rpx;
padding: 0 16rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
background: #fff;
position: relative;
&::after {
content: '▼';
position: absolute;
right: 16rpx;
font-size: 20rpx;
color: #999;
}
}
}
.popup_filter_buttons {
display: flex;
gap: 20rpx;
padding: 32rpx;
margin-top: auto;
border-top: 1px solid #f0f0f0;
background: #fff;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
}
.popup_filter_btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 600;
&.search_btn {
background: #29d3b4;
color: #fff;
}
&.reset_btn {
background: #f5f5f5;
color: #666;
border: 1px solid #ddd;
}
&.close_btn {
background: #666;
color: #fff;
}
}
</style>

1292
uniapp/pages-market/clue/class_arrangement_detail.vue

File diff suppressed because it is too large

431
uniapp/pages-market/clue/clue_info.less

@ -0,0 +1,431 @@
// 客户详情页样式文件
.assemble {
background-color: #292929;
min-height: 100vh;
}
.content {
padding: 20rpx;
}
.tab-switcher-container {
margin: 20rpx 0;
}
// 学生信息区域
.student-section {
margin-top: 20rpx;
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
.add-student-btn {
display: flex;
align-items: center;
background: #29d3b4;
color: #fff;
border-radius: 20rpx;
padding: 12rpx 20rpx;
font-size: 24rpx;
.add-icon {
margin-right: 8rpx;
font-weight: bold;
font-size: 20rpx;
}
&:active {
background: #1ea08e;
transform: scale(0.95);
}
}
}
.student-cards {
width: 100%;
min-height: 600rpx;
.student-swiper {
width: 100%;
height: 560rpx;
.student-swiper-content {
display: flex;
flex-direction: column;
height: 580rpx;
padding: 20rpx;
box-sizing: border-box;
background-color: #434544;
border-radius: 16rpx;
margin: 0 10rpx;
color: #fff;
}
}
}
}
.popup-footer {
display: flex;
padding: 30rpx 40rpx;
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
border-top: 1px solid #333;
gap: 30rpx;
flex-shrink: 0; /* 确保底部按钮区域不被压缩 */
background: #1a1a1a; /* 确保背景色一致 */
.popup-footer-btns {
display: flex;
justify-content: space-between;
.footer-btn {
flex: 1;
height: 88rpx;
display: flex;
width: 45%;
align-items: center;
justify-content: center;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 500;
&.cancel-btn {
background: #cccccc;
}
&.confirm-btn {
background: linear-gradient(45deg, #29D3B4, #1DB584);
color: #ffffff;
}
}
}
}
// 课程、通话、体测记录等区域
.course-section, .call-section, .fitness-section, .study-plan-section {
background-color: #434544;
border-radius: 16rpx;
margin: 20rpx 0;
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.context-title {
font-size: 28rpx;
font-weight: bold;
color: #fff;
}
.add-record-btn {
display: flex;
align-items: center;
background: #29d3b4;
color: #fff;
border-radius: 20rpx;
padding: 12rpx 20rpx;
font-size: 24rpx;
.add-icon {
margin-right: 8rpx;
font-weight: bold;
}
&:active {
background: #1ea08e;
transform: scale(0.95);
}
}
}
}
// 操作按钮区域 - 独立于Swiper外部
.student-section .action-buttons-section {
display: flex;
gap: 8rpx;
margin-top: 15rpx;
padding: 15rpx;
flex-wrap: nowrap;
height: 100rpx;
box-sizing: border-box;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
background-color: #434544;
border-radius: 16rpx;
margin: 15rpx 0;
&::-webkit-scrollbar {
display: none;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(41, 211, 180, 0.3);
border-radius: 8rpx;
padding: 12rpx 8rpx;
min-width: 90rpx;
flex-shrink: 0;
transition: all 0.3s ease;
.action-icon {
font-size: 24rpx;
margin-bottom: 5rpx;
line-height: 1;
}
.action-text {
font-size: 18rpx;
color: #fff;
text-align: center;
line-height: 1.2;
word-break: break-all;
}
&:active {
background: rgba(41, 211, 180, 0.2);
border-color: #29d3b4;
transform: scale(0.95);
.action-text {
color: #29d3b4;
}
}
}
}
// 通用空状态样式
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 20rpx;
min-height: 200rpx;
.empty-icon {
font-size: 60rpx;
opacity: 0.6;
margin-bottom: 20rpx;
}
.empty-text {
color: #ccc;
font-size: 28rpx;
margin-bottom: 20rpx;
}
.empty-add-btn {
background: #29d3b4;
color: #fff;
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
transition: all 0.3s ease;
&:active {
background: #1ea08e;
transform: scale(0.95);
}
}
}
// 备注弹窗样式
.remark-dialog {
width: 600rpx;
padding: 30rpx;
background: #fff;
border-radius: 16rpx;
textarea {
width: 100%;
height: 200rpx;
padding: 20rpx;
border: 2rpx solid #e9ecef;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
background-color: #fff;
box-sizing: border-box;
resize: none;
&::placeholder {
color: #999;
}
&:focus {
border-color: #29d3b4;
outline: none;
box-shadow: 0 0 0 2rpx rgba(41, 211, 180, 0.1);
}
}
.dialog-btns {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
.btn {
flex: 1;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
text-align: center;
&.cancel {
background: #f8f9fa;
color: #666;
border: 2rpx solid #e9ecef;
}
&.confirm {
background: #29d3b4;
color: #fff;
}
}
}
}
// 二维码支付弹窗样式
.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;
&:active {
background: #e5e5e5;
transform: scale(0.95);
}
}
}
.order-info {
padding: 30rpx 40rpx;
.info-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.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-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;
&.secondary {
background: #f5f5f5;
color: #666;
&:active {
background: #e5e5e5;
transform: scale(0.98);
}
}
&.primary {
background: #29d3b4;
color: #fff;
&:active {
background: #1ea08e;
transform: scale(0.98);
}
}
}
}
}

1470
uniapp/pages-market/clue/clue_info.vue

File diff suppressed because it is too large

164
uniapp/pages-market/clue/clue_table.vue

@ -0,0 +1,164 @@
<template>
<view class="dark-table-container">
<view v-for="(row, rIdx) in data" :key="rIdx" class="card">
<view class="card-title" @click="toggleExpand(rIdx)">
{{ row.channel }}
<text class="expand-icon">{{ expanded[rIdx] ? '▲' : '▼' }}</text>
</view>
<view class="card-content">
<view
class="card-row"
v-for="(col, cIdx) in getVisibleColumns(rIdx)"
:key="cIdx"
>
<view class="card-label">
{{ col.label }}
<text v-if="col.tip" class="tip" @click.stop="showTip(col.tip)">?</text>
</view>
<view class="card-value">{{ row[col.key] || '-' }}</view>
</view>
</view>
<view v-if="columns.length > 6" class="expand-btn" @click="toggleExpand(rIdx)">
{{ expanded[rIdx] ? '收起' : '展开全部' }}
</view>
</view>
<!-- 说明弹窗 -->
<uni-popup ref="popup" type="center">
<view class="popup-content">{{ tipContent }}</view>
</uni-popup>
<AQTabber></AQTabber>
</view>
</template>
<script>
import AQTabber from "@/components/AQ/AQTabber.vue"
export default {
components: {
AQTabber,
},
data() {
return {
columns: [
{ label: '渠道', key: 'channel' },
{ label: '资源数', key: 'resource', tip: '本月的新资源' },
{ label: '到访数(一访)', key: 'visit1', tip: '本月资源的一访到访数' },
{ label: '到访率(一访)', key: 'visitRate1', tip: '一访到访数/本月新资源数' },
{ label: '到访数(二访)', key: 'visit2', tip: '二访到访数/一访到访数' },
{ label: '到访率(二访)', key: 'visitRate2', tip: '二访到访数/一访到访数' },
{ label: '关单数(一访)', key: 'close1', tip: '本月一访到访的关单数' },
{ label: '关单率(一访)', key: 'closeRate1', tip: '一访关单数/一访到访数' },
{ label: '关单数(二访)', key: 'close2', tip: '二访到访的关单数' },
{ label: '关单率(二访)', key: 'closeRate2', tip: '二访关单数/二访到访数' },
{ label: '月总转', key: 'monthTrans', tip: '关单数(合计)/资源数' },
{ label: '往月到访数', key: 'lastMonthVisit', tip: '本月资源在往月到访的到访数' },
{ label: '月共到访', key: 'monthTotalVisit', tip: '本月资源任在本月到访/关单' },
{ label: '往月关单数', key: 'lastMonthClose', tip: '本月关单-往月关单' },
{ label: '月共关单', key: 'monthTotalClose', tip: '本月关单+往月关单' },
],
data: [
{ channel: '体检包(地推)', resource: 10, visit1: 5, visitRate1: '50%', visit2: 2, visitRate2: '40%', close1: 1, closeRate1: '20%', close2: 1, closeRate2: '50%', monthTrans: '10%', lastMonthVisit: 0, monthTotalVisit: 5, lastMonthClose: 0, monthTotalClose: 1 },
],
tipContent: '',
expanded: {},
}
},
methods: {
showTip(content) {
this.tipContent = content
this.$refs.popup.open()
},
toggleExpand(idx) {
this.$set(this.expanded, idx, !this.expanded[idx])
},
getVisibleColumns(idx) {
// 5
const cols = this.columns.slice(1)
if (this.expanded[idx]) return cols
return cols.slice(0, 5)
}
}
}
</script>
<style scoped>
.dark-table-container {
background: #18181c;
color: #f1f1f1;
min-height: 100vh;
padding: 24rpx 12rpx;
}
.card {
background: #23232a;
border-radius: 18rpx;
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.18);
margin-bottom: 32rpx;
padding: 24rpx 20rpx 8rpx 20rpx;
transition: box-shadow 0.2s;
}
.card-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 18rpx;
color: #ffb300;
letter-spacing: 2rpx;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.expand-icon {
font-size: 24rpx;
color: #bdbdbd;
margin-left: 12rpx;
}
.card-content {
display: flex;
flex-direction: column;
gap: 12rpx;
overflow: hidden;
transition: max-height 0.3s;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px dashed #333;
padding: 8rpx 0;
}
.card-label {
color: #bdbdbd;
font-size: 26rpx;
display: flex;
align-items: center;
}
.card-value {
color: #f1f1f1;
font-size: 26rpx;
text-align: right;
max-width: 60%;
word-break: break-all;
}
.tip {
color: #ffb300;
margin-left: 8rpx;
font-size: 24rpx;
cursor: pointer;
}
.expand-btn {
color: #ffb300;
text-align: center;
font-size: 26rpx;
margin: 10rpx 0 0 0;
padding-bottom: 8rpx;
cursor: pointer;
}
.popup-content {
background: #23232a;
color: #fff;
padding: 32rpx;
border-radius: 16rpx;
min-width: 300rpx;
text-align: center;
}
</style>

1806
uniapp/pages-market/clue/edit_clues.vue

File diff suppressed because it is too large

365
uniapp/pages-market/clue/edit_clues_log.vue

@ -0,0 +1,365 @@
<!--编辑客户-->
<template>
<view class="assemble">
<!--切换-->
<view class="tab_section">
<fui-segmented-control
:values="optionTable"
:current="(Number(optionTableId))"
type="button"
radius="8"
height="80"
color="#29d3b4"
bold="true"
@click="segmented">
</fui-segmented-control>
</view>
<!-- 客户资源修改记录-->
<scroll-view
v-show="filteredData.type == `resource`"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="margin-top:40rpx;height: 80vh;"
>
<!--时间轴-->
<view class="table_list">
<fui-timeaxis background="#292929" :padding="['36rpx','16rpx']">
<fui-timeaxis-node
:lineColor="k<3 ? v.activeColor:'#ccc'"
:lined="k!==tableList.length-1"
v-for="(v,k) in tableList"
:key="k"
>
<!--inco-->
<view
class="fui-node"
:style="{borderColor:(k != tableList.length-1 ? '#29d3b4' :'' ) }"
>
</view>
<template v-slot:right>
<view
class="fui-process__node table_itme"
:style="{paddingBottom:(k !== tableList.length-1 ? '50rpx' :'0rpx')}"
:class="{'fui-node__pb':k!==tableList.length-1}"
>
<view class="fui-title">
<!--修改时间-->
{{v.modification_time}}
</view>
<view class="itme_box">
<view class="title_name">修改人{{v.staff_id_name}} 修改了如下内容</view>
<view class="table_section">
<table class="table_box">
<!-- 自定义表头 -->
<thead>
<tr>
<th style="width: 30%; border: 1px solid #ddd; padding: 12rpx;">字段名称</th>
<th style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">原数据</th>
<th style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">新数据</th>
</tr>
</thead>
<!-- 自定义每一行数据 -->
<tbody>
<tr v-for="(row, index) in v.update_arr" :key="index">
<td style="width: 30%; border: 1px solid #ddd; padding: 12rpx;">{{ row.field_name_zh }}</td>
<td style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">{{ row.old_value }}</td>
<td style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">{{ row.new_value }}</td>
</tr>
</tbody>
</table>
</view>
</view>
</view>
</template>
</fui-timeaxis-node>
</fui-timeaxis>
</view>
</scroll-view>
<!-- 六要素修改记录-->
<scroll-view
v-show="filteredData.type == `six_speed`"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="margin-top:40rpx;height: 80vh;"
>
<!--时间轴-->
<view class="table_list">
<fui-timeaxis background="#292929" :padding="['36rpx','16rpx']">
<fui-timeaxis-node
:lineColor="k<3?v.activeColor:'#ccc'"
:lined="k!==tableList.length-1"
v-for="(v,k) in tableList"
:key="k"
>
<!--inco-->
<view
class="fui-node"
:style="{borderColor:(k != tableList.length-1 ? '#29d3b4' :'' ) }"
>
</view>
<template v-slot:right>
<view
class="fui-process__node table_itme"
:style="{paddingBottom:(k !== tableList.length-1 ? '50rpx' :'0rpx')}"
:class="{'fui-node__pb':k!==tableList.length-1}"
>
<view class="fui-title">
<!--修改时间-->
{{v.modification_time}}
</view>
<view class="itme_box">
<view class="title_name">修改人{{v.staff_id_name}} 修改了如下内容</view>
<view class="table_section">
<table class="table_box">
<!-- 自定义表头 -->
<thead>
<tr>
<th style="width: 30%; border: 1px solid #ddd; padding: 12rpx;">字段名称</th>
<th style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">原数据</th>
<th style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">新数据</th>
</tr>
</thead>
<!-- 自定义每一行数据 -->
<tbody>
<tr v-for="(row, index) in v.update_arr" :key="index">
<td style="width: 30%; border: 1px solid #ddd; padding: 12rpx;">{{ row.field_name_zh }}</td>
<td style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">{{ row.old_value }}</td>
<td style="width: 34%; border: 1px solid #ddd; padding: 12rpx;">{{ row.new_value }}</td>
</tr>
</tbody>
</table>
</view>
</view>
</view>
</template>
</fui-timeaxis-node>
</fui-timeaxis>
</view>
</scroll-view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
type:'resource',//|resource=,six_speed=
customer_resource_id: '',//id
},
//
tableList:[],//
//tab
optionTableId:0,//
optionTable:[
{
id: 0,
name: '客户资源修改记录',
type:'resource',
},
{
id: 1,
name: '六要素修改记录',
type:'six_speed',
}
]
}
},
onLoad(options) {
this.filteredData.customer_resource_id = options.resource_id//id
},
onShow() {
this.init()
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init() {
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 apiRoute.xs_customerResourcesGetEditLogList(data)
this.loading = false
this.isReachedBottom = false;
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
console.log(123123123,res)
this.tableList = this.tableList.concat(res.data.data); // 使 concat
console.log('列表1',this.tableList)
this.filteredData.total = res.data.total
this.filteredData.page++
},
//tag
async segmented(e) {
console.log('切换',e)
//e.id|0= 1=
let status = e.id
this.optionTableId = String(status)
this.filteredData.type = e.type//|resource=,six_speed=
await this.resetFilteredData()
await this.getList()
},
}
}
</script>
<style lang="less" scoped>
.assemble{
width: 100%;
height: 100%;
background-color: #292929;
overflow: auto;
.tab_section{
padding: 0 20rpx;
padding-top: 30rpx;
}
.table_list{
color: #fff;
::v-deep .fui-timeaxis__line{
background-color: #fff;
}
.itme_box {
margin-top: 30rpx;
display: flex;
flex-direction: column;
gap: 25rpx;
.title_name{
font-size: 28rpx;
}
.table_section{
.table_box{
width: 100%;
border-collapse: collapse;
table-layout: fixed;
tr{
display: flex;
th{}
td{}
}
}
}
}
::v-deep .fui-timeaxis__wrap{
}
.fui-node {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
border: 4px solid #ccc;
box-sizing: border-box;
}
.fui-process__node {
padding-left: 20rpx;
}
.fui-title {
font-size: 34rpx;
line-height: 34rpx;
font-weight: bold;
}
.fui-descr {
font-size: 28rpx;
padding-top: 12rpx;
color: #7F7F7F;
}
.fui-time__left {
font-size: 36rpx;
line-height: 36rpx;
text-align: right;
padding-right: 20rpx;
}
.fui-node__pbtm {
padding-bottom: 88rpx;
}
}
}
</style>

1760
uniapp/pages-market/clue/index.vue

File diff suppressed because it is too large

343
uniapp/pages-market/data/statistics.vue

@ -0,0 +1,343 @@
<!--市场数据统计页面-->
<template>
<view class="main_box" :class="{'has-safe-area': hasSafeArea}">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="title">市场数据统计</view>
</view>
<!-- 顶部筛选 -->
<view class="filter-section">
<view class="filter-item">
<view class="label">统计时间</view>
<picker mode="date" fields="month" :value="currentDate" @change="dateChange">
<view class="picker-value">{{currentDate}}</view>
</picker>
</view>
</view>
<!-- 市场人员资源量统计 -->
<view class="table-section">
<view class="table-title">市场人员资源量统计</view>
<view class="table-container">
<view class="table-header">
<view class="th th-person">人员</view>
<view class="th th-week">周数量</view>
<view class="th th-month">月数量</view>
<view class="th th-year">年数量</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(item, index) in staffData" :key="index">
<view class="td td-person">{{item.name}}</view>
<view class="td td-week">{{item.weekCount}}</view>
<view class="td td-month">{{item.monthCount}}</view>
<view class="td td-year">{{item.yearCount}}</view>
</view>
</view>
</view>
</view>
<!-- 校区人员资源渠道量 -->
<view class="table-section">
<view class="table-title">{{currentMonth}} - 年度校区人员资源渠道量</view>
<view class="table-container">
<view class="table-header">
<view class="th th-channel" style="width: 100px;">渠道</view>
<view class="th" v-for="(school, index) in schoolList" :key="index">{{school.campus_name}}</view>
<view class="th th-total">渠道合计</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(channel, index) in channelList" :key="index">
<view class="td td-channel" style="width: 100px;">{{channel.name}}</view>
<view class="td" v-for="(school, sIndex) in schoolList" :key="sIndex">
{{channel[school.id] || 0}}
</view>
<view class="td td-total">{{channel.total}}</view>
</view>
</view>
</view>
</view>
<!-- 市场人员资源渠道统计 -->
<view class="table-section">
<view class="table-title">{{currentMonth}} - 年度市场人员资源渠道统计</view>
<view class="table-container">
<view class="table-header">
<view class="th th-channel" style="width: 100px;">渠道</view>
<view class="th" v-for="(staff, index) in staffData" :key="index">市场{{staff.name}}</view>
<view class="th th-total">资源合计</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(channel, index) in channelList" :key="index">
<view class="td td-channel" style="width: 100px;">{{channel.name}}</view>
<view class="td" v-for="(staff, sIndex) in staffData" :key="sIndex">
{{staff['channel'][channel.value] || 0}}
</view>
<view class="td td-total">{{channel.total}}</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 {
currentDate: this.formatDate(new Date()),
currentMonth: new Date().getMonth() + 1,
//
staffData: [],
//
schoolList: [],
//
hasSafeArea: false,
//
channelList: [],
//
marketStaffList: [
{ id: 'A', name: 'A' },
{ id: 'B', name: 'B' },
{ id: 'C', name: 'C' },
{ id: 'D', name: 'D' }
],
//
channelSchoolData: [],
//
channelStaffData: []
}
},
onLoad() {
this.initData();
this.checkSafeArea();
},
methods: {
// YYYY-MM
formatDate(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
return `${year}-${month}`;
},
//
dateChange(e) {
this.currentDate = e.detail.value;
const [year, month] = this.currentDate.split('-');
this.currentMonth = parseInt(month);
this.initData();
},
//
async initData() {
// API
await this.getStaffStatistics();
// await this.getChannelSchoolStatistics();
// this.getChannelStaffStatistics();
},
//
async getStaffStatistics() {
// API
let res = await apiRoute.getStaffStatistics({date: this.currentDate})
console.log(res.data.staffData,"================");
this.staffData = res.data.staffData
this.schoolList = res.data.schoolList
this.channelList = res.data.channelList
},
//
getChannelSchoolStatistics() {
// API
this.channelSchoolData = this.channelList.map(channel => {
const schools = {};
let total = 0;
this.schoolList.forEach(school => {
const count = Math.floor(Math.random() * 50);
schools[school.id] = count;
total += count;
});
return {
id: channel.id,
name: channel.name,
schools,
total
}
});
},
//
getChannelStaffStatistics() {
// API
this.channelStaffData = this.channelList.map(channel => {
const staffs = {};
let total = 0;
this.marketStaffList.forEach(staff => {
const count = Math.floor(Math.random() * 40);
staffs[staff.id] = count;
total += count;
});
return {
id: channel.id,
name: channel.name,
staffs,
total
}
});
},
//
checkSafeArea() {
try {
const systemInfo = uni.getSystemInfoSync();
// iPhone X
if (systemInfo.safeAreaInsets && systemInfo.safeAreaInsets.bottom > 0) {
this.hasSafeArea = true;
}
} catch (e) {
console.error('获取系统信息失败', e);
}
}
}
}
</script>
<style lang="scss" scoped>
.main_box {
min-height: 100vh;
background-color: #1a1a1a;
color: #ffffff;
padding-bottom: 150rpx; /* 增加底部内边距,防止内容被底部导航栏遮挡 */
}
/* 适配有安全区域的设备(如iPhone X及以上机型) */
.has-safe-area {
padding-bottom: calc(150rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
padding-bottom: calc(150rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
.navbar_section {
background-color: #1a1a1a;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.title {
font-size: 36rpx;
font-weight: bold;
}
}
.filter-section {
padding: 20rpx;
background-color: #222222;
margin: 20rpx;
border-radius: 10rpx;
.filter-item {
display: flex;
align-items: center;
.label {
font-size: 28rpx;
margin-right: 10rpx;
}
.picker-value {
padding: 10rpx 20rpx;
background-color: #333333;
border-radius: 6rpx;
font-size: 28rpx;
}
}
}
.table-section {
margin: 30rpx 20rpx;
background-color: #222222;
border-radius: 10rpx;
overflow: hidden;
.table-title {
font-size: 32rpx;
font-weight: bold;
padding: 20rpx;
border-bottom: 1px solid #333333;
}
.table-container {
width: 100%;
overflow-x: auto;
}
.table-header {
display: flex;
background-color: #333333;
.th {
padding: 15rpx 10rpx;
font-size: 26rpx;
text-align: center;
flex: 1;
min-width: 100rpx;
font-weight: bold;
}
}
.table-body {
.table-row {
display: flex;
border-bottom: 1px solid #333333;
&:last-child {
border-bottom: none;
}
.td {
padding: 15rpx 10rpx;
font-size: 26rpx;
text-align: center;
flex: 1;
min-width: 100rpx;
}
}
}
.th-person, .td-person {
min-width: 80rpx;
flex: 0.8;
}
.th-week, .td-week,
.th-month, .td-month,
.th-year, .td-year {
flex: 1;
}
.th-channel, .td-channel {
flex: 1.2;
text-align: left;
padding-left: 20rpx;
}
.th-total, .td-total {
flex: 1.2;
font-weight: bold;
}
}
</style>

427
uniapp/pages-market/index/index.vue

@ -0,0 +1,427 @@
<template>
<view class="assemble">
<view style="height: 20rpx;"></view>
<!-- 时间筛选 -->
<view class="filter-section">
<picker mode="date" fields="month" :value="currentDate" @change="onDateChange">
<view class="date-picker">
<text>{{currentDate}}</text>
<image class="drop-image" :src="$util.img('/static/images/drop.png')" mode="aspectFit"></image>
</view>
</picker>
</view>
<!-- 本月提成卡片 -->
<view class="commission-card">
<view class="card-title">本月提成</view>
<view class="commission-amount">¥{{totalCommission}}</view>
</view>
<!-- 续费提成记录 -->
<view class="record-card">
<view class="card-title">续费提成</view>
<view class="table">
<view class="table-header">
<view class="th">时间</view>
<view class="th">到期数</view>
<view class="th">续费数</view>
<view class="th">续费率</view>
<view class="th">提成</view>
</view>
<view class="table-body">
<view class="tr" v-for="(item, index) in renewalRecords" :key="index">
<view class="td">{{item.time}}</view>
<view class="td">{{item.expireCount}}</view>
<view class="td">{{item.renewCount}}</view>
<view class="td">{{item.renewRate}}%</view>
<view class="td">¥{{item.commission}}</view>
</view>
</view>
</view>
</view>
<!-- 新招课包 -->
<view class="record-card">
<view class="card-title">新招课包</view>
<view class="table">
<view class="table-header">
<view class="th">成交数</view>
<view class="th">提成</view>
<view class="th">合计</view>
</view>
<view class="table-body">
<view class="tr">
<view class="td">{{newPackageCount}}</view>
<view class="td">¥{{newPackageCommission}}</view>
<view class="td">¥{{newPackageTotal}}</view>
</view>
</view>
</view>
</view>
<!-- 私教课包 -->
<view class="record-card">
<view class="card-title">私教课包</view>
<view class="table">
<view class="table-header">
<view class="th">数量</view>
<view class="th">提成</view>
</view>
<view class="table-body">
<view class="tr">
<view class="td">{{privatePackageCount}}</view>
<view class="td">¥{{privatePackageCommission}}</view>
</view>
</view>
</view>
</view>
<!-- 时间卡 -->
<view class="record-card">
<view class="card-title">时间卡</view>
<view class="table">
<view class="table-header">
<view class="th">时间卡</view>
<view class="th">数量</view>
<view class="th">提成</view>
</view>
<view class="table-body">
<view class="tr">
<view class="td">{{timeCardCount}}</view>
<view class="td">{{timeCardAmount}}</view>
<view class="td">¥{{timeCardCommission}}</view>
</view>
</view>
</view>
</view>
<!-- 其他提成 -->
<view class="record-card">
<view class="card-title">其他提成</view>
<view class="table">
<view class="table-header">
<view class="th">项目</view>
<view class="th">金额</view>
</view>
<view class="table-body">
<view class="tr" v-for="(item, index) in otherCommissions" :key="index">
<view class="td">{{item.project}}</view>
<view class="td">¥{{item.amount}}</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
export default {
data() {
return {
currentDate: this.formatDate(new Date()),
totalCommission: 0,
renewalRecords: [],
newPackageCount: 0,
newPackageCommission: 0,
newPackageTotal: 0,
privatePackageCount: 0,
privatePackageCommission: 0,
timeCardCount: 0,
timeCardAmount: 0,
timeCardCommission: 0,
otherCommissions: []
}
},
onShow() {
this.getStatisticsData()
},
methods: {
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
return `${year}-${month}`
},
onDateChange(e) {
this.currentDate = e.detail.value
this.getStatisticsData()
},
async getStatisticsData() {
try {
const res = await apiRoute.xs_statisticsMarketData({
date: this.currentDate
})
if (res && res.code === 1) {
const data = res.data
this.totalCommission = data.totalCommission || 0
this.renewalRecords = data.renewalRecords || []
this.newPackageCount = data.newPackageCount || 0
this.newPackageCommission = data.newPackageCommission || 0
this.newPackageTotal = data.newPackageTotal || 0
this.privatePackageCount = data.privatePackageCount || 0
this.privatePackageCommission = data.privatePackageCommission || 0
this.timeCardCount = data.timeCardCount || 0
this.timeCardAmount = data.timeCardAmount || 0
this.timeCardCommission = data.timeCardCommission || 0
this.otherCommissions = data.otherCommissions || []
}
} catch (error) {
console.error('获取统计数据失败:', error)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
}
}
}
}
</script>
<style lang="less" scoped>
//
.navbar_section {
border: 1px solid #23262F;
display: flex;
justify-content: center;
align-items: center;
background: #23262F;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.18);
.title {
padding: 40rpx 0rpx;
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #F5F6FA;
font-weight: bold;
letter-spacing: 2rpx;
}
}
.assemble {
width: 100%;
min-height: 100vh;
background: #181A20;
padding-bottom: 100rpx;
}
.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;
filter: brightness(0.8);
}
.title {
font-size: 30rpx;
color: #F5F6FA;
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;
}
.filter-section {
padding: 20rpx 30rpx;
background: #23262F;
margin-bottom: 20rpx;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.2);
.date-picker {
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #F5F6FA;
.drop-image {
width: 30rpx;
height: 30rpx;
margin-left: 10rpx;
filter: brightness(0.8);
}
}
}
.commission-card {
background: linear-gradient(135deg, #FF6B6B, #FF8E8E);
margin: 20rpx;
padding: 30rpx;
border-radius: 16rpx;
color: #fff;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.25);
.card-title {
font-size: 28rpx;
margin-bottom: 20rpx;
color: #fff;
}
.commission-amount {
font-size: 48rpx;
font-weight: bold;
color: #fff;
}
}
.record-card {
background: #23262F;
margin: 20rpx;
padding: 20rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.18);
.card-title {
font-size: 30rpx;
color: #F5F6FA;
margin-bottom: 20rpx;
font-weight: bold;
}
.table {
width: 100%;
.table-header {
display: flex;
background: #181A20;
padding: 20rpx 0;
border-radius: 8rpx 8rpx 0 0;
.th {
flex: 1;
text-align: center;
font-size: 26rpx;
color: #A0A3B1;
font-weight: 500;
}
}
.table-body {
.tr {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #33343A;
transition: background 0.2s;
&:last-child {
border-bottom: none;
}
.td {
flex: 1;
text-align: center;
font-size: 26rpx;
color: #F5F6FA;
font-weight: 400;
}
&:hover {
background: #23262F;
}
}
}
}
}
//
::-webkit-scrollbar {
display: none;
}
</style>

495
uniapp/pages-market/my/campus_data.vue

@ -0,0 +1,495 @@
<!--校区数据-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="navbar_content">
<view class="back_btn" @click="goBack">
<fui-icon name="arrowleft" size="32" color="#fff"></fui-icon>
</view>
<view class="title">校区数据</view>
<view class="placeholder"></view>
</view>
</view>
<view class="content_section">
<!-- 校区选择器 -->
<view class="campus_selector">
<view class="selector_label">选择校区</view>
<view class="selector_box">
<view class="selected_campus">请选择校区</view>
<fui-icon name="dropdown" size="24" color="#999"></fui-icon>
</view>
</view>
<!-- 数据概览卡片 -->
<view class="overview_cards">
<view class="card_item">
<view class="card_icon">
<fui-icon name="home" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">部门数量</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="addressbook" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">员工总数</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="star" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">客户总数</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="wallet" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">校区业绩</view>
<view class="card_value">--</view>
</view>
</view>
</view>
<!-- 部门对比 -->
<view class="dept_comparison">
<view class="section_title">部门业绩对比</view>
<view class="comparison_list">
<view class="comparison_item">
<view class="dept_info">
<view class="dept_name">销售部</view>
<view class="dept_score">120000</view>
</view>
<view class="progress_bar">
<view class="progress_fill" style="width: 80%"></view>
</view>
<view class="dept_percent">80%</view>
</view>
<view class="comparison_item">
<view class="dept_info">
<view class="dept_name">市场部</view>
<view class="dept_score">100000</view>
</view>
<view class="progress_bar">
<view class="progress_fill" style="width: 67%"></view>
</view>
<view class="dept_percent">67%</view>
</view>
<view class="comparison_item">
<view class="dept_info">
<view class="dept_name">运营部</view>
<view class="dept_score">80000</view>
</view>
<view class="progress_bar">
<view class="progress_fill" style="width: 53%"></view>
</view>
<view class="dept_percent">53%</view>
</view>
<view class="comparison_item">
<view class="dept_info">
<view class="dept_name">客服部</view>
<view class="dept_score">60000</view>
</view>
<view class="progress_bar">
<view class="progress_fill" style="width: 40%"></view>
</view>
<view class="dept_percent">40%</view>
</view>
</view>
</view>
<!-- 月度趋势 -->
<view class="trend_section">
<view class="section_title">月度趋势</view>
<view class="trend_chart">
<view class="chart_placeholder">
<fui-icon name="linechart" size="80" color="#ddd"></fui-icon>
<view class="placeholder_text">图表功能待开发</view>
</view>
</view>
</view>
<!-- 功能按钮区域 -->
<view class="function_section">
<view class="section_title">数据分析</view>
<view class="function_grid">
<view class="function_item">
<view class="function_icon">
<fui-icon name="barchart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">校区对比</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="piechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">部门分析</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="linechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">趋势分析</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="list" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">详细报表</view>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="tips_section">
<view class="tips_title">功能说明</view>
<view class="tips_content">
这里将显示校区的各项数据统计包括部门数量员工总数客户数量校区业绩等
具体功能待后续开发实现
</view>
</view>
</view>
</view>
</template>
<script>
import fuiIcon from "@/components/firstui/fui-icon/fui-icon.vue"
export default {
components: {
fuiIcon,
},
data() {
return {
}
},
onLoad() {
},
methods: {
//
goBack() {
uni.navigateBack()
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f5f5f5;
min-height: 100vh;
}
//
.navbar_section {
background: #29D3B4;
padding-top: 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 90rpx;
// #endif
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx 40rpx 24rpx;
.back_btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
.placeholder {
width: 60rpx;
}
}
}
//
.content_section {
padding: 40rpx 24rpx;
//
.campus_selector {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
margin-bottom: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.selector_label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.selector_box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background: #f8f8f8;
border-radius: 12rpx;
.selected_campus {
font-size: 26rpx;
color: #666;
}
}
}
//
.overview_cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
margin-bottom: 40rpx;
.card_item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.card_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.card_content {
flex: 1;
.card_title {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.card_value {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
}
}
}
//
.dept_comparison {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.comparison_list {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.comparison_item {
display: flex;
align-items: center;
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.dept_info {
width: 150rpx;
.dept_name {
font-size: 26rpx;
color: #333;
margin-bottom: 8rpx;
font-weight: 500;
}
.dept_score {
font-size: 22rpx;
color: #29D3B4;
font-weight: 600;
}
}
.progress_bar {
flex: 1;
height: 16rpx;
background: #f0f0f0;
border-radius: 8rpx;
margin: 0 20rpx;
overflow: hidden;
.progress_fill {
height: 100%;
background: linear-gradient(90deg, #29D3B4, #5CE1E6);
border-radius: 8rpx;
transition: width 0.3s ease;
}
}
.dept_percent {
width: 60rpx;
text-align: right;
font-size: 24rpx;
color: #29D3B4;
font-weight: 600;
}
}
}
}
//
.trend_section {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.trend_chart {
background: #fff;
border-radius: 16rpx;
padding: 40rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.chart_placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
.placeholder_text {
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
}
}
}
}
//
.function_section {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.function_grid {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.function_item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&:active {
background-color: #f5f5f5;
transform: scale(0.95);
}
.function_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.function_text {
font-size: 24rpx;
color: #333;
}
}
}
}
//
.tips_section {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.tips_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 16rpx;
}
.tips_content {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
}
}
</style>

445
uniapp/pages-market/my/dept_data.vue

@ -0,0 +1,445 @@
<!--部门数据-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="navbar_content">
<view class="back_btn" @click="goBack">
<fui-icon name="arrowleft" size="32" color="#fff"></fui-icon>
</view>
<view class="title">部门数据</view>
<view class="placeholder"></view>
</view>
</view>
<view class="content_section">
<!-- 部门选择器 -->
<view class="dept_selector">
<view class="selector_label">选择部门</view>
<view class="selector_box">
<view class="selected_dept">请选择部门</view>
<fui-icon name="dropdown" size="24" color="#999"></fui-icon>
</view>
</view>
<!-- 数据概览卡片 -->
<view class="overview_cards">
<view class="card_item">
<view class="card_icon">
<fui-icon name="addressbook" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">部门人数</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="star" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">总客户数</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="wallet" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">部门业绩</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="check" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">完成率</view>
<view class="card_value">--</view>
</view>
</view>
</view>
<!-- 排行榜 -->
<view class="ranking_section">
<view class="section_title">部门排行榜</view>
<view class="ranking_list">
<view class="ranking_item">
<view class="rank_number first">1</view>
<view class="member_info">
<view class="member_name">张三</view>
<view class="member_score">业绩50000</view>
</view>
<view class="medal">
<fui-icon name="star-fill" size="24" color="#FFD700"></fui-icon>
</view>
</view>
<view class="ranking_item">
<view class="rank_number second">2</view>
<view class="member_info">
<view class="member_name">李四</view>
<view class="member_score">业绩45000</view>
</view>
<view class="medal">
<fui-icon name="star-fill" size="24" color="#C0C0C0"></fui-icon>
</view>
</view>
<view class="ranking_item">
<view class="rank_number third">3</view>
<view class="member_info">
<view class="member_name">王五</view>
<view class="member_score">业绩40000</view>
</view>
<view class="medal">
<fui-icon name="star-fill" size="24" color="#CD7F32"></fui-icon>
</view>
</view>
</view>
</view>
<!-- 功能按钮区域 -->
<view class="function_section">
<view class="section_title">数据分析</view>
<view class="function_grid">
<view class="function_item">
<view class="function_icon">
<fui-icon name="barchart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">业绩对比</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="piechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">人员分布</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="linechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">趋势分析</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="list" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">详细报表</view>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="tips_section">
<view class="tips_title">功能说明</view>
<view class="tips_content">
这里将显示部门的各项数据统计包括部门人员客户数量销售业绩排行榜等
具体功能待后续开发实现
</view>
</view>
</view>
</view>
</template>
<script>
import fuiIcon from "@/components/firstui/fui-icon/fui-icon.vue"
export default {
components: {
fuiIcon,
},
data() {
return {
}
},
onLoad() {
},
methods: {
//
goBack() {
uni.navigateBack()
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f5f5f5;
min-height: 100vh;
}
//
.navbar_section {
background: #29D3B4;
padding-top: 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 90rpx;
// #endif
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx 40rpx 24rpx;
.back_btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
.placeholder {
width: 60rpx;
}
}
}
//
.content_section {
padding: 40rpx 24rpx;
//
.dept_selector {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
margin-bottom: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.selector_label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.selector_box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background: #f8f8f8;
border-radius: 12rpx;
.selected_dept {
font-size: 26rpx;
color: #666;
}
}
}
//
.overview_cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
margin-bottom: 40rpx;
.card_item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.card_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.card_content {
flex: 1;
.card_title {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.card_value {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
}
}
}
//
.ranking_section {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.ranking_list {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.ranking_item {
display: flex;
align-items: center;
padding: 20rpx 16rpx;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.rank_number {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 600;
color: #fff;
margin-right: 24rpx;
&.first {
background: #FFD700;
}
&.second {
background: #C0C0C0;
}
&.third {
background: #CD7F32;
}
}
.member_info {
flex: 1;
.member_name {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
font-weight: 500;
}
.member_score {
font-size: 24rpx;
color: #666;
}
}
.medal {
margin-left: 16rpx;
}
}
}
}
//
.function_section {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.function_grid {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.function_item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&:active {
background-color: #f5f5f5;
transform: scale(0.95);
}
.function_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.function_text {
font-size: 24rpx;
color: #333;
}
}
}
}
//
.tips_section {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.tips_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 16rpx;
}
.tips_content {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
}
}
</style>

523
uniapp/pages-market/my/index.vue

@ -0,0 +1,523 @@
<!--销售我的-首页-->
<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="openViewMyInfo()">
<image class="pic" :src="userInfo.head_img"></image>
<view class="name">{{userInfo.name}}</view>
</view>
<view class="right">
<view class="btn"></view>
<!-- <view class="btn">切换身份</view>-->
<view class="btn"></view>
</view>
</view>
<view class="bottom" v-for="(v,k) in userInfo.cameus_dept_arr">
<view class="left">
<view class="title">校区</view>
<view class="title">{{v.campus_id_name || ''}}</view>
</view>
<view class="division"></view>
<view class="right">
<view class="title">部门</view>
<view class="title dept">{{v.dept_name_str || ''}}</view>
</view>
</view>
</view>
</view>
<view class="main_section">
<view class="grid_container">
<view class="grid_item" @click="openViewReimbursementList()">
<view class="icon_wrapper">
<fui-icon name="bankcard" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">报销记录</view>
</view>
<view class="grid_item" @click="openViewMyAttendance()">
<view class="icon_wrapper">
<fui-icon name="calendar" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">我的考勤</view>
</view>
<view class="grid_item" @click="goCourseSchedule()">
<view class="icon_wrapper">
<fui-icon name="list" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">课程安排</view>
</view>
<view class="grid_item" @click="openViewMyMessage()">
<view class="icon_wrapper">
<fui-icon name="message" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">我的消息</view>
</view>
<view class="grid_item" @click="my_contract()">
<view class="icon_wrapper">
<fui-icon name="order" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">我的合同</view>
</view>
<view class="grid_item" @click="openMyData()">
<view class="icon_wrapper">
<fui-icon name="barchart" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">我的数据</view>
</view>
<view class="grid_item" @click="openDeptData()">
<view class="icon_wrapper">
<fui-icon name="piechart" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">部门数据</view>
</view>
<view class="grid_item" @click="openCampusData()">
<view class="icon_wrapper">
<fui-icon name="linechart" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">校区数据</view>
</view>
<view class="grid_item" @click="openViewSetUp()">
<view class="icon_wrapper">
<fui-icon name="setup" size="48" color="#29D3B4"></fui-icon>
</view>
<view class="item_text">设置</view>
</view>
</view>
</view>
<!-- 底部导航-->
<AQTabber/>
</view>
</template>
<script>
import marketApi from '@/api/market.js';
import apiRoute from '@/api/apiRoute.js';
import {
Api_url
} from "@/common/config.js";
import AQTabber from "@/components/AQ/AQTabber.vue"
import fuiIcon from "@/components/firstui/fui-icon/fui-icon.vue"
export default {
components: {
AQTabber,
fuiIcon,
},
data() {
return {
formData:{},
userInfo:{},//
//APi
uploadUrl: `${Api_url}/file/image`,
signedClientListCount:0,//
}
},
onLoad() {
},
onShow() {
this.init();
},
methods: {
//
async init(){
await this.getUserInfo()//
// await this.getSignedClientListCount()//
},
//
async getSignedClientListCount(){
let data = {
page:1,
limit:1,
}
let res = await marketApi.signClient(data);
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.signedClientListCount = res.data.total
},
//
async getUserInfo(){
let data = {}
let res = await apiRoute.getPersonnelInfo(data);
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
res.data.cameus_dept_arr.forEach((v,k)=>{
let d_arr = []
v.dept_arr.forEach((dv,dk)=>{
d_arr.push(dv.dept_name)
})
//
v.dept_name_str = d_arr.join(',')
})
this.userInfo = res.data
},
//
openViewArrivalStatistics(){
uni.navigateTo({
url: '/pages-market/my/arrival_statistics'
})
},
//
openViewDueSoon(){
uni.navigateTo({
url: '/pages-market/my/due_soon'
})
},
//
openViewSchoolingStatistics(){
uni.navigateTo({
url: '/pages-market/my/schooling_statistics'
})
},
//
openViewFeedback(){
uni.navigateTo({
url: '/pages-common/feedback'
})
},
//
openViewMyInfo(){
uni.navigateTo({
url: '/pages-market/my/info'
})
},
//-
openViewSignedClientList(){
uni.navigateTo({
url: '/pages-market/my/signed_client_list'
})
},
//-
openViewMyAttendance(){
uni.navigateTo({
url: '/pages-common/my_attendance'
})
},
//
openViewFirmInfo(){
uni.navigateTo({
url: '/pages-market/my/firm_info'
})
},
//
openViewSetUp(){
uni.navigateTo({
url: '/pages-market/my/set_up'
})
},
//-
openViewMyMessage(){
uni.navigateTo({
url: '/pages-common/my_message'
})
},
//-
openViewReimbursementList(){
uni.navigateTo({
url: '/pages-market/reimbursement/list'
})
},
goCourseSchedule(){
uni.navigateTo({
url: '/pages-coach/coach/schedule/schedule_table'
})
},
my_contract(){
uni.navigateTo({
url: '/pages-common/contract/my_contract'
})
},
//
openMyData(){
uni.navigateTo({
url: '/pages-market/my/my_data'
})
},
//
openDeptData(){
uni.navigateTo({
url: '/pages-market/my/dept_data'
})
},
//
openCampusData(){
uni.navigateTo({
url: '/pages-market/my/campus_data'
})
},
}
}
</script>
<style lang="less" scoped>
.main_box{
background: #292929;
//min-height: 28vh;
min-height: 100%;
}
//
.navbar_section{
border: 1px solid #29D3B4;
display: flex;
justify-content: center;
align-items: center;
background: #29D3B4;
.title{
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding-top: 110rpx;
padding-bottom: 40rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
//
.user_section {
background-color: #29D3B4;
padding-top: 10rpx;
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;
}
}
}
.bottom{
margin-top: 30rpx;
padding: 0rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: flex-start;
.title{
font-size: 28rpx;
}
//线
.division{
width: 2px;
height: 35rpx;
background-color: #fff;
}
.left{
width: 48%;
display: flex;
}
.right{
width: 48%;
display: flex;
.dept{
width: 70%;
}
}
}
}
//
.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{
margin-left: 10rpx;
color: #29D3B4;
}
.reduce{
color: #ef95a0;
}
}
}
}
.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;
.grid_container {
background: #fff;
border-radius: 16rpx;
padding: 40rpx 24rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 40rpx 20rpx;
.grid_item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx 10rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&:active {
background-color: #f5f5f5;
transform: scale(0.95);
}
.icon_wrapper {
width: 80rpx;
height: 80rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.item_text {
font-size: 24rpx;
color: #333333;
text-align: center;
font-weight: 400;
line-height: 1.2;
}
}
}
}
</style>

505
uniapp/pages-market/my/info.vue

@ -0,0 +1,505 @@
<!--销售-个人资料-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section">
<view class="item">
<image
@click="changeAvatar()"
class="pic"
:src="$util.img(formData.head_img)"
></image>
<view class="btn" @click="changeAvatar()">修改头像</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
姓名 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.name" placeholder="请输入姓名" />
</view>
</view>
<view class="item">
<view class="title">
账号 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.username" disabled placeholder="暂无" />
</view>
</view>
<view class="item">
<view class="title">
部门 <text class="required"></text>
</view>
<view class="input">
<!-- <input disabled :placeholder="formData.department_name_str" />-->
<view class="dept disabled">{{formData.department_name_str || '暂无'}}</view>
</view>
</view>
<view class="item">
<view class="title">
等级 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.member_level_name" disabled placeholder="暂无" />
</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
性别 <text class="required">*</text>
</view>
<view class="input">
<input placeholder="请选择性别" v-model="formData.gender_str" @click="picker_show_sex=true"/>
<fui-picker
layer="1"
:linkage="true"
:options="options_sex_arr"
:show="picker_show_sex"
@change="changePickerSex"
@cancel="picker_show_sex=false"
></fui-picker>
</view>
</view>
<view class="item">
<view class="title">
生日 <text class="required">*</text>
</view>
<view class="input">
<input placeholder="请选择生日" @click="picker_show_birthday=true" v-model="formData.birthday"/>
<fui-date-picker
:minDate="minDate"
:maxDate="maxDate"
:show="picker_show_birthday"
type="3"
@change="changePickerBirthday"
@cancel="picker_show_birthday=false"
></fui-date-picker>
</view>
</view>
<view class="item">
<view class="title">
邮箱 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.email" placeholder="请输入邮箱" />
</view>
</view>
<view class="item">
<view class="title">
手机 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.phone" placeholder="请输入手机" />
</view>
</view>
<view class="item">
<view class="title">
微信 <text class="required"></text>
</view>
<view class="input">
<input v-model="formData.wx" placeholder="请输入微信" />
</view>
</view>
</view>
<view class="submet_btn" @click="submit">提交</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import marketApi from '@/api/market.js';
import {
Api_url
} from "@/common/config.js";
import AQTabber from "@/components/AQ/AQTabber"
export default {
components: {
AQTabber,
},
data() {
return {
formData:{
head_img:'',//
name:'',//
username:'',//
address:'',//
gender:'',//|1,2
gender_str:'',
birthday:'',//
email:'',//
phone:'',//
wx:'',//
},
userInfo: {},
//APi
uploadUrl: `${Api_url}/uploadImage`,
//
picker_show_sex: false,
sex_name:'请选择',
options_sex_arr: [
{
value: 1,
text: '男'
},
{
value: 2,
text: '女'
},
],
//
minDate: '',
maxDate: '',
picker_show_birthday: false,
upload_type: 1,
uploadHeadimg: '',
editHeadimg: '',
}
},
onLoad() {
},
onShow() {
this.init()
},
methods: {
async init(){
// this.getBirthday()
this.setDateYear()
await this.getUserInfo()
},
//
setDateYear() {
let currentYear = new Date().getFullYear();
this.minDate = String(currentYear - 100);
this.maxDate = String(currentYear + 1);
},
//
async getUserInfo(){
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
let gender_str = ''
if(res.data.gender == 1){
gender_str = '男'
}else if(res.data.gender == 2){
gender_str = '女'
}
//
this.formData = {
head_img: res.data.head_img,//
name: res.data.name,//
username: res.data.phone,//
address: res.data.address,//
gender: res.data.gender,//|1,2
gender_str:gender_str,
birthday: res.data.birthday,//
email: res.data.email || '',//
phone: res.data.phone,//
wx: res.data.wx || '',//
member_level_name: res.data.member_level_name || '',//
department_name_str:res.data.department_name_str || '暂无',//
}
},
//
changeAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
//
this.uploadFilePromise(tempFilePath)
}
})
},
uploadFilePromise(url) {
let token = uni.getStorageSync('token') || ''
let a = uni.uploadFile({
url: this.uploadUrl, //
filePath: url,
name: 'file',
header: {
'token': `${token}`, //token
},
success: (e) => {
let res = JSON.parse(e.data.replace(/\ufeff/g, "") || "{}")
console.log('上传成功2', res)
if (res.code == 1) {
this.upload_type = 2
this.formData.head_img = res.data.url
// this.editHeadimg = res.data.path
// this.uploadHeadimg = res.data.url
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
});
},
//
changePickerSex(e) {
console.log('监听选择', e)
this.formData.gender = e.value
this.formData.gender_str = e.text
this.picker_show_sex = false
},
//
//+30
getBirthday() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let year_30 = year - 30;
let month_30 = month;
let day_30 = day;
if (month_30 == 2 && day_30 > 28) {
month_30 = 3;
day_30 = 1;
}
if (month_30 == 4 && day_30 > 30) {
month_30 = 5;
day_30 = 1;
}
if (month_30 == 6 && day_30 > 30) {
month_30 = 7;
day_30 = 1;
}
if (month_30 == 9 && day_30 > 30) {
month_30 = 10;
day_30 = 1;
}
if (month_30 == 11 && day_30 > 30) {
month_30 = 12;
day_30 = 1;
}
if (month_30 > 12) {
month_30 = month_30 - 12;
year_30 = year_30 + 1;
}
let minDate = year_30 + "-" + month_30 + "-" + day_30
let maxDate = year + "-" + month + "-" + day
this.minDate = minDate
this.maxDate = maxDate
},
//
changePickerBirthday(e) {
console.log('监听生日选择', e)
this.formData.birthday = e.result
this.picker_show_birthday = false
},
//
async submit() {
let data = {...this.formData}
if(!data.head_img){
uni.showToast({
title: '请上传头像',
icon: 'none'
})
return
}
if(!data.name){
uni.showToast({
title: '请填写',
icon: 'none'
})
return
}
if(!data.gender){
uni.showToast({
title: '请选择性别',
icon: 'none'
})
return
}
if(!data.birthday){
uni.showToast({
title: '请选择生日',
icon: 'none'
})
return
}
if(!data.email){
uni.showToast({
title: '请填写邮箱',
icon: 'none'
})
return
}
if(!data.phone){
uni.showToast({
title: '请填写手机',
icon: 'none'
})
return
}
let res = await apiRoute.editPersonnelInfo(data)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
this.getUserInfo()
}, 1000)
},
}
}
</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 0rpx;
padding-top: 32rpx;
padding-bottom: 150rpx;
font-size: 28rpx;
color: #fff;
display: flex;
flex-direction: column;
gap: 20rpx;
.section{
background-color: #434544;
.item{
padding: 20rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
.pic{
width: 82rpx;
height: 82rpx;
border-radius: 50%;
}
.btn{}
.title{
min-width: 100rpx;
display: flex;
align-items: center;
font-size: 26rpx;
color: #D7D7D7;
.required{
margin-left: 10rpx;
color: red;
}
}
.input{
display: flex;
justify-content: flex-end;
input{
text-align: right;
}
.dept{
width: 50%;
}
.disabled{
color: #808080;
//
cursor: not-allowed;
}
}
}
}
.submet_btn{
margin: 0 auto;
margin-top: 40rpx;
border: 2px solid #25a18b;
color: #25a18b;
width: 80%;
height: 80rpx;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

292
uniapp/pages-market/my/my_data.vue

@ -0,0 +1,292 @@
<!--我的数据-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<view class="navbar_section">
<view class="navbar_content">
<view class="back_btn" @click="goBack">
<fui-icon name="arrowleft" size="32" color="#fff"></fui-icon>
</view>
<view class="title">我的数据</view>
<view class="placeholder"></view>
</view>
</view>
<view class="content_section">
<!-- 数据概览卡片 -->
<view class="overview_cards">
<view class="card_item">
<view class="card_icon">
<fui-icon name="star" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">总客户数</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="check" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">已签客户</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="wallet" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">销售业绩</view>
<view class="card_value">--</view>
</view>
</view>
<view class="card_item">
<view class="card_icon">
<fui-icon name="calendar" size="40" color="#29D3B4"></fui-icon>
</view>
<view class="card_content">
<view class="card_title">本月任务</view>
<view class="card_value">--</view>
</view>
</view>
</view>
<!-- 功能按钮区域 -->
<view class="function_section">
<view class="section_title">数据统计</view>
<view class="function_grid">
<view class="function_item">
<view class="function_icon">
<fui-icon name="barchart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">客户统计</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="piechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">业绩统计</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="linechart" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">趋势分析</view>
</view>
<view class="function_item">
<view class="function_icon">
<fui-icon name="list" size="32" color="#29D3B4"></fui-icon>
</view>
<view class="function_text">详细报表</view>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="tips_section">
<view class="tips_title">功能说明</view>
<view class="tips_content">
这里将显示您个人的各项数据统计包括客户数量销售业绩任务完成情况等
具体功能待后续开发实现
</view>
</view>
</view>
</view>
</template>
<script>
import fuiIcon from "@/components/firstui/fui-icon/fui-icon.vue"
export default {
components: {
fuiIcon,
},
data() {
return {
}
},
onLoad() {
},
methods: {
//
goBack() {
uni.navigateBack()
},
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f5f5f5;
min-height: 100vh;
}
//
.navbar_section {
background: #29D3B4;
padding-top: 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 90rpx;
// #endif
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx 40rpx 24rpx;
.back_btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
.placeholder {
width: 60rpx;
}
}
}
//
.content_section {
padding: 40rpx 24rpx;
//
.overview_cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
margin-bottom: 40rpx;
.card_item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.card_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.card_content {
flex: 1;
.card_title {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.card_value {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
}
}
}
//
.function_section {
margin-bottom: 40rpx;
.section_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 24rpx;
}
.function_grid {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.function_item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&:active {
background-color: #f5f5f5;
transform: scale(0.95);
}
.function_icon {
width: 64rpx;
height: 64rpx;
background: rgba(41, 211, 180, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.function_text {
font-size: 24rpx;
color: #333;
}
}
}
}
//
.tips_section {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.tips_title {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 16rpx;
}
.tips_content {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
}
}
</style>

105
uniapp/pages-market/my/set_up.vue

@ -0,0 +1,105 @@
<!--设置页-->
<template>
<view class="assemble">
<view style="height: 30rpx;"></view>
<view class="option" @click="update_pass()">修改密码</view>
<view class="option" @click="privacy_agreement(1)">用户协议</view>
<view class="option" @click="privacy_agreement(2)">隐私策略</view>
<view class="option" @click="clearCache()">清空缓存</view>
<view style="width:90%;margin: 60rpx auto;">
<fui-button background="#29d3b4" @click="loginOut()">退出账号</fui-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
//退
loginOut(){
this.$util.loginOut()
},
privacy_agreement(type){
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
})
},
update_pass(){
uni.navigateTo({
url: '/pages-market/my/update_pass'
})
},
//
clearCache() {
uni.showModal({
title: '清空缓存',
content: '确定要清空所有字典缓存吗?',
success: (res) => {
if (res.confirm) {
this.performClearCache();
}
}
});
},
//
performClearCache() {
try {
//
const storageInfo = uni.getStorageInfoSync();
const keys = storageInfo.keys;
// dict_
let clearCount = 0;
keys.forEach(key => {
if (key.startsWith('dict_')) {
uni.removeStorageSync(key);
clearCount++;
}
});
//
uni.showToast({
title: `已清空${clearCount}个字典缓存`,
icon: 'success',
duration: 2000
});
console.log(`清空缓存完成,共清理${clearCount}个dict_开头的缓存项`);
} catch (error) {
console.error('清空缓存失败:', error);
uni.showToast({
title: '清空缓存失败',
icon: 'none',
duration: 2000
});
}
}
}
}
</script>
<style lang="less" scoped>
.assemble{
width: 100%;
height: 100vh;
background: #333333;
}
.option{
margin-bottom: 20rpx;
background: #404045;
width: 100%;
font-size: 28rpx;
color: #fff;
line-height: 56rpx;
padding: 20rpx 0 20rpx 100rpx;
}
</style>

275
uniapp/pages-market/my/signed_client_list.vue

@ -0,0 +1,275 @@
<!--已签客户-列表-->
<template>
<view class="main_box">
<!--自定义导航栏-->
<!-- <view class="navbar_section">-->
<!-- <view class="title">班级详情</view>-->
<!-- </view>-->
<view class="main_section">
<!-- 班级成员列表-->
<scroll-view
class="section_4"
scroll-y="true"
:lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData"
style="height: 83vh;"
>
<view class="ul">
<view class="li"
v-for="(v,k) in tableList"
:key="k"
@click="openViewStudentInfo(v)">
<view class="left">
<view class="box_1">
<image class="pic"
v-if="v.header" :src="$util.img(v.header)"></image>
<image v-else class="pic" :src="$util.img('/uniapp_src/static/images/index/myk.png')"></image>
<!-- <view class="tag_box">-->
<!-- 即将到期-->
<!-- </view>-->
</view>
<view class="box_2">
<view class="name">{{v.name}}</view>
<view class="date">课程截止时间{{v.end_time}}</view>
</view>
</view>
<view class="right">
<view class="item">
<view>{{v.have_study_time}}</view>
<view>已上课时</view>
</view>
<view class="item">
<view>{{v.end_study_time ? v.end_study_time:0}}</view>
<view>剩余课时</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 底部导航-->
<!-- <AQTabber/>-->
</view>
</template>
<script>
import marketApi from '@/api/market.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,//
name: '',//
},
tableList:[],//
}
},
onLoad() {
},
onShow() {
this.init();
},
methods: {
//
async init(){
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.signClient(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++
},
//
openViewStudentInfo(item){
let students_id= item.id
uni.navigateTo({
url: `/pages-coach/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: 95vh;
background: #292929 100%;
padding: 0 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
//
.section_4{
.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;
}
}
}
}
}
}
}
</style>

229
uniapp/pages-market/my/update_pass.vue

@ -0,0 +1,229 @@
<!--修改密码-->
<template>
<view>
<view class="title">
<view :class="{'green-text': tset_style === 1}">1.验证旧密码</view>
<view :class="{'green-text': tset_style === 2}">2.设置新密码</view>
</view>
<view :style="{'background-color':'#fff','width':'100%','height':'100vh' }">
<view style="width: 95%;height: 30rpx;"></view>
<view v-if="tset_style == 1">
<view class="describe">
为保障您的账号安全修改密码前请填写原密码
</view>
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请输入原登录密码" v-model="old_password"
backgroundColor="#f2f2f2"></fui-input>
</view>
</view>
<view v-if="tset_style == 2">
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请设置6-20位新的登录密码" v-model="formData.new_password" @input="input"
backgroundColor="#f2f2f2"></fui-input>
</view>
<view style="width: 95%;margin: auto;">
<fui-input borderTop :padding="['20rpx','32rpx']" v-model="formData.new_password_2" placeholder="请再次输入新的登录密码" @input="input"
backgroundColor="#f2f2f2">
</fui-input>
</view>
</view>
<view style="width: 95%;margin:60rpx auto;">
<view class="btn_box">
<fui-button
background="#465cff"
radius="5rpx"
@click="nextStep(1)"
v-if="tset_style != 1">上一步
</fui-button>
<fui-button background="#00be8c" radius="5rpx" @click="nextStep(2)" v-if="tset_style == 1">下一步</fui-button>
<fui-button background="#00be8c" radius="5rpx" @click="submit" v-if="tset_style == 2">提交</fui-button>
</view>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#fff" radius="5rpx" @click="forgot" color="#999999" v-if="tset_style == 1">忘记原密码</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
code: '',
user: '',
tset_style: 1,//tab|1=,2=
old_password:'',//
formData:{
phone:'',//
new_password:'',//
new_password_2:'',//
key_value:'',//key_value
},
}
},
onLoad() {},
onShow() {
this.init()//
},
methods: {
//
async init(){
await this.getUserInfo()
},
//
async getUserInfo() {
let res = await apiRoute.getPersonnelInfo({})
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData.phone = res.data.phone//
},
///
async nextStep(tset_style){
//tset_style|1=,2=
if(tset_style == 2){
if(!this.old_password){
uni.showToast({
title: '请输入原登录密码',
icon: 'none'
})
return
}
//
let params = {
old_password: this.old_password
}
//
let res = await apiRoute.common_personnelCheckOldPwd(params)
if(!res.code){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData.key_value = res.data.key_value
}
this.tset_style = Number(tset_style)
},
//
forgot() {
uni.navigateTo({
url: '/pages/student/login/forgot'
})
},
//
async submit() {
//
if (!this.formData.new_password) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
})
return
}
if (this.formData.new_password.length < 6 || this.formData.new_password.length > 20) {
uni.showToast({
title: '新密码长度为6-20位',
icon: 'none'
})
return
}
if (!this.formData.new_password) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
})
return
}
//
if (this.formData.new_password != this.formData.new_password_2) {
uni.showToast({
title: '两次密码不一致',
icon: 'none'
})
return
}
let res = await apiRoute.common_personnelEdidPassword(this.formData)
if(!res.code){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
//1s
setTimeout(() => {
//-
//
uni.redirectTo({
url: `/pages-market/my/index`
})
}, 1000)
},
}
}
</script>
<style lang="less" scoped>
page {
font-weight: normal;
}
.fui-section__title {
margin-left: 32rpx;
}
.fui-left__icon {
padding-right: 24rpx;
}
.title {
display: flex;
justify-content: space-around;
align-items: center;
height: 100rpx;
width: 100%;
background-color: #fff;
font-size: 26rpx;
border: 4rpx #f5f5f5 solid;
}
.green-text{
color: #36d6b9;
}
.describe{
color: #999999;
padding-left: 30rpx;
}
.btn_box{
display: flex;
flex-direction: column;
gap: 40rpx;
}
</style>

272
uniapp/pages-market/reimbursement/add.vue

@ -0,0 +1,272 @@
<template>
<view class="reim-add-page">
<view class="header-bar">
<view class="title">{{ pageTitle }}</view>
</view>
<view class="form-box">
<!-- 金额 -->
<view class="form-row">
<view class="label">报销金额</view>
<input class="input-amount" type="number" v-model="form.amount" placeholder="0.00"
:disabled="disabled" />
</view>
<!-- 描述 -->
<view class="form-row-top">
<view class="label">报销描述</view>
<textarea class="textarea" v-model="form.description" placeholder="请输入报销事由" :disabled="disabled" />
</view>
<!-- 附件 -->
<view class="form-row">
<view class="label">发票/收据</view>
<view class="file-upload-wrapper">
<view v-if="form.receipt_url" class="preview-box" @click="previewImage">
<image v-if="isImage(form.receipt_url)" :src="form.receipt_url" class="preview-img"
mode="aspectFit" />
<view v-else class="file-link">{{ getFileName(form.receipt_url) }}</view>
</view>
<button class="upload-btn" @click="chooseFile" :disabled="disabled">
{{ form.receipt_url ? '重新选择' : '选择附件' }}
</button>
</view>
</view>
</view>
<view class="save-btn-box" v-if="!disabled">
<fui-button background="#434544" color="#24BA9F" borderColor="#24BA9F" @click="submit">提交</fui-button>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import {
Api_url
} from "@/common/config.js";
export default {
data() {
return {
uploadUrl: `${Api_url}/uploadImage`,
pageTitle: '新增报销',
disabled: false,
form: {
id: null,
amount: '',
description: '',
receipt_url: '',
}
}
},
onLoad(options) {
if (options.id) {
this.form.id = options.id;
this.fetchDetail(options.id);
}
},
methods: {
async fetchDetail(id) {
uni.showLoading({
title: '加载中...'
});
let res = await apiRoute.reimbursement_info({id:id})
let mockData = res.data
if (mockData.status !== 'pending') {
this.disabled = true;
this.pageTitle = '查看报销';
} else {
this.pageTitle = '编辑报销';
}
this.form.amount = mockData.amount;
this.form.description = mockData.description;
this.form.receipt_url = mockData.receipt_url;
uni.hideLoading();
},
chooseFile() {
uni.chooseImage({
count: 1,
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
//
this.uploadFilePromise(tempFilePath)
}
})
},
uploadFilePromise(url) {
let token = uni.getStorageSync('token') || ''
let a = uni.uploadFile({
url: this.uploadUrl, //
filePath: url,
name: 'file',
header: {
'token': `${token}`, //token
},
success: (e) => {
let res = JSON.parse(e.data.replace(/\ufeff/g, "") || "{}")
if (res.code == 1) {
this.form.receipt_url = res.data.url
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
});
},
isImage(url) {
if (!url) return false;
return /\.(jpg|jpeg|png|gif|bmp)$/i.test(url);
},
getFileName(path) {
if (!path) return '';
return path.split('/').pop();
},
previewImage() {
if (this.form.receipt_url && this.isImage(this.form.receipt_url)) {
uni.previewImage({
urls: [this.form.receipt_url]
});
}
},
submit() {
if (!this.form.amount || !this.form.description) {
uni.showToast({
title: '请填写完整',
icon: 'none'
});
return;
}
let res = apiRoute.reimbursement_add(this.form)
if (res['code'] == 1) {
uni.showToast({
title: '提交成功',
icon: 'success'
});
}
setTimeout(() => {
uni.navigateBack();
}, 1000);
}
}
}
</script>
<style lang="less" scoped>
.reim-add-page {
min-height: 100vh;
background: #292929;
padding-bottom: 120rpx;
}
.header-bar {
padding: 32rpx;
.title {
font-size: 44rpx;
color: #fff;
font-weight: bold;
}
}
.form-box {
margin: 0 32rpx;
background: #434544;
border-radius: 18rpx;
padding: 0 24rpx;
}
.form-row,
.form-row-top {
display: flex;
padding: 32rpx 0;
border-bottom: 1rpx solid #3a3a3a;
&:last-of-type {
border-bottom: none;
}
}
.form-row {
align-items: center;
}
.form-row-top {
align-items: flex-start;
}
.label {
color: #aaa;
font-size: 28rpx;
width: 180rpx;
flex-shrink: 0;
}
.input-amount {
flex: 1;
color: #fff;
font-size: 28rpx;
text-align: right;
}
.textarea {
flex: 1;
height: 180rpx;
color: #fff;
font-size: 28rpx;
background-color: transparent;
padding: 0;
width: 100%;
}
.file-upload-wrapper {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 20rpx;
}
.upload-btn {
background: #24BA9F;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 0 32rpx;
line-height: 64rpx;
height: 64rpx;
font-size: 26rpx;
margin: 0;
}
.preview-box {
.preview-img {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
background: #222;
border: 1rpx solid #333;
}
.file-link {
color: #24BA9F;
font-size: 26rpx;
word-break: break-all;
}
}
.save-btn-box {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: #292929;
padding: 20rpx 40rpx 40rpx 40rpx;
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.12);
}
</style>

136
uniapp/pages-market/reimbursement/detail.vue

@ -0,0 +1,136 @@
<template>
<view class="reim-detail-page">
<view class="header-bar">
<view class="title">报销详情</view>
</view>
<view class="detail-box">
<view class="row">
<view class="label">报销金额</view>
<view class="value">{{ detail.amount }}</view>
</view>
<view class="row">
<view class="label">报销描述</view>
<view class="value">{{ detail.description }}</view>
</view>
<view class="row">
<view class="label">发票/收据</view>
<view class="value">
<image v-if="detail.receipt_url && isImage(detail.receipt_url)" :src="detail.receipt_url" class="receipt-img" mode="aspectFit" />
<view v-else-if="detail.receipt_url" class="file-link">{{ detail.receipt_url }}</view>
<text v-else>无附件</text>
</view>
</view>
<view class="row">
<view class="label">状态</view>
<view :class="['value', 'status-' + detail.status]">{{ statusMap[detail.status] || detail.status }}</view>
</view>
<view class="row">
<view class="label">创建时间</view>
<view class="value">{{ detail.created_at }}</view>
</view>
<view class="row">
<view class="label">修改时间</view>
<view class="value">{{ detail.updated_at }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
detail: {},
statusMap: {
pending: '待审批',
approved: '已批准',
rejected: '已拒绝',
},
}
},
onLoad(options) {
if (options.id) {
this.fetchDetail(options.id);
} else {
uni.showToast({ title: '缺少报销ID', icon: 'none' });
uni.navigateBack();
}
},
methods: {
fetchDetail(id) {
//
uni.showLoading({ title: '加载中...' });
setTimeout(() => {
const mockData = {
id: id,
amount: 300.00,
description: '办公用品采购(已批准)',
receipt_url: 'https://cdn.uviewui.com/uview/swiper/1.jpg',
status: 'approved',
created_at: '2024-06-02 09:30',
updated_at: '2024-06-03 12:00',
};
this.detail = mockData;
uni.hideLoading();
}, 500);
},
isImage(url) {
if (!url) return false;
return /\.(jpg|jpeg|png|gif|bmp)$/i.test(url)
}
}
}
</script>
<style lang="less" scoped>
.reim-detail-page {
min-height: 100vh;
background: #292929;
}
.header-bar {
padding: 32rpx 32rpx 0 32rpx;
.title {
font-size: 36rpx;
color: #24BA9F;
font-weight: bold;
}
}
.detail-box {
margin: 32rpx;
background: #434544;
border-radius: 18rpx;
padding: 32rpx 24rpx 24rpx 24rpx;
}
.row {
display: flex;
align-items: flex-start;
margin-bottom: 32rpx;
.label {
color: #aaa;
font-size: 28rpx;
width: 180rpx;
flex-shrink: 0;
margin-top: 10rpx;
}
.value {
color: #fff;
font-size: 28rpx;
word-break: break-all;
}
.status-pending { color: #f0ad4e; }
.status-approved { color: #24BA9F; }
.status-rejected { color: #e74c3c; }
.receipt-img {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
background: #222;
border: 1rpx solid #333;
}
.file-link {
color: #24BA9F;
font-size: 26rpx;
word-break: break-all;
}
}
</style>

144
uniapp/pages-market/reimbursement/list.vue

@ -0,0 +1,144 @@
<template>
<view class="reim-list-page">
<view class="header-bar">
<view class="title">报销列表</view>
<fui-button
background="transparent"
color="#24BA9F"
borderColor="#24BA9F"
width="180rpx"
height="64rpx"
radius="32rpx"
@click="goAdd">
新增报销
</fui-button>
</view>
<view v-if="list.length === 0" class="empty-tip">暂无报销记录</view>
<view v-for="item in list" :key="item.id" class="reim-card" @click="goDetail(item)">
<view class="row">
<view class="label">金额</view>
<view class="value">{{ item.amount }}</view>
</view>
<view class="row">
<view class="label">描述</view>
<view class="value">{{ item.description }}</view>
</view>
<view class="row">
<view class="label">发票/收据</view>
<view class="value">
<image v-if="item.receipt_url" :src="item.receipt_url" class="receipt-img" mode="aspectFit" />
<text v-else>无附件</text>
</view>
</view>
<view class="row">
<view class="label">状态</view>
<view :class="['value', 'status-' + item.status]">{{ statusMap[item.status] || item.status }}</view>
</view>
<view class="row">
<view class="label">创建时间</view>
<view class="value">{{ item.created_at }}</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
list: [],
statusMap: {
pending: '待审批',
approved: '已批准',
rejected: '已拒绝',
},
}
},
async onShow() {
await this.reimbursementList();
},
methods: {
async reimbursementList(){
let res = await apiRoute.reimbursement_list({})
this.list = res.data;
},
goAdd() {
uni.navigateTo({
url: '/pages-market/reimbursement/add'
});
},
goDetail(item) {
//
if (item.status === 'pending') {
this.$navigateToPage(`/pages-market/reimbursement/add`, {
id: item.id
});
} else {
this.$navigateToPage(`/pages-market/reimbursement/detail`, {
id: item.id
});
}
}
}
}
</script>
<style lang="less" scoped>
.reim-list-page {
min-height: 100vh;
background: #292929;
padding-bottom: 120rpx;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 32rpx 0 32rpx;
.title {
font-size: 36rpx;
color: #fff;
font-weight: bold;
}
}
.empty-tip {
color: #888;
text-align: center;
margin: 80rpx 0;
}
.reim-card {
background: #434544;
border-radius: 18rpx;
margin: 32rpx;
margin-bottom: 0;
padding: 32rpx 24rpx 24rpx 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
.row {
display: flex;
align-items: center;
margin-bottom: 18rpx;
.label {
color: #aaa;
font-size: 26rpx;
width: 140rpx;
flex-shrink: 0;
}
.value {
color: #fff;
font-size: 28rpx;
word-break: break-all;
}
.status-pending { color: #f0ad4e; }
.status-approved { color: #24BA9F; }
.status-rejected { color: #e74c3c; }
.receipt-img {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
background: #222;
border: 1rpx solid #333;
}
}
}
</style>

590
uniapp/pages-optimized.json

@ -0,0 +1,590 @@
{
"pages": [
{
"path": "pages/student/login/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/home/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/home/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
}
],
"subPackages": [
{
"root": "pages-student",
"name": "student",
"pages": [
{
"path": "profile/index",
"style": {
"navigationBarTitleText": "个人信息管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "physical-test/index",
"style": {
"navigationBarTitleText": "体测数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "schedule/index",
"style": {
"navigationBarTitleText": "课程安排",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "course-booking/index",
"style": {
"navigationBarTitleText": "课程预约",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "orders/index",
"style": {
"navigationBarTitleText": "订单管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "contracts/index",
"style": {
"navigationBarTitleText": "合同管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "knowledge/index",
"style": {
"navigationBarTitleText": "知识库",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "messages/index",
"style": {
"navigationBarTitleText": "消息管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "settings/index",
"style": {
"navigationBarTitleText": "系统设置",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "child/add",
"style": {
"navigationBarTitleText": "添加孩子",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/set_up",
"style": {
"navigationBarTitleText": "设置",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29D3B4",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/update_pass",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "my/personal_data",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#333333",
"navigationBarTextStyle": "white"
}
}
]
},
{
"root": "pages-market",
"name": "market",
"pages": [
{
"path": "clue/index",
"style": {
"navigationBarTitleText": "线索",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "clue/add_clues",
"style": {
"navigationBarTitleText": "添加客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "clue/edit_clues",
"style": {
"navigationBarTitleText": "编辑客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "clue/edit_clues_log",
"style": {
"navigationBarTitleText": "修改记录",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "clue/clue_info",
"style": {
"navigationBarTitleText": "客户详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "black"
}
},
{
"path": "clue/class_arrangement",
"style": {
"navigationBarTitleText": "课程安排",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#232323",
"navigationBarTextStyle": "white"
}
},
{
"path": "clue/class_arrangement_detail",
"style": {
"navigationBarTitleText": "课程安排详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#232323",
"navigationBarTextStyle": "white"
}
},
{
"path": "clue/clue_table",
"style": {
"navigationBarTitleText": "数据统计",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "index/index",
"style": {
"navigationBarTitleText": "销售数据"
}
},
{
"path": "my/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "my/signed_client_list",
"style": {
"navigationBarTitleText": "已签客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/info",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/set_up",
"style": {
"navigationBarTitleText": "设置",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/update_pass",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "my/my_data",
"style": {
"navigationBarTitleText": "我的数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/dept_data",
"style": {
"navigationBarTitleText": "部门数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/campus_data",
"style": {
"navigationBarTitleText": "校区数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "reimbursement/list",
"style": {
"navigationBarTitleText": "报销列表",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "reimbursement/add",
"style": {
"navigationBarTitleText": "新增报销",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "reimbursement/detail",
"style": {
"navigationBarTitleText": "报销详情",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "data/statistics",
"style": {
"navigationBarTitleText": "市场数据统计",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
}
]
},
{
"root": "pages-coach",
"name": "coach",
"pages": [
{
"path": "student/student_list",
"style": {
"navigationBarTitleText": "我的学员",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/teaching_management",
"style": {
"navigationBarTitleText": "教研管理列表",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#171717",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/gotake_exam",
"style": {
"navigationBarTitleText": "考试",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#171717",
"navigationBarTextStyle": "white"
}
},
{
"path": "my/exam_results",
"style": {
"navigationBarTitleText": "考试结果",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "my/salary",
"style": {
"navigationBarTitleText": "我的工资",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "schedule/schedule_table",
"style": {
"navigationBarTitleText": "课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "schedule/add_schedule",
"style": {
"navigationBarTitleText": "添加课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "schedule/adjust_course",
"style": {
"navigationBarTitleText": "调整课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "schedule/sign_in",
"style": {
"navigationBarTitleText": "课程点名",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
}
]
},
{
"root": "pages-common",
"name": "common",
"pages": [
{
"path": "privacy_agreement",
"style": {
"navigationBarTitleText": "隐私协议",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "my_message",
"style": {
"navigationBarTitleText": "我的消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "im_chat_info",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "sys_msg_list",
"style": {
"navigationBarTitleText": "系统消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "article_info",
"style": {
"navigationBarTitleText": "文章详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "feedback",
"style": {
"navigationBarTitleText": "意见反馈",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "my_attendance",
"style": {
"navigationBarTitleText": "我的考勤",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "personnel/add_personnel",
"style": {
"navigationBarTitleText": "新员工信息填写",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "contract/my_contract",
"style": {
"navigationBarTitleText": "我的合同",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
}
},
{
"path": "contract/contract_detail",
"style": {
"navigationBarTitleText": "合同详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "contract/contract_sign",
"style": {
"navigationBarTitleText": "电子签名",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white",
"backgroundColor": "#181A20"
}
},
{
"path": "profile/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
},
{
"path": "profile/personal_info",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
}
]
}
],
"preloadRule": {
"pages/student/home/index": {
"network": "all",
"packages": ["student"]
},
"pages/common/home/index": {
"network": "all",
"packages": ["market", "coach", "common"]
}
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "智慧教务",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/student/home/index",
"iconPath": "static/icon-img/home.png",
"selectedIconPath": "static/icon-img/home-active.png",
"text": "首页"
},
{
"pagePath": "pages-student/profile/index",
"iconPath": "static/icon-img/profile.png",
"selectedIconPath": "static/icon-img/profile-active.png",
"text": "我的"
}
]
},
"condition": {
"current": 0,
"list": []
}
}

501
uniapp/pages-student/child/add.vue

@ -0,0 +1,501 @@
<!--添加孩子页面-->
<template>
<view class="add_child_container">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_content">
<view class="back_button" @click="goBack">
<image src="/static/icon-img/back.png" class="back_icon"></image>
</view>
<view class="navbar_title">添加孩子</view>
<view class="navbar_placeholder"></view>
</view>
</view>
<!-- 表单区域 -->
<view class="form_section">
<view class="form_card">
<view class="form_title">孩子基本信息</view>
<!-- 头像选择 -->
<view class="form_item">
<view class="item_label">头像</view>
<view class="avatar_selector" @click="selectAvatar">
<image
:src="formData.headimg || '/static/default-avatar.png'"
class="avatar_preview"
mode="aspectFill"
></image>
<view class="avatar_text">点击选择头像</view>
</view>
</view>
<!-- 姓名输入 -->
<view class="form_item">
<view class="item_label required">姓名</view>
<input
v-model="formData.name"
placeholder="请输入孩子姓名"
class="form_input"
maxlength="20"
/>
</view>
<!-- 性别选择 -->
<view class="form_item">
<view class="item_label required">性别</view>
<view class="gender_selector">
<view
:class="['gender_option', formData.gender === '1' ? 'active' : '']"
@click="selectGender('1')"
>
<image src="/static/icon-img/male.png" class="gender_icon"></image>
<text>男孩</text>
</view>
<view
:class="['gender_option', formData.gender === '2' ? 'active' : '']"
@click="selectGender('2')"
>
<image src="/static/icon-img/female.png" class="gender_icon"></image>
<text>女孩</text>
</view>
</view>
</view>
<!-- 出生日期 -->
<view class="form_item">
<view class="item_label required">出生日期</view>
<picker
mode="date"
:value="formData.birthday"
@change="onBirthdayChange"
class="date_picker"
>
<view class="picker_display">
{{ formData.birthday || '请选择出生日期' }}
</view>
</picker>
</view>
<!-- 紧急联系人 -->
<view class="form_item">
<view class="item_label">紧急联系人</view>
<input
v-model="formData.emergency_contact"
placeholder="请输入紧急联系人姓名"
class="form_input"
maxlength="20"
/>
</view>
<!-- 联系电话 -->
<view class="form_item">
<view class="item_label">联系电话</view>
<input
v-model="formData.contact_phone"
placeholder="请输入联系电话"
class="form_input"
type="number"
maxlength="11"
/>
</view>
<!-- 备注信息 -->
<view class="form_item">
<view class="item_label">备注</view>
<textarea
v-model="formData.note"
placeholder="请输入备注信息(选填)"
class="form_textarea"
maxlength="500"
></textarea>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit_section">
<button
class="submit_button"
@click="submitForm"
:disabled="submitting"
>
{{ submitting ? '提交中...' : '添加孩子' }}
</button>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/member.js'
export default {
data() {
return {
formData: {
name: '',
gender: '1', // '1': '2':
birthday: '',
headimg: '',
emergency_contact: '',
contact_phone: '',
note: ''
},
submitting: false,
uploadingAvatar: false
}
},
methods: {
goBack() {
uni.navigateBack()
},
selectAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
this.formData.headimg = tempFilePath
//
this.uploadAvatar(tempFilePath)
},
fail: (err) => {
console.error('选择图片失败:', err)
}
})
},
async uploadAvatar(filePath) {
this.uploadingAvatar = true
uni.showLoading({
title: '上传中...'
})
try {
// APIstudent_id
const response = await apiRoute.uploadAvatarForAdd(filePath)
console.log('头像上传响应:', response)
if (response.code === 1) {
//
this.formData.headimg = response.data.url
uni.showToast({
title: '头像上传成功',
icon: 'success'
})
} else {
throw new Error(response.msg || '上传失败')
}
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: error.message || '头像上传失败',
icon: 'none'
})
//
this.formData.headimg = filePath
} finally {
uni.hideLoading()
this.uploadingAvatar = false
}
},
selectGender(gender) {
this.formData.gender = gender
},
onBirthdayChange(e) {
this.formData.birthday = e.detail.value
},
validateForm() {
if (!this.formData.name.trim()) {
uni.showToast({
title: '请输入孩子姓名',
icon: 'none'
})
return false
}
if (!this.formData.gender) {
uni.showToast({
title: '请选择性别',
icon: 'none'
})
return false
}
if (!this.formData.birthday) {
uni.showToast({
title: '请选择出生日期',
icon: 'none'
})
return false
}
//
if (this.formData.contact_phone && !/^1[3-9]\d{9}$/.test(this.formData.contact_phone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return false
}
return true
},
async submitForm() {
if (!this.validateForm()) {
return
}
this.submitting = true
try {
console.log('提交表单数据:', this.formData)
// API
const response = await apiRoute.addChild(this.formData)
console.log('添加孩子API响应:', response)
if (response.code === 1) {
uni.showToast({
title: '添加成功',
icon: 'success'
})
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: response.msg || '添加失败',
icon: 'none'
})
}
} catch (error) {
console.error('添加孩子失败:', error)
uni.showToast({
title: error.message || '添加失败',
icon: 'none'
})
} finally {
this.submitting = false
}
}
}
}
</script>
<style lang="less" scoped>
.add_child_container {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
background: linear-gradient(135deg, #29D3B4 0%, #1BA297 100%);
padding: 40rpx 32rpx 32rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
.back_button {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.back_icon {
width: 24rpx;
height: 24rpx;
}
}
.navbar_title {
color: #fff;
font-size: 36rpx;
font-weight: 600;
}
.navbar_placeholder {
width: 40rpx;
}
}
}
//
.form_section {
padding: 32rpx 20rpx;
.form_card {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
.form_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 32rpx;
text-align: center;
}
.form_item {
margin-bottom: 32rpx;
.item_label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
&.required::after {
content: '*';
color: #ff4757;
margin-left: 4rpx;
}
}
.form_input {
width: 100%;
padding: 24rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 1px solid #e9ecef;
font-size: 28rpx;
color: #333;
&:focus {
border-color: #29d3b4;
}
}
.form_textarea {
width: 100%;
min-height: 120rpx;
padding: 24rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 1px solid #e9ecef;
font-size: 28rpx;
color: #333;
resize: none;
}
//
.avatar_selector {
display: flex;
align-items: center;
gap: 24rpx;
padding: 24rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 1px solid #e9ecef;
.avatar_preview {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 2px solid #29d3b4;
}
.avatar_text {
font-size: 28rpx;
color: #666;
}
}
//
.gender_selector {
display: flex;
gap: 32rpx;
.gender_option {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
padding: 32rpx 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 2px solid #e9ecef;
&.active {
border-color: #29d3b4;
background: rgba(41, 211, 180, 0.1);
text {
color: #29d3b4;
font-weight: 600;
}
}
.gender_icon {
width: 48rpx;
height: 48rpx;
}
text {
font-size: 26rpx;
color: #666;
}
}
}
//
.date_picker {
.picker_display {
width: 100%;
padding: 24rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 1px solid #e9ecef;
font-size: 28rpx;
color: #333;
}
}
}
}
}
//
.submit_section {
padding: 32rpx 20rpx 60rpx;
.submit_button {
width: 100%;
background: linear-gradient(135deg, #29D3B4 0%, #1BA297 100%);
color: #fff;
border: none;
border-radius: 16rpx;
padding: 28rpx 0;
font-size: 32rpx;
font-weight: 600;
&:disabled {
opacity: 0.6;
}
&:active:not(:disabled) {
opacity: 0.8;
}
}
}
</style>

988
uniapp/pages-student/contracts/index.vue

@ -0,0 +1,988 @@
<!--学员合同管理页面-->
<template>
<view class="main_box">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_back" @click="goBack">
<text class="back_icon"></text>
</view>
<view class="navbar_title">合同管理</view>
<view class="navbar_action"></view>
</view>
<!-- 学员信息 -->
<view class="student_info_section" v-if="studentInfo">
<view class="student_name">{{ studentInfo.name }}</view>
<view class="contract_stats">
<text class="stat_item">有效合同{{ contractStats.active_contracts }}</text>
<text class="stat_item">剩余课时{{ contractStats.remaining_hours }}</text>
</view>
</view>
<!-- 合同状态筛选 -->
<view class="filter_section">
<view class="filter_tabs">
<view
v-for="tab in statusTabs"
:key="tab.value"
:class="['filter_tab', activeStatus === tab.value ? 'active' : '']"
@click="changeStatus(tab.value)"
>
{{ tab.text }}
<view class="tab_badge" v-if="tab.count > 0">{{ tab.count }}</view>
</view>
</view>
</view>
<!-- 合同列表 -->
<view class="contracts_section">
<view v-if="loading" class="loading_section">
<view class="loading_text">加载中...</view>
</view>
<view v-else-if="filteredContracts.length === 0" class="empty_section">
<view class="empty_icon">📋</view>
<view class="empty_text">暂无合同</view>
<view class="empty_hint">签署合同后会在这里显示</view>
</view>
<view v-else class="contracts_list">
<view
v-for="contract in filteredContracts"
:key="contract.id"
class="contract_item"
@click="viewContractDetail(contract)"
>
<view class="contract_header">
<view class="contract_info">
<view class="contract_name">{{ contract.contract_name }}</view>
<view class="contract_number">合同编号{{ contract.contract_no }}</view>
</view>
<view class="contract_status" :class="contract.status">
{{ getStatusText(contract.status) }}
</view>
</view>
<view class="contract_content">
<view class="contract_details">
<view class="detail_row">
<text class="detail_label">课程类型</text>
<text class="detail_value">{{ contract.course_type }}</text>
</view>
<view class="detail_row">
<text class="detail_label">总课时</text>
<text class="detail_value">{{ contract.total_hours }}</text>
</view>
<view class="detail_row">
<text class="detail_label">剩余课时</text>
<text class="detail_value remaining">{{ contract.remaining_hours }}</text>
</view>
<view class="detail_row">
<text class="detail_label">合同金额</text>
<text class="detail_value amount">¥{{ contract.total_amount }}</text>
</view>
</view>
<view class="contract_dates">
<view class="date_row">
<text class="date_label">签署日期</text>
<text class="date_value">{{ formatDate(contract.sign_date) }}</text>
</view>
<view class="date_row">
<text class="date_label">生效日期</text>
<text class="date_value">{{ formatDate(contract.start_date) }}</text>
</view>
<view class="date_row">
<text class="date_label">到期日期</text>
<text class="date_value">{{ formatDate(contract.end_date) }}</text>
</view>
</view>
</view>
<view class="contract_progress" v-if="contract.status === 'active'">
<view class="progress_info">
<text class="progress_text">课时使用进度</text>
<text class="progress_percent">{{ getProgressPercent(contract) }}%</text>
</view>
<view class="progress_bar">
<view class="progress_fill" :style="{ width: getProgressPercent(contract) + '%' }"></view>
</view>
</view>
<view class="contract_actions">
<fui-button
v-if="contract.status === 'active'"
background="transparent"
color="#29d3b4"
size="small"
@click.stop="viewContractDetail(contract)"
>
查看详情
</fui-button>
<fui-button
v-if="contract.status === 'active' && contract.can_renew"
background="#29d3b4"
size="small"
@click.stop="renewContract(contract)"
>
续约
</fui-button>
<fui-button
v-if="contract.status === 'pending'"
background="#f39c12"
size="small"
@click.stop="signContract(contract)"
>
签署合同
</fui-button>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load_more_section" v-if="!loading && hasMore">
<fui-button
background="transparent"
color="#666"
@click="loadMoreContracts"
:loading="loadingMore"
>
{{ loadingMore ? '加载中...' : '加载更多' }}
</fui-button>
</view>
<!-- 合同详情弹窗 -->
<view class="contract_popup" v-if="showContractPopup" @click="closeContractPopup">
<view class="popup_content" @click.stop>
<view class="popup_header">
<view class="popup_title">合同详情</view>
<view class="popup_close" @click="closeContractPopup">×</view>
</view>
<view class="popup_contract_detail" v-if="selectedContract">
<view class="detail_section">
<view class="section_title">基本信息</view>
<view class="info_grid">
<view class="info_row">
<text class="info_label">合同名称</text>
<text class="info_value">{{ selectedContract.contract_name }}</text>
</view>
<view class="info_row">
<text class="info_label">合同编号</text>
<text class="info_value">{{ selectedContract.contract_no }}</text>
</view>
<view class="info_row">
<text class="info_label">课程类型</text>
<text class="info_value">{{ selectedContract.course_type }}</text>
</view>
<view class="info_row">
<text class="info_label">总课时</text>
<text class="info_value">{{ selectedContract.total_hours }}</text>
</view>
<view class="info_row">
<text class="info_label">剩余课时</text>
<text class="info_value">{{ selectedContract.remaining_hours }}</text>
</view>
<view class="info_row">
<text class="info_label">合同金额</text>
<text class="info_value">¥{{ selectedContract.total_amount }}</text>
</view>
</view>
</view>
<view class="detail_section">
<view class="section_title">时间信息</view>
<view class="info_grid">
<view class="info_row">
<text class="info_label">签署日期</text>
<text class="info_value">{{ formatFullDate(selectedContract.sign_date) }}</text>
</view>
<view class="info_row">
<text class="info_label">生效日期</text>
<text class="info_value">{{ formatFullDate(selectedContract.start_date) }}</text>
</view>
<view class="info_row">
<text class="info_label">到期日期</text>
<text class="info_value">{{ formatFullDate(selectedContract.end_date) }}</text>
</view>
</view>
</view>
<view class="detail_section" v-if="selectedContract.terms">
<view class="section_title">合同条款</view>
<view class="contract_terms">{{ selectedContract.terms }}</view>
</view>
</view>
<view class="popup_actions">
<fui-button
v-if="selectedContract && selectedContract.contract_file_url"
background="#3498db"
@click="downloadContract"
>
下载合同
</fui-button>
<fui-button
background="#f8f9fa"
color="#666"
@click="closeContractPopup"
>
关闭
</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
export default {
data() {
return {
studentId: 0,
studentInfo: {},
contractsList: [],
filteredContracts: [],
contractStats: {},
loading: false,
loadingMore: false,
hasMore: true,
currentPage: 1,
activeStatus: 'all',
showContractPopup: false,
selectedContract: null,
statusTabs: [
{ value: 'all', text: '全部', count: 0 },
{ value: 'active', text: '生效中', count: 0 },
{ value: 'pending', text: '待签署', count: 0 },
{ value: 'expired', text: '已到期', count: 0 },
{ value: 'terminated', text: '已终止', count: 0 }
]
}
},
onLoad(options) {
this.studentId = parseInt(options.student_id) || 0
if (this.studentId) {
this.initPage()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
}
},
methods: {
goBack() {
uni.navigateBack()
},
async initPage() {
await this.loadStudentInfo()
await this.loadContracts()
this.updateStatusCounts()
},
async loadStudentInfo() {
try {
//
const mockStudentInfo = {
id: this.studentId,
name: '小明'
}
this.studentInfo = mockStudentInfo
} catch (error) {
console.error('获取学员信息失败:', error)
}
},
async loadContracts() {
this.loading = true
try {
console.log('加载合同列表:', this.studentId)
// API
// const response = await apiRoute.getStudentContracts({
// student_id: this.studentId,
// page: this.currentPage,
// limit: 10
// })
// 使
const mockResponse = {
code: 1,
data: {
list: [
{
id: 1,
contract_no: 'HT202401150001',
contract_name: '少儿体适能训练合同',
course_type: '少儿体适能',
total_hours: 48,
remaining_hours: 32,
used_hours: 16,
total_amount: '4800.00',
status: 'active',
sign_date: '2024-01-15',
start_date: '2024-01-20',
end_date: '2024-07-20',
can_renew: true,
contract_file_url: '/uploads/contracts/contract_001.pdf',
terms: '1. 本合同自签署之日起生效\n2. 学员应按时参加课程\n3. 如需请假,请提前24小时通知\n4. 课程有效期为6个月\n5. 未使用完的课时可申请延期'
},
{
id: 2,
contract_no: 'HT202312100002',
contract_name: '基础体能训练合同',
course_type: '基础体能',
total_hours: 24,
remaining_hours: 0,
used_hours: 24,
total_amount: '2400.00',
status: 'expired',
sign_date: '2023-12-10',
start_date: '2023-12-15',
end_date: '2024-01-15',
can_renew: false,
contract_file_url: '/uploads/contracts/contract_002.pdf',
terms: '已到期的合同条款...'
},
{
id: 3,
contract_no: 'HT202401200003',
contract_name: '专项技能训练合同',
course_type: '专项技能',
total_hours: 36,
remaining_hours: 36,
used_hours: 0,
total_amount: '3600.00',
status: 'pending',
sign_date: null,
start_date: '2024-02-01',
end_date: '2024-08-01',
can_renew: false,
contract_file_url: '/uploads/contracts/contract_003.pdf',
terms: '待签署的合同条款...'
}
],
total: 3,
has_more: false,
stats: {
active_contracts: 1,
remaining_hours: 32,
total_amount: '4800.00'
}
}
}
if (mockResponse.code === 1) {
const newList = mockResponse.data.list || []
if (this.currentPage === 1) {
this.contractsList = newList
} else {
this.contractsList = [...this.contractsList, ...newList]
}
this.hasMore = mockResponse.data.has_more || false
this.contractStats = mockResponse.data.stats || {}
this.applyStatusFilter()
console.log('合同数据加载成功:', this.contractsList)
} else {
uni.showToast({
title: mockResponse.msg || '获取合同列表失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取合同列表失败:', error)
uni.showToast({
title: '获取合同列表失败',
icon: 'none'
})
} finally {
this.loading = false
this.loadingMore = false
}
},
async loadMoreContracts() {
if (this.loadingMore || !this.hasMore) return
this.loadingMore = true
this.currentPage++
await this.loadContracts()
},
changeStatus(status) {
this.activeStatus = status
this.applyStatusFilter()
},
applyStatusFilter() {
if (this.activeStatus === 'all') {
this.filteredContracts = [...this.contractsList]
} else {
this.filteredContracts = this.contractsList.filter(contract => contract.status === this.activeStatus)
}
},
updateStatusCounts() {
const counts = {}
this.contractsList.forEach(contract => {
counts[contract.status] = (counts[contract.status] || 0) + 1
})
this.statusTabs.forEach(tab => {
if (tab.value === 'all') {
tab.count = this.contractsList.length
} else {
tab.count = counts[tab.value] || 0
}
})
},
getStatusText(status) {
const statusMap = {
'active': '生效中',
'pending': '待签署',
'expired': '已到期',
'terminated': '已终止'
}
return statusMap[status] || status
},
getProgressPercent(contract) {
if (contract.total_hours === 0) return 0
return Math.round((contract.used_hours / contract.total_hours) * 100)
},
formatDate(dateString) {
if (!dateString) return '未设置'
const date = new Date(dateString)
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${month}-${day}`
},
formatFullDate(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}`
},
viewContractDetail(contract) {
this.selectedContract = contract
this.showContractPopup = true
},
closeContractPopup() {
this.showContractPopup = false
this.selectedContract = null
},
async renewContract(contract) {
uni.showModal({
title: '确认续约',
content: '确定要续约此合同吗?',
success: async (res) => {
if (res.confirm) {
try {
console.log('续约合同:', contract.id)
// API
await new Promise(resolve => setTimeout(resolve, 1000))
const mockResponse = { code: 1, message: '续约申请已提交' }
if (mockResponse.code === 1) {
uni.showToast({
title: '续约申请已提交',
icon: 'success'
})
} else {
uni.showToast({
title: mockResponse.message || '续约申请失败',
icon: 'none'
})
}
} catch (error) {
console.error('续约申请失败:', error)
uni.showToast({
title: '续约申请失败',
icon: 'none'
})
}
}
}
})
},
async signContract(contract) {
uni.showModal({
title: '确认签署',
content: '确定要签署此合同吗?',
success: async (res) => {
if (res.confirm) {
try {
console.log('签署合同:', contract.id)
// API
await new Promise(resolve => setTimeout(resolve, 1500))
const mockResponse = { code: 1, message: '合同签署成功' }
if (mockResponse.code === 1) {
uni.showToast({
title: '合同签署成功',
icon: 'success'
})
//
const contractIndex = this.contractsList.findIndex(c => c.id === contract.id)
if (contractIndex !== -1) {
this.contractsList[contractIndex].status = 'active'
this.contractsList[contractIndex].sign_date = new Date().toISOString().split('T')[0]
}
this.applyStatusFilter()
this.updateStatusCounts()
} else {
uni.showToast({
title: mockResponse.message || '合同签署失败',
icon: 'none'
})
}
} catch (error) {
console.error('合同签署失败:', error)
uni.showToast({
title: '合同签署失败',
icon: 'none'
})
}
}
}
})
},
downloadContract() {
if (!this.selectedContract || !this.selectedContract.contract_file_url) {
uni.showToast({
title: '合同文件不存在',
icon: 'none'
})
return
}
uni.showModal({
title: '提示',
content: '合同下载功能开发中',
showCancel: false
})
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
display: flex;
justify-content: space-between;
align-items: center;
background: #29D3B4;
padding: 40rpx 32rpx 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_back {
width: 60rpx;
.back_icon {
color: #fff;
font-size: 40rpx;
font-weight: 600;
}
}
.navbar_title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.navbar_action {
width: 60rpx;
}
}
//
.student_info_section {
background: #fff;
padding: 24rpx 32rpx;
border-bottom: 1px solid #f0f0f0;
.student_name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.contract_stats {
display: flex;
gap: 24rpx;
.stat_item {
font-size: 24rpx;
color: #666;
}
}
}
//
.filter_section {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 24rpx 32rpx;
.filter_tabs {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
.filter_tab {
position: relative;
padding: 10rpx 20rpx;
font-size: 24rpx;
color: #666;
background: #f8f9fa;
border-radius: 16rpx;
&.active {
color: #fff;
background: #29D3B4;
}
.tab_badge {
position: absolute;
top: -6rpx;
right: -6rpx;
background: #ff4757;
color: #fff;
font-size: 18rpx;
padding: 2rpx 6rpx;
border-radius: 10rpx;
min-width: 16rpx;
text-align: center;
}
}
}
}
//
.contracts_section {
margin: 0 20rpx;
.loading_section, .empty_section {
background: #fff;
border-radius: 16rpx;
padding: 80rpx 32rpx;
text-align: center;
.loading_text, .empty_text {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
}
.empty_icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty_hint {
font-size: 24rpx;
color: #999;
}
}
.contracts_list {
.contract_item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
.contract_header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.contract_info {
flex: 1;
.contract_name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.contract_number {
font-size: 24rpx;
color: #666;
}
}
.contract_status {
font-size: 22rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
&.active {
color: #27ae60;
background: rgba(39, 174, 96, 0.1);
}
&.pending {
color: #f39c12;
background: rgba(243, 156, 18, 0.1);
}
&.expired {
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
}
&.terminated {
color: #95a5a6;
background: rgba(149, 165, 166, 0.1);
}
}
}
.contract_content {
display: flex;
gap: 32rpx;
margin-bottom: 20rpx;
.contract_details {
flex: 1;
.detail_row {
display: flex;
margin-bottom: 8rpx;
.detail_label {
font-size: 24rpx;
color: #666;
min-width: 120rpx;
}
.detail_value {
font-size: 24rpx;
color: #333;
&.remaining {
color: #29D3B4;
font-weight: 600;
}
&.amount {
color: #e74c3c;
font-weight: 600;
}
}
}
}
.contract_dates {
.date_row {
display: flex;
margin-bottom: 8rpx;
.date_label {
font-size: 22rpx;
color: #999;
min-width: 100rpx;
}
.date_value {
font-size: 22rpx;
color: #666;
}
}
}
}
.contract_progress {
margin-bottom: 20rpx;
.progress_info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
.progress_text {
font-size: 22rpx;
color: #666;
}
.progress_percent {
font-size: 22rpx;
color: #29D3B4;
font-weight: 600;
}
}
.progress_bar {
height: 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
overflow: hidden;
.progress_fill {
height: 100%;
background: linear-gradient(90deg, #29D3B4, #26c6a0);
border-radius: 4rpx;
transition: width 0.3s ease;
}
}
}
.contract_actions {
display: flex;
gap: 16rpx;
justify-content: flex-end;
}
}
}
}
//
.load_more_section {
padding: 40rpx 20rpx 80rpx;
}
//
.contract_popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.popup_content {
background: #fff;
border-radius: 16rpx;
width: 90%;
max-height: 80vh;
overflow: hidden;
.popup_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
.popup_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup_close {
font-size: 48rpx;
color: #999;
font-weight: 300;
}
}
.popup_contract_detail {
padding: 32rpx;
max-height: 60vh;
overflow-y: auto;
.detail_section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.section_title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
padding-bottom: 8rpx;
border-bottom: 2rpx solid #29D3B4;
}
.info_grid {
.info_row {
display: flex;
margin-bottom: 12rpx;
.info_label {
font-size: 26rpx;
color: #666;
min-width: 120rpx;
}
.info_value {
font-size: 26rpx;
color: #333;
flex: 1;
}
}
}
.contract_terms {
font-size: 24rpx;
color: #666;
line-height: 1.6;
white-space: pre-line;
}
}
}
.popup_actions {
padding: 24rpx 32rpx;
display: flex;
gap: 16rpx;
border-top: 1px solid #f0f0f0;
fui-button {
flex: 1;
}
}
}
}
</style>

1023
uniapp/pages-student/course-booking/index.vue

File diff suppressed because it is too large

1304
uniapp/pages-student/knowledge/index.vue

File diff suppressed because it is too large

887
uniapp/pages-student/messages/index.vue

@ -0,0 +1,887 @@
<!--学员消息管理页面-->
<template>
<view class="main_box">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_back" @click="goBack">
<text class="back_icon"></text>
</view>
<view class="navbar_title">消息管理</view>
<view class="navbar_action">
<view class="mark_all_read" @click="markAllAsRead" v-if="unreadCount > 0">
<text class="mark_text">全部已读</text>
</view>
</view>
</view>
<!-- 学员信息 -->
<view class="student_info_section" v-if="studentInfo">
<view class="student_name">{{ studentInfo.name }}</view>
<view class="message_stats">
<text class="stat_item">未读消息{{ unreadCount }}</text>
<text class="stat_item">总消息{{ messagesList.length }}</text>
</view>
</view>
<!-- 消息类型筛选 -->
<view class="filter_section">
<view class="filter_tabs">
<view
v-for="tab in typeTabs"
:key="tab.value"
:class="['filter_tab', activeType === tab.value ? 'active' : '']"
@click="changeType(tab.value)"
>
{{ tab.text }}
<view class="tab_badge" v-if="tab.count > 0">{{ tab.count }}</view>
</view>
</view>
</view>
<!-- 消息列表 -->
<view class="messages_section">
<view v-if="loading" class="loading_section">
<view class="loading_text">加载中...</view>
</view>
<view v-else-if="filteredMessages.length === 0" class="empty_section">
<view class="empty_icon">💬</view>
<view class="empty_text">暂无消息</view>
<view class="empty_hint">新消息会在这里显示</view>
</view>
<view v-else class="messages_list">
<view
v-for="message in filteredMessages"
:key="message.id"
:class="['message_item', !message.is_read ? 'unread' : '']"
@click="viewMessage(message)"
>
<view class="message_header">
<view class="message_type" :class="message.type">
{{ getTypeText(message.type) }}
</view>
<view class="message_time">{{ formatTime(message.send_time) }}</view>
<view class="unread_dot" v-if="!message.is_read"></view>
</view>
<view class="message_content">
<view class="message_title">{{ message.title }}</view>
<view class="message_preview">{{ message.content | truncate(50) }}</view>
</view>
<view class="message_meta" v-if="message.sender_name">
<text class="sender_name">来自{{ message.sender_name }}</text>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load_more_section" v-if="!loading && hasMore">
<fui-button
background="transparent"
color="#666"
@click="loadMoreMessages"
:loading="loadingMore"
>
{{ loadingMore ? '加载中...' : '加载更多' }}
</fui-button>
</view>
<!-- 消息详情弹窗 -->
<view class="message_popup" v-if="showMessagePopup" @click="closeMessagePopup">
<view class="popup_content" @click.stop>
<view class="popup_header">
<view class="popup_title">消息详情</view>
<view class="popup_close" @click="closeMessagePopup">×</view>
</view>
<view class="popup_message_detail" v-if="selectedMessage">
<view class="detail_header">
<view class="detail_type" :class="selectedMessage.type">
{{ getTypeText(selectedMessage.type) }}
</view>
<view class="detail_time">{{ formatFullTime(selectedMessage.send_time) }}</view>
</view>
<view class="detail_title">{{ selectedMessage.title }}</view>
<view class="detail_content">{{ selectedMessage.content }}</view>
<view class="detail_sender" v-if="selectedMessage.sender_name">
<text class="sender_label">发送人</text>
<text class="sender_value">{{ selectedMessage.sender_name }}</text>
</view>
<view class="detail_attachment" v-if="selectedMessage.attachment_url">
<fui-button
background="#29d3b4"
size="small"
@click="viewAttachment(selectedMessage.attachment_url)"
>
查看附件
</fui-button>
</view>
</view>
<view class="popup_actions">
<fui-button
v-if="selectedMessage && selectedMessage.type === 'homework'"
background="#f39c12"
@click="submitHomework"
>
提交作业
</fui-button>
<fui-button
v-if="selectedMessage && selectedMessage.type === 'notification'"
background="#29d3b4"
@click="confirmNotification"
>
确认已读
</fui-button>
<fui-button
background="#f8f9fa"
color="#666"
@click="closeMessagePopup"
>
关闭
</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
export default {
filters: {
truncate(text, length) {
if (!text) return ''
if (text.length <= length) return text
return text.substring(0, length) + '...'
}
},
data() {
return {
studentId: 0,
studentInfo: {},
messagesList: [],
filteredMessages: [],
loading: false,
loadingMore: false,
hasMore: true,
currentPage: 1,
activeType: 'all',
showMessagePopup: false,
selectedMessage: null,
typeTabs: [
{ value: 'all', text: '全部', count: 0 },
{ value: 'system', text: '系统消息', count: 0 },
{ value: 'notification', text: '通知公告', count: 0 },
{ value: 'homework', text: '作业任务', count: 0 },
{ value: 'feedback', text: '反馈评价', count: 0 },
{ value: 'reminder', text: '课程提醒', count: 0 }
]
}
},
computed: {
unreadCount() {
return this.messagesList.filter(msg => !msg.is_read).length
}
},
onLoad(options) {
this.studentId = parseInt(options.student_id) || 0
if (this.studentId) {
this.initPage()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
}
},
methods: {
goBack() {
uni.navigateBack()
},
async initPage() {
await this.loadStudentInfo()
await this.loadMessages()
this.updateTypeCounts()
},
async loadStudentInfo() {
try {
//
const mockStudentInfo = {
id: this.studentId,
name: '小明'
}
this.studentInfo = mockStudentInfo
} catch (error) {
console.error('获取学员信息失败:', error)
}
},
async loadMessages() {
this.loading = true
try {
console.log('加载消息列表:', this.studentId)
// API
// const response = await apiRoute.getStudentMessages({
// student_id: this.studentId,
// page: this.currentPage,
// limit: 10
// })
// 使
const mockResponse = {
code: 1,
data: {
list: [
{
id: 1,
type: 'system',
title: '欢迎加入运动识堂',
content: '欢迎您的孩子加入我们的运动训练课程!我们将为您的孩子提供专业的体能训练指导,帮助孩子健康成长。课程安排和相关信息会及时通过消息推送给您,请注意查收。',
sender_name: '系统管理员',
send_time: '2024-01-15 09:00:00',
is_read: false,
attachment_url: ''
},
{
id: 2,
type: 'notification',
title: '本周课程安排通知',
content: '本周课程安排已更新,请及时查看课程表。周三下午16:00-17:00的基础体能训练课程请准时参加,课程地点:训练馆A。如有疑问请联系教练。',
sender_name: '张教练',
send_time: '2024-01-14 15:30:00',
is_read: true,
attachment_url: ''
},
{
id: 3,
type: 'homework',
title: '体能训练作业',
content: '请完成以下体能训练作业:1. 每天跳绳100个 2. 俯卧撑10个 3. 仰卧起坐15个。请在下次课程前完成并提交训练视频。',
sender_name: '李教练',
send_time: '2024-01-13 18:20:00',
is_read: false,
attachment_url: '/uploads/homework/homework_guide.pdf'
},
{
id: 4,
type: 'feedback',
title: '上次课程反馈',
content: '您的孩子在上次的基础体能训练中表现优秀,动作标准,学习积极。建议继续加强核心力量训练,可以适当增加训练强度。',
sender_name: '王教练',
send_time: '2024-01-12 20:15:00',
is_read: true,
attachment_url: ''
},
{
id: 5,
type: 'reminder',
title: '明日课程提醒',
content: '提醒您的孩子明天下午14:00有专项技能训练课程,请准时到达训练馆B。建议提前10分钟到场进行热身准备。',
sender_name: '系统提醒',
send_time: '2024-01-11 19:00:00',
is_read: true,
attachment_url: ''
}
],
total: 5,
has_more: false
}
}
if (mockResponse.code === 1) {
const newList = mockResponse.data.list || []
if (this.currentPage === 1) {
this.messagesList = newList
} else {
this.messagesList = [...this.messagesList, ...newList]
}
this.hasMore = mockResponse.data.has_more || false
this.applyTypeFilter()
console.log('消息数据加载成功:', this.messagesList)
} else {
uni.showToast({
title: mockResponse.msg || '获取消息列表失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取消息列表失败:', error)
uni.showToast({
title: '获取消息列表失败',
icon: 'none'
})
} finally {
this.loading = false
this.loadingMore = false
}
},
async loadMoreMessages() {
if (this.loadingMore || !this.hasMore) return
this.loadingMore = true
this.currentPage++
await this.loadMessages()
},
changeType(type) {
this.activeType = type
this.applyTypeFilter()
},
applyTypeFilter() {
if (this.activeType === 'all') {
this.filteredMessages = [...this.messagesList]
} else {
this.filteredMessages = this.messagesList.filter(message => message.type === this.activeType)
}
},
updateTypeCounts() {
const counts = {}
this.messagesList.forEach(message => {
counts[message.type] = (counts[message.type] || 0) + 1
})
this.typeTabs.forEach(tab => {
if (tab.value === 'all') {
tab.count = this.messagesList.length
} else {
tab.count = counts[tab.value] || 0
}
})
},
getTypeText(type) {
const typeMap = {
'system': '系统消息',
'notification': '通知公告',
'homework': '作业任务',
'feedback': '反馈评价',
'reminder': '课程提醒'
}
return typeMap[type] || type
},
formatTime(timeString) {
const now = new Date()
const msgTime = new Date(timeString)
const diffHours = (now - msgTime) / (1000 * 60 * 60)
if (diffHours < 1) {
return '刚刚'
} else if (diffHours < 24) {
return Math.floor(diffHours) + '小时前'
} else if (diffHours < 48) {
return '昨天'
} else {
const month = String(msgTime.getMonth() + 1).padStart(2, '0')
const day = String(msgTime.getDate()).padStart(2, '0')
return `${month}-${day}`
}
},
formatFullTime(timeString) {
const date = new Date(timeString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
},
viewMessage(message) {
this.selectedMessage = message
this.showMessagePopup = true
//
if (!message.is_read) {
this.markAsRead(message)
}
},
closeMessagePopup() {
this.showMessagePopup = false
this.selectedMessage = null
},
async markAsRead(message) {
try {
console.log('标记已读:', message.id)
// API
await new Promise(resolve => setTimeout(resolve, 200))
//
const index = this.messagesList.findIndex(msg => msg.id === message.id)
if (index !== -1) {
this.messagesList[index].is_read = true
}
} catch (error) {
console.error('标记已读失败:', error)
}
},
async markAllAsRead() {
if (this.unreadCount === 0) {
uni.showToast({
title: '暂无未读消息',
icon: 'none'
})
return
}
try {
console.log('标记全部已读')
// API
await new Promise(resolve => setTimeout(resolve, 500))
//
this.messagesList.forEach(message => {
message.is_read = true
})
uni.showToast({
title: '已全部标记为已读',
icon: 'success'
})
} catch (error) {
console.error('标记全部已读失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
},
viewAttachment(attachmentUrl) {
console.log('查看附件:', attachmentUrl)
uni.showModal({
title: '提示',
content: '附件下载功能开发中',
showCancel: false
})
},
submitHomework() {
console.log('提交作业')
uni.showModal({
title: '提示',
content: '作业提交功能开发中',
showCancel: false
})
},
confirmNotification() {
console.log('确认通知')
this.closeMessagePopup()
uni.showToast({
title: '已确认',
icon: 'success'
})
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
display: flex;
justify-content: space-between;
align-items: center;
background: #29D3B4;
padding: 40rpx 32rpx 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_back {
width: 60rpx;
.back_icon {
color: #fff;
font-size: 40rpx;
font-weight: 600;
}
}
.navbar_title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.navbar_action {
width: 120rpx;
display: flex;
justify-content: flex-end;
.mark_all_read {
background: rgba(255, 255, 255, 0.2);
padding: 8rpx 16rpx;
border-radius: 16rpx;
.mark_text {
color: #fff;
font-size: 24rpx;
}
}
}
}
//
.student_info_section {
background: #fff;
padding: 24rpx 32rpx;
border-bottom: 1px solid #f0f0f0;
.student_name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.message_stats {
display: flex;
gap: 24rpx;
.stat_item {
font-size: 24rpx;
color: #666;
}
}
}
//
.filter_section {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 24rpx 32rpx;
.filter_tabs {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
.filter_tab {
position: relative;
padding: 12rpx 24rpx;
font-size: 26rpx;
color: #666;
background: #f8f9fa;
border-radius: 20rpx;
&.active {
color: #fff;
background: #29D3B4;
}
.tab_badge {
position: absolute;
top: -8rpx;
right: -8rpx;
background: #ff4757;
color: #fff;
font-size: 18rpx;
padding: 2rpx 8rpx;
border-radius: 12rpx;
min-width: 16rpx;
text-align: center;
}
}
}
}
//
.messages_section {
margin: 0 20rpx;
.loading_section, .empty_section {
background: #fff;
border-radius: 16rpx;
padding: 80rpx 32rpx;
text-align: center;
.loading_text, .empty_text {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
}
.empty_icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty_hint {
font-size: 24rpx;
color: #999;
}
}
.messages_list {
.message_item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
position: relative;
&.unread {
background: #f8fdff;
border-left: 4rpx solid #29D3B4;
}
.message_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.message_type {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
&.system {
color: #3498db;
background: rgba(52, 152, 219, 0.1);
}
&.notification {
color: #f39c12;
background: rgba(243, 156, 18, 0.1);
}
&.homework {
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
}
&.feedback {
color: #27ae60;
background: rgba(39, 174, 96, 0.1);
}
&.reminder {
color: #9b59b6;
background: rgba(155, 89, 182, 0.1);
}
}
.message_time {
font-size: 22rpx;
color: #999;
}
.unread_dot {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 12rpx;
height: 12rpx;
background: #ff4757;
border-radius: 50%;
}
}
.message_content {
margin-bottom: 12rpx;
.message_title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.message_preview {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
}
.message_meta {
.sender_name {
font-size: 22rpx;
color: #999;
}
}
}
}
}
//
.load_more_section {
padding: 40rpx 20rpx 80rpx;
}
//
.message_popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.popup_content {
background: #fff;
border-radius: 16rpx;
width: 90%;
max-height: 80vh;
overflow: hidden;
.popup_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
.popup_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup_close {
font-size: 48rpx;
color: #999;
font-weight: 300;
}
}
.popup_message_detail {
padding: 32rpx;
max-height: 60vh;
overflow-y: auto;
.detail_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.detail_type {
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 16rpx;
&.system {
color: #3498db;
background: rgba(52, 152, 219, 0.1);
}
&.notification {
color: #f39c12;
background: rgba(243, 156, 18, 0.1);
}
&.homework {
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
}
&.feedback {
color: #27ae60;
background: rgba(39, 174, 96, 0.1);
}
&.reminder {
color: #9b59b6;
background: rgba(155, 89, 182, 0.1);
}
}
.detail_time {
font-size: 24rpx;
color: #999;
}
}
.detail_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.detail_content {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 20rpx;
}
.detail_sender {
margin-bottom: 20rpx;
padding-top: 20rpx;
border-top: 1px solid #f8f9fa;
.sender_label {
font-size: 24rpx;
color: #999;
}
.sender_value {
font-size: 24rpx;
color: #666;
}
}
.detail_attachment {
margin-bottom: 20rpx;
}
}
.popup_actions {
padding: 24rpx 32rpx;
display: flex;
gap: 16rpx;
border-top: 1px solid #f0f0f0;
fui-button {
flex: 1;
}
}
}
}
</style>

195
uniapp/pages-student/my/lesson_consumption.vue

@ -0,0 +1,195 @@
<!--课时消耗列表-->
<template>
<view class="assemble">
<view style="height: 50rpx;"></view>
<view class="message_box" v-if="!tableList.length">
暂无更多数据
</view>
<view class="ul" v-if="tableList.length">
<view class="li" v-for="(v,k) in tableList" :key="k">
<view class="left">
<view class="title">{{v.name}}</view>
<view class="date">课程使用日期{{v.usage_date}}</view>
</view>
<view class="right">
<view
class="btn"
style="background-color: #29d3b4;"
>
{{v.usage_date}}课时
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
loading:false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
//
filteredData:{
page:1,//
limit:10,//
total:10,//
resources_id:'',//id
},
tableList:[],//
memberInfo:{},//
}
},
onLoad(options) {
},
onShow(){
this.init()//
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init(){
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
},
//()
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_personCourseScheduleGetStudentCourseUsageList(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++
},
}
}
</script>
<style lang="less" scoped>
.message_box{
font-size: 30rpx;
text-align: center;
color: #fff;
}
.assemble {
width: 100%;
height: 100vh;
background: #333333;
overflow: auto;
}
.ul {
display: flex;
flex-direction: column;
gap: 12rpx;
background-color: #fff;
width: 90%;
margin: 0 auto 30rpx;
padding: 26rpx;
border-radius: 16rpx;
}
.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;
}
.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;
}
</style>

211
uniapp/pages-student/my/my_coach.vue

@ -0,0 +1,211 @@
<!--我的教练-列表-->
<template>
<view class="main_box">
<scroll-view scroll-y="true" :lower-threshold="lowerThreshold"
@scrolltolower="loadMoreData" style="height: 100%;padding-top:50rpx;padding-bottom: 50rpx">
<!-- <view class="data_hint" v-if="!this.tableList.length">暂无更多数据</view>-->
<view class="main_section" v-for="(v,k) in tableList" :key="k">
<view class="left">
<!-- 头像-->
<image :src="v.head_img ? v.head_img : $util.img('/uniapp_src/static/images/common/yong_hu.png')" class="pic"></image>
</view>
<view class="right">
<view class="title">姓名{{v.name}}</view>
<view class="title">电话{{v.phone}}</view>
</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,//
resources_id:'',//id
},
tableList:[],//
}
},
onLoad(options) {},
onShow() {
this.init()//
},
//
async onPullDownRefresh() {
//
await this.resetFilteredData()
await this.getList()
},
methods: {
//
async init() {
await this.getMemberInfo();
await this.getList();
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
//
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
},
//()
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 - 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_personCourseScheduleGetMyCoach(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++
},
}
}
</script>
<style lang="less" scoped>
.main_box {
width: 100%;
height: 100%;
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: 30rpx;
color: #fff;
display: flex;
align-items: center;
.left{
display: flex;
flex-direction: column;
align-items: center;
.pic{
width: 100rpx;
height: 100rpx;
border-radius: 50%;
}
}
.right{
margin-left: 20rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
}
.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>

158
uniapp/pages-student/my/my_members.vue

@ -0,0 +1,158 @@
<template>
<view class="assemble">
<view style="height: 50rpx;"></view>
<view class="ul">
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
<view class="li">
<view class="left">
<view class="title">智卓燕</view>
<view class="date">哥哥</view>
</view>
<view class="right">
<view class="btn" style="background-color: #29d3b4;">5课时</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.assemble {
width: 100%;
height: 100vh;
background: #333333;
overflow: auto;
}
.ul {
display: flex;
flex-direction: column;
gap: 12rpx;
background-color: #fff;
width: 90%;
margin: 0 auto 30rpx;
padding: 26rpx;
border-radius: 16rpx;
}
.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;
}
.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;
}
</style>

421
uniapp/pages-student/my/personal_data.vue

@ -0,0 +1,421 @@
<!--授课统计-详情-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section">
<view class="item">
<image @click="previewImage(editHeadimg)" class="pic" :src="editHeadimg"></image>
<view class="btn">
<AQUplodeImage
:uploadUrl=uploadUrl
:extraData="{ input_name: 'headimg', formData:{} }"
@uplodeImageRes="uplodeImageRes"
>
修改头像
</AQUplodeImage>
</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
学员姓名 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.name" placeholder="请输入姓名" />
</view>
</view>
<view class="item">
<view class="title">
账号 <text class="required"></text>
</view>
<view class="input">
<input disabled placeholder="暂无" v-model="formData.memberHasOne.mobile" />
</view>
</view>
<!-- <view class="item">-->
<!-- <view class="title">-->
<!-- 住址 <text class="required"></text>-->
<!-- </view>-->
<!-- <view class="input">-->
<!-- <input placeholder="暂无" v-model="formData.address" />-->
<!-- </view>-->
<!-- </view>-->
<view class="item">
<view class="title">
课程 <text class="required"></text>
</view>
<view class="input">
<input disabled placeholder="暂无" :value="formData.classes_list" />
</view>
</view>
</view>
<view class="section">
<view class="item">
<view class="title">
性别 <text class="required">*</text>
</view>
<view class="input">
<input placeholder="请选择性别" v-model="formData.gender_name" @click="picker_show_sex=true" />
<fui-picker layer="1" :linkage="true" :options="options_sex_arr" :show="picker_show_sex"
@change="changePickerSex" @cancel="cancelPickerSex"></fui-picker>
</view>
</view>
<view class="item">
<view class="title">
年龄 <text class="required">*</text>
</view>
<view class="input">
<input type="number" v-model.number="formData.age" :min="3" :max="18" placeholder="请输入年龄" @input="handleAgeInput" /> </view>
</view>
</view>
<view class="section">
<!-- <view class="item">-->
<!-- <view class="title">-->
<!-- 邮箱 <text class="required">*</text>-->
<!-- </view>-->
<!-- <view class="input">-->
<!-- <input v-model="formData.email" placeholder="请输入邮箱" />-->
<!-- </view>-->
<!-- </view>-->
<view class="item">
<view class="title">
手机 <text class="required">*</text>
</view>
<view class="input">
<input v-model="formData.phone_number" placeholder="请输入手机" />
</view>
</view>
</view>
<view class="submet_btn" @click="submit">提交</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import member from '@/api/member.js';
import {
Api_url
} from "@/common/config.js";
import AQTabber from "@/components/AQ/AQTabber"
import AQUplodeImage from '@/components/AQ/AQUplodeImage';//
export default {
components: {
AQTabber,
AQUplodeImage,
},
data() {
return {
member_info: {},
//APi
uploadUrl: `${Api_url}/memberUploadImage`,
//
picker_show_sex: false,
sex_name: '请选择',
options_sex_arr: [
// {
// value: 1,
// text: ''
// },
],
//
minDate: '',
maxDate: '',
picker_show_birthday: false,
upload_type: 1,
uploadHeadimg: '',
editHeadimg: '',
//
formData: {
headimg:'',//
name:'',//
course_arr:[],//
gender:'',//: male-, female-, other-
gender_name:'',//: male-, female-, other-
age:'',//
phone_number:'',//
},
}
},
onLoad() {},
onShow() {
this.init()
},
methods: {
async init() {
await this.memberInfo()
await this.getSexDict()
this.getBirthday()
},
//
async memberInfo() {
let res = await apiRoute.xy_memberInfo({})
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.formData = res.data
this.formData.headimg = res.data.memberHasOne ? res.data.memberHasOne.headimg : $util.img('/uniapp_src/static/images/common/yong_hu.png')
this.editHeadimg = this.formData.headimg
console.log('xq',this.formData)
},
//
async getSexDict() {
let res = await apiRoute.common_Dictionary({key:'zy_sex'})
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.options_sex_arr = arr
},
//
changePickerSex(e) {
console.log('监听选择', e)
this.formData.gender_name = e.text
this.formData.gender = e.value
this.cancelPickerSex()
},
cancelPickerSex(){
this.picker_show_sex = false
},
//
//+30
getBirthday() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let year_30 = year - 30;
let month_30 = month;
let day_30 = day;
if (month_30 == 2 && day_30 > 28) {
month_30 = 3;
day_30 = 1;
}
if (month_30 == 4 && day_30 > 30) {
month_30 = 5;
day_30 = 1;
}
if (month_30 == 6 && day_30 > 30) {
month_30 = 7;
day_30 = 1;
}
if (month_30 == 9 && day_30 > 30) {
month_30 = 10;
day_30 = 1;
}
if (month_30 == 11 && day_30 > 30) {
month_30 = 12;
day_30 = 1;
}
if (month_30 > 12) {
month_30 = month_30 - 12;
year_30 = year_30 + 1;
}
let minDate = year_30 + "-" + month_30 + "-" + day_30
let maxDate = year + "-" + month + "-" + day
this.minDate = minDate
this.maxDate = maxDate
},
//
changePickerBirthday(e) {
console.log('监听生日选择', e)
this.formData.birthday = e.result
this.picker_show_birthday = false
},
//-
handleAgeInput(e) {
let age = e.detail.value;
if (age < 3) {
uni.showToast({
title: '年龄不能小于3岁',
icon: 'none'
});
this.formData.age = 3;
} else if (age > 18) {
uni.showToast({
title: '年龄不能大于18岁',
icon: 'none'
});
this.formData.age = 18;
} else {
this.formData.age = age;
}
},
//
uplodeImageRes(resData,extraData){
console.log('上传成功回调',resData,extraData)
//
if (extraData.input_name == 'headimg') {
console.log('收到的图片地址:', resData.url);
this.editHeadimg = resData.url;
}
},
//
previewImage(url){
uni.previewImage({
current: url, //
urls: [url] //
});
},
//
async submit() {
if (this.editHeadimg) {
this.formData.headimg = this.editHeadimg
}
let params = {
...this.formData
}
let res = await apiRoute.xy_memberEdit(params)
if(res.code != 1){
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
uni.showToast({
title: res.msg,
icon: 'success'
})
},
}
}
</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;
color: #fff;
display: flex;
flex-direction: column;
gap: 20rpx;
.section {
background-color: #434544;
.item {
padding: 20rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
.pic {
width: 82rpx;
height: 82rpx;
border-radius: 50%;
}
.btn {}
.title {
display: flex;
align-items: center;
font-size: 26rpx;
color: #D7D7D7;
.required {
margin-left: 10rpx;
color: red;
}
}
.input {
input {
text-align: right;
}
}
}
}
.submet_btn {
margin: 0 auto;
margin-top: 40rpx;
border: 2px solid #25a18b;
color: #25a18b;
width: 80%;
height: 80rpx;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

57
uniapp/pages-student/my/set_up.vue

@ -0,0 +1,57 @@
<template>
<view class="assemble">
<view style="height: 30rpx;"></view>
<view class="option" @click="update_pass()">修改密码</view>
<view class="option" @click="privacy_agreement(1)">用户协议</view>
<view class="option" @click="privacy_agreement(2)">隐私策略</view>
<view class="option">清空缓存</view>
<view style="width:90%;margin: 60rpx auto;">
<fui-button background="#29d3b4" @click="loginOut()">退出账号</fui-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
//退
loginOut(){
this.$util.loginOut()
},
privacy_agreement(type){
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
})
},
update_pass(){
uni.navigateTo({
url: '/pages-student/my/update_pass'
})
}
}
}
</script>
<style lang="less" scoped>
.assemble{
width: 100%;
height: 100vh;
background: #333333;
}
.option{
margin-bottom: 20rpx;
background: #404045;
width: 100%;
font-size: 28rpx;
color: #fff;
line-height: 56rpx;
padding: 20rpx 0 20rpx 100rpx;
}
</style>

125
uniapp/pages-student/my/update_pass.vue

@ -0,0 +1,125 @@
<template>
<view>
<view class="title">
<view :class="{'green-text': tset_style === 1}">1.验证原密码</view>
<view :class="{'green-text': tset_style === 2}">2.设置新密码</view>
</view>
<view :style="{'background-color':'#fff','width':'100%','height':'100vh' }">
<view style="width: 95%;height: 30rpx;"></view>
<view v-if="tset_style == 1">
<view class="describe">
为保障您的账号安全修改密码前请填写原密码
</view>
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请输入原登录密码" v-model="password" backgroundColor="#f2f2f2"></fui-input>
</view>
</view>
<view v-if="tset_style == 2">
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="请设置6-20位新的登录密码" v-model="passwords" backgroundColor="#f2f2f2"></fui-input>
</view>
<view style="width: 95%;margin: auto;">
<fui-input borderTop :padding="['20rpx','32rpx']" v-model="old_password" placeholder="请再次输入新的登录密码" backgroundColor="#f2f2f2">
</fui-input>
</view>
</view>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#00be8c" radius="5rpx" @click="nextStep" v-if="tset_style == 1">下一步</fui-button>
<fui-button background="#00be8c" radius="5rpx" @click="submit" v-if="tset_style == 2">提交</fui-button>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#fff" radius="5rpx" @click="forgot" color="#999999"
v-if="tset_style == 1">忘记原密码</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import member from '@/api/member.js';
export default {
data() {
return {
password: '',
passwords: '',
old_password: '',
tset_style: 1,
}
},
onLoad() {
},
methods: {
//
nextStep() {
member.is_pass({
password: this.password
}).then(res => {
if (res.code == 1) {
this.tset_style = 2
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
});
},
//
submit() {
member.set_pass({
password: this.passwords,
old_password: this.old_password,
}).then(res => {
if (res.code == 1) {
this.$util.loginOut();
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
});
},
forgot() {
uni.navigateTo({
url: '/pages-student/login/forgot'
})
},
}
}
</script>
<style lang="less" scoped>
page {
font-weight: normal;
}
.fui-section__title {
margin-left: 32rpx;
}
.fui-left__icon {
padding-right: 24rpx;
}
.title {
display: flex;
justify-content: space-around;
align-items: center;
height: 100rpx;
width: 100%;
background-color: #fff;
font-size: 26rpx;
border: 4rpx #f5f5f5 solid;
}
.green-text {
color: #36d6b9;
}
.describe {
color: #999999;
padding-left: 30rpx;
}
</style>

1024
uniapp/pages-student/orders/index.vue

File diff suppressed because it is too large

823
uniapp/pages-student/physical-test/index.vue

@ -0,0 +1,823 @@
<!--学员体测数据页面-->
<template>
<view class="main_box">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_content">
<view class="navbar_back" @click="goBack">
<text class="back_icon"></text>
</view>
<view class="navbar_title">体测数据</view>
<view class="navbar_action">
<view class="share_button" @click="sharePhysicalTest" v-if="physicalTestList.length > 0">
<image src="/static/icon-img/share.png" class="share_icon"></image>
</view>
</view>
</view>
</view>
<!-- 学员基本信息 -->
<view class="student_basic_section" v-if="studentInfo">
<view class="student_name">{{ studentInfo.name }}</view>
<view class="student_meta">
<text class="meta_item">{{ studentInfo.gender_text }}</text>
<text class="meta_item">{{ studentInfo.age }}</text>
</view>
</view>
<!-- 数据统计卡片 -->
<view class="stats_section">
<view class="stat_card">
<view class="stat_number">{{ physicalTestList.length }}</view>
<view class="stat_label">测试次数</view>
</view>
<view class="stat_card" v-if="latestTest">
<view class="stat_number">{{ latestTest.height }}cm</view>
<view class="stat_label">最新身高</view>
</view>
<view class="stat_card" v-if="latestTest">
<view class="stat_number">{{ latestTest.weight }}kg</view>
<view class="stat_label">最新体重</view>
</view>
</view>
<!-- 体测记录列表 -->
<view class="test_list_section">
<view v-if="loading" class="loading_section">
<view class="loading_text">加载中...</view>
</view>
<view v-else-if="physicalTestList.length === 0" class="empty_section">
<view class="empty_icon">📊</view>
<view class="empty_text">暂无体测数据</view>
<view class="empty_hint">完成体测后数据会在这里显示</view>
</view>
<view v-else class="test_list">
<view
v-for="test in physicalTestList"
:key="test.id"
class="test_item"
>
<view class="test_header">
<view class="test_date">{{ formatDate(test.created_at) }}</view>
<view class="test_status">体测记录</view>
</view>
<view class="test_content">
<view class="measurement_row">
<view class="measurement_item">
<view class="item_label">身高</view>
<view class="item_value">{{ test.height || '-' }}cm</view>
</view>
<view class="measurement_item">
<view class="item_label">体重</view>
<view class="item_value">{{ test.weight || '-' }}kg</view>
</view>
</view>
<!-- PDF报告列表 -->
<view class="pdf_reports" v-if="test.physical_test_report">
<view class="reports_title">体测报告</view>
<view class="reports_list">
<view
v-for="(pdfUrl, index) in getPdfList(test.physical_test_report)"
:key="index"
class="pdf_item"
@click="previewPdf(pdfUrl, test.id, index)"
>
<view class="pdf_icon">
<image src="/static/icon-img/pdf.png" class="icon_image"></image>
</view>
<view class="pdf_info">
<view class="pdf_name">体测报告{{ index + 1 }}</view>
<view class="pdf_action">点击预览</view>
</view>
<view class="pdf_arrow">
<image src="/static/icon-img/arrow-right.png" class="arrow_icon"></image>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load_more_section" v-if="!loading && hasMore">
<button class="load_more_button" @click="loadMoreTests" :disabled="loadingMore">
{{ loadingMore ? '加载中...' : '加载更多' }}
</button>
</view>
<!-- PDF预览弹窗 -->
<view class="pdf_preview_popup" v-if="showPdfPreview" @click="closePdfPreview">
<view class="preview_content" @click.stop>
<view class="preview_header">
<view class="preview_title">体测报告预览</view>
<view class="preview_close" @click="closePdfPreview">×</view>
</view>
<view class="preview_body">
<view v-if="pdfPreviewLoading" class="preview_loading">
<view class="loading_text">加载中...</view>
</view>
<view v-else-if="pdfImageUrl" class="preview_image_container">
<image
:src="pdfImageUrl"
class="preview_image"
mode="widthFix"
@error="onImageError"
></image>
</view>
<view v-else class="preview_error">
<view class="error_text">预览失败</view>
<button class="retry_button" @click="retryPreview">重试</button>
</view>
</view>
<view class="preview_footer">
<button class="share_pdf_button" @click="sharePdfImage" :disabled="!pdfImageUrl">
分享报告
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/member.js'
export default {
data() {
return {
studentId: 0,
studentInfo: {},
physicalTestList: [],
loading: false,
loadingMore: false,
hasMore: true,
currentPage: 1,
showPdfPreview: false,
pdfPreviewLoading: false,
pdfImageUrl: '',
currentPdfUrl: '',
currentTestId: 0,
currentPdfIndex: 0
}
},
computed: {
latestTest() {
if (this.physicalTestList.length === 0) return null
return this.physicalTestList[0]
}
},
onLoad(options) {
this.studentId = parseInt(options.student_id) || 0
if (this.studentId) {
this.initPage()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
}
},
methods: {
goBack() {
uni.navigateBack()
},
async initPage() {
await this.loadStudentInfo()
await this.loadPhysicalTests()
},
async loadStudentInfo() {
try {
// API
const response = await apiRoute.getStudentSummary(this.studentId)
if (response.code === 1) {
this.studentInfo = response.data
} else {
console.error('获取学员信息失败:', response)
}
} catch (error) {
console.error('获取学员信息失败:', error)
}
},
async loadPhysicalTests() {
this.loading = true
try {
console.log('加载体测数据:', this.studentId)
// API
const response = await apiRoute.getPhysicalTestList({
student_id: this.studentId,
page: this.currentPage,
limit: 10
})
if (response.code === 1) {
const newList = response.data.list || []
if (this.currentPage === 1) {
this.physicalTestList = newList
} else {
this.physicalTestList = [...this.physicalTestList, ...newList]
}
this.hasMore = response.data.has_more || false
console.log('体测数据加载成功:', this.physicalTestList)
} else {
uni.showToast({
title: response.msg || '获取体测数据失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取体测数据失败:', error)
uni.showToast({
title: '获取体测数据失败',
icon: 'none'
})
} finally {
this.loading = false
this.loadingMore = false
}
},
async loadMoreTests() {
if (this.loadingMore || !this.hasMore) return
this.loadingMore = true
this.currentPage++
await this.loadPhysicalTests()
},
getPdfList(pdfReportString) {
if (!pdfReportString || pdfReportString.trim() === '') {
return []
}
// PDF URL
return pdfReportString.split(',').filter(url => url.trim() !== '')
},
async previewPdf(pdfUrl, testId, pdfIndex) {
console.log('预览PDF:', pdfUrl)
this.currentPdfUrl = pdfUrl
this.currentTestId = testId
this.currentPdfIndex = pdfIndex
this.showPdfPreview = true
this.pdfPreviewLoading = true
this.pdfImageUrl = ''
try {
// APIPDF
const response = await apiRoute.convertPdfToImage({
pdf_url: pdfUrl,
test_id: testId,
pdf_index: pdfIndex
})
if (response.code === 1) {
this.pdfImageUrl = response.data.image_url
} else {
throw new Error(response.msg || 'PDF转换失败')
}
} catch (error) {
console.error('PDF预览失败:', error)
uni.showToast({
title: error.message || 'PDF预览失败',
icon: 'none'
})
} finally {
this.pdfPreviewLoading = false
}
},
closePdfPreview() {
this.showPdfPreview = false
this.pdfImageUrl = ''
this.currentPdfUrl = ''
},
retryPreview() {
this.previewPdf(this.currentPdfUrl, this.currentTestId, this.currentPdfIndex)
},
onImageError() {
console.error('图片加载失败')
uni.showToast({
title: '图片加载失败',
icon: 'none'
})
},
async sharePdfImage() {
if (!this.pdfImageUrl) {
uni.showToast({
title: '没有可分享的内容',
icon: 'none'
})
return
}
try {
uni.showActionSheet({
itemList: ['保存到相册', '分享给朋友'],
success: async (res) => {
if (res.tapIndex === 0) {
//
try {
await uni.saveImageToPhotosAlbum({
filePath: this.pdfImageUrl
})
uni.showToast({
title: '已保存到相册',
icon: 'success'
})
} catch (error) {
console.error('保存图片失败:', error)
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
} else if (res.tapIndex === 1) {
//
uni.showToast({
title: '分享功能开发中',
icon: 'none'
})
}
}
})
} catch (error) {
console.error('分享操作失败:', error)
}
},
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')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
},
async sharePhysicalTest() {
try {
uni.showLoading({
title: '生成分享图片...'
})
// API
const response = await apiRoute.generateShareImage({
student_id: this.studentId,
type: 'physical_test'
})
if (response.code === 1) {
uni.hideLoading()
uni.showActionSheet({
itemList: ['保存到相册', '分享给朋友'],
success: async (res) => {
if (res.tapIndex === 0) {
try {
await uni.saveImageToPhotosAlbum({
filePath: response.data.image_url
})
uni.showToast({
title: '已保存到相册',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
} else if (res.tapIndex === 1) {
uni.showToast({
title: '分享功能开发中',
icon: 'none'
})
}
}
})
} else {
throw new Error(response.msg || '生成分享图片失败')
}
} catch (error) {
console.error('分享失败:', error)
uni.hideLoading()
uni.showToast({
title: error.message || '分享失败',
icon: 'none'
})
}
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
background: linear-gradient(135deg, #29D3B4 0%, #1BA297 100%);
padding: 40rpx 32rpx 32rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_back {
width: 60rpx;
.back_icon {
color: #fff;
font-size: 40rpx;
font-weight: 600;
}
}
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
.back_button {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.back_icon {
width: 24rpx;
height: 24rpx;
}
}
.navbar_title {
color: #fff;
font-size: 36rpx;
font-weight: 600;
}
.navbar_action {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.share_button {
background: rgba(255, 255, 255, 0.2);
padding: 12rpx;
border-radius: 50%;
.share_icon {
width: 20rpx;
height: 20rpx;
}
}
}
}
}
//
.student_basic_section {
background: #fff;
padding: 24rpx 32rpx;
border-bottom: 1px solid #f0f0f0;
.student_name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.student_meta {
display: flex;
gap: 16rpx;
.meta_item {
font-size: 24rpx;
color: #666;
background: #f0f0f0;
padding: 6rpx 12rpx;
border-radius: 12rpx;
}
}
}
//
.stats_section {
display: flex;
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
.stat_card {
flex: 1;
text-align: center;
.stat_number {
font-size: 36rpx;
font-weight: 600;
color: #29D3B4;
margin-bottom: 8rpx;
}
.stat_label {
font-size: 24rpx;
color: #666;
}
}
}
//
.test_list_section {
margin: 0 20rpx;
.loading_section, .empty_section {
background: #fff;
border-radius: 16rpx;
padding: 80rpx 32rpx;
text-align: center;
.loading_text, .empty_text {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
}
.empty_icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty_hint {
font-size: 24rpx;
color: #999;
}
}
.test_list {
.test_item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
.test_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.test_date {
font-size: 28rpx;
color: #333;
font-weight: 600;
}
.test_status {
font-size: 24rpx;
color: #29D3B4;
background: rgba(41, 211, 180, 0.1);
padding: 6rpx 16rpx;
border-radius: 12rpx;
}
}
.test_content {
.measurement_row {
display: flex;
gap: 32rpx;
margin-bottom: 24rpx;
.measurement_item {
flex: 1;
text-align: center;
background: #f8f9fa;
padding: 24rpx;
border-radius: 12rpx;
.item_label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.item_value {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
}
}
.pdf_reports {
.reports_title {
font-size: 26rpx;
color: #333;
font-weight: 600;
margin-bottom: 16rpx;
}
.reports_list {
.pdf_item {
display: flex;
align-items: center;
gap: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.pdf_icon {
width: 60rpx;
height: 60rpx;
background: rgba(231, 76, 60, 0.1);
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
.icon_image {
width: 32rpx;
height: 32rpx;
}
}
.pdf_info {
flex: 1;
.pdf_name {
font-size: 26rpx;
color: #333;
font-weight: 500;
margin-bottom: 4rpx;
}
.pdf_action {
font-size: 22rpx;
color: #666;
}
}
.pdf_arrow {
.arrow_icon {
width: 16rpx;
height: 16rpx;
opacity: 0.6;
}
}
}
}
}
}
}
}
}
//
.load_more_section {
padding: 40rpx 20rpx 80rpx;
.load_more_button {
width: 100%;
background: #f8f9fa;
color: #666;
border: 1px solid #e9ecef;
border-radius: 16rpx;
padding: 24rpx 0;
font-size: 28rpx;
&:disabled {
opacity: 0.6;
}
}
}
// PDF
.pdf_preview_popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.preview_content {
background: #fff;
border-radius: 16rpx;
width: 90%;
max-height: 80vh;
overflow: hidden;
.preview_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
.preview_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.preview_close {
font-size: 48rpx;
color: #999;
font-weight: 300;
}
}
.preview_body {
max-height: 60vh;
overflow-y: auto;
.preview_loading, .preview_error {
padding: 80rpx 32rpx;
text-align: center;
.loading_text, .error_text {
font-size: 28rpx;
color: #666;
margin-bottom: 24rpx;
}
.retry_button {
background: #29D3B4;
color: #fff;
border: none;
border-radius: 12rpx;
padding: 16rpx 32rpx;
font-size: 26rpx;
}
}
.preview_image_container {
padding: 20rpx;
.preview_image {
width: 100%;
border-radius: 12rpx;
}
}
}
.preview_footer {
padding: 32rpx;
border-top: 1px solid #f0f0f0;
.share_pdf_button {
width: 100%;
background: linear-gradient(135deg, #29D3B4 0%, #1BA297 100%);
color: #fff;
border: none;
border-radius: 16rpx;
padding: 24rpx 0;
font-size: 28rpx;
font-weight: 600;
&:disabled {
opacity: 0.6;
}
}
}
}
}
</style>

605
uniapp/pages-student/profile/index.vue

@ -0,0 +1,605 @@
<!--学员个人信息管理页面-->
<template>
<view class="main_box">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_back" @click="goBack">
<text class="back_icon"></text>
</view>
<view class="navbar_title">个人信息管理</view>
<view class="navbar_action"></view>
</view>
<!-- 学员头像区域 -->
<view class="avatar_section">
<view class="avatar_container">
<image
:src="studentInfo.headimg || '/static/default-avatar.png'"
class="avatar_image"
mode="aspectFill"
@click="uploadAvatar"
></image>
<view class="avatar_edit_icon" @click="uploadAvatar">
<text class="edit_text"></text>
</view>
</view>
<view class="student_name">{{ studentInfo.name || '学员姓名' }}</view>
<view class="student_basic_info">
<text class="info_tag">{{ studentInfo.gender_text }}</text>
<text class="info_tag">{{ studentInfo.ageText }}</text>
</view>
</view>
<!-- 基本信息表单 -->
<view class="form_section">
<view class="section_title">基本信息</view>
<view class="form_item">
<view class="form_label">姓名</view>
<fui-input
v-model="formData.name"
placeholder="请输入学员姓名"
borderColor="transparent"
backgroundColor="#f8f9fa"
:maxlength="20"
></fui-input>
</view>
<view class="form_item">
<view class="form_label">性别</view>
<fui-input
v-model="genderText"
placeholder="请选择性别"
borderColor="transparent"
backgroundColor="#f8f9fa"
readonly
@click="showGenderPicker = true"
>
<fui-icon name="arrowdown" color="#B2B2B2" :size="40"></fui-icon>
</fui-input>
<fui-picker
:options="genderOptions"
:show="showGenderPicker"
@change="changeGender"
@cancel="showGenderPicker = false"
></fui-picker>
</view>
<view class="form_item">
<view class="form_label">生日</view>
<fui-date-picker
:show="showDatePicker"
v-model="formData.birthday"
@change="changeBirthday"
@cancel="showDatePicker = false"
>
<fui-input
:value="birthdayText"
placeholder="请选择生日"
borderColor="transparent"
backgroundColor="#f8f9fa"
readonly
@click="showDatePicker = true"
>
<fui-icon name="arrowdown" color="#B2B2B2" :size="40"></fui-icon>
</fui-input>
</fui-date-picker>
</view>
<view class="form_item">
<view class="form_label">年龄</view>
<fui-input
:value="ageText"
placeholder="根据生日自动计算"
borderColor="transparent"
backgroundColor="#f0f0f0"
readonly
></fui-input>
</view>
</view>
<!-- 体测信息只读展示 -->
<view class="form_section">
<view class="section_title">体测信息</view>
<view class="form_item">
<view class="form_label">身高 (cm)</view>
<fui-input
:value="physicalTestInfo.height || '暂无数据'"
placeholder="暂无体测数据"
borderColor="transparent"
backgroundColor="#f0f0f0"
readonly
></fui-input>
</view>
<view class="form_item">
<view class="form_label">体重 (kg)</view>
<fui-input
:value="physicalTestInfo.weight || '暂无数据'"
placeholder="暂无体测数据"
borderColor="transparent"
backgroundColor="#f0f0f0"
readonly
></fui-input>
</view>
<view class="form_item" v-if="physicalTestInfo.test_date">
<view class="form_label">体测日期</view>
<fui-input
:value="physicalTestInfo.test_date"
placeholder="暂无体测数据"
borderColor="transparent"
backgroundColor="#f0f0f0"
readonly
></fui-input>
</view>
</view>
<!-- 联系信息 -->
<view class="form_section">
<view class="section_title">紧急联系人</view>
<view class="form_item">
<view class="form_label">联系人姓名</view>
<fui-input
v-model="formData.emergency_contact"
placeholder="请输入紧急联系人姓名"
borderColor="transparent"
backgroundColor="#f8f9fa"
:maxlength="20"
></fui-input>
</view>
<view class="form_item">
<view class="form_label">联系电话</view>
<fui-input
v-model="formData.contact_phone"
placeholder="请输入联系电话"
borderColor="transparent"
backgroundColor="#f8f9fa"
type="number"
:maxlength="11"
></fui-input>
</view>
<view class="form_item">
<view class="form_label">备注信息</view>
<fui-textarea
v-model="formData.note"
placeholder="其他需要说明的信息"
backgroundColor="#f8f9fa"
:maxlength="500"
:rows="4"
></fui-textarea>
</view>
</view>
<!-- 保存按钮 -->
<view class="save_section">
<fui-button
background="#29d3b4"
radius="12rpx"
:loading="saving"
@click="saveStudentInfo"
>
{{ saving ? '保存中...' : '保存信息' }}
</fui-button>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/member.js'
export default {
data() {
return {
studentId: 0,
studentInfo: {},
physicalTestInfo: {}, //
formData: {
name: '',
gender: '',
birthday: '',
emergency_contact: '',
contact_phone: '',
note: ''
},
saving: false,
showGenderPicker: false,
showDatePicker: false,
genderOptions: [
{ value: '1', text: '男' },
{ value: '2', text: '女' }
]
}
},
computed: {
genderText() {
const gender = this.genderOptions.find(item => item.value === this.formData.gender)
return gender ? gender.text : ''
},
birthdayText() {
return this.formData.birthday || ''
},
ageText() {
if (!this.formData.birthday) return ''
const today = new Date()
const birthDate = new Date(this.formData.birthday)
let age = today.getFullYear() - birthDate.getFullYear()
let months = today.getMonth() - birthDate.getMonth()
if (months < 0 || (months === 0 && today.getDate() < birthDate.getDate())) {
age--
months += 12
}
if (months < 0) {
months = 0
}
return age > 0 ? `${age}.${months.toString().padStart(2, '0')}` : `${months}个月`
}
},
onLoad(options) {
this.studentId = parseInt(options.student_id) || 0
if (this.studentId) {
this.loadStudentInfo()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
}
},
methods: {
goBack() {
uni.navigateBack()
},
async loadStudentInfo() {
try {
console.log('加载学员信息:', this.studentId)
// API
const response = await apiRoute.getStudentInfo(this.studentId)
console.log('学员信息API响应:', response)
if (response.code === 1) {
this.studentInfo = response.data.student_info
this.physicalTestInfo = response.data.physical_test_info || {}
//
this.formData = {
name: this.studentInfo.name || '',
gender: String(this.studentInfo.gender || ''),
birthday: this.studentInfo.birthday || '',
emergency_contact: this.studentInfo.emergency_contact || '',
contact_phone: this.studentInfo.contact_phone || '',
note: this.studentInfo.note || ''
}
console.log('学员信息加载成功:', this.studentInfo)
console.log('体测信息加载成功:', this.physicalTestInfo)
} else {
uni.showToast({
title: response.msg || '获取学员信息失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取学员信息失败:', error)
uni.showToast({
title: '获取学员信息失败',
icon: 'none'
})
}
},
changeGender(e) {
this.formData.gender = e.value
this.showGenderPicker = false
},
changeBirthday(e) {
this.formData.birthday = e.result
this.showDatePicker = false
},
async uploadAvatar() {
try {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0]
console.log('选择的图片:', tempFilePath)
//
uni.showLoading({
title: '上传中...'
})
try {
// APItoken
const response = await apiRoute.uploadAvatarForAdd(tempFilePath)
console.log('头像上传API响应:', response)
if (response.code === 1 && response.data && response.data.url) {
//
this.studentInfo.headimg = response.data.url
//
await this.saveAvatarToDatabase(response.data.url)
uni.showToast({
title: '头像上传成功',
icon: 'success'
})
} else {
throw new Error(response.msg || '上传接口返回数据异常')
}
} catch (uploadError) {
console.error('头像上传失败:', uploadError)
uni.showToast({
title: uploadError.message || '头像上传失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
fail: (error) => {
console.error('选择图片失败:', error)
uni.showToast({
title: '选择图片失败',
icon: 'none'
})
}
})
} catch (error) {
console.error('选择头像失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
},
async saveAvatarToDatabase(avatarUrl) {
try {
// name
const updateData = {
student_id: this.studentId,
name: this.formData.name || this.studentInfo.name,
gender: this.formData.gender || String(this.studentInfo.gender || ''),
headimg: avatarUrl
}
console.log('保存头像到数据库:', updateData)
// API
const response = await apiRoute.updateStudentInfo(updateData)
console.log('保存头像API响应:', response)
if (response.code !== 1) {
throw new Error(response.msg || '保存头像失败')
}
} catch (error) {
console.error('保存头像到数据库失败:', error)
throw error
}
},
async saveStudentInfo() {
//
if (!this.formData.name.trim()) {
uni.showToast({
title: '请输入学员姓名',
icon: 'none'
})
return
}
if (!this.formData.gender) {
uni.showToast({
title: '请选择性别',
icon: 'none'
})
return
}
//
if (this.formData.contact_phone && !/^1[3-9]\d{9}$/.test(this.formData.contact_phone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return
}
this.saving = true
try {
const updateData = {
student_id: this.studentId,
...this.formData
}
console.log('保存学员信息:', updateData)
// API
const response = await apiRoute.updateStudentInfo(updateData)
console.log('更新学员信息API响应:', response)
if (response.code === 1) {
//
Object.assign(this.studentInfo, this.formData)
uni.showToast({
title: '保存成功',
icon: 'success'
})
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: response.message || '保存失败',
icon: 'none'
})
}
} catch (error) {
console.error('保存学员信息失败:', error)
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
})
} finally {
this.saving = false
}
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
display: flex;
justify-content: space-between;
align-items: center;
background: #29D3B4;
padding: 40rpx 32rpx 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_back {
width: 60rpx;
.back_icon {
color: #fff;
font-size: 40rpx;
font-weight: 600;
}
}
.navbar_title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.navbar_action {
width: 60rpx;
}
}
//
.avatar_section {
background: #fff;
padding: 40rpx 32rpx;
text-align: center;
.avatar_container {
position: relative;
display: inline-block;
margin-bottom: 24rpx;
.avatar_image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 4rpx solid #f0f0f0;
}
.avatar_edit_icon {
position: absolute;
bottom: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background: #29d3b4;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #fff;
.edit_text {
color: #fff;
font-size: 20rpx;
}
}
}
.student_name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.student_basic_info {
.info_tag {
display: inline-block;
font-size: 22rpx;
color: #666;
background: #f0f0f0;
padding: 6rpx 16rpx;
border-radius: 16rpx;
margin: 0 8rpx;
}
}
}
//
.form_section {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 32rpx;
.section_title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 32rpx;
padding-bottom: 16rpx;
border-bottom: 1px solid #f0f0f0;
}
.form_item {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.form_label {
font-size: 26rpx;
color: #333;
margin-bottom: 12rpx;
font-weight: 500;
}
}
}
//
.save_section {
padding: 40rpx 32rpx;
padding-bottom: 80rpx;
}
</style>

933
uniapp/pages-student/schedule/index.vue

@ -0,0 +1,933 @@
<!--学员课程安排页面-->
<template>
<view class="main_box">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_back" @click="goBack">
<text class="back_icon"></text>
</view>
<view class="navbar_title">课程安排</view>
<view class="navbar_action">
<view class="today_button" @click="goToToday">
<text class="today_text">今天</text>
</view>
</view>
</view>
<!-- 学员信息 -->
<view class="student_info_section" v-if="studentInfo">
<view class="student_name">{{ studentInfo.name }}</view>
<view class="schedule_stats">
<text class="stat_item">本周课程{{ weeklyStats.total_courses }}</text>
<text class="stat_item">已完成{{ weeklyStats.completed_courses }}</text>
</view>
</view>
<!-- 日历切换 -->
<view class="calendar_section">
<view class="calendar_header">
<view class="month_controls">
<view class="control_button" @click="prevWeek"></view>
<view class="current_period">{{ currentWeekText }}</view>
<view class="control_button" @click="nextWeek"></view>
</view>
</view>
<view class="week_tabs">
<view
v-for="day in weekDays"
:key="day.date"
:class="['week_tab', selectedDate === day.date ? 'active' : '', day.isToday ? 'today' : '']"
@click="selectDate(day.date)"
>
<view class="tab_weekday">{{ day.weekday }}</view>
<view class="tab_date">{{ day.day }}</view>
<view class="tab_indicator" v-if="day.hasCourse"></view>
</view>
</view>
</view>
<!-- 课程列表 -->
<view class="courses_section">
<view v-if="loading" class="loading_section">
<view class="loading_text">加载中...</view>
</view>
<view v-else-if="dailyCourses.length === 0" class="empty_section">
<view class="empty_icon">📅</view>
<view class="empty_text">当日暂无课程安排</view>
<view class="empty_hint">选择其他日期查看课程</view>
</view>
<view v-else class="courses_list">
<view
v-for="course in dailyCourses"
:key="course.id"
:class="['course_item', course.status]"
@click="viewCourseDetail(course)"
>
<view class="course_time">
<view class="time_range">{{ course.start_time }}</view>
<view class="time_duration">{{ course.duration }}分钟</view>
</view>
<view class="course_info">
<view class="course_name">{{ course.course_name }}</view>
<view class="course_details">
<view class="detail_row">
<text class="detail_label">教练</text>
<text class="detail_value">{{ course.coach_name }}</text>
</view>
<view class="detail_row">
<text class="detail_label">场地</text>
<text class="detail_value">{{ course.venue_name }}</text>
</view>
</view>
</view>
<view class="course_status">
<view :class="['status_badge', course.status]">
{{ getStatusText(course.status) }}
</view>
</view>
</view>
</view>
</view>
<!-- 课程详情弹窗 -->
<view class="course_popup" v-if="showCoursePopup" @click="closeCoursePopup">
<view class="popup_content" @click.stop>
<view class="popup_header">
<view class="popup_title">课程详情</view>
<view class="popup_close" @click="closeCoursePopup">×</view>
</view>
<view class="popup_course_detail" v-if="selectedCourse">
<view class="detail_section">
<view class="section_title">基本信息</view>
<view class="info_grid">
<view class="info_row">
<text class="info_label">课程名称</text>
<text class="info_value">{{ selectedCourse.course_name }}</text>
</view>
<view class="info_row">
<text class="info_label">上课时间</text>
<text class="info_value">{{ formatDateTime(selectedCourse.course_date, selectedCourse.start_time) }}</text>
</view>
<view class="info_row">
<text class="info_label">课程时长</text>
<text class="info_value">{{ selectedCourse.duration }}分钟</text>
</view>
<view class="info_row">
<text class="info_label">授课教练</text>
<text class="info_value">{{ selectedCourse.coach_name }}</text>
</view>
<view class="info_row">
<text class="info_label">上课地点</text>
<text class="info_value">{{ selectedCourse.venue_name }}</text>
</view>
</view>
</view>
<view class="detail_section" v-if="selectedCourse.course_description">
<view class="section_title">课程介绍</view>
<view class="course_description">{{ selectedCourse.course_description }}</view>
</view>
<view class="detail_section" v-if="selectedCourse.preparation_items">
<view class="section_title">课前准备</view>
<view class="preparation_list">
<view
v-for="item in selectedCourse.preparation_items"
:key="item"
class="preparation_item"
>
{{ item }}
</view>
</view>
</view>
</view>
<view class="popup_actions">
<fui-button
v-if="selectedCourse && selectedCourse.status === 'scheduled'"
background="#f39c12"
@click="requestLeave"
>
请假
</fui-button>
<fui-button
background="#f8f9fa"
color="#666"
@click="closeCoursePopup"
>
关闭
</fui-button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
export default {
data() {
return {
studentId: 0,
studentInfo: {},
selectedDate: '',
currentWeekStart: new Date(),
weekDays: [],
dailyCourses: [],
weeklyStats: {},
loading: false,
showCoursePopup: false,
selectedCourse: null
}
},
computed: {
currentWeekText() {
const start = new Date(this.currentWeekStart)
const end = new Date(start)
end.setDate(start.getDate() + 6)
const startMonth = start.getMonth() + 1
const startDay = start.getDate()
const endMonth = end.getMonth() + 1
const endDay = end.getDate()
if (startMonth === endMonth) {
return `${startMonth}${startDay}日-${endDay}`
} else {
return `${startMonth}${startDay}日-${endMonth}${endDay}`
}
}
},
onLoad(options) {
this.studentId = parseInt(options.student_id) || 0
if (this.studentId) {
this.initPage()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
}
},
methods: {
goBack() {
uni.navigateBack()
},
async initPage() {
await this.loadStudentInfo()
this.generateWeekDays()
//
const today = this.formatDateString(new Date())
this.selectedDate = today
await this.loadDailyCourses()
await this.loadWeeklyStats()
},
async loadStudentInfo() {
try {
//
const mockStudentInfo = {
id: this.studentId,
name: '小明'
}
this.studentInfo = mockStudentInfo
} catch (error) {
console.error('获取学员信息失败:', error)
}
},
generateWeekDays() {
const days = []
const startDate = new Date(this.currentWeekStart)
const today = new Date()
const todayStr = this.formatDateString(today)
for (let i = 0; i < 7; i++) {
const date = new Date(startDate)
date.setDate(startDate.getDate() + i)
const dateStr = this.formatDateString(date)
days.push({
date: dateStr,
day: date.getDate(),
weekday: this.getWeekday(date.getDay()),
isToday: dateStr === todayStr,
hasCourse: false //
})
}
this.weekDays = days
},
prevWeek() {
this.currentWeekStart.setDate(this.currentWeekStart.getDate() - 7)
this.generateWeekDays()
this.loadDailyCourses()
this.loadWeeklyStats()
},
nextWeek() {
this.currentWeekStart.setDate(this.currentWeekStart.getDate() + 7)
this.generateWeekDays()
this.loadDailyCourses()
this.loadWeeklyStats()
},
goToToday() {
const today = new Date()
this.currentWeekStart = this.getWeekStartDate(today)
this.generateWeekDays()
this.selectedDate = this.formatDateString(today)
this.loadDailyCourses()
this.loadWeeklyStats()
},
selectDate(date) {
this.selectedDate = date
this.loadDailyCourses()
},
async loadDailyCourses() {
if (!this.selectedDate) return
this.loading = true
try {
console.log('加载课程安排:', this.selectedDate, '学员ID:', this.studentId)
// API
const response = await apiRoute.getCourseScheduleList({
student_id: this.studentId,
date: this.selectedDate
})
console.log('课程安排API响应:', response)
if (response.code === 1) {
// API
const courses = response.data.list || []
this.dailyCourses = courses.map(course => ({
id: course.id,
course_date: course.course_date,
start_time: course.start_time,
end_time: course.end_time,
duration: course.duration,
course_name: course.course_name,
course_description: course.course_description,
coach_name: course.coach_name,
venue_name: course.venue_name,
status: this.mapApiStatusToFrontend(course.status),
preparation_items: course.preparation_items || []
}))
//
this.updateWeekCourseIndicators()
console.log('课程数据加载成功:', this.dailyCourses)
} else {
console.warn('API返回错误,使用模拟数据:', response.msg)
// API使
this.loadMockCourseData()
}
} catch (error) {
console.error('获取课程安排失败:', error)
console.warn('API调用失败,使用模拟数据')
// API使
this.loadMockCourseData()
} finally {
this.loading = false
}
},
//
loadMockCourseData() {
const mockCourses = [
{
id: 1,
course_date: this.selectedDate,
start_time: '09:00',
end_time: '10:00',
duration: 60,
course_name: '基础体能训练',
course_description: '通过基础的体能训练动作,提升学员的身体素质,包括力量、耐力、协调性等方面的训练。',
coach_name: '张教练',
venue_name: '训练馆A',
status: 'scheduled',
preparation_items: ['运动服装', '运动鞋', '毛巾', '水杯']
},
{
id: 2,
course_date: this.selectedDate,
start_time: '14:00',
end_time: '15:30',
duration: 90,
course_name: '专项技能训练',
course_description: '针对特定运动项目进行专项技能训练,提高学员在该项目上的技术水平。',
coach_name: '李教练',
venue_name: '训练馆B',
status: 'completed',
preparation_items: ['专项器材', '护具', '运动服装']
}
]
this.dailyCourses = mockCourses
this.updateWeekCourseIndicators()
},
// API
mapApiStatusToFrontend(apiStatus) {
const statusMap = {
0: 'scheduled', //
1: 'completed', //
2: 'leave_requested', //
3: 'cancelled' //
}
return statusMap[apiStatus] || 'scheduled'
},
async loadWeeklyStats() {
try {
//
const mockStats = {
total_courses: 8,
completed_courses: 3,
scheduled_courses: 4,
cancelled_courses: 1
}
this.weeklyStats = mockStats
} catch (error) {
console.error('获取周统计失败:', error)
}
},
updateWeekCourseIndicators() {
//
this.weekDays.forEach(day => {
//
day.hasCourse = day.date === this.selectedDate && this.dailyCourses.length > 0
})
},
viewCourseDetail(course) {
this.selectedCourse = course
this.showCoursePopup = true
},
closeCoursePopup() {
this.showCoursePopup = false
this.selectedCourse = null
},
async requestLeave() {
if (!this.selectedCourse) return
uni.showModal({
title: '确认请假',
content: '确定要为此课程申请请假吗?',
success: async (res) => {
if (res.confirm) {
try {
console.log('申请请假:', this.selectedCourse.id)
// API
const response = await apiRoute.requestCourseLeave({
schedule_id: this.selectedCourse.id,
reason: '学员申请请假'
})
console.log('请假申请API响应:', response)
if (response.code === 1) {
uni.showToast({
title: '请假申请已提交',
icon: 'success'
})
//
const courseIndex = this.dailyCourses.findIndex(c => c.id === this.selectedCourse.id)
if (courseIndex !== -1) {
this.dailyCourses[courseIndex].status = 'leave_requested'
}
this.closeCoursePopup()
} else {
uni.showToast({
title: response.msg || '请假申请失败',
icon: 'none'
})
}
} catch (error) {
console.error('请假申请失败:', error)
uni.showToast({
title: '请假申请失败',
icon: 'none'
})
}
}
}
})
},
//
formatDateString(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}`
},
formatDateTime(dateString, timeString) {
const date = new Date(dateString)
const month = date.getMonth() + 1
const day = date.getDate()
const weekday = this.getWeekday(date.getDay())
return `${month}${day}${weekday} ${timeString}`
},
getWeekday(dayIndex) {
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
return weekdays[dayIndex]
},
getWeekStartDate(date) {
const start = new Date(date)
const day = start.getDay()
const diff = start.getDate() - day
start.setDate(diff)
return start
},
getStatusText(status) {
const statusMap = {
'scheduled': '待上课',
'completed': '已完成',
'cancelled': '已取消',
'leave_requested': '请假中'
}
return statusMap[status] || status
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
display: flex;
justify-content: space-between;
align-items: center;
background: #29D3B4;
padding: 40rpx 32rpx 20rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_back {
width: 60rpx;
.back_icon {
color: #fff;
font-size: 40rpx;
font-weight: 600;
}
}
.navbar_title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.navbar_action {
width: 80rpx;
display: flex;
justify-content: flex-end;
.today_button {
background: rgba(255, 255, 255, 0.2);
padding: 8rpx 16rpx;
border-radius: 16rpx;
.today_text {
color: #fff;
font-size: 24rpx;
}
}
}
}
//
.student_info_section {
background: #fff;
padding: 24rpx 32rpx;
border-bottom: 1px solid #f0f0f0;
.student_name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.schedule_stats {
display: flex;
gap: 24rpx;
.stat_item {
font-size: 24rpx;
color: #666;
}
}
}
//
.calendar_section {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 24rpx 32rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
.calendar_header {
margin-bottom: 24rpx;
.month_controls {
display: flex;
justify-content: center;
align-items: center;
gap: 32rpx;
.control_button {
width: 40rpx;
height: 40rpx;
background: #f8f9fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #666;
}
.current_period {
font-size: 28rpx;
font-weight: 600;
color: #333;
min-width: 200rpx;
text-align: center;
}
}
}
.week_tabs {
display: flex;
justify-content: space-between;
.week_tab {
flex: 1;
text-align: center;
padding: 16rpx 8rpx;
border-radius: 12rpx;
position: relative;
&.active {
background: #29D3B4;
color: #fff;
}
&.today {
background: rgba(41, 211, 180, 0.1);
&.active {
background: #29D3B4;
}
}
.tab_weekday {
font-size: 22rpx;
margin-bottom: 4rpx;
opacity: 0.8;
}
.tab_date {
font-size: 28rpx;
font-weight: 600;
}
.tab_indicator {
position: absolute;
bottom: 4rpx;
left: 50%;
transform: translateX(-50%);
width: 6rpx;
height: 6rpx;
background: #ff4757;
border-radius: 50%;
}
}
}
}
//
.courses_section {
margin: 0 20rpx;
.loading_section, .empty_section {
background: #fff;
border-radius: 16rpx;
padding: 80rpx 32rpx;
text-align: center;
.loading_text, .empty_text {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
}
.empty_icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty_hint {
font-size: 24rpx;
color: #999;
}
}
.courses_list {
.course_item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
&.scheduled {
border-left: 4rpx solid #29D3B4;
}
&.completed {
background: #f8f9fa;
border-left: 4rpx solid #27ae60;
}
&.cancelled {
background: #f8f9fa;
border-left: 4rpx solid #e74c3c;
opacity: 0.7;
}
&.leave_requested {
background: #f8f9fa;
border-left: 4rpx solid #f39c12;
}
.course_time {
min-width: 100rpx;
text-align: center;
.time_range {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 4rpx;
}
.time_duration {
font-size: 22rpx;
color: #999;
}
}
.course_info {
flex: 1;
.course_name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.course_details {
.detail_row {
display: flex;
margin-bottom: 6rpx;
.detail_label {
font-size: 24rpx;
color: #666;
min-width: 80rpx;
}
.detail_value {
font-size: 24rpx;
color: #333;
}
}
}
}
.course_status {
.status_badge {
font-size: 22rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
&.scheduled {
color: #29D3B4;
background: rgba(41, 211, 180, 0.1);
}
&.completed {
color: #27ae60;
background: rgba(39, 174, 96, 0.1);
}
&.cancelled {
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
}
&.leave_requested {
color: #f39c12;
background: rgba(243, 156, 18, 0.1);
}
}
}
}
}
}
//
.course_popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.popup_content {
background: #fff;
border-radius: 16rpx;
width: 90%;
max-height: 80vh;
overflow: hidden;
.popup_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
.popup_title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup_close {
font-size: 48rpx;
color: #999;
font-weight: 300;
}
}
.popup_course_detail {
padding: 32rpx;
max-height: 60vh;
overflow-y: auto;
.detail_section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.section_title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
padding-bottom: 8rpx;
border-bottom: 2rpx solid #29D3B4;
}
.info_grid {
.info_row {
display: flex;
margin-bottom: 12rpx;
.info_label {
font-size: 26rpx;
color: #666;
min-width: 120rpx;
}
.info_value {
font-size: 26rpx;
color: #333;
flex: 1;
}
}
}
.course_description {
font-size: 26rpx;
color: #333;
line-height: 1.6;
}
.preparation_list {
.preparation_item {
font-size: 24rpx;
color: #666;
line-height: 1.5;
margin-bottom: 8rpx;
}
}
}
}
.popup_actions {
padding: 24rpx 32rpx;
display: flex;
gap: 16rpx;
border-top: 1px solid #f0f0f0;
fui-button {
flex: 1;
}
}
}
}
</style>

254
uniapp/pages-student/settings/index.vue

@ -0,0 +1,254 @@
<!--学员端系统设置页面-->
<template>
<view class="settings_container">
<!-- 自定义导航栏 -->
<view class="navbar_section">
<view class="navbar_content">
<view class="back_button" @click="goBack">
<image src="/static/icon-img/back.png" class="back_icon"></image>
</view>
<view class="navbar_title">系统设置</view>
<view class="navbar_placeholder"></view>
</view>
</view>
<!-- 设置选项列表 -->
<view class="settings_section">
<view class="setting_item" @click="navigateToProfile">
<view class="setting_left">
<view class="setting_icon profile_setting">
<image src="/static/icon-img/profile.png" class="icon_image"></image>
</view>
<view class="setting_text">个人资料</view>
</view>
<view class="setting_arrow">
<image src="/static/icon-img/arrow-right.png" class="arrow_icon"></image>
</view>
</view>
<view class="setting_item" @click="navigateToPassword">
<view class="setting_left">
<view class="setting_icon password_setting">
<image src="/static/icon-img/lock.png" class="icon_image"></image>
</view>
<view class="setting_text">修改密码</view>
</view>
<view class="setting_arrow">
<image src="/static/icon-img/arrow-right.png" class="arrow_icon"></image>
</view>
</view>
<view class="setting_item" @click="navigateToAbout">
<view class="setting_left">
<view class="setting_icon about_setting">
<image src="/static/icon-img/info.png" class="icon_image"></image>
</view>
<view class="setting_text">关于我们</view>
</view>
<view class="setting_arrow">
<image src="/static/icon-img/arrow-right.png" class="arrow_icon"></image>
</view>
</view>
<view class="setting_item" @click="navigateToPrivacy">
<view class="setting_left">
<view class="setting_icon privacy_setting">
<image src="/static/icon-img/shield.png" class="icon_image"></image>
</view>
<view class="setting_text">隐私协议</view>
</view>
<view class="setting_arrow">
<image src="/static/icon-img/arrow-right.png" class="arrow_icon"></image>
</view>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout_section">
<button class="logout_button" @click="logout">退出登录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
goBack() {
uni.navigateBack()
},
navigateToProfile() {
uni.navigateTo({
url: '/pages-common/profile/personal_info'
})
},
navigateToPassword() {
uni.navigateTo({
url: '/pages-student/my/update_pass'
})
},
navigateToAbout() {
uni.navigateTo({
url: '/pages-student/settings/about'
})
},
navigateToPrivacy() {
uni.navigateTo({
url: '/pages-common/privacy_agreement'
})
},
logout() {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
//
uni.clearStorageSync()
//
uni.reLaunch({
url: '/pages-student/login/login'
})
}
}
})
}
}
}
</script>
<style lang="less" scoped>
.settings_container {
background: #f8f9fa;
min-height: 100vh;
}
//
.navbar_section {
background: linear-gradient(135deg, #29D3B4 0%, #1BA297 100%);
padding: 40rpx 32rpx 32rpx;
//
// #ifdef MP-WEIXIN
padding-top: 80rpx;
// #endif
.navbar_content {
display: flex;
align-items: center;
justify-content: space-between;
.back_button {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.back_icon {
width: 24rpx;
height: 24rpx;
}
}
.navbar_title {
color: #fff;
font-size: 36rpx;
font-weight: 600;
}
.navbar_placeholder {
width: 40rpx;
}
}
}
//
.settings_section {
margin: 32rpx 20rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
.setting_item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.setting_left {
display: flex;
align-items: center;
gap: 24rpx;
.setting_icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.profile_setting { background: rgba(52, 152, 219, 0.15); }
&.password_setting { background: rgba(231, 76, 60, 0.15); }
&.about_setting { background: rgba(155, 89, 182, 0.15); }
&.privacy_setting { background: rgba(46, 204, 113, 0.15); }
.icon_image {
width: 32rpx;
height: 32rpx;
}
}
.setting_text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
.setting_arrow {
.arrow_icon {
width: 20rpx;
height: 20rpx;
opacity: 0.6;
}
}
}
}
// 退
.logout_section {
margin: 60rpx 20rpx;
.logout_button {
width: 100%;
background: #ff4757;
color: #fff;
border: none;
border-radius: 16rpx;
padding: 24rpx 0;
font-size: 28rpx;
font-weight: 600;
&:active {
background: #ff3742;
}
}
}
</style>

1134
uniapp/pages.json

File diff suppressed because it is too large

601
uniapp/pages.json.backup

@ -0,0 +1,601 @@
{
"pages": [
{
"path": "pages/student/login/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/home/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/home/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
}
{
"path": "pages/student/my/set_up",
"style": {
"navigationBarTitleText": "设置",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29D3B4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/my/update_pass",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/student/my/personal_data",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#333333",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/profile/index",
"style": {
"navigationBarTitleText": "个人信息管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/physical-test/index",
"style": {
"navigationBarTitleText": "体测数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/schedule/index",
"style": {
"navigationBarTitleText": "课程安排",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/course-booking/index",
"style": {
"navigationBarTitleText": "课程预约",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/orders/index",
"style": {
"navigationBarTitleText": "订单管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/contracts/index",
"style": {
"navigationBarTitleText": "合同管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/knowledge/index",
"style": {
"navigationBarTitleText": "知识库",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/messages/index",
"style": {
"navigationBarTitleText": "消息管理",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/settings/index",
"style": {
"navigationBarTitleText": "系统设置",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/child/add",
"style": {
"navigationBarTitleText": "添加孩子",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/privacy_agreement",
"style": {
"navigationBarTitleText": "隐私协议",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/common/my_message",
"style": {
"navigationBarTitleText": "我的消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/im_chat_info",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/sys_msg_list",
"style": {
"navigationBarTitleText": "系统消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/article_info",
"style": {
"navigationBarTitleText": "文章详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/feedback",
"style": {
"navigationBarTitleText": "意见反馈",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/my_attendance",
"style": {
"navigationBarTitleText": "我的考勤",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/personnel/add_personnel",
"style": {
"navigationBarTitleText": "新员工信息填写",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/common/contract/my_contract",
"style": {
"navigationBarTitleText": "我的合同",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
}
},
{
"path": "pages/common/contract/contract_detail",
"style": {
"navigationBarTitleText": "合同详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/student/student_list",
"style": {
"navigationBarTitleText": "我的学员",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/teaching_management",
"style": {
"navigationBarTitleText": "教研管理列表",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#171717",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/gotake_exam",
"style": {
"navigationBarTitleText": "考试",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#171717",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/my/exam_results",
"style": {
"navigationBarTitleText": "考试结果",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/coach/my/salary",
"style": {
"navigationBarTitleText": "我的工资",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/add_clues",
"style": {
"navigationBarTitleText": "添加客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/edit_clues",
"style": {
"navigationBarTitleText": "编辑客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/edit_clues_log",
"style": {
"navigationBarTitleText": "修改记录",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/clue_info",
"style": {
"navigationBarTitleText": "客户详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/clue/index",
"style": {
"navigationBarTitleText": "线索",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/index/index",
"style": {
"navigationBarTitleText": "销售数据"
}
},
{
"path": "pages/market/my/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/my/signed_client_list",
"style": {
"navigationBarTitleText": "已签客户",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/info",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/set_up",
"style": {
"navigationBarTitleText": "设置",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/update_pass",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/market/my/my_data",
"style": {
"navigationBarTitleText": "我的数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/dept_data",
"style": {
"navigationBarTitleText": "部门数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/my/campus_data",
"style": {
"navigationBarTitleText": "校区数据",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/class_arrangement",
"style": {
"navigationBarTitleText": "课程安排",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#232323",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/class_arrangement_detail",
"style": {
"navigationBarTitleText": "课程安排详情",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#232323",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/reimbursement/list",
"style": {
"navigationBarTitleText": "报销列表",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/reimbursement/add",
"style": {
"navigationBarTitleText": "新增报销",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/reimbursement/detail",
"style": {
"navigationBarTitleText": "报销详情",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/data/statistics",
"style": {
"navigationBarTitleText": "市场数据统计",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/schedule_table",
"style": {
"navigationBarTitleText": "课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/add_schedule",
"style": {
"navigationBarTitleText": "添加课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/adjust_course",
"style": {
"navigationBarTitleText": "调整课程安排",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/coach/schedule/sign_in",
"style": {
"navigationBarTitleText": "课程点名",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/clue_table",
"style": {
"navigationBarTitleText": "数据统计",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/home/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/profile/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
}
,
{
"path": "pages/common/profile/personal_info",
"style": {
"navigationBarTitleText": "个人资料",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
}
,
{
"path": "pages/contract/list",
"style": {
"navigationBarTitleText": "我的合同",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white",
"backgroundColor": "#181A20"
}
},
{
"path": "pages/contract/fill",
"style": {
"navigationBarTitleText": "填写信息",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white",
"backgroundColor": "#181A20"
}
},
{
"path": "pages/contract/detail",
"style": {
"navigationBarTitleText": "合同详情",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white",
"backgroundColor": "#181A20"
}
},
{
"path": "pages/common/contract/contract_sign",
"style": {
"navigationBarTitleText": "电子签名",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white",
"backgroundColor": "#181A20"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#181A20",
"backgroundColor": "#FDFDFD",
"enablePullDownRefresh": true
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#29d3b4",
"borderStyle": "black",
"backgroundColor": "#1a1a1a",
"list": [
{
"pagePath": "pages/common/home/index",
"iconPath": "static/icon-img/home.png",
"selectedIconPath": "static/icon-img/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/common/profile/index",
"iconPath": "static/icon-img/profile.png",
"selectedIconPath": "static/icon-img/profile-active.png",
"text": "我的"
}
]
},
"easycom": {
"autoscan": true,
"custom": {
"fui-(.*)": "@/components/firstui/fui-$1/fui-$1.vue",
"uni-icons": "@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue",
"uni-calendar": "@/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue",
"uni-file-picker": "@/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue"
}
}
}

32
uniapp/pages/coach/home/index.vue

@ -250,60 +250,60 @@ export default {
//-
openObjAddView(){
this.$navigateTo({
url: '/pages/coach/job/add'
uni.navigateTo({
url: '/pages-coach/coach/job/add'
})
},
//
openObjListView(){
this.$navigateTo({
url: '/pages/coach/job/list'
uni.navigateTo({
url: '/pages-coach/coach/job/list'
})
},
//-
openViewCourseInfoList(item){
let id = item.id
this.$navigateTo({
url: `/pages/coach/course/info_list?id=${id}`
uni.navigateTo({
url: `/pages-coach/coach/course/info_list?id=${id}`
})
},
//-
openViewWorkDetails(item){
let id = item.id
this.$navigateTo({
url: `/pages/coach/student/work_details?id=${id}`
uni.navigateTo({
url: `/pages-coach/coach/student/work_details?id=${id}`
})
},
//
openServiceListView() {
this.$navigateTo({
url: '/pages/coach/my/service_list'
uni.navigateTo({
url: '/pages-coach/coach/my/service_list'
})
},
//
viewServiceDetail(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/my/service_detail?id=${id}`
uni.navigateTo({
url: `/pages-coach/coach/my/service_detail?id=${id}`
})
},
//
goToStudentList() {
this.$navigateTo({
url: '/pages/coach/student/student_list'
uni.navigateTo({
url: '/pages-coach/coach/student/student_list'
})
},
//
goToClassList() {
this.$navigateTo({
url: '/pages/coach/class/list'
uni.navigateTo({
url: '/pages-coach/coach/class/list'
})
},

8
uniapp/pages/coach/my/due_soon.vue

@ -81,14 +81,14 @@
},
//
openViewCourseInfo(item) {
this.$navigateTo({
url: '/pages/coach/course/info'
uni.navigateTo({
url: '/pages-coach/coach/course/info'
})
},
//
openViewStudentInfo(item) {
this.$navigateTo({
url: '/pages/coach/student/info'
uni.navigateTo({
url: '/pages-coach/coach/student/info'
})
},
}

4
uniapp/pages/coach/my/exam_results.vue

@ -34,8 +34,8 @@
},
methods: {
back() {
this.$navigateTo({
url: '/pages/coach/my/teaching_management'
uni.navigateTo({
url: '/pages-coach/coach/my/teaching_management'
})
}
}

4
uniapp/pages/coach/my/gotake_exam.vue

@ -105,8 +105,8 @@
})
const res = await apiRoute.submitTestPaper({optionList: this.optionList,testPaperId: this.testPaperId, id: this.zid});
if(res.code === 1) {
this.$navigateTo({
url: '/pages/coach/my/exam_results?error=' + res.data.error + '&success=' + res.data.success + '&num=' + res.data.num
uni.navigateTo({
url: '/pages-coach/coach/my/exam_results?error=' + res.data.error + '&success=' + res.data.success + '&num=' + res.data.num
})
} else {
uni.showToast({

4
uniapp/pages/coach/my/schooling_statistics.vue

@ -141,8 +141,8 @@ export default {
//
openViewCourseInfo(item){
this.$navigateTo({
url: '/pages/coach/course/info'
uni.navigateTo({
url: '/pages-coach/coach/course/info'
})
},
}

8
uniapp/pages/coach/my/set_up.vue

@ -27,13 +27,13 @@
},
privacy_agreement(type){
this.$navigateTo({
url: '/pages/common/privacy_agreement?type='+type
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
})
},
update_pass(){
this.$navigateTo({
url: '/pages/coach/my/update_pass'
uni.navigateTo({
url: '/pages-coach/coach/my/update_pass'
})
}
}

8
uniapp/pages/coach/my/teaching_management.vue

@ -104,8 +104,8 @@
return this.tableTypeName[text]
},
info(id) {
this.$navigateTo({
url: '/pages/coach/my/teaching_management_info?id=' + id
uni.navigateTo({
url: '/pages-coach/coach/my/teaching_management_info?id=' + id
})
},
onReachBottom() {
@ -127,8 +127,8 @@
})
},
goTake(id,zid) {
this.$navigateTo({
url: '/pages/coach/my/gotake_exam?id=' + id + '&zid=' + zid
uni.navigateTo({
url: '/pages-coach/coach/my/gotake_exam?id=' + id + '&zid=' + zid
})
}
}

4
uniapp/pages/coach/my/update_pass.vue

@ -123,7 +123,7 @@ import apiRoute from '@/api/apiRoute.js';
},
//
forgot() {
this.$navigateTo({
uni.navigateTo({
url: '/pages/student/login/forgot'
})
},
@ -180,7 +180,7 @@ import apiRoute from '@/api/apiRoute.js';
//-
//
uni.redirectTo({
url: `/pages/coach/my/index`
url: `/pages-coach/coach/my/index`
})
}, 1000)
},

16
uniapp/pages/coach/schedule/schedule_table.vue

@ -1266,7 +1266,7 @@ export default {
//
openAddCourseForm(timeSlot, date, teacherId = null, venueId = null, classId = null) {
let url = `/pages/coach/schedule/add_schedule?date=${date.date}`;
let url = `/pages-coach/coach/schedule/add_schedule?date=${date.date}`;
//
if (timeSlot && timeSlot.time) {
@ -1286,14 +1286,14 @@ export default {
}
//
this.$navigateTo({ url });
uni.navigateTo({ url });
},
//
addCourse() {
//
this.$navigateTo({
url: '/pages/coach/schedule/add_schedule',
uni.navigateTo({
url: '/pages-coach/coach/schedule/add_schedule',
})
},
@ -1306,22 +1306,22 @@ export default {
//
handleEditCourse(data) {
this.$navigateTo({
url: `/pages/coach/schedule/adjust_course?id=${data.scheduleId}`,
uni.navigateTo({
url: `/pages-coach/coach/schedule/adjust_course?id=${data.scheduleId}`,
})
},
//
handleAddNewCourse(data) {
const { date, timeSlot } = data;
let url = `/pages/coach/schedule/add_schedule?date=${date}`;
let url = `/pages-coach/coach/schedule/add_schedule?date=${date}`;
if (timeSlot) {
const [startTime] = timeSlot.split('-');
url += `&time=${startTime}&time_slot=${timeSlot}`;
}
this.$navigateTo({ url });
uni.navigateTo({ url });
},
//

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

@ -212,7 +212,7 @@
}
},
goToDetail(student) {
this.$navigateToPage(`/pages/market/clue/clue_info`, {
this.$navigateToPage(`/pages-market/clue/clue_info`, {
resource_sharing_id: student.resource_sharing_id
});
},

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

@ -209,7 +209,7 @@ export default {
//
goToSign() {
uni.navigateTo({
url: `/pages/common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent(this.contractData.contract_name)}`
url: `/pages-common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent(this.contractData.contract_name)}`
})
},

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

@ -194,14 +194,14 @@ export default {
//
goToDetail(contract) {
uni.navigateTo({
url: `/pages/common/contract/contract_detail?id=${contract.id}`
url: `/pages-common/contract/contract_detail?id=${contract.id}`
})
},
//
goToSign(contract) {
uni.navigateTo({
url: `/pages/common/contract/contract_sign?id=${contract.id}&contractName=${encodeURIComponent(contract.contract_name)}`
url: `/pages-common/contract/contract_sign?id=${contract.id}&contractName=${encodeURIComponent(contract.contract_name)}`
})
},

39
uniapp/pages/common/home/index.vue

@ -49,57 +49,62 @@
{
title: '客户资源',
icon: 'person-filled',
path: '/pages/market/clue/index'
path: '/pages-market/clue/index'
},
{
title: '添加资源',
icon: 'plus-filled',
path: '/pages/market/clue/add_clues'
path: '/pages-market/clue/add_clues'
},
{
title: '课程安排',
icon: 'calendar-filled',
path: '/pages/market/clue/class_arrangement'
path: '/pages-market/clue/class_arrangement'
},
{
title: '课程查询',
icon: 'search',
path: '/pages/coach/schedule/schedule_table'
path: '/pages-coach/coach/schedule/schedule_table'
},
{
title: '学员管理',
icon: 'contact-filled',
path: '/pages/coach/student/student_list'
path: '/pages-coach/coach/student/student_list'
},
{
title: '我的数据',
icon: 'bars',
path: '/pages/market/my/my_data'
path: '/pages-market/my/my_data'
},
{
title: '部门数据',
icon: 'staff',
path: '/pages/market/my/dept_data'
path: '/pages-market/my/dept_data'
},
{
title: '校区数据',
icon: 'location-filled',
path: '/pages/market/my/campus_data'
path: '/pages-market/my/campus_data'
},
{
title: '我的消息',
icon: 'chat-filled',
path: '/pages/common/my_message'
path: '/pages-common/my_message'
},
{
title: '报销管理',
icon: 'wallet-filled',
path: '/pages/market/reimbursement/list'
path: '/pages-market/reimbursement/list'
},
{
title: '资料库',
icon: 'folder-add-filled',
path: '/pages/coach/my/teaching_management'
path: '/pages-coach/coach/my/teaching_management'
},
{
title: '系统设置',
icon: 'gear-filled',
path: '/pages-common/profile/index'
}
]
}
@ -116,8 +121,16 @@
}
},
handleGridClick(item) {
this.$navigateTo({
url: item.path
console.log('点击功能按钮:', item.title, item.path);
uni.navigateTo({
url: item.path,
fail: (err) => {
console.error('页面跳转失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
}
}

8
uniapp/pages/common/my_message.vue

@ -244,8 +244,8 @@ export default {
//-
openViewSysMsgList(e){
let hair_staff_id = e.hair_staff_id//id
this.$navigateTo({
url: `/pages/common/sys_msg_list?hair_staff_id=${hair_staff_id}`
uni.navigateTo({
url: `/pages-common/sys_msg_list?hair_staff_id=${hair_staff_id}`
})
},
@ -267,8 +267,8 @@ export default {
to_id = e.personnel_id
}
this.$navigateTo({
url: `/pages/common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
uni.navigateTo({
url: `/pages-common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
})
}

12
uniapp/pages/common/profile/index.vue

@ -57,7 +57,7 @@
title: '我的合同',
icon: 'compose',
desc: '查看签署合同',
path: '/pages/contract/list'
path: '/pages-common/contract/list'
},
{
title: '我的工资',
@ -68,12 +68,12 @@
{
title: '我的考勤',
icon: 'calendar',
path: '/pages/common/my_attendance'
path: '/pages-common/my_attendance'
},
{
title: '系统设置',
icon: 'settings',
path: '/pages/market/my/set_up'
path: '/pages-market/my/set_up'
}
]
}
@ -105,7 +105,7 @@
}
} else if (item.path) {
//
this.$navigateTo({
uni.navigateTo({
url: item.path
});
}
@ -113,13 +113,13 @@
viewPersonalProfile() {
//
uni.navigateTo({
url: '/pages/common/profile/personal_info'
url: '/pages-common/profile/personal_info'
});
},
viewSalaryInfo() {
//
uni.navigateTo({
url: '/pages/coach/my/salary'
url: '/pages-coach/coach/my/salary'
});
}
}

4
uniapp/pages/common/sys_msg_list.vue

@ -134,8 +134,8 @@ export default {
openViewArticleInfo(item) {
let id = item.id
let redirect = item.redirect//
this.$navigateTo({
url: `/pages/common/article_info?id=${id}`
uni.navigateTo({
url: `/pages-common/article_info?id=${id}`
})
},

4
uniapp/pages/contract/detail.vue

@ -163,13 +163,13 @@ export default {
goToFill() {
uni.navigateTo({
url: `/pages/contract/fill?id=${this.contractId}`
url: `/pages-common/contract/fill?id=${this.contractId}`
})
},
goToSign() {
uni.navigateTo({
url: `/pages/common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent(this.contractInfo.contract_name)}`
url: `/pages-common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent(this.contractInfo.contract_name)}`
})
},

2
uniapp/pages/contract/fill.vue

@ -176,7 +176,7 @@ export default {
setTimeout(() => {
uni.navigateTo({
url: `/pages/common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent('合同签署')}`
url: `/pages-common/contract/contract_sign?id=${this.contractId}&contractName=${encodeURIComponent('合同签署')}`
})
}, 1500)
} catch (error) {

2
uniapp/pages/contract/list.vue

@ -179,7 +179,7 @@ export default {
goToDetail(contract) {
uni.navigateTo({
url: `/pages/contract/detail?id=${contract.id}`
url: `/pages-common/contract/detail?id=${contract.id}`
})
},

12
uniapp/pages/market/clue/add_clues.vue

@ -1202,8 +1202,8 @@ export default {
return;
}
this.$navigateTo({
url: `/pages/market/clue/clue_info?resource_sharing_id=${resource_sharing_id}`
uni.navigateTo({
url: `/pages-market/clue/clue_info?resource_sharing_id=${resource_sharing_id}`
})
},
@ -1211,8 +1211,8 @@ export default {
openViewMyMessage(item) {
let from_id = this.userInfo.id//id
let to_id = item.customerResource.id//ID
this.$navigateTo({
url: `/pages/common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
uni.navigateTo({
url: `/pages-common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
})
},
@ -1340,7 +1340,7 @@ export default {
this.showDuplicateCheck = false
setTimeout(() => {
uni.redirectTo({
url: `/pages/market/clue/index`
url: `/pages-market/clue/index`
})
}, 1000)
@ -1726,7 +1726,7 @@ export default {
//-线
//
uni.redirectTo({
url: `/pages/market/clue/index`
url: `/pages-market/clue/index`
})
}, 1000)
},

4
uniapp/pages/market/clue/class_arrangement.vue

@ -177,8 +177,8 @@
//
const resourceId = this.resource_id || '';
const studentId = this.student_id || '';
this.$navigateTo({
url: '/pages/market/clue/class_arrangement_detail?schedule_id=' + course.id + '&resource_id=' + resourceId + '&student_id=' + studentId
uni.navigateTo({
url: '/pages-market/clue/class_arrangement_detail?schedule_id=' + course.id + '&resource_id=' + resourceId + '&student_id=' + studentId
});
},
onCalendarConfirm(e) {

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

@ -560,7 +560,7 @@ export default {
switch (action.key) {
case 'course_arrangement':
this.$navigateToPage(`/pages/market/clue/class_arrangement`, {
this.$navigateToPage(`/pages-market/clue/class_arrangement`, {
resource_id: this.clientInfo.id,
student_id: student.id
})
@ -615,7 +615,7 @@ export default {
uni.showToast({ title: '课程信息不完整', icon: 'none' })
return
}
this.$navigateToPage(`/pages/market/course/course_detail`, {
this.$navigateToPage(`/pages-market/course/course_detail`, {
id: course.id,
resource_id: this.clientInfo.resource_id
})
@ -628,11 +628,11 @@ export default {
if (tabId === 3) await this.getListCallUp()
if (tabId === 4) await this.getFitnessRecords()
if (tabId === 6) {
this.$navigateToPage(`/pages/market/clue/edit_clues_log`, {
this.$navigateToPage(`/pages-market/clue/edit_clues_log`, {
resource_id: this.clientInfo.resource_id
})
}
if (tabId === 7) this.$navigateToPage(`/pages/market/clue/edit_clues`, {
if (tabId === 7) this.$navigateToPage(`/pages-market/clue/edit_clues`, {
resource_sharing_id: this.clientInfo.id
})
},

4
uniapp/pages/market/clue/edit_clues.vue

@ -1101,7 +1101,7 @@
}
uni.navigateTo({
url: `/pages/market/clue/edit_clues_log?customer_resource_id=${this.resource_sharing_id}`
url: `/pages-market/clue/edit_clues_log?customer_resource_id=${this.resource_sharing_id}`
});
},
@ -1383,7 +1383,7 @@
//-
//
uni.redirectTo({
url: `/pages/market/clue/clue_info?resource_sharing_id=${this.resource_sharing_id}`
url: `/pages-market/clue/clue_info?resource_sharing_id=${this.resource_sharing_id}`
})
}, 1000)
},

8
uniapp/pages/market/clue/index.vue

@ -719,8 +719,8 @@
openViewMyMessage(item) {
let from_id = this.userInfo.id //id
let to_id = item.customerResource.id //ID
this.$navigateTo({
url: `/pages/common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
uni.navigateTo({
url: `/pages-common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
})
},
@ -764,8 +764,8 @@
//
clue_info(item) {
let resource_sharing_id = item.id ///id
this.$navigateTo({
url: `/pages/market/clue/clue_info?resource_sharing_id=${resource_sharing_id}`
uni.navigateTo({
url: `/pages-market/clue/clue_info?resource_sharing_id=${resource_sharing_id}`
})
},

64
uniapp/pages/market/my/index.vue

@ -190,114 +190,114 @@ export default {
//
openViewArrivalStatistics(){
this.$navigateTo({
url: '/pages/market/my/arrival_statistics'
uni.navigateTo({
url: '/pages-market/my/arrival_statistics'
})
},
//
openViewDueSoon(){
this.$navigateTo({
url: '/pages/market/my/due_soon'
uni.navigateTo({
url: '/pages-market/my/due_soon'
})
},
//
openViewSchoolingStatistics(){
this.$navigateTo({
url: '/pages/market/my/schooling_statistics'
uni.navigateTo({
url: '/pages-market/my/schooling_statistics'
})
},
//
openViewFeedback(){
this.$navigateTo({
url: '/pages/common/feedback'
uni.navigateTo({
url: '/pages-common/feedback'
})
},
//
openViewMyInfo(){
this.$navigateTo({
url: '/pages/market/my/info'
uni.navigateTo({
url: '/pages-market/my/info'
})
},
//-
openViewSignedClientList(){
this.$navigateTo({
url: '/pages/market/my/signed_client_list'
uni.navigateTo({
url: '/pages-market/my/signed_client_list'
})
},
//-
openViewMyAttendance(){
this.$navigateTo({
url: '/pages/common/my_attendance'
uni.navigateTo({
url: '/pages-common/my_attendance'
})
},
//
openViewFirmInfo(){
this.$navigateTo({
url: '/pages/market/my/firm_info'
uni.navigateTo({
url: '/pages-market/my/firm_info'
})
},
//
openViewSetUp(){
this.$navigateTo({
url: '/pages/market/my/set_up'
uni.navigateTo({
url: '/pages-market/my/set_up'
})
},
//-
openViewMyMessage(){
this.$navigateTo({
url: '/pages/common/my_message'
uni.navigateTo({
url: '/pages-common/my_message'
})
},
//-
openViewReimbursementList(){
this.$navigateTo({
url: '/pages/market/reimbursement/list'
uni.navigateTo({
url: '/pages-market/reimbursement/list'
})
},
goCourseSchedule(){
this.$navigateTo({
url: '/pages/coach/schedule/schedule_table'
uni.navigateTo({
url: '/pages-coach/coach/schedule/schedule_table'
})
},
my_contract(){
this.$navigateTo({
url: '/pages/common/contract/my_contract'
uni.navigateTo({
url: '/pages-common/contract/my_contract'
})
},
//
openMyData(){
this.$navigateTo({
url: '/pages/market/my/my_data'
uni.navigateTo({
url: '/pages-market/my/my_data'
})
},
//
openDeptData(){
this.$navigateTo({
url: '/pages/market/my/dept_data'
uni.navigateTo({
url: '/pages-market/my/dept_data'
})
},
//
openCampusData(){
this.$navigateTo({
url: '/pages/market/my/campus_data'
uni.navigateTo({
url: '/pages-market/my/campus_data'
})
},
}

8
uniapp/pages/market/my/set_up.vue

@ -27,13 +27,13 @@
},
privacy_agreement(type){
this.$navigateTo({
url: '/pages/common/privacy_agreement?type='+type
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
})
},
update_pass(){
this.$navigateTo({
url: '/pages/market/my/update_pass'
uni.navigateTo({
url: '/pages-market/my/update_pass'
})
},

4
uniapp/pages/market/my/signed_client_list.vue

@ -147,8 +147,8 @@ export default {
//
openViewStudentInfo(item){
let students_id= item.id
this.$navigateTo({
url: `/pages/coach/student/info?students_id=${students_id}`
uni.navigateTo({
url: `/pages-coach/coach/student/info?students_id=${students_id}`
})
},
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save