Browse Source

修改 bug

master
王泽彦 5 months ago
parent
commit
b8a510485d
  1. 26
      admin/src/app/views/classroom/components/classroom-edit.vue
  2. 17
      admin/src/app/views/course_schedule/components/course-schedule-edit.vue
  3. 66
      niucloud/app/api/controller/apiController/Course.php
  4. 31
      niucloud/app/api/controller/apiController/CourseSchedule.php
  5. 2
      niucloud/app/api/controller/apiController/CourseScheduleController.php
  6. 3
      niucloud/app/api/route/route.php
  7. 2
      niucloud/app/model/course_schedule/CourseSchedule.php
  8. 56
      niucloud/app/service/admin/classroom/ClassroomService.php
  9. 173
      niucloud/app/service/api/apiService/CourseScheduleService.php
  10. 33
      niucloud/app/service/api/apiService/CourseService.php
  11. 67
      uniapp/api/apiRoute.js
  12. 387
      uniapp/components/schedule/ScheduleDetail.vue
  13. 175
      uniapp/components/student-info-card/student-info-card.vue
  14. 129
      uniapp/pages-coach/coach/schedule/adjust_course.vue
  15. 130
      uniapp/pages-coach/coach/schedule/sign_in.vue
  16. 139
      uniapp/pages-market/clue/class_arrangement_detail.vue
  17. 5
      uniapp/pages-market/clue/clue_info.vue

26
admin/src/app/views/classroom/components/classroom-edit.vue

@ -321,16 +321,18 @@ const setEducationalList = async (campusId?: number | string) => {
setEducationalList()
//
watch(() => formData.campus_id, (newCampusId) => {
watch(() => formData.campus_id, (newCampusId, oldCampusId) => {
if (newCampusId) {
//
setCoachLists(newCampusId)
setEducationalList(newCampusId)
//
formData.head_coach = ''
formData.assistant_coach = ''
formData.educational_id = ''
//
if (oldCampusId !== undefined && oldCampusId !== '' && oldCampusId !== newCampusId) {
formData.head_coach = ''
formData.assistant_coach = ''
formData.educational_id = ''
}
} else {
//
setCoachLists()
@ -342,10 +344,18 @@ const setFormData = async (row: any = null) => {
loading.value = true
if (row) {
const data = await (await getClassroomInfo(row.id)).data
if (data)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
if (data[key] != undefined) {
// head_coachassistant_coacheducational_idselectvalue
if (['head_coach', 'assistant_coach', 'educational_id'].includes(key)) {
formData[key] = String(data[key])
} else {
formData[key] = data[key]
}
}
})
}
}
loading.value = false
}

17
admin/src/app/views/course_schedule/components/course-schedule-edit.vue

@ -147,7 +147,7 @@ import { getWithCampusList, getAllVenueList } from '@/app/api/venue'
import { getVenueInfo } from '@/app/api/venue'
import { generateTimeSlots } from '@/utils/timeslots'
import { getAllCourseList } from '@/app/api/course'
import { getWithCoachList } from '@/app/api/customer_resources'
import { getCoachPersonnel } from '@/app/api/classroom'
let showDialog = ref(false)
const loading = ref(false)
@ -272,7 +272,7 @@ const loadCourseList = () => {
}
//
const loadCoachList = (campus_id?: string | number) => {
getWithCoachList({ campus_id })
getCoachPersonnel(campus_id)
.then((res) => {
coachList.value = res.data || []
})
@ -298,7 +298,7 @@ watch(
})
.catch(() => {})
getWithCoachList({ campus_id: newValue })
getCoachPersonnel(newValue)
.then((res) => {
coachList.value = res.data || []
})
@ -396,7 +396,7 @@ const setFormData = async (row: any = null) => {
});
}),
new Promise<void>((resolve) => {
getWithCoachList({ campus_id: detailData.campus_id })
getCoachPersonnel(detailData.campus_id)
.then((res) => {
coachList.value = res.data || [];
resolve();
@ -444,9 +444,14 @@ const setFormData = async (row: any = null) => {
if (key === 'campus_id' || key === 'venue_id' || key === 'time_slot') {
return;
}
if (detailData[key] !== undefined) {
formData[key] = detailData[key];
// IDselectvalue
if (['coach_id', 'course_id', 'venue_id'].includes(key)) {
formData[key] = Number(detailData[key]);
} else {
formData[key] = detailData[key];
}
} else if (key === 'auto_schedule' && detailData[key] === undefined) {
// 使1
formData.auto_schedule = 1;

66
niucloud/app/api/controller/apiController/Course.php

@ -144,17 +144,17 @@ class Course extends BaseApiService
{
try {
$params = $request->all();
// 验证必要参数
if (empty($params['student_course_id'])) {
return fail('学员课程ID不能为空');
}
$res = (new CourseService())->updateStudentCoursePersonnel($params);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data'], '更新成功');
} catch (\Exception $e) {
return fail('更新学员课程人员配置失败:' . $e->getMessage());
@ -174,12 +174,12 @@ class Course extends BaseApiService
["course_type", ""], // 课程类型筛选
["status", 1] // 状态筛选,默认获取有效课程
]);
$result = (new CourseService())->getCourseListForSchedule($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success('获取成功', $result['data']);
} catch (\Exception $e) {
return fail('获取课程列表失败:' . $e->getMessage());
@ -197,16 +197,16 @@ class Course extends BaseApiService
$data = $this->request->params([
["schedule_id", 0] // 课程安排ID
]);
if (empty($data['schedule_id'])) {
return fail('课程安排ID不能为空');
}
$result = (new CourseService())->getScheduleDetail($data['schedule_id']);
if (!$result['code']) {
return fail($result['msg']);
}
return success('获取成功', $result['data']);
} catch (\Exception $e) {
return fail('获取课程安排详情失败:' . $e->getMessage());
@ -226,16 +226,16 @@ class Course extends BaseApiService
["search_type", ""], // 搜索类型(name或phone)
["schedule_id", 0] // 课程安排ID(用于排除已添加的学员)
]);
if (empty($data['keyword'])) {
return success('搜索成功', []);
}
$result = (new CourseService())->searchAvailableStudents($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success('搜索成功', $result['data']);
} catch (\Exception $e) {
return fail('搜索学员失败:' . $e->getMessage());
@ -259,20 +259,20 @@ class Course extends BaseApiService
["course_type", 1], // 课程类型:1-加课,2-补课,3-等待位
["remarks", ""] // 备注
]);
if (empty($data['schedule_id'])) {
return fail('课程安排ID不能为空');
}
if (empty($data['student_id']) && empty($data['resources_id'])) {
return fail('学员ID或资源ID不能都为空');
}
$result = (new CourseService())->addStudentToSchedule($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success('添加成功', $result['data']);
} catch (\Exception $e) {
return fail('添加学员失败:' . $e->getMessage());
@ -292,16 +292,16 @@ class Course extends BaseApiService
["reason", ""], // 移除原因
["remark", ""] // 备注
]);
if (empty($data['person_schedule_id'])) {
return fail('人员课程安排关系ID不能为空');
}
$result = (new CourseService())->removeStudentFromSchedule($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success('移除成功', $result['data']);
} catch (\Exception $e) {
return fail('移除学员失败:' . $e->getMessage());
@ -321,22 +321,22 @@ class Course extends BaseApiService
["status", 0], // 状态:0-待上课,1-已上课,2-请假
["remark", ""] // 备注
]);
if (empty($data['person_schedule_id'])) {
return fail('人员课程安排关系ID不能为空');
}
$result = (new CourseService())->updateStudentStatus($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success('更新成功', $result['data']);
} catch (\Exception $e) {
return fail('更新学员状态失败:' . $e->getMessage());
}
}
/**
* 获取教练列表
* @param Request $request
@ -350,13 +350,13 @@ class Course extends BaseApiService
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取教练列表失败:' . $e->getMessage());
}
}
/**
* 获取教务人员列表
* @param Request $request
@ -370,13 +370,13 @@ class Course extends BaseApiService
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取教务人员列表失败:' . $e->getMessage());
}
}
/**
* 更新学员课程信息(主教练、助教、教务)
* @param Request $request
@ -392,22 +392,22 @@ class Course extends BaseApiService
["education_id", 0],
["class_id", 0] // 可选,如果需要更新班级关联
]);
if (empty($data['student_course_id'])) {
return fail('学员课程ID不能为空');
}
$res = (new CourseService())->updateCourseInfo($data);
if (!$res['code']) {
return fail($res['msg']);
}
return success('更新成功', $res['data']);
} catch (\Exception $e) {
return fail('更新失败:' . $e->getMessage());
}
}
/**
* 检查学员班级关联情况
* @param Request $request
@ -417,16 +417,16 @@ class Course extends BaseApiService
{
try {
$resource_id = $request->param('resource_id', '');
if (empty($resource_id)) {
return fail('资源ID不能为空');
}
$res = (new CourseService())->checkClassRelation($resource_id);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('检查班级关联失败:' . $e->getMessage());

31
niucloud/app/api/controller/apiController/CourseSchedule.php

@ -384,4 +384,35 @@ class CourseSchedule extends BaseApiService
return fail('签到失败:' . $e->getMessage());
}
}
/**
* 删除学员所有未来课程安排
* @param Request $request
* @return \think\Response
*/
public function deleteStudentFutureSchedules(Request $request)
{
try {
$data = $this->request->params([
["student_id", 0],
["person_id", 0],
["resources_id", 0],
["reason", ""] // 删除原因
]);
// 验证必填参数
if (empty($data['student_id']) && empty($data['person_id']) && empty($data['resources_id'])) {
return fail('学员ID、人员ID或资源ID不能为空');
}
$result = (new CourseScheduleService())->deleteStudentFutureSchedules($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success($result['msg'], $result['data'] ?? []);
} catch (\Exception $e) {
return fail('删除失败:' . $e->getMessage());
}
}
}

2
niucloud/app/api/controller/apiController/CourseScheduleController.php

@ -240,4 +240,4 @@ class CourseScheduleController extends BaseApiController
$service = new CourseScheduleService();
return success('获取成功', $service->getFilterOptions($data));
}
}
}

3
niucloud/app/api/route/route.php

@ -354,6 +354,8 @@ Route::group(function () {
Route::post('courseSchedule/batchSignIn', 'apiController.CourseSchedule/batchSignIn');
//员工端-单个学员签到
Route::post('courseSchedule/updateStudentStatus', 'apiController.CourseSchedule/updateStudentStatus');
//员工端-删除学员所有未来课程安排
Route::post('courseSchedule/deleteStudentFutureSchedules', 'apiController.CourseSchedule/deleteStudentFutureSchedules');
// 课程安排统一选项接口(新增-支持校区过滤)
@ -371,6 +373,7 @@ Route::group(function () {
// 添加课程安排页面专用接口(保留兼容性)
//获取课程列表(用于添加课程安排)
Route::get('course/list', 'apiController.Course/getCourseList');
Route::post('course/upgradeStudent', 'apiController.Course/upgradeStudent');
//获取班级列表(用于添加课程安排)
Route::get('class/list', 'apiController.ClassApi/getClassList');
//获取教练列表(用于添加课程安排)

2
niucloud/app/model/course_schedule/CourseSchedule.php

@ -96,7 +96,7 @@ class CourseSchedule extends BaseModel
public function coach()
{
return $this->hasOne(Personnel::class, 'id', 'coach_id');
return $this->hasOne(\app\model\school\SchoolPersonnel::class, 'id', 'coach_id');
}
public function course()

56
niucloud/app/service/admin/classroom/ClassroomService.php

@ -150,22 +150,58 @@ class ClassroomService extends BaseAdminService
{
$campus_id = $params['campus_id'] ?? 0;
// 如果没有传校区ID,返回所有教练人员
if (empty($campus_id)) {
// 这里可以直接查询所有教练角色的人员,但为了保持一致性,我们使用公共方法
$personnelModel = new Personnel();
return $personnelModel->field('id, name, phone')->select()->toArray();
}
// 返回所有教练角色的人员(不限制校区,因为课程安排可能有跨校区教练)
$personnel_list = \think\facade\Db::table('school_personnel')
->alias('p')
->leftJoin('school_campus_person_role cpr', 'p.id = cpr.person_id')
->leftJoin('school_sys_role sr', 'cpr.role_id = sr.role_id')
->where([
['sr.role_key', 'in', ['teacher_manager', 'teacher']],
['p.status', '=', 2] // 只返回状态正常的教练
])
->where(function ($query) {
$query->where('cpr.deleted_at', 0)->whereOr('cpr.deleted_at', null);
})
->field('p.id, p.name, p.phone, cpr.role_id, cpr.dept_id, cpr.campus_id')
->group('p.id') // 去重,防止一个教练有多个角色记录
->select()
->toArray();
// 根据校区ID获取教练人员(角色ID: 1=教练主管, 5=普通教练)
$role_ids = [1, 5];
$personnel_list = get_personnel_by_campus_role($campus_id, $role_ids);
// 如果指定了校区,优先返回该校区的教练,但也包含其他校区的教练
if (!empty($campus_id)) {
// 将指定校区的教练排在前面
$campus_coaches = [];
$other_coaches = [];
foreach ($personnel_list as $person) {
if ($person['campus_id'] == $campus_id) {
$campus_coaches[] = [
'id' => $person['id'],
'name' => $person['name'],
'phone' => $person['phone'],
'role_id' => $person['role_id'],
'dept_id' => $person['dept_id']
];
} else {
$other_coaches[] = [
'id' => $person['id'],
'name' => $person['name'],
'phone' => $person['phone'],
'role_id' => $person['role_id'],
'dept_id' => $person['dept_id']
];
}
}
// 合并结果,校区教练在前
return array_merge($campus_coaches, $other_coaches);
}
// 格式化返回数据
$result = [];
foreach ($personnel_list as $person) {
$result[] = [
'id' => $person['person_id'],
'id' => $person['id'],
'name' => $person['name'],
'phone' => $person['phone'],
'role_id' => $person['role_id'],

173
niucloud/app/service/api/apiService/CourseScheduleService.php

@ -2757,12 +2757,13 @@ class CourseScheduleService extends BaseApiService
}
// 记录日志
trace('Trial class checkin processed', [
trace('Trial class checkin processed', 'info');
trace('Student details: ' . json_encode([
'student_id' => $student->id,
'old_trial_count' => $currentTrialCount,
'new_trial_count' => $updateData['trial_class_count'],
'update_fields' => array_keys($updateData)
]);
]), 'info');
} catch (\Exception $e) {
// 抛出异常以便外层事务回滚
@ -2779,28 +2780,47 @@ class CourseScheduleService extends BaseApiService
* @param string $reason 备注
* @return array 处理结果
*/
private function processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $reason = '')
private function processSingleStudentSignIn($scheduleId, $personId, $resourceId, $status, $reason = '')
{
try {
// 查找学员课程安排记录
$whereCondition = [
'schedule_id' => $scheduleId,
'deleted_at' => 0
];
$enrollment = null;
if (!empty($studentId)) {
$whereCondition['student_id'] = $studentId;
// 优先使用student_id字段查询(前端传递的student_id)
if (!empty($personId)) {
// 首先尝试按student_id字段查询
$enrollment = Db::name('person_course_schedule')
->where('student_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
// 如果按student_id没找到,再尝试按主键id查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
// 如果还没找到,最后尝试按person_id字段查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('person_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
} elseif (!empty($resourceId)) {
$whereCondition['resources_id'] = $resourceId;
} else {
return ['success' => false, 'error' => '学员ID或资源ID不能为空'];
$enrollment = Db::name('person_course_schedule')
->where('resources_id', $resourceId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
$enrollment = Db::name('person_course_schedule')
->where($whereCondition)
->find();
if (!$enrollment) {
if (empty($enrollment)) {
return ['success' => false, 'error' => '找不到学员记录'];
}
@ -2810,6 +2830,13 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => '找不到学员信息'];
}
// 检查体验课次数(仅对未付费学员且状态为签到时)
if ($status === 1 && $student->pay_status != 1) {
if ($student->trial_class_count <= 0) {
return ['success' => false, 'error' => '该学员体验课次数已用完,无法签到'];
}
}
// 准备更新数据
$updateData = [
'status' => $status,
@ -2840,4 +2867,116 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* 删除学员所有未来课程安排
* @param array $data 删除参数
* @return array 删除结果
*/
public function deleteStudentFutureSchedules(array $data)
{
try {
$studentId = $data['student_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$resourcesId = $data['resources_id'] ?? 0;
if (empty($studentId) && empty($personId) && empty($resourcesId)) {
return [
'code' => 0,
'msg' => '学员ID、人员ID或资源ID不能为空'
];
}
// 开启事务
Db::startTrans();
// 构建查询条件
$where = [
['course_date', '>=', date('Y-m-d')], // 只删除今天及未来的课程
['deleted_at', '=', 0]
];
// 根据传入的参数确定查询条件
if (!empty($studentId)) {
$where[] = ['student_id', '=', $studentId];
}
if (!empty($personId)) {
$where[] = ['person_id', '=', $personId];
}
if (!empty($resourcesId)) {
$where[] = ['resources_id', '=', $resourcesId];
}
// 查询要删除的课程安排数量
$count = Db::name('person_course_schedule')
->where($where)
->count();
if ($count == 0) {
Db::rollback();
return [
'code' => 1,
'msg' => '没有找到需要删除的未来课程安排',
'data' => ['deleted_count' => 0]
];
}
// 执行物理删除
$result = Db::name('person_course_schedule')
->where($where)
->delete();
if ($result === false) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除课程安排失败'
];
}
// 记录删除日志
$this->recordDeleteScheduleLog([
'student_id' => $studentId,
'person_id' => $personId,
'resources_id' => $resourcesId,
'deleted_count' => $result,
'operator_id' => $this->user_id ?? 0,
'delete_time' => date('Y-m-d H:i:s'),
'reason' => $data['reason'] ?? '体验课次数用完,删除未来课程安排'
]);
// 提交事务
Db::commit();
return [
'code' => 1,
'msg' => "成功删除{$result}个未来课程安排",
'data' => [
'deleted_count' => $result
]
];
} catch (\Exception $e) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除失败:' . $e->getMessage()
];
}
}
/**
* 记录删除课程安排日志
* @param array $logData 日志数据
*/
private function recordDeleteScheduleLog(array $logData)
{
try {
// 这里可以记录到日志表或系统日志
trace('Delete student future schedules: ' . json_encode($logData), 'info');
} catch (\Exception $e) {
// 日志记录失败不影响主流程
trace('Record delete schedule log failed: ' . $e->getMessage(), 'error');
}
}
}

33
niucloud/app/service/api/apiService/CourseService.php

@ -419,7 +419,7 @@ class CourseService extends BaseApiService
case '上午(8:00-12:00)':
return ['08:00', '12:00']; // 上午时间范围
case '下午(12:00-18:00)':
return ['12:00', '18:00']; // 下午时间范围
return ['12:00', '18:00']; // 下午时间范围
case '晚上(18:00-22:00)':
return ['18:00', '22:00']; // 晚上时间范围
default:
@ -459,6 +459,33 @@ class CourseService extends BaseApiService
return fail("重复添加");
}
// 验证场地容量限制
$schedule = $CourseSchedule->where('id', $data['schedule_id'])->find();
if (!$schedule) {
return fail("课程安排不存在");
}
// 获取场地信息
$venue = Db::name('venue')->where('id', $schedule['venue_id'])->find();
if ($venue && $venue['capacity'] > 0) {
// 如果添加的是正式学员(非等待位),检查容量限制
if (($data['course_type'] ?? 1) != 3) { // course_type=3是等待位
// 查询当前时间段正式学员人数(排除请假、软删除和等待位)
$currentFormalCount = $personCourseSchedule
->where('schedule_id', $data['schedule_id'])
->where('course_type', '<>', 3) // 排除等待位
->where('status', '<>', 2) // 排除请假(status=2)
->where(function ($query) {
$query->where('deleted_at', 0)->whereOr('deleted_at', null);
})
->count();
if ($currentFormalCount >= $venue['capacity']) {
return fail("该时间段场地已满({$venue['capacity']}人),当前正式学员{$currentFormalCount}人,无法添加更多学员");
}
}
}
$insertData = [
'student_id' => $student->id, // 正确设置student_id
'resources_id' => $data['resources_id'], // 正确设置resources_id
@ -723,7 +750,7 @@ class CourseService extends BaseApiService
// 获取课程安排基本信息
$schedule = $CourseSchedule
->where('id', $scheduleId)
->with(['course', 'venue', 'campus'])
->with(['course', 'venue', 'campus', 'coach'])
->find();
if (!$schedule) {
@ -905,6 +932,8 @@ class CourseService extends BaseApiService
'time_slot' => $schedule['time_slot'],
'venue_name' => $schedule['venue']['venue_name'] ?? '',
'campus_name' => $schedule['campus']['campus_name'] ?? '',
'coach_name' => $schedule['coach']['name'] ?? '',
'coach_id' => $schedule['coach_id'] ?: 0,
'available_capacity' => $schedule['available_capacity'] ?: 0,
'max_students' => $maxStudents,
'available_slots' => $availableSlots,

67
uniapp/api/apiRoute.js

@ -992,22 +992,14 @@ export default {
return response
} catch (error) {
console.error('获取课程安排列表错误:', error)
// 当发生错误时,返回模拟数据
return await this.getCourseScheduleListMock(data)
uni.showModal({
title: '获取课程安排列表错误',
content: JSON.stringify(error),
showCancel: false,
})
}
},
// 获取课程安排详情
async getCourseScheduleDetail(data = {}) {
try {
const response = await http.get('/course-schedule/detail/' + data.schedule_id)
return response
} catch (error) {
console.error('获取课程安排详情错误:', error)
// 当发生错误时,返回模拟数据
return await this.getCourseScheduleInfoMock(data)
}
},
// 申请课程请假
async requestCourseLeave(data = {}) {
@ -1208,14 +1200,6 @@ export default {
}
}
},
// 学员加入课程安排
async joinCourseSchedule(data = {}) {
return await http.post('/courseSchedule/joinSchedule', data)
},
// 学员退出课程安排
async leaveCourseSchedule(data = {}) {
return await http.post('/courseSchedule/leaveSchedule', data)
},
// 获取筛选选项
async getCourseScheduleFilterOptions(data = {}) {
return await http.get('/courseSchedule/filterOptions', data)
@ -1227,14 +1211,14 @@ export default {
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----添加课程安排页面专用接口-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取所有排课选项(统一接口-支持校区过滤)
async getAllScheduleOptions(data = {}) {
const token = uni.getStorageSync('token')
const apiPath = token ? '/schedule/options/all' : '/test/schedule/options/all'
return await http.get(apiPath, data)
},
// 获取课程列表(用于添加课程安排)
async getCourseListForSchedule(data = {}) {
// 检查是否有token,如果没有则使用测试接口
@ -1310,21 +1294,6 @@ export default {
}
},
// 升级学员(统一接口)
async upgradeStudent(data = {}) {
try {
const response = await http.post('/course/upgradeStudent', data)
return response
} catch (error) {
console.error('升级学员失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '升级学员成功(模拟)',
data: {}
}
}
},
// 恢复学员(统一接口)
async restoreStudent(data = {}) {
try {
@ -1342,7 +1311,7 @@ export default {
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排详情管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取课程安排详情(新统一接口 - 对接admin端)
async getCourseArrangementDetail(data = {}) {
try {
@ -1431,6 +1400,22 @@ export default {
}
},
// 删除学员所有未来课程安排
async deleteStudentFutureSchedules(data = {}) {
try {
const response = await http.post('/courseSchedule/deleteStudentFutureSchedules', data)
return response
} catch (error) {
console.error('删除学员未来课程安排失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '删除成功(模拟)',
data: { deleted_count: 0 }
}
}
},
// 恢复学员(新统一接口 - 对接admin端)
async restoreStudentInArrangement(data = {}) {
try {
@ -1744,7 +1729,7 @@ export default {
signature_image: data.signature_image,
})
},
// 员工端提交合同签署
async signStaffContract(data = {}) {
return await http.post('/contract/signStaffContract', {
@ -2516,4 +2501,4 @@ export default {
// 复用消息列表的Mock数据,根据关键词进行筛选
return await this.getStudentMessageListMock(data)
},
}
}

387
uniapp/components/schedule/ScheduleDetail.vue

@ -31,7 +31,7 @@
</view>
<view class="info-item">
<text class="item-label">课程状态</text>
<text :class="['item-value',statusClass]">{{ scheduleInfo.status_text }}</text>
<text :class="['item-value',statusClass]">{{ scheduleInfo.status_text || statusText }}</text>
</view>
<view class="info-item">
<text class="item-label">班级</text>
@ -65,31 +65,31 @@
</view>
</view>
<view class="cards-grid" v-if="formalStudents && formalStudents.length > 0">
<view class="student-card filled" v-for="(student, index) in formalStudents" :key="index"
<view class="student-card filled" v-for="(student, index) in formalStudents" :key="index"
@click="handleStudentClick(student, index)">
<!-- 续费提醒徽章 -->
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view>
<!-- 体验课学员标识 -->
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view>
<view class="avatar">{{ student.name.charAt(0) }}</view>
<view class="student-info">
<view class="student-name">{{ student.name }}</view>
<view class="student-age">年龄{{ student.age }}</view>
<view class="course-status">课程状态{{ student.courseStatus }}</view>
<view class="course-arrangement">课程安排{{ student.courseType === 'fixed' ? '固定课' : '临时课' }}</view>
<!-- 体验课学员显示 -->
<view v-if="student.isTrialStudent" class="trial-info">
<view class="trial-hours">体验课时{{ student.trialClassCount }}</view>
</view>
<!-- 付费学员显示 -->
<view v-else class="paid-student-info">
<view class="remaining-hours">剩余课时{{ student.remainingHours }}</view>
<view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view>
<!-- 课时进度条 -->
<view class="progress-container">
<view class="progress-label">
@ -97,8 +97,8 @@
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text>
</view>
<view class="progress-bar">
<view
class="progress-fill"
<view
class="progress-fill"
:style="{ width: student.course_progress.percentage + '%' }"
></view>
</view>
@ -121,34 +121,34 @@
</view>
</view>
<view class="cards-grid">
<view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index"
<view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index"
@click="handleStudentClick(student, index)">
<!-- 转换提示图标 -->
<view class="convert-icon"></view>
<!-- 续费提醒徽章 -->
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view>
<!-- 体验课学员标识 -->
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view>
<view class="avatar waiting-avatar">{{ student.name.charAt(0) }}</view>
<view class="student-info">
<view class="student-name">{{ student.name }}</view>
<view class="student-age">年龄{{ student.age }}</view>
<view class="course-status">课程状态{{ student.courseStatus }}</view>
<view class="course-arrangement">课程安排等待位</view>
<!-- 体验课学员显示 -->
<view v-if="student.isTrialStudent" class="trial-info">
<view class="trial-hours">体验课时{{ student.trialClassCount }}</view>
</view>
<!-- 付费学员显示 -->
<view v-else class="paid-student-info">
<view class="remaining-hours">剩余课时{{ student.remainingHours }}</view>
<view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view>
<!-- 课时进度条 -->
<view class="progress-container">
<view class="progress-label">
@ -156,8 +156,8 @@
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text>
</view>
<view class="progress-bar">
<view
class="progress-fill"
<view
class="progress-fill"
:style="{ width: student.course_progress.percentage + '%' }"
></view>
</view>
@ -170,8 +170,12 @@
<!-- 操作按钮 -->
<view class="action-buttons">
<fui-button type="primary" @click="handleEditCourse">编辑课程</fui-button>
<fui-button type="success" @click="handleAddNewCourse">新增课程</fui-button>
<view class="action-button edit-btn" @click="handleEditCourse">
<text class="btn-text">编辑课程</text>
</view>
<view class="action-button add-btn" @click="handleAddNewCourse">
<text class="btn-text">新增课程</text>
</view>
</view>
</view>
@ -187,7 +191,7 @@
<text>重试</text>
</view>
</view>
<!-- 学员点名底部弹窗 -->
<fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]">
<view class="attendance-modal" v-if="selectedStudent">
@ -198,12 +202,12 @@
<view class="student-name-large">{{ selectedStudent.name }}</view>
<view class="current-status">当前状态{{ selectedStudent.status_text }}</view>
</view>
<view class="attendance-options">
<view class="option-btn sign-in" @click="handleAttendanceAction('sign_in')">
<text>签到</text>
</view>
<view class="option-btn leave" @click="handleAttendanceAction('leave')">
<view v-if="!selectedStudent.isTrialStudent" class="option-btn leave" @click="handleAttendanceAction('leave')">
<text>请假</text>
</view>
<view class="option-btn cancel" @click="closeAttendanceModal">
@ -212,7 +216,7 @@
</view>
</view>
</fui-modal>
<!-- 升级确认弹窗 -->
<fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10000">
<view class="upgrade-confirm-modal" v-if="upgradeStudent">
@ -225,7 +229,7 @@
升级后将占用一个正式位
</view>
</view>
<view class="confirm-buttons">
<view class="confirm-btn cancel-btn" @click="cancelUpgrade">
<text>取消</text>
@ -236,6 +240,33 @@
</view>
</view>
</fui-modal>
<!-- 删除课程安排确认弹窗 -->
<fui-modal :show="showDeleteSchedulesModal" title="删除确认" @cancel="closeDeleteSchedulesModal" :buttons="[]" :zIndex="10001">
<view class="delete-confirm-modal" v-if="studentToDelete">
<view class="confirm-content">
<view class="error-icon"></view>
<view class="error-message">
{{ deleteErrorMessage }}
</view>
<view class="confirm-text">
是否删除学员 <text class="student-name-highlight">{{ studentToDelete.name }}</text> 的所有未来课程安排
</view>
<view class="confirm-tip">
删除后该学员的所有未来课程安排将被清除但历史记录会保留
</view>
</view>
<view class="confirm-buttons">
<view class="confirm-btn cancel-btn" @click="closeDeleteSchedulesModal">
<text>取消</text>
</view>
<view class="confirm-btn delete-btn" @click="confirmDeleteSchedules">
<text>确认删除</text>
</view>
</view>
</view>
</fui-modal>
</fui-modal>
</template>
@ -273,6 +304,15 @@
};
return statusMap[this.scheduleInfo?.status] || '';
},
statusText() {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[this.scheduleInfo?.status] || '待开始';
},
studentList() {
const statusMap = {
0: 'status-absent',
@ -289,12 +329,12 @@
//
progressPercentage() {
if (!this.scheduleInfo || !this.scheduleInfo.course_info) return 0;
const totalHours = this.scheduleInfo.course_info.total_hours || 0;
const usedHours = this.scheduleInfo.course_info.use_total_hours || 0;
if (totalHours <= 0) return 0;
const percentage = Math.round((usedHours / totalHours) * 100);
return Math.min(percentage, 100); // 100%
}
@ -310,7 +350,10 @@
selectedStudentIndex: -1,
showUpgradeConfirm: false,
upgradeStudent: null,
upgradeStudentIndex: -1
upgradeStudentIndex: -1,
showDeleteSchedulesModal: false,
deleteErrorMessage: '',
studentToDelete: null
}
},
watch: {
@ -334,7 +377,7 @@
this.closePopup();
}
},
// 使API - admin
async fetchScheduleDetail() {
if (!this.scheduleId) {
@ -352,16 +395,16 @@
const res = await api.getCourseArrangementDetail({
schedule_id: this.scheduleId
});
if (res.code === 1) {
//
const data = res.data;
// 使APIadmin
if (data.schedule_info) {
// schedule_infoformal_studentswaiting_students
const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])];
this.scheduleInfo = {
// schedule_info
id: data.schedule_info.id,
@ -375,7 +418,7 @@
coach_avatar: data.schedule_info.coach_avatar || '',
//
status: data.schedule_info.status,
status_text: data.schedule_info.status_text || '待定',
status_text: data.schedule_info.status_text || this.getStatusTextFromCode(data.schedule_info.status),
//
class_info: data.schedule_info.class_info || null,
//
@ -427,7 +470,7 @@
coach_avatar: data.coach_avatar || '',
//
status: data.status,
status_text: data.status_text || '待定',
status_text: data.status_text || this.getStatusTextFromCode(data.status),
//
class_info: data.class_info || null,
//
@ -465,7 +508,7 @@
remaining_capacity: data.remaining_capacity || 0
};
}
console.log('课程安排详情加载成功:', this.scheduleInfo);
} else {
uni.showToast({
@ -530,7 +573,7 @@
this.upgradeStudentIndex = index;
this.showUpgradeConfirm = true;
},
//
confirmUpgrade() {
this.showUpgradeConfirm = false;
@ -538,7 +581,7 @@
this.convertWaitingToFormal(this.upgradeStudent, this.upgradeStudentIndex);
}
},
//
cancelUpgrade() {
this.showUpgradeConfirm = false;
@ -546,40 +589,39 @@
this.upgradeStudentIndex = -1;
},
// 使API - admin
//
async convertWaitingToFormal(student, index) {
try {
uni.showLoading({
title: '升级中...'
});
// 使APIadmin
console.log('升级学员信息:', student);
// - 使
const upgradeData = {
schedule_id: this.scheduleId,
person_id: student.person_id || student.id,
person_id: student.person_id || null, // person_id
person_type: student.person_type || 'customer_resource',
from_type: 2, //
to_type: 1 //
from_schedule_type: 2, //
to_schedule_type: 1, //
resources_id: student.resources_id || student.resource_id || student.id
};
// ID
// student_id
if (student.person_type === 'student') {
upgradeData.student_id = student.student_id || student.id;
} else {
upgradeData.resources_id = student.resources_id || student.resource_id || student.id;
}
console.log('升级学员数据:', upgradeData);
//
const response = await api.upgradeStudentInArrangement(upgradeData);
//
const response = await api.upgradeStudentSchedule(upgradeData);
if (response.code === 1) {
//
await this.fetchScheduleDetail();
uni.showToast({
title: `学员 ${student.name} 升级成功!已移至正式位`,
title: `学员 ${student.name} 升级成功!已转为正式学员`,
icon: 'success',
duration: 3000
});
@ -591,8 +633,8 @@
});
}
} catch (error) {
console.error('升级等待位学员失败:', error);
console.error('升级学员失败:', error);
let errorMessage = '升级失败,请重试';
if (error.message) {
if (error.message.includes('timeout')) {
@ -601,7 +643,7 @@
errorMessage = '网络异常,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
@ -619,15 +661,84 @@
this.selectedStudentIndex = -1;
},
//
showDeleteSchedulesConfirm(errorMessage) {
this.deleteErrorMessage = errorMessage;
this.studentToDelete = this.selectedStudent;
this.showDeleteSchedulesModal = true;
},
//
closeDeleteSchedulesModal() {
this.showDeleteSchedulesModal = false;
this.deleteErrorMessage = '';
this.studentToDelete = null;
},
//
async confirmDeleteSchedules() {
if (!this.studentToDelete) return;
try {
uni.showLoading({
title: '删除中...'
});
// API
const deleteData = {
student_id: this.studentToDelete.id,
reason: '体验课次数用完,清理未来课程安排'
};
// resources_id
if (this.studentToDelete.resources_id) {
deleteData.resources_id = this.studentToDelete.resources_id;
}
const response = await api.deleteStudentFutureSchedules(deleteData);
if (response.code === 1) {
//
const deletedCount = response.data?.deleted_count || 0;
uni.showToast({
title: `成功删除${deletedCount}个未来课程安排`,
icon: 'success',
duration: 3000
});
//
this.fetchScheduleDetail();
} else {
uni.showToast({
title: response.msg || '删除失败',
icon: 'none',
duration: 3000
});
}
} catch (error) {
console.error('删除课程安排失败:', error);
uni.showToast({
title: '删除失败,请重试',
icon: 'none',
duration: 3000
});
} finally {
uni.hideLoading();
this.closeDeleteSchedulesModal();
this.closeAttendanceModal();
}
},
// 使API - admin
async handleAttendanceAction(action) {
if (!this.selectedStudent) return;
const actionMap = {
'sign_in': { status: 1, text: '已签到' },
'leave': { status: 2, text: '请假' }
};
if (actionMap[action]) {
try {
uni.showLoading({
@ -654,23 +765,23 @@
if (action === 'leave') {
updateData.reason = '移动端请假操作';
}
console.log('更新学员状态数据:', updateData);
//
const response = await api.updateStudentStatusInArrangement(updateData);
if (response.code === 1) {
// API
this.selectedStudent.status = actionMap[action].status;
this.selectedStudent.status_text = actionMap[action].text;
this.selectedStudent.statusClass = this.getStudentStatusClass(actionMap[action].status);
// scheduleInfo
if (this.scheduleInfo && this.scheduleInfo.students && this.selectedStudentIndex >= 0) {
this.$set(this.scheduleInfo.students, this.selectedStudentIndex, this.selectedStudent);
}
//
this.$emit('student-attendance', {
scheduleId: this.scheduleId,
@ -679,7 +790,7 @@
status: actionMap[action].status,
studentName: this.selectedStudent.name
});
uni.showToast({
title: `${this.selectedStudent.name} ${actionMap[action].text}成功`,
icon: 'success',
@ -687,15 +798,21 @@
});
} else {
// API
uni.showToast({
title: response.msg || `${actionMap[action].text}失败`,
icon: 'none',
duration: 3000
});
//
if (response.msg && response.msg.includes('体验课次数已用完') && action === 'checkin') {
//
this.showDeleteSchedulesConfirm(response.msg);
} else {
uni.showToast({
title: response.msg || `${actionMap[action].text}失败`,
icon: 'none',
duration: 3000
});
}
}
} catch (error) {
console.error(`${action} API调用失败:`, error);
let errorMessage = `${actionMap[action].text}失败,请重试`;
if (error.message) {
if (error.message.includes('timeout')) {
@ -704,7 +821,7 @@
errorMessage = '网络异常,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
@ -714,7 +831,7 @@
uni.hideLoading();
}
}
this.closeAttendanceModal();
},
@ -736,7 +853,18 @@
};
return statusTextMap[status] || '未知状态';
},
//
getStatusTextFromCode(statusCode) {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[statusCode] || '待开始';
},
//
batchCheckIn() {
uni.navigateTo({
@ -762,7 +890,7 @@
//
const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`;
console.log('跳转到学员管理页面:', url);
uni.navigateTo({
url: url,
success: () => {
@ -1019,9 +1147,59 @@
.action-buttons {
display: flex;
justify-content: space-around;
justify-content: space-between;
margin-top: 30rpx;
gap: 30rpx;
gap: 20rpx;
width: 100%;
}
.action-button {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&:active {
transform: scale(0.98);
}
.btn-text {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
}
.edit-btn {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
border: 1px solid #007bff;
&:hover {
background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
}
&:active {
background: linear-gradient(135deg, #004085 0%, #002752 100%);
}
}
.add-btn {
background: linear-gradient(135deg, #29d3b4 0%, #22a68b 100%);
border: 1px solid #29d3b4;
&:hover {
background: linear-gradient(135deg, #22a68b 0%, #1a8b6f 100%);
}
&:active {
background: linear-gradient(135deg, #1a8b6f 0%, #136045 100%);
}
}
@ -1138,8 +1316,8 @@
/* 学员卡片网格样式 */
.cards-grid {
display: grid;
grid-template-columns: 1fr 1fr;
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 20rpx;
}
@ -1153,6 +1331,12 @@
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
width: calc(50% - 8rpx);
flex: 0 0 calc(50% - 8rpx);
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
}
.student-card.filled {
@ -1242,6 +1426,7 @@
font-weight: 600;
font-size: 24rpx;
margin-bottom: 12rpx;
flex-shrink: 0;
}
.waiting-avatar {
@ -1252,6 +1437,10 @@
.student-info {
font-size: 22rpx;
line-height: 1.4;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.student-name {
@ -1262,6 +1451,8 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
box-sizing: border-box;
}
.student-age,
@ -1329,7 +1520,7 @@
border-radius: 4rpx;
transition: width 0.3s ease;
}
/* 升级确认弹窗样式 */
.upgrade-confirm-modal {
padding: 40rpx 30rpx;
@ -1425,4 +1616,46 @@
background: linear-gradient(45deg, #7c3aed, #9333ea);
transform: scale(0.98);
}
</style>
/* 删除确认弹窗样式 */
.delete-confirm-modal {
padding: 40rpx 30rpx;
text-align: center;
}
.error-icon {
font-size: 60rpx;
margin-bottom: 20rpx;
color: #ef4444;
animation: shake 2s ease-in-out infinite;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5rpx); }
75% { transform: translateX(5rpx); }
}
.error-message {
font-size: 28rpx;
color: #ef4444;
font-weight: 600;
margin-bottom: 20rpx;
padding: 20rpx;
background: rgba(239, 68, 68, 0.1);
border-radius: 16rpx;
border: 2rpx solid rgba(239, 68, 68, 0.2);
}
.delete-btn {
background: linear-gradient(45deg, #ef4444, #f87171);
color: #fff;
border: none;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
box-shadow: 0 2rpx 8rpx rgba(239, 68, 68, 0.4);
}
}
</style>

175
uniapp/components/student-info-card/student-info-card.vue

@ -2,27 +2,33 @@
<template>
<view class="student-info-card" >
<!-- 学生基本信息 -->
<view class="student-header" @click="handleStudentClick">
<view class="student-avatar">
<text>{{ student.name ? student.name.charAt(0) : '学' }}</text>
</view>
<view class="student-details">
<view class="student-name">{{ student.name || '未知学生' }}</view>
<view class="student-meta">
<text class="student-age">{{ calculateAge(student.birthday) }}</text>
<text class="student-gender">{{ formatGender(student.gender) }}</text>
<view class="student-header">
<view class="student-content" @click="handleStudentClick">
<view class="student-avatar">
<text>{{ student.name ? student.name.charAt(0) : '学' }}</text>
</view>
<!-- 学员标签 -->
<view class="student-tags" v-if="student.student_tags && student.student_tags.length > 0">
<view
class="student-tag"
v-for="tag in student.student_tags"
:key="tag"
>
{{ tag }}
<view class="student-details">
<view class="student-name">{{ student.name || '未知学生' }}</view>
<view class="student-meta">
<text class="student-age">{{ calculateAge(student.birthday) }}</text>
<text class="student-gender">{{ formatGender(student.gender) }}</text>
</view>
<!-- 学员标签 -->
<view class="student-tags" v-if="student.student_tags && student.student_tags.length > 0">
<view
class="student-tag"
v-for="tag in student.student_tags"
:key="tag"
>
{{ tag }}
</view>
</view>
</view>
</view>
<!-- 编辑按钮 -->
<view class="edit-button" @click.stop="handleEditClick">
<text class="edit-icon"></text>
</view>
</view >
<!-- 学生详细信息 -->
@ -81,6 +87,9 @@ export default {
handleStudentClick(){
this.$emit('action',{ action: 'edit', student: this.student})
},
handleEditClick(){
this.$emit('action',{ action: 'edit', student: this.student})
},
// xx
calculateAge(birthday) {
if (!birthday) return ''
@ -139,72 +148,92 @@ export default {
.student-header {
display: flex;
align-items: center;
.student-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #29d3b4;
align-items: flex-start;
justify-content: space-between;
.student-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
text {
color: white;
font-size: 24rpx;
font-weight: bold;
}
}
.student-details {
flex: 1;
.student-name {
color: white;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.student-meta {
.student-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #29d3b4;
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 8rpx;
.student-age,
.student-gender {
color: #999;
font-size: 22rpx;
justify-content: center;
margin-right: 20rpx;
text {
color: white;
font-size: 24rpx;
font-weight: bold;
}
}
.student-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 8rpx;
.student-tag {
color: #29d3b4;
font-size: 18rpx;
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5);
padding: 4rpx 10rpx;
border-radius: 10rpx;
display: inline-block;
.student-details {
flex: 1;
.student-name {
color: white;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.student-meta {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 8rpx;
.student-age,
.student-gender {
color: #999;
font-size: 22rpx;
}
}
.student-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 8rpx;
.student-tag {
color: #29d3b4;
font-size: 18rpx;
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5);
padding: 4rpx 10rpx;
border-radius: 10rpx;
display: inline-block;
}
}
}
}
.action-toggle {
padding: 10rpx;
.toggle-icon {
.edit-button {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
.edit-icon {
font-size: 20rpx;
color: #29d3b4;
font-size: 24rpx;
}
&:active {
background-color: rgba(41, 211, 180, 0.4);
transform: scale(0.95);
}
}
}

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

@ -5,7 +5,7 @@
<fui-loading></fui-loading>
<text class="loading-text">加载中...</text>
</view>
<fui-form v-else>
<!-- 课程信息 -->
<view class="section-title">当前课程信息</view>
@ -23,9 +23,12 @@
<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>
<text class="info-value">助教{{ scheduleInfo.coach_name }}</text>
<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.assistants.join(',') }}</text>
</view>
<view class="info-row">
<text class="info-label">上课场地</text>
@ -36,12 +39,12 @@
<text class="info-value">{{ scheduleInfo.class_info ? scheduleInfo.class_info.class_name : '未指定班级' }}</text>
</view>
</view>
<view class="section-title">调整后信息</view>
<!-- 班级选择 -->
<fui-form-item label="所属班级">
<picker
<picker
:value="classPickerIndex"
:range="classOptions"
:range-key="'class_name'"
@ -53,10 +56,10 @@
</view>
</picker>
</fui-form-item>
<!-- 教练选择 -->
<fui-form-item label="授课教练">
<picker
<picker
:value="coachPickerIndex"
:range="coachOptions"
:range-key="'name'"
@ -68,10 +71,10 @@
</view>
</picker>
</fui-form-item>
<!-- 场地选择 -->
<fui-form-item label="上课场地">
<picker
<picker
:value="venuePickerIndex"
:range="venueOptions"
:range-key="'venue_name'"
@ -83,10 +86,10 @@
</view>
</picker>
</fui-form-item>
<!-- 日期选择 -->
<fui-form-item label="上课日期">
<picker
<picker
mode="date"
:value="formData.course_date || scheduleInfo.course_date || getCurrentDate()"
:start="getMinDate()"
@ -99,10 +102,10 @@
</view>
</picker>
</fui-form-item>
<!-- 时间选择 -->
<fui-form-item label="上课时间">
<picker
<picker
:value="timePickerIndex"
:range="timeSlotOptions"
:range-key="'text'"
@ -114,7 +117,7 @@
</view>
</picker>
</fui-form-item>
<!-- 容量设置 -->
<fui-form-item label="课程容量">
<fui-input
@ -124,8 +127,8 @@
@input="formData.available_capacity = $event"
></fui-input>
</fui-form-item>
<!-- 提交按钮 -->
<view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button>
@ -144,13 +147,13 @@ export default {
//
loading: true,
submitting: false,
// ID
scheduleId: null,
//
scheduleInfo: {},
//
formData: {
schedule_id: '',
@ -161,20 +164,20 @@ export default {
time_slot: '',
available_capacity: ''
},
// showDatePicker
//
classOptions: [],
coachOptions: [],
venueOptions: [],
timeSlotOptions: [],
//
selectedClass: null,
selectedCoach: null,
selectedVenue: null,
// picker
classPickerIndex: 0,
coachPickerIndex: 0,
@ -182,7 +185,7 @@ export default {
timePickerIndex: 0
};
},
onLoad(options) {
if (options.id) {
this.scheduleId = options.id;
@ -199,21 +202,21 @@ export default {
}, 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.class_id = this.scheduleInfo.class_id;
this.formData.coach_id = this.scheduleInfo.coach_id;
@ -235,25 +238,25 @@ export default {
});
}
},
//
async loadFilterOptions() {
try {
const res = await api.getCourseScheduleFilterOptions();
if (res.code === 1) {
//
this.classOptions = res.data.classes || [];
//
this.coachOptions = res.data.coaches || [];
//
this.venueOptions = res.data.venues || [];
//
this.generateTimeSlotOptions();
//
this.findSelectedOptions();
} else {
@ -272,7 +275,7 @@ export default {
this.loading = false;
}
},
//
findSelectedOptions() {
//
@ -281,34 +284,34 @@ export default {
this.classPickerIndex = this.classOptions.findIndex(classItem => classItem.id === this.scheduleInfo.class_id);
if (this.classPickerIndex === -1) this.classPickerIndex = 0;
}
//
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();
},
//
onClassSelect(e) {
const index = e.detail.value;
@ -318,7 +321,7 @@ export default {
this.formData.class_id = this.selectedClass.id;
}
},
onCoachSelect(e) {
const index = e.detail.value;
this.coachPickerIndex = index;
@ -327,28 +330,28 @@ export default {
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;
@ -356,7 +359,7 @@ export default {
this.formData.time_slot = this.timeSlotOptions[index].value;
}
},
//
validateForm() {
//
@ -366,7 +369,7 @@ export default {
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: '未进行任何修改',
@ -374,11 +377,11 @@ export default {
});
return false;
}
return true;
},
//
async loadVenueTimeOptions(venueId) {
if (!venueId) {
@ -386,10 +389,10 @@ export default {
this.generateDefaultTimeOptions();
return;
}
try {
const res = await api.getVenueTimeOptions({ venue_id: venueId });
if (res.code === 1) {
this.timeSlotOptions = res.data.time_options || [];
// picker
@ -411,20 +414,20 @@ export default {
// 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;
},
@ -469,18 +472,18 @@ export default {
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();
@ -625,4 +628,4 @@ export default {
font-size: 28rpx;
color: #fff;
}
</style>
</style>

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

@ -25,10 +25,10 @@
<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 class="action-buttons">-->
<!-- <view class="view-btn primary" @click="checkAllStudents">全部签到</view>-->
<!-- <view class="view-btn danger" @click="uncheckAllStudents">全部取消</view>-->
<!-- </view>-->
</view>
<view class="empty-list" v-if="studentList.length === 0">
@ -50,19 +50,9 @@
</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 class="current-status" @click.stop="toggleStudentStatus(index)"
:class="'status-' + student.status">
{{ student.status_text }}
</view>
</view>
</view>
@ -103,6 +93,7 @@
<script>
import api from '@/api/apiRoute.js';
import util from '@/common/util.js';
export default {
data() {
@ -121,6 +112,7 @@
//
classPhoto: '',
classPhotoRemoteUrl: '', //
//
submitting: false
@ -276,7 +268,35 @@
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.classPhoto = res.tempFilePaths[0];
const tempFilePath = res.tempFilePaths[0];
this.classPhoto = tempFilePath;
//
uni.showLoading({
title: '上传中...'
});
// 使uploadFile
util.uploadFile(
tempFilePath,
(fileData) => {
// URL
this.classPhotoRemoteUrl = fileData.url;
uni.hideLoading();
uni.showToast({
title: '上传成功',
icon: 'success'
});
console.log('图片上传成功:', fileData);
},
(error) => {
//
this.classPhoto = '';
this.classPhotoRemoteUrl = '';
uni.hideLoading();
console.error('图片上传失败:', error);
}
);
},
fail: (error) => {
console.error('选择图片失败:', error);
@ -304,6 +324,7 @@
success: (res) => {
if (res.confirm) {
this.classPhoto = '';
this.classPhotoRemoteUrl = '';
}
}
});
@ -311,6 +332,15 @@
//
async submitSignIn() {
//
if (this.classPhoto && !this.classPhotoRemoteUrl) {
uni.showToast({
title: '图片上传中,请稍候',
icon: 'none'
});
return;
}
//
const studentData = this.studentList.map(student => ({
student_id: student.student_id,
@ -322,7 +352,7 @@
schedule_id: this.scheduleId,
students: studentData,
remark: this.signInRemark,
class_photo: this.classPhoto
class_photo: this.classPhotoRemoteUrl || '' // 使
};
this.submitting = true;
@ -442,6 +472,32 @@
gap: 16rpx;
}
.view-btn {
padding: 12rpx 24rpx;
font-size: 24rpx;
border-radius: 6rpx;
text-align: center;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
&.primary {
background-color: #29d3b4;
&:active {
background-color: #23b89d;
}
}
&.danger {
background-color: #ff3b30;
&:active {
background-color: #d63126;
}
}
}
.student-list {
max-height: 600rpx;
overflow-y: auto;
@ -518,20 +574,32 @@
margin-left: 16rpx;
}
.status-select {
display: flex;
gap: 10rpx;
}
.status-option {
padding: 8rpx 16rpx;
.current-status {
padding: 12rpx 20rpx;
font-size: 24rpx;
border-radius: 30rpx;
background-color: #3a3a3a;
color: #fff;
text-align: center;
min-width: 80rpx;
cursor: pointer;
transition: all 0.3s ease;
&.active {
background-color: #29d3b4;
&.status-0 {
background-color: #3a3a3a;
color: #fff;
}
&.status-1 {
background-color: #34c759;
color: #fff;
}
&.status-2 {
background-color: #ff9500;
color: #fff;
}
&:active {
transform: scale(0.95);
}
}
@ -644,4 +712,4 @@
margin-top: 40rpx;
padding-bottom: 40rpx;
}
</style>
</style>

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

@ -6,7 +6,7 @@
<view class="course-detail">
<text class="course-item">日期{{ schedule_info.course_date }}</text>
<!-- <text class="course-item">课程名称{{ schedule_info.course_name }}</text>-->
<text class="course-item">班级名称{{ schedule_info.class_name }}</text>
<text class="course-item">班级名称{{ schedule_info.class_name || '未设置' }}</text>
<text class="course-item">课程时间{{ schedule_info.time_slot }}</text>
<text class="course-item">主教练{{ schedule_info.coach_name || '待安排' }}</text>
<text class="course-item">场地信息{{ schedule_info.venue_name }}</text>
@ -18,9 +18,9 @@
<text class="section-title">正式学员</text>
<view class="cards-grid">
<!-- Student Card with Data -->
<view
v-for="(stu, idx) in formalStudents"
:key="idx"
<view
v-for="(stu, idx) in formalStudents"
:key="idx"
class="student-card filled"
@tap="viewStudent(stu)"
>
@ -38,10 +38,10 @@
</view>
<!-- Empty Slots -->
<view
v-for="n in formalEmptySeats"
:key="n"
class="student-card empty"
<view
v-for="n in formalEmptySeats"
:key="n"
class="student-card empty"
:data-type="'formal'"
:data-index="n"
@tap="openStudentModal"
@ -62,9 +62,9 @@
<text class="section-title waiting-title">等待位</text>
<view class="cards-grid">
<!-- Waiting Students -->
<view
v-for="(stu, idx) in waitingStudents"
:key="idx"
<view
v-for="(stu, idx) in waitingStudents"
:key="idx"
class="student-card waiting filled"
@tap="viewStudent(stu)"
>
@ -79,12 +79,12 @@
<view class="expiry-date" v-if="stu.expiryDate">到期时间{{ stu.expiryDate || '未设置' }}</view>
</view>
</view>
<!-- Waiting Empty Slots -->
<view
v-for="n in waitingEmptySeats"
:key="n"
class="student-card waiting"
<view
v-for="n in waitingEmptySeats"
:key="n"
class="student-card waiting"
:data-type="'waiting'"
:data-index="n"
@tap="openStudentModal"
@ -106,7 +106,7 @@
<view class="modal-header">
<text>添加学员</text>
</view>
<view class="modal-body">
<!-- 预设学生信息显示 - 优先显示 -->
<view v-if="presetStudent && presetStudent.name && presetStudent.phone" class="form-section">
@ -123,18 +123,18 @@
<!-- Customer Selection - 只在没有预设学生时显示 -->
<view v-if="!presetStudent || !presetStudent.name || !presetStudent.phone" class="form-section">
<text class="form-label">客户选择</text>
<input
<input
v-model="searchQuery"
placeholder="请输入手机号或姓名"
class="search-input"
@input="searchStudents"
/>
<!-- Search Results -->
<view v-if="searchResults.length > 0" class="search-results">
<view
v-for="student in searchResults"
<view
v-for="student in searchResults"
:key="student.id"
:class="['student-item', { selected: selectedStudent && selectedStudent.id === student.id }]"
@tap="selectStudent(student)"
@ -159,16 +159,16 @@
<text class="form-label">课程安排</text>
<view class="radio-group">
<label class="radio-item">
<radio
value="1"
<radio
value="1"
:checked="courseArrangement === '1'"
@tap="courseArrangement = '1'"
/>
<text class="radio-text">临时课</text>
</label>
<label class="radio-item" :class="{ disabled: !canSelectFixedCourse }">
<radio
value="2"
<radio
value="2"
:checked="courseArrangement === '2'"
:disabled="!canSelectFixedCourse"
@tap="selectFixedCourse"
@ -184,7 +184,7 @@
<!-- Remarks -->
<view class="form-section">
<text class="form-label">备注</text>
<textarea
<textarea
v-model="remarks"
placeholder="请输入备注信息"
class="remarks-textarea"
@ -209,9 +209,9 @@
<view class="leave-form">
<view class="leave-label">请假原因</view>
<view class="leave-input">
<textarea
v-model="leaveReason"
placeholder="请输入请假原因"
<textarea
v-model="leaveReason"
placeholder="请输入请假原因"
maxlength="200"
class="leave-textarea"
></textarea>
@ -228,7 +228,7 @@
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
name: 'ClassArrangementDetail',
components: {
@ -248,7 +248,7 @@
currentStudent: null, //
formalEmptySeats: [], //
waitingEmptySeats: [1], // 1
//
showModal: false,
searchQuery: '',
@ -266,14 +266,14 @@
this.course_id = query.id || query.schedule_id || '';
this.resource_id = query.resource_id || '';
this.student_id = query.student_id || '';
console.log('初始化参数 - course_id:', this.course_id, 'resource_id:', this.resource_id, 'student_id:', this.student_id);
// resource_idstudent_id
if (this.resource_id && this.student_id) {
this.loadPresetStudent();
}
// IDAPI
if (this.course_id) {
this.getScheduleDetail();
@ -305,15 +305,15 @@
async loadPresetStudent() {
try {
// 使API
const res = await apiRoute.getPresetStudentInfo({
const res = await apiRoute.getPresetStudentInfo({
resource_id: this.resource_id,
student_id: this.student_id
});
console.log('预设学员信息完整响应:', res);
if (res.code === 1 && res.data) {
const studentData = res.data;
this.presetStudent = {
id: studentData.student_id || this.student_id,
name: studentData.name || `学员${this.student_id}`,
@ -326,7 +326,7 @@
course_info: studentData.course_info || [],
student_course_id: studentData.student_course_id || null
};
this.selectedStudent = this.presetStudent;
console.log('加载预设学生信息成功:', this.presetStudent);
console.log('是否可选择固定课:', this.presetStudent.is_formal_student);
@ -341,7 +341,7 @@
this.createFallbackPresetStudent();
}
},
//
createFallbackPresetStudent() {
if (this.resource_id && this.student_id) {
@ -412,7 +412,7 @@
//
const isPhoneNumber = /^\d+$/.test(this.searchQuery.trim());
try {
uni.showLoading({
title: '搜索中...'
@ -427,7 +427,7 @@
}
const res = await apiRoute.searchStudents(params);
uni.hideLoading();
if (res.code === 1 && Array.isArray(res.data)) {
@ -485,7 +485,7 @@
});
return;
}
console.log(this.selectedStudent);
// 2.
if (!this.selectedStudent.name || !this.selectedStudent.phone) {
@ -557,7 +557,7 @@
} catch (error) {
uni.hideLoading();
console.error('添加学员失败:', error);
//
let errorMsg = '添加失败';
if (error && error.data && error.data.msg) {
@ -567,7 +567,7 @@
} else if (typeof error === 'string') {
errorMsg = error;
}
uni.showToast({
title: errorMsg,
icon: 'none'
@ -604,7 +604,7 @@
itemList: itemList,
success: (res) => {
const selectedOption = itemList[res.tapIndex];
if (selectedOption === '取消课程') {
//
uni.showModal({
@ -665,7 +665,7 @@
}
});
},
//
confirmUpgradeStudent(stu) {
//
@ -728,7 +728,7 @@
} catch (error) {
uni.hideLoading();
console.error('升级学员失败:', error);
//
let errorMsg = '升级失败';
if (error && error.data && error.data.msg) {
@ -738,14 +738,14 @@
} else if (typeof error === 'string') {
errorMsg = error;
}
uni.showToast({
title: errorMsg,
icon: 'none'
});
}
},
getStatusText(status) {
const statusMap = {
0: '待上课',
@ -930,7 +930,7 @@
} catch (error) {
uni.hideLoading();
console.error('添加学员失败:', error);
//
let errorMsg = '添加失败';
if (error && error.data && error.data.msg) {
@ -940,7 +940,7 @@
} else if (typeof error === 'string') {
errorMsg = error;
}
uni.showToast({
title: errorMsg,
icon: 'none'
@ -952,7 +952,7 @@
this.$refs.leaveReasonModal.close();
this.leaveReason = '';
},
//
submitLeaveRequest() {
if (!this.leaveReason.trim()) {
@ -1032,7 +1032,7 @@
display: flex;
align-items: center;
justify-content: center;
.back-icon {
color: #fff;
font-size: 48rpx;
@ -1075,13 +1075,13 @@
.section {
margin: 30rpx;
.section-title {
color: #ffd86b;
font-size: 28rpx;
margin-bottom: 30rpx;
display: block;
&.waiting-title {
color: #8a7fff !important;
}
@ -1240,12 +1240,12 @@
border-radius: 8rpx;
font-size: 20rpx;
font-weight: bold;
&.signed {
background-color: #29d3b4;
color: #fff;
}
&.unsigned {
background-color: #ffd86b;
color: #232323;
@ -1270,7 +1270,6 @@
background: #fff;
border-radius: 24rpx 24rpx 0 0;
width: 100%;
max-height: 80vh;
overflow: hidden;
}
@ -1373,7 +1372,7 @@
display: flex;
align-items: center;
gap: 16rpx;
.formal-badge {
background: #29d3b4;
color: #fff;
@ -1439,19 +1438,19 @@
.radio-item {
display: flex;
align-items: center;
.radio-text {
margin-left: 16rpx;
font-size: 28rpx;
color: #333;
}
&.disabled {
opacity: 0.5;
.radio-text {
color: #999;
.disabled-tip {
font-size: 24rpx;
color: #999;
@ -1510,7 +1509,7 @@
padding: 40rpx 40rpx 20rpx 40rpx;
text-align: center;
border-bottom: 1rpx solid #eee;
.leave-modal-title {
font-size: 32rpx;
font-weight: bold;
@ -1529,7 +1528,7 @@
.leave-input {
margin-bottom: 40rpx;
.leave-textarea {
width: 100%;
min-height: 200rpx;
@ -1546,19 +1545,19 @@
.leave-buttons {
display: flex;
gap: 20rpx;
.leave-btn {
flex: 1;
padding: 24rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
&.cancel-btn {
background: #f5f5f5;
color: #666;
}
&.confirm-btn {
background: #29d3b4;
color: #fff;
@ -1566,4 +1565,4 @@
}
}
}
</style>
</style>

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

@ -30,8 +30,7 @@
@change="onStudentSwiperChange">
<swiper-item v-for="(student, index) in studentList" :key="student.id">
<view class="student-swiper-content">
<StudentInfoCard :student="student" :show-details="true"
@action="handleStudentAction" />
<StudentInfoCard :student="student" :show-details="true" @action="handleStudentAction" />
</view>
</swiper-item>
</swiper>
@ -1826,4 +1825,4 @@ ${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at
}
@import './clue_info.less';
</style>
</style>

Loading…
Cancel
Save