36 changed files with 5159 additions and 3759 deletions
@ -0,0 +1,216 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\api\controller\apiController; |
|||
|
|||
use app\Request; |
|||
use app\service\api\apiService\CourseScheduleService; |
|||
use core\base\BaseApiService; |
|||
|
|||
/** |
|||
* 课程安排相关接口 |
|||
* Class CourseSchedule |
|||
* @package app\api\controller\apiController |
|||
*/ |
|||
class CourseSchedule extends BaseApiService |
|||
{ |
|||
/** |
|||
* 获取课程安排列表 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getScheduleList(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
return success((new CourseScheduleService())->getScheduleList($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排详情 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getScheduleInfo(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["id", 0] |
|||
]); |
|||
$result = (new CourseScheduleService())->getScheduleInfo($data['id']); |
|||
if (isset($result['code']) && $result['code'] === 0) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success('SUCCESS', $result); |
|||
} |
|||
|
|||
/** |
|||
* 创建课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function createSchedule(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
$result = (new CourseScheduleService())->createSchedule($data); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '创建成功', $result['data'] ?? []); |
|||
} |
|||
|
|||
/** |
|||
* 批量创建课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function batchCreateSchedule(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
$result = (new CourseScheduleService())->batchCreateSchedule($data); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '批量创建成功', $result['data'] ?? []); |
|||
} |
|||
|
|||
/** |
|||
* 更新课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function updateSchedule(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
$result = (new CourseScheduleService())->updateSchedule($data); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '更新成功', $result['data'] ?? []); |
|||
} |
|||
|
|||
/** |
|||
* 删除课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function deleteSchedule(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["id", 0] |
|||
]); |
|||
$result = (new CourseScheduleService())->deleteSchedule($data['id']); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '删除成功'); |
|||
} |
|||
|
|||
/** |
|||
* 获取场地列表 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getVenueList(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
return success((new CourseScheduleService())->getVenueList($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取场地可用时间 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getVenueAvailableTime(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["venue_id", 0], |
|||
["date", ""] |
|||
]); |
|||
return success((new CourseScheduleService())->getVenueAvailableTime($data)); |
|||
} |
|||
|
|||
/** |
|||
* 检查教练时间冲突 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function checkCoachConflict(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["coach_id", 0], |
|||
["date", ""], |
|||
["time_slot", ""], |
|||
["schedule_id", 0] // 排除当前正在编辑的课程安排 |
|||
]); |
|||
return success((new CourseScheduleService())->checkCoachConflict($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排统计 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getScheduleStatistics(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
return success((new CourseScheduleService())->getScheduleStatistics($data)); |
|||
} |
|||
|
|||
/** |
|||
* 学员加入课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function joinSchedule(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["schedule_id", 0], |
|||
["student_id", 0], |
|||
["course_type", 0], // 0-正常, 1-加课, 2-补课, 3-等待位 |
|||
["resources_id", 0] |
|||
]); |
|||
$result = (new CourseScheduleService())->joinSchedule($data); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '添加成功', $result['data'] ?? []); |
|||
} |
|||
|
|||
/** |
|||
* 学员退出课程安排 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function leaveSchedule(Request $request) |
|||
{ |
|||
$data = $this->request->params([ |
|||
["schedule_id", 0], |
|||
["student_id", 0], |
|||
["remark", ""] |
|||
]); |
|||
$result = (new CourseScheduleService())->leaveSchedule($data); |
|||
if (!$result['code']) { |
|||
return fail($result['msg']); |
|||
} |
|||
return success($result['msg'] ?? '操作成功'); |
|||
} |
|||
|
|||
/** |
|||
* 获取筛选选项 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function getFilterOptions(Request $request) |
|||
{ |
|||
$data = $request->all(); |
|||
return success((new CourseScheduleService())->getFilterOptions($data)); |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\api\controller\apiController; |
|||
|
|||
use app\service\api\apiService\CourseScheduleService; |
|||
use core\base\BaseApiController; |
|||
|
|||
/** |
|||
* 课程安排控制器 |
|||
*/ |
|||
class CourseScheduleController extends BaseApiController |
|||
{ |
|||
/** |
|||
* 获取课程安排列表(支持多维度筛选) |
|||
*/ |
|||
public function getScheduleList() |
|||
{ |
|||
$data = $this->request->get(); |
|||
$service = new CourseScheduleService(); |
|||
return success('获取成功', $service->getScheduleList($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排详情 |
|||
*/ |
|||
public function getScheduleInfo() |
|||
{ |
|||
$scheduleId = $this->request->get('schedule_id', 0); |
|||
if (empty($scheduleId)) { |
|||
return fail('课程安排ID不能为空'); |
|||
} |
|||
|
|||
$service = new CourseScheduleService(); |
|||
$result = $service->getScheduleInfo($scheduleId); |
|||
|
|||
if (isset($result['code']) && $result['code'] == 0) { |
|||
return fail($result['msg'] ?? '获取课程安排详情失败'); |
|||
} |
|||
|
|||
return success('获取成功', $result); |
|||
} |
|||
|
|||
/** |
|||
* 创建课程安排 |
|||
*/ |
|||
public function createSchedule() |
|||
{ |
|||
$data = $this->request->post(); |
|||
$service = new CourseScheduleService(); |
|||
$result = $service->createSchedule($data); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg'], $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 批量创建课程安排 |
|||
*/ |
|||
public function batchCreateSchedule() |
|||
{ |
|||
$data = $this->request->post(); |
|||
$service = new CourseScheduleService(); |
|||
$result = $service->batchCreateSchedule($data); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg'], $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 更新课程安排 |
|||
*/ |
|||
public function updateSchedule() |
|||
{ |
|||
$data = $this->request->post(); |
|||
$scheduleId = $data['schedule_id'] ?? 0; |
|||
|
|||
if (empty($scheduleId)) { |
|||
return fail('课程安排ID不能为空'); |
|||
} |
|||
|
|||
$service = new CourseScheduleService(); |
|||
$result = $service->updateSchedule($scheduleId, $data); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg'], $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除课程安排 |
|||
*/ |
|||
public function deleteSchedule() |
|||
{ |
|||
$scheduleId = $this->request->post('schedule_id', 0); |
|||
if (empty($scheduleId)) { |
|||
return fail('课程安排ID不能为空'); |
|||
} |
|||
|
|||
$service = new CourseScheduleService(); |
|||
$result = $service->deleteSchedule($scheduleId); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取场地列表 |
|||
*/ |
|||
public function getVenueList() |
|||
{ |
|||
$data = $this->request->get(); |
|||
$service = new CourseScheduleService(); |
|||
return success('获取成功', $service->getVenueList($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取场地可用时间 |
|||
*/ |
|||
public function getVenueAvailableTime() |
|||
{ |
|||
$venueId = $this->request->get('venue_id', 0); |
|||
$date = $this->request->get('date', ''); |
|||
|
|||
if (empty($venueId)) { |
|||
return fail('场地ID不能为空'); |
|||
} |
|||
|
|||
if (empty($date)) { |
|||
return fail('日期不能为空'); |
|||
} |
|||
|
|||
$service = new CourseScheduleService(); |
|||
return success('获取成功', $service->getVenueAvailableTime($venueId, $date)); |
|||
} |
|||
|
|||
/** |
|||
* 检查教练时间冲突 |
|||
*/ |
|||
public function checkCoachConflict() |
|||
{ |
|||
$data = $this->request->get(); |
|||
$service = new CourseScheduleService(); |
|||
return success('检查完成', $service->checkCoachConflict($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排统计 |
|||
*/ |
|||
public function getScheduleStatistics() |
|||
{ |
|||
$data = $this->request->get(); |
|||
$service = new CourseScheduleService(); |
|||
return success('获取成功', $service->getScheduleStatistics($data)); |
|||
} |
|||
|
|||
/** |
|||
* 学员加入课程安排 |
|||
*/ |
|||
public function joinSchedule() |
|||
{ |
|||
$data = $this->request->post(); |
|||
$service = new CourseScheduleService(); |
|||
$result = $service->joinSchedule($data); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg'], $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 学员退出课程安排 |
|||
*/ |
|||
public function leaveSchedule() |
|||
{ |
|||
$data = $this->request->post(); |
|||
$service = new CourseScheduleService(); |
|||
$result = $service->leaveSchedule($data); |
|||
|
|||
if ($result['code'] == 1) { |
|||
return success($result['msg']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取筛选选项(教练、课程、班级等) |
|||
*/ |
|||
public function getFilterOptions() |
|||
{ |
|||
$data = $this->request->get(); |
|||
$service = new CourseScheduleService(); |
|||
return success('获取成功', $service->getFilterOptions($data)); |
|||
} |
|||
} |
|||
@ -0,0 +1,567 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\service\api\apiService; |
|||
|
|||
use core\base\BaseApiService; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* 课程安排服务类 |
|||
*/ |
|||
class CourseScheduleService extends BaseApiService |
|||
{ |
|||
// 定义表前缀,可以更方便地引用数据表 |
|||
protected $prefix = 'school_'; |
|||
|
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排列表(支持多维度筛选) |
|||
* @param array $data 筛选参数 |
|||
* @return array 课程安排列表数据 |
|||
*/ |
|||
public function getScheduleList($data = []) |
|||
{ |
|||
try { |
|||
// 构建查询条件 |
|||
$where = $this->buildScheduleWhere($data); |
|||
|
|||
// 分页参数 |
|||
$page = intval($data['page'] ?? 1); |
|||
$limit = intval($data['limit'] ?? 20); |
|||
$offset = ($page - 1) * $limit; |
|||
|
|||
// 基础查询 |
|||
$query = Db::name($this->prefix . 'course_schedule') |
|||
->alias('cs') |
|||
->leftJoin($this->prefix . 'course c', 'cs.course_id = c.id') |
|||
->leftJoin($this->prefix . 'venue v', 'cs.venue_id = v.id') |
|||
->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id') |
|||
->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id') |
|||
->leftJoin($this->prefix . 'personnel edu', 'cs.education_id = edu.id') |
|||
->where($where) |
|||
->where('cs.deleted_at', 0); |
|||
|
|||
// 获取总数 |
|||
$total = $query->count(); |
|||
|
|||
// 获取列表数据 |
|||
$list = $query->field([ |
|||
'cs.*', |
|||
'c.course_name', |
|||
'c.course_type', |
|||
'c.duration as course_duration', |
|||
'c.session_count', |
|||
'c.single_session_count', |
|||
'v.venue_name', |
|||
'v.capacity as venue_capacity', |
|||
'cap.campus_name', |
|||
'coach.name as coach_name', |
|||
'coach.head_img as coach_avatar', |
|||
'edu.name as education_name' |
|||
]) |
|||
->order('cs.course_date DESC, cs.time_slot ASC') |
|||
->limit($offset, $limit) |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 处理列表数据 |
|||
foreach ($list as &$item) { |
|||
// 解析时间段 |
|||
$item['time_info'] = $this->parseTimeSlot($item['time_slot']); |
|||
|
|||
// 获取参与学员信息 |
|||
$item['students'] = $this->getScheduleStudents($item['id']); |
|||
|
|||
// 获取助教信息 |
|||
$item['assistants'] = $this->getScheduleAssistants($item['assistant_ids']); |
|||
|
|||
// 计算已报名人数 |
|||
$item['enrolled_count'] = count($item['students']); |
|||
|
|||
// 计算剩余容量 |
|||
$item['remaining_capacity'] = max(0, ($item['available_capacity'] ?? $item['venue_capacity']) - $item['enrolled_count']); |
|||
|
|||
// 格式化状态 |
|||
$item['status_text'] = $this->getStatusText($item['status']); |
|||
|
|||
// 格式化创建方式 |
|||
$item['created_by_text'] = $item['created_by'] == 'manual' ? '手动安排' : '系统创建'; |
|||
|
|||
// 处理图片路径 |
|||
$item['coach_avatar'] = $item['coach_avatar'] ? $this->formatImageUrl($item['coach_avatar']) : ''; |
|||
} |
|||
|
|||
return [ |
|||
'list' => $list, |
|||
'total' => $total, |
|||
'page' => $page, |
|||
'limit' => $limit, |
|||
'pages' => ceil($total / $limit) |
|||
]; |
|||
|
|||
} catch (\Exception $e) { |
|||
return [ |
|||
'list' => [], |
|||
'total' => 0, |
|||
'page' => 1, |
|||
'limit' => $limit ?? 20, |
|||
'pages' => 0, |
|||
'error' => $e->getMessage() |
|||
]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 构建查询条件 |
|||
* @param array $data 筛选参数 |
|||
* @return array 条件数组 |
|||
*/ |
|||
private function buildScheduleWhere($data) |
|||
{ |
|||
$where = []; |
|||
|
|||
// 日期范围筛选 |
|||
if (!empty($data['start_date'])) { |
|||
$where[] = ['cs.course_date', '>=', $data['start_date']]; |
|||
} |
|||
if (!empty($data['end_date'])) { |
|||
$where[] = ['cs.course_date', '<=', $data['end_date']]; |
|||
} |
|||
|
|||
// 校区筛选 |
|||
if (!empty($data['campus_id'])) { |
|||
if (is_array($data['campus_id'])) { |
|||
$where[] = ['cs.campus_id', 'in', $data['campus_id']]; |
|||
} else { |
|||
$where[] = ['cs.campus_id', '=', $data['campus_id']]; |
|||
} |
|||
} |
|||
|
|||
// 场地筛选 |
|||
if (!empty($data['venue_id'])) { |
|||
if (is_array($data['venue_id'])) { |
|||
$where[] = ['cs.venue_id', 'in', $data['venue_id']]; |
|||
} else { |
|||
$where[] = ['cs.venue_id', '=', $data['venue_id']]; |
|||
} |
|||
} |
|||
|
|||
// 教练筛选 |
|||
if (!empty($data['coach_id'])) { |
|||
if (is_array($data['coach_id'])) { |
|||
$where[] = ['cs.coach_id', 'in', $data['coach_id']]; |
|||
} else { |
|||
$where[] = ['cs.coach_id', '=', $data['coach_id']]; |
|||
} |
|||
} |
|||
|
|||
// 课程筛选 |
|||
if (!empty($data['course_id'])) { |
|||
if (is_array($data['course_id'])) { |
|||
$where[] = ['cs.course_id', 'in', $data['course_id']]; |
|||
} else { |
|||
$where[] = ['cs.course_id', '=', $data['course_id']]; |
|||
} |
|||
} |
|||
|
|||
// 状态筛选 |
|||
if (!empty($data['status'])) { |
|||
if (is_array($data['status'])) { |
|||
$where[] = ['cs.status', 'in', $data['status']]; |
|||
} else { |
|||
$where[] = ['cs.status', '=', $data['status']]; |
|||
} |
|||
} |
|||
|
|||
// 教务筛选 |
|||
if (!empty($data['education_id'])) { |
|||
$where[] = ['cs.education_id', '=', $data['education_id']]; |
|||
} |
|||
|
|||
// 时间段筛选 |
|||
if (!empty($data['time_range'])) { |
|||
switch ($data['time_range']) { |
|||
case 'morning': |
|||
$where[] = ['cs.time_slot', 'like', '0%']; |
|||
break; |
|||
case 'afternoon': |
|||
$where[] = ['cs.time_slot', 'like', '1%']; |
|||
break; |
|||
case 'evening': |
|||
$where[] = ['cs.time_slot', 'like', '1[8-9]%']; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// 自动排课筛选 |
|||
if (isset($data['auto_schedule'])) { |
|||
$where[] = ['cs.auto_schedule', '=', $data['auto_schedule']]; |
|||
} |
|||
|
|||
// 创建方式筛选 |
|||
if (!empty($data['created_by'])) { |
|||
$where[] = ['cs.created_by', '=', $data['created_by']]; |
|||
} |
|||
|
|||
return $where; |
|||
} |
|||
|
|||
/** |
|||
* 解析时间段 |
|||
* @param string $timeSlot 时间段字符串(格式如:09:00-10:30) |
|||
* @return array 解析后的时间段信息 |
|||
*/ |
|||
private function parseTimeSlot($timeSlot) |
|||
{ |
|||
if (strpos($timeSlot, '-') !== false) { |
|||
list($startTime, $endTime) = explode('-', $timeSlot); |
|||
return [ |
|||
'start_time' => trim($startTime), |
|||
'end_time' => trim($endTime), |
|||
'duration' => $this->calculateDuration(trim($startTime), trim($endTime)) |
|||
]; |
|||
} |
|||
|
|||
return [ |
|||
'start_time' => $timeSlot, |
|||
'end_time' => '', |
|||
'duration' => 60 // 默认1小时 |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 计算时长(分钟) |
|||
* @param string $startTime 开始时间 |
|||
* @param string $endTime 结束时间 |
|||
* @return int 时长(分钟) |
|||
*/ |
|||
private function calculateDuration($startTime, $endTime) |
|||
{ |
|||
try { |
|||
$start = strtotime($startTime); |
|||
$end = strtotime($endTime); |
|||
return ($end - $start) / 60; |
|||
} catch (\Exception $e) { |
|||
return 60; // 默认1小时 |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排的学员信息 |
|||
* @param int $scheduleId 课程安排ID |
|||
* @return array 学员信息数组 |
|||
*/ |
|||
private function getScheduleStudents($scheduleId) |
|||
{ |
|||
try { |
|||
$students = Db::name($this->prefix . 'person_course_schedule') |
|||
->alias('pcs') |
|||
->leftJoin($this->prefix . 'student s', 'pcs.student_id = s.id') |
|||
->leftJoin($this->prefix . 'customer_resources cr', 'pcs.resources_id = cr.id') |
|||
->leftJoin($this->prefix . 'member m', 'cr.member_id = m.member_id') |
|||
->where('pcs.schedule_id', $scheduleId) |
|||
->where('pcs.deleted_at', 0) |
|||
->field([ |
|||
'pcs.*', |
|||
's.name as student_name', |
|||
'cr.name as resource_name', |
|||
'cr.phone_number', |
|||
'cr.age', |
|||
'cr.gender', |
|||
'm.headimg as avatar' |
|||
]) |
|||
->select() |
|||
->toArray(); |
|||
|
|||
foreach ($students as &$student) { |
|||
$student['name'] = $student['student_name'] ?: $student['resource_name']; |
|||
$student['avatar'] = $student['avatar'] ? $this->formatImageUrl($student['avatar']) : ''; |
|||
$student['status_text'] = $this->getStudentStatusText($student['status']); |
|||
$student['person_type_text'] = $student['person_type'] == 'student' ? '正式学员' : '体验学员'; |
|||
$student['course_type_text'] = $this->getCourseTypeText($student['course_type']); |
|||
} |
|||
|
|||
return $students; |
|||
} catch (\Exception $e) { |
|||
return []; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取助教信息 |
|||
* @param string $assistantIds 助教ID字符串,使用逗号分隔 |
|||
* @return array 助教信息数组 |
|||
*/ |
|||
private function getScheduleAssistants($assistantIds) |
|||
{ |
|||
if (empty($assistantIds)) { |
|||
return []; |
|||
} |
|||
|
|||
try { |
|||
$ids = explode(',', $assistantIds); |
|||
$assistants = Db::name($this->prefix . 'personnel') |
|||
->whereIn('id', $ids) |
|||
->field('id, name, head_img, phone') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
foreach ($assistants as &$assistant) { |
|||
$assistant['head_img'] = $assistant['head_img'] ? $this->formatImageUrl($assistant['head_img']) : ''; |
|||
} |
|||
|
|||
return $assistants; |
|||
} catch (\Exception $e) { |
|||
return []; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取状态文本 |
|||
* @param string $status 状态码 |
|||
* @return string 状态文本描述 |
|||
*/ |
|||
private function getStatusText($status) |
|||
{ |
|||
$statusMap = [ |
|||
'pending' => '待开始', |
|||
'upcoming' => '即将开始', |
|||
'ongoing' => '进行中', |
|||
'completed' => '已结束' |
|||
]; |
|||
|
|||
return $statusMap[$status] ?? $status; |
|||
} |
|||
|
|||
/** |
|||
* 获取学员状态文本 |
|||
* @param int $status 学员状态码 |
|||
* @return string 学员状态文本描述 |
|||
*/ |
|||
private function getStudentStatusText($status) |
|||
{ |
|||
$statusMap = [ |
|||
0 => '待上课', |
|||
1 => '已上课', |
|||
2 => '请假' |
|||
]; |
|||
|
|||
return $statusMap[$status] ?? '未知'; |
|||
} |
|||
|
|||
/** |
|||
* 获取课程类型文本 |
|||
* @param int $courseType 课程类型码 |
|||
* @return string 课程类型文本描述 |
|||
*/ |
|||
private function getCourseTypeText($courseType) |
|||
{ |
|||
$typeMap = [ |
|||
1 => '加课', |
|||
2 => '补课', |
|||
3 => '等待位' |
|||
]; |
|||
|
|||
return $typeMap[$courseType] ?? '正常课程'; |
|||
} |
|||
|
|||
/** |
|||
* 格式化图片URL |
|||
* @param string $imagePath 图片路径 |
|||
* @return string 格式化后的图片URL |
|||
*/ |
|||
private function formatImageUrl($imagePath) |
|||
{ |
|||
if (empty($imagePath)) { |
|||
return ''; |
|||
} |
|||
|
|||
// 如果已经是完整URL,直接返回 |
|||
if (strpos($imagePath, 'http') === 0) { |
|||
return $imagePath; |
|||
} |
|||
|
|||
// 拼接域名 |
|||
$domain = request()->domain(); |
|||
return $domain . '/' . ltrim($imagePath, '/'); |
|||
} |
|||
|
|||
/** |
|||
* 获取筛选选项(教练、课程、班级等) |
|||
* @param array $data 请求参数 |
|||
* @return array 筛选选项数据 |
|||
*/ |
|||
public function getFilterOptions($data = []) |
|||
{ |
|||
try { |
|||
$result = [ |
|||
'coaches' => [], // 教练列表 |
|||
'courses' => [], // 课程列表 |
|||
'classes' => [], // 班级列表 |
|||
'venues' => [], // 场地列表 |
|||
'campuses' => [], // 校区列表 |
|||
'status_options' => [] // 状态选项 |
|||
]; |
|||
|
|||
// 获取教练列表 |
|||
$result['coaches'] = Db::name($this->prefix . 'personnel') |
|||
->where('is_coach', 1) |
|||
->where('deleted_at', 0) |
|||
->field('id, name, head_img as avatar, phone') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
foreach ($result['coaches'] as &$coach) { |
|||
$coach['avatar'] = $coach['avatar'] ? $this->formatImageUrl($coach['avatar']) : ''; |
|||
} |
|||
|
|||
// 获取课程列表 |
|||
$result['courses'] = Db::name($this->prefix . 'course') |
|||
->where('deleted_at', 0) |
|||
->field('id, course_name, course_type, duration') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 获取班级列表 |
|||
$result['classes'] = Db::name($this->prefix . 'class') |
|||
->where('deleted_at', 0) |
|||
->field('id, class_name, class_level, total_students') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 获取场地列表 |
|||
$result['venues'] = Db::name($this->prefix . 'venue') |
|||
->where('deleted_at', 0) |
|||
->field('id, venue_name, capacity, description') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 获取校区列表 |
|||
$result['campuses'] = Db::name($this->prefix . 'campus') |
|||
->where('deleted_at', 0) |
|||
->field('id, campus_name, address') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 状态选项 |
|||
$result['status_options'] = [ |
|||
['value' => 'pending', 'label' => '待开始'], |
|||
['value' => 'upcoming', 'label' => '即将开始'], |
|||
['value' => 'ongoing', 'label' => '进行中'], |
|||
['value' => 'completed', 'label' => '已结束'] |
|||
]; |
|||
|
|||
return $result; |
|||
|
|||
} catch (\Exception $e) { |
|||
return [ |
|||
'coaches' => [], |
|||
'courses' => [], |
|||
'classes' => [], |
|||
'venues' => [], |
|||
'campuses' => [], |
|||
'status_options' => [], |
|||
'error' => $e->getMessage() |
|||
]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取课程安排详情 |
|||
* @param int $scheduleId 课程安排ID |
|||
* @return array 课程安排详细信息或错误信息 |
|||
*/ |
|||
public function getScheduleInfo($scheduleId) |
|||
{ |
|||
try { |
|||
// 查询课程安排信息 |
|||
$schedule = Db::name($this->prefix . 'course_schedule') |
|||
->alias('cs') |
|||
->leftJoin($this->prefix . 'course c', 'cs.course_id = c.id') |
|||
->leftJoin($this->prefix . 'venue v', 'cs.venue_id = v.id') |
|||
->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id') |
|||
->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id') |
|||
->leftJoin($this->prefix . 'personnel edu', 'cs.education_id = edu.id') |
|||
->where('cs.id', $scheduleId) |
|||
->where('cs.deleted_at', 0) |
|||
->field([ |
|||
'cs.*', |
|||
'c.course_name', |
|||
'c.course_type', |
|||
'c.duration as course_duration', |
|||
'c.session_count', |
|||
'c.single_session_count', |
|||
'v.venue_name', |
|||
'v.capacity as venue_capacity', |
|||
'cap.campus_name', |
|||
'coach.name as coach_name', |
|||
'coach.head_img as coach_avatar', |
|||
'edu.name as education_name' |
|||
]) |
|||
->find(); |
|||
|
|||
if (empty($schedule)) { |
|||
return ['code' => 0, 'msg' => '课程安排不存在或已被删除']; |
|||
} |
|||
|
|||
// 解析时间段 |
|||
$schedule['time_info'] = $this->parseTimeSlot($schedule['time_slot']); |
|||
|
|||
// 获取参与学员信息 |
|||
$schedule['students'] = $this->getScheduleStudents($schedule['id']); |
|||
|
|||
// 获取助教信息 |
|||
$schedule['assistants'] = $this->getScheduleAssistants($schedule['assistant_ids']); |
|||
|
|||
// 计算已报名人数 |
|||
$schedule['enrolled_count'] = count($schedule['students']); |
|||
|
|||
// 计算剩余容量 |
|||
$schedule['remaining_capacity'] = max(0, ($schedule['available_capacity'] ?? $schedule['venue_capacity']) - $schedule['enrolled_count']); |
|||
|
|||
// 格式化状态 |
|||
$schedule['status_text'] = $this->getStatusText($schedule['status']); |
|||
|
|||
// 格式化创建方式 |
|||
$schedule['created_by_text'] = $schedule['created_by'] == 'manual' ? '手动安排' : '系统创建'; |
|||
|
|||
// 处理图片路径 |
|||
$schedule['coach_avatar'] = $schedule['coach_avatar'] ? $this->formatImageUrl($schedule['coach_avatar']) : ''; |
|||
|
|||
// 获取班级相关信息 |
|||
if (!empty($schedule['class_id'])) { |
|||
$schedule['class_info'] = Db::name($this->prefix . 'class') |
|||
->where('id', $schedule['class_id']) |
|||
->field('id, class_name, class_level, total_students') |
|||
->find(); |
|||
} else { |
|||
$schedule['class_info'] = null; |
|||
} |
|||
|
|||
// 获取历史变更记录 |
|||
$schedule['change_history'] = Db::name($this->prefix . 'course_schedule_changes') |
|||
->where('schedule_id', $scheduleId) |
|||
->order('created_at DESC') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
return $schedule; |
|||
|
|||
} catch (\Exception $e) { |
|||
return ['code' => 0, 'msg' => $e->getMessage()]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
school_campus(校区表) |
|||
CREATE TABLE `school_campus` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', |
|||
`campus_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '校区名称', |
|||
`campus_address` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '校区地址', |
|||
`campus_preview_image` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '校区预览图,存储图片路径', |
|||
`campus_coordinates` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '校区坐标,格式为经度,纬度', |
|||
`campus_introduction` text COLLATE utf8mb4_general_ci COMMENT '校区介绍', |
|||
`campus_status` tinyint DEFAULT '1' COMMENT '校区状态:0-禁用,1-启用', |
|||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '校区创建时间', |
|||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '校区更新时间', |
|||
`delete_time` int NOT NULL DEFAULT '0' COMMENT '逻辑删除字段,0表示未删除,非空表示已删除', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='校区表'; |
|||
做为基础数据,下面一级就是场地。 |
|||
|
|||
school_venue(场地表) |
|||
CREATE TABLE `school_venue` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '场地编号', |
|||
`campus_id` int NOT NULL COMMENT '校区ID', |
|||
`venue_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '场地名称', |
|||
`capacity` int NOT NULL COMMENT '场地可容纳人数上限', |
|||
`availability_status` tinyint(1) NOT NULL COMMENT '场地可用状态: 1-可用, 0-不可用', |
|||
`time_range_type` enum('range','fixed','all') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '场地可用时间范围类型: range-范围类型, fixed-固定时间范围类型', |
|||
`time_range_start` varchar(8) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '范围类型的开始时间', |
|||
`time_range_end` varchar(8) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '范围类型的结束时间', |
|||
`fixed_time_ranges` json DEFAULT NULL COMMENT '固定时间范围类型的可用时间, 存储为JSON数组', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', |
|||
`deleted_at` int DEFAULT '0' COMMENT '逻辑删除时间', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='场地表'; |
|||
场地表做为课程的容器,主要是用来限制人数,时间,和可用状态。如果场地被设置为不可用,那么该场地下的所有课程都会被取消。其中time_range_type字段决定这个时间范围是范围类型还是固定时间类型。 |
|||
这个字段配合time_range_start,time_range_end,fixed_time_ranges这三个字段,可以确定这个场地可用的时间范围。 |
|||
|
|||
school_course(课程表) |
|||
CREATE TABLE `school_course` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '课程编号', |
|||
`course_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '课程名称', |
|||
`course_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '课程类型', |
|||
`duration` int NOT NULL COMMENT '课程时长', |
|||
`session_count` int NOT NULL COMMENT '课时数量', |
|||
`single_session_count` int NOT NULL DEFAULT '0' COMMENT '单次消课数量', |
|||
`gift_session_count` int NOT NULL DEFAULT '0' COMMENT '赠送课时数量', |
|||
`price` decimal(10,2) NOT NULL COMMENT '课程价格', |
|||
`internal_reminder` int NOT NULL COMMENT '内部提醒课时', |
|||
`customer_reminder` int NOT NULL COMMENT '客户提醒课时', |
|||
`remarks` text COLLATE utf8mb4_general_ci COMMENT '课程备注', |
|||
`created_at` int DEFAULT '0' COMMENT '创建时间', |
|||
`updated_at` int DEFAULT '0' COMMENT '更新时间', |
|||
`deleted_at` int DEFAULT '0' COMMENT '逻辑删除时间', |
|||
`contract_id` int DEFAULT NULL, |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='课程表'; |
|||
课程表主要是用户上课的内容,在排课阶段主要是看用户排的是什么课在哪个课程安排里展示。课程表和课程安排表是一对多的关系,一个课程可以有多个课程安排。但是在一个周期范围内,这个安排的数量始终是不会超过课程表的课时数量的。 |
|||
举例来说single_session_count字段表示一次课程安排要消耗多少课时,如果值为 2,session_count课时数量是 10,那么这次课程周期一个用户只能排 5 次课程安排。 |
|||
|
|||
school_course_schedule(课程安排表) |
|||
CREATE TABLE `school_course_schedule` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '课程安排编号', |
|||
`campus_id` int NOT NULL COMMENT '校区ID', |
|||
`venue_id` int NOT NULL COMMENT '场地ID', |
|||
`course_date` date NOT NULL COMMENT '上课日期', |
|||
`time_slot` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '上课时段', |
|||
`course_id` int NOT NULL COMMENT '课程ID', |
|||
`coach_id` int NOT NULL COMMENT '上课教练ID', |
|||
`participants` json DEFAULT NULL COMMENT '参与人员列表,存储为JSON数组,包含学员ID和来源信息', |
|||
`student_ids` json DEFAULT NULL COMMENT '上课学生ID列表,存储为JSON数组', |
|||
`available_capacity` int DEFAULT NULL COMMENT '根据场地容量判断的可安排学员位置数量', |
|||
`status` enum('pending','upcoming','ongoing','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'pending' COMMENT '课程状态: pending-待开始, upcoming-即将开始, ongoing-进行中, completed-已结束', |
|||
`auto_schedule` tinyint(1) DEFAULT NULL COMMENT '是否自动排课1是0否', |
|||
`created_by` enum('manual','system') COLLATE utf8mb4_general_ci NOT NULL COMMENT '课程安排创建方式: manual-人员安排, system-系统创建', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', |
|||
`deleted_at` int DEFAULT '0' COMMENT '逻辑删除时间', |
|||
`assistant_ids` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '助教ID列表(逗号分隔)', |
|||
`education_id` int DEFAULT NULL COMMENT '教务ID', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=120 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='课程安排表'; |
|||
这个表是在学校层面针对教室的情况做的课程安排情况表。这个表和课程表是一对多的关系,一个课程可以有多个课程安排。而且支持一次配置后自动排课的功能。这个表的主要作用是记录课程安排的情况,包括上课日期,时间段,教练,参与人员等信息。 |
|||
|
|||
school_person_course_schedule(人员与课程安排关系表) |
|||
CREATE TABLE `school_person_course_schedule` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '关系编号', |
|||
`resources_id` int DEFAULT NULL COMMENT '资源ID', |
|||
`person_id` int DEFAULT NULL COMMENT '人员ID', |
|||
`student_id` int DEFAULT NULL COMMENT '学员ID', |
|||
`person_type` enum('student','customer_resource') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '人员类型: student-正式学员, customer_resource-客户资源,teacher教练', |
|||
`schedule_id` int NOT NULL COMMENT '课程安排ID', |
|||
`course_date` date NOT NULL COMMENT '上课日期', |
|||
`schedule_type` tinyint(1) DEFAULT NULL COMMENT '课程安排类型1临时课2固定课', |
|||
`course_type` tinyint(1) DEFAULT NULL COMMENT '课程类型1加课2补课3 等待位', |
|||
`time_slot` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '上课时段', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
`deleted_at` int DEFAULT '0' COMMENT '删除', |
|||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态0待上课1已上课2请假', |
|||
`remark` varchar(512) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请假备注', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='人员与课程安排关系表'; |
|||
这个表记录着学员和school_course_schedule中安排的关系。这里面有几种情况 1.正式的付费学员上课(临时的)2.临时的体验学员上课 3.正式的付费学员固定这个时段上课的。 |
|||
然后这个课程安排的属性还有是正常消耗课时的,还是补课、还是送课。还是这个教室没有位置单纯是等待的课程。 |
|||
school_student_courses(学员课程表) |
|||
CREATE TABLE `school_student_courses` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '记录编号', |
|||
`student_id` int NOT NULL COMMENT '学员ID', |
|||
`course_id` int NOT NULL COMMENT '课程ID', |
|||
`total_hours` int NOT NULL COMMENT '总正式课时数', |
|||
`gift_hours` int DEFAULT '0' COMMENT '赠送课时数', |
|||
`start_date` date NOT NULL COMMENT '课程开始日期', |
|||
`end_date` date NOT NULL COMMENT '课程结束日期', |
|||
`use_total_hours` int NOT NULL DEFAULT '0' COMMENT '已使用课包课时数', |
|||
`use_gift_hours` int NOT NULL DEFAULT '0' COMMENT '已使用课包赠送课时数', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', |
|||
`single_session_count` int DEFAULT NULL COMMENT '单次消课数量', |
|||
`status` tinyint(1) DEFAULT NULL COMMENT '课程状态1有效2过期3等待期4延期', |
|||
`resource_id` int DEFAULT NULL COMMENT '资源ID', |
|||
`main_coach_id` int DEFAULT NULL COMMENT '主教练ID', |
|||
`assistant_ids` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '助教ID列表(逗号分隔)', |
|||
`education_id` int DEFAULT NULL COMMENT '教务ID', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学员课程表'; |
|||
这个是一个学员在一个课程周期里可以上课的课时记录以及课时有效期记录,在这个周期范围内为这个学员服务的教练和助教信息。这个表的主要作用是记录学员的课程信息,包括总课时数,赠送课时数,开始和结束日期等信息。这个表和人员与课程安排关系表是一对多的关系,一个学员可以有多个课程安排。 |
|||
|
|||
school_student_course_usage(学员课程使用记录表) |
|||
CREATE TABLE `school_student_course_usage` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '记录编号', |
|||
`student_course_id` int NOT NULL COMMENT '学员课程ID(关联到student_courses表)', |
|||
`used_hours` int NOT NULL COMMENT '本次使用的课时数', |
|||
`usage_date` date NOT NULL COMMENT '课时使用日期', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学员课时消费记录表'; |
|||
这个表记录了学员在某个课程周期内使用的课时数,这个表和学员课程表是一对多的关系,一个学员可以有多个课时使用记录。 |
|||
school_class(班级表) |
|||
CREATE TABLE `school_class` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '班级编号', |
|||
`campus_id` int NOT NULL COMMENT '校区ID', |
|||
`campus_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '校区名称', |
|||
`class_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '班级名称', |
|||
`head_coach` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '班继续 |
|||
级主教练', |
|||
`age_group` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '班级授课年龄段', |
|||
`class_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '班级类型', |
|||
`assistant_coach` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '班级助教', |
|||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', |
|||
`deleted_at` int DEFAULT '0' COMMENT '逻辑删除时间', |
|||
`status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '班级状态(1开启 2关闭)', |
|||
`sort_order` int NOT NULL COMMENT '班级排序', |
|||
`remarks` text COLLATE utf8mb4_general_ci COMMENT '班级备注', |
|||
`educational_id` int NOT NULL DEFAULT '0' COMMENT '教务id', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='班级表'; |
|||
学员报名时会分配班级,默认班级里的主教练和助教会被分配到学员课程表里。班级表主要是记录班级的信息,包括班级名称,主教练,助教,授课年龄段等信息。这个表和学员课程表是一对多的关系,一个班级可以有多个学员。 |
|||
school_class_resources_rel(班级学员关系表) |
|||
CREATE TABLE `school_class_resources_rel` ( |
|||
`id` int NOT NULL AUTO_INCREMENT, |
|||
`class_id` int NOT NULL COMMENT '班级id', |
|||
`resource_id` int DEFAULT NULL COMMENT '资源id', |
|||
`campus_id` int DEFAULT NULL COMMENT '校区id', |
|||
`source_id` int DEFAULT NULL COMMENT '数据id', |
|||
`source_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '数据资源类型student是学员,temporary是非正式学员', |
|||
`join_time` int DEFAULT NULL COMMENT '加入时间', |
|||
`out_time` int DEFAULT NULL COMMENT '离开时间', |
|||
`status` tinyint DEFAULT NULL COMMENT '状态1新入2续费3过期4转班5转校', |
|||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='班级和资源关系表'; |
|||
这个表记录了班级和资源的关系,包括班级id,资源id,校区id,数据id,数据资源类型,加入时间,离开时间,状态等信息。这个表和班级表是一对多的关系,一个班级可以有多个资源。 |
|||
@ -1,240 +0,0 @@ |
|||
<template> |
|||
<view> |
|||
<view style="" v-if="list.length>0"> |
|||
<fui-collapse-item v-for="(item,index) in list" :key="index" :disabled="shouldDisableItem(item)" :arrow="shouldDisableItems(item)"> |
|||
<view class="fui-item__box"> |
|||
<view style="display: flex;width: 100%;position: relative;"> |
|||
<view class="content_img" v-if="item.headimg != 0"> |
|||
<image style="width: 50rpx;height:50rpx;border-radius: 50%;" :src="item.headimg"></image> |
|||
</view> |
|||
<view class="content_img" v-else> |
|||
<image style="width: 50rpx;height: 50rpx;border-radius: 50%;" src="/static/images/home/tixing.png"></image> |
|||
</view> |
|||
<view style="padding: 5rpx 20rpx;"> |
|||
{{item.content}} |
|||
</view> |
|||
<view style="position: absolute;right: 7%;top: 1%;" @click="comments(item.comment_id)"> |
|||
评论 |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="middles_con" v-if="item.imagesarr!=0" style=""> |
|||
<view style="width: 32%;height: 200rpx;margin:2rpx;" v-for="(item2, index2) in item.imagesarr" |
|||
:key="index2" @click="tankuang(item2,item.imagesarr)"> |
|||
<image :src="item2" style="width: 100%;height: 100%;"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<template v-slot:content> |
|||
<view class="fui-descr" v-if="item.comments_list.length>0"> |
|||
<view class="flex" style="position: relative;" v-for="(item1,index1) in item.comments_list" :key="index1"> |
|||
|
|||
|
|||
<view style="display: flex;width: 100%;position: relative;"> |
|||
<view class="content_img" v-if="item1.headimg != 0"> |
|||
<image style="width: 50rpx;height:50rpx;border-radius: 50%;" :src="item1.headimg"></image> |
|||
</view> |
|||
<view class="content_img" v-else> |
|||
<image style="width: 50rpx;height: 50rpx;border-radius: 50%;" src="/static/images/home/tixing.png"></image> |
|||
</view> |
|||
<view style="padding: 5rpx 20rpx;"> |
|||
{{item1.content}} |
|||
</view> |
|||
</view> |
|||
<view class="middles_con" v-if="item1.imagesarr!=0" style=""> |
|||
<view style="width: 32%;height: 200rpx;margin:2rpx;" v-for="(item2, index2) in item1.imagesarr" |
|||
:key="index2" @click="tankuang(item2,item1.imagesarr)"> |
|||
<image :src="item2" style="width: 100%;height: 100%;"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="fui-descr" v-else style="text-align: center;"> |
|||
暂无子评论 |
|||
</view> |
|||
</template> |
|||
</fui-collapse-item> |
|||
</view> |
|||
<view style="font-size: 40rpx;font-weight: bold;text-align: center;padding-top: 50rpx;" v-else> |
|||
来当第一个评论的人吧! |
|||
</view> |
|||
|
|||
<view style="margin-top: 30rpx;width: 100%;background-color: #fff;"> |
|||
<view style="width: 95%;height: 200rpx;margin: auto;"> |
|||
<textarea v-model="textAreaValue" placeholder="输入内容" |
|||
:style="{ width: '100%',padding:'30rpx'}"></textarea> |
|||
</view> |
|||
<view class="fui-section__title" style="width: 95%;margin: auto;z-index: 9999;"> |
|||
<fui-upload :max="3" immediate :url="url" ref="upload" @complete="complete" @success="success" |
|||
:isDel="false"> |
|||
<fui-icon name="plus"></fui-icon> |
|||
</fui-upload> |
|||
</view> |
|||
|
|||
<fui-button background="#00be8c" width="95%" :margin="['35rpx','0','0','35rpx']" @click="submit()">发 |
|||
布</fui-button> |
|||
</view> |
|||
<view style="height: 30rpx;width: 100%;"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// import user from '@/api/user.js'; |
|||
import fuiCollapse from "@/components/firstui/fui-collapse/fui-collapse.vue" |
|||
import fuiCollapseItem from "@/components/firstui/fui-collapse-item/fui-collapse-item.vue" |
|||
import fuiUpload from "@/components/firstui/fui-upload/fui-upload.vue" |
|||
import fuiIcon from "@/components/firstui/fui-icon/fui-icon.vue" |
|||
import fuiButton from "@/components/firstui/fui-button/fui-button.vue" |
|||
|
|||
export default { |
|||
name: 'CommentList', |
|||
props: { |
|||
list: Array, // 将您的列表数据作为属性传递 |
|||
actid: { |
|||
type: String, |
|||
default: '' // 在这里设置默认值 |
|||
}, // 将您的列表数据作为属性传递 |
|||
um_id: { |
|||
type: String, |
|||
default: '' // 在这里设置默认值 |
|||
}, // 将您的列表数据作为属性传递 |
|||
}, |
|||
components: { |
|||
fuiCollapse, |
|||
fuiCollapseItem, |
|||
fuiUpload, |
|||
fuiIcon, |
|||
fuiButton |
|||
}, |
|||
data() { |
|||
return { |
|||
url: 'http://medication.zeyan.wang/dnseyeapi/Base/upload1', |
|||
urls: [], |
|||
status: '', |
|||
textAreaValue: '', |
|||
lists: [], |
|||
listlength: '' |
|||
} |
|||
}, |
|||
onLoad() { |
|||
this.actid = uni.getStorageSync('actid'); |
|||
this.um_id = uni.getStorageSync('um_id'); |
|||
this.fetchData(this.actid) |
|||
}, |
|||
methods: { |
|||
fetchData(actid) { |
|||
user.comment_list({ |
|||
act_id: actid |
|||
}).then(res => { |
|||
console.log(res) |
|||
if (res.status == 200) { |
|||
this.list = res.data |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg, |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}); |
|||
}, |
|||
onPullDownRefresh(){ |
|||
this.fetchData(this.actid) |
|||
}, |
|||
complete(e) { |
|||
console.log(e) |
|||
this.status = e.status |
|||
// this.urls = e.urls |
|||
if (this.status === 'success' && e.action === 'upload') { |
|||
this.fui.toast('上传完成!') |
|||
//已上传完成的图片列表 this.urls |
|||
// console.log(this.urls) |
|||
} |
|||
}, |
|||
success(e) { |
|||
var responseString = e.res.data; |
|||
var cleanedString = responseString.replace('{"status":"200","msg":"文件上传成功","data":"', '').replace('"}', |
|||
''); |
|||
console.log(cleanedString) |
|||
this.urls += cleanedString + ',' |
|||
}, |
|||
submit() { |
|||
user.comment({ |
|||
act_id: this.actid, |
|||
um_id: this.um_id, |
|||
content: this.textAreaValue, |
|||
image: this.urls, |
|||
}).then(res => { |
|||
console.log(res) |
|||
if (res.status == 200) { |
|||
this.fetchData(this.actid) |
|||
this.textAreaValue = '' |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg, |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}); |
|||
}, |
|||
tankuang(url,arr){ |
|||
uni.previewImage({ |
|||
current: url, |
|||
urls: arr, |
|||
loop: true, |
|||
}); |
|||
}, |
|||
comments(id){ |
|||
uni.setStorageSync('comment_id',id); |
|||
uni.redirectTo({ |
|||
url: '/pages/index/comments' |
|||
}); |
|||
}, |
|||
shouldDisableItem(item){ |
|||
return item.comments_list.length == 0; |
|||
}, |
|||
shouldDisableItems(item){ |
|||
return !item.comments_list.length == 0; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.middles_con { |
|||
width: 100%; |
|||
display: flex; |
|||
margin: 20rpx 0; |
|||
font-size: 30rpx; |
|||
|
|||
} |
|||
|
|||
.flex { |
|||
padding: 20rpx 45rpx; |
|||
} |
|||
|
|||
.fui-item__box { |
|||
width: 100%; |
|||
padding: 26rpx 32rpx; |
|||
box-sizing: border-box; |
|||
display: flex; |
|||
align-items: center; |
|||
flex-wrap: wrap; |
|||
} |
|||
|
|||
.fui-logo { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
margin-right: 24rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.fui-descr { |
|||
width: 100%; |
|||
padding: 32rpx; |
|||
font-size: 28rpx; |
|||
line-height: 52rpx; |
|||
color: #7F7F7F; |
|||
word-break: break-all; |
|||
box-sizing: border-box; |
|||
} |
|||
</style> |
|||
@ -1,166 +0,0 @@ |
|||
<template> |
|||
<view> |
|||
<view v-if="loading" class="skeleton" :class="{ animate: animate }" :style="{ justifyContent: flexType}"> |
|||
<!-- 轮播图 --> |
|||
<view |
|||
v-if="imgTitle" |
|||
class="skeleton-imgTitle" |
|||
style="width: 95%;border-radius: 10px;height: 100px;display: block;" |
|||
></view> |
|||
<!-- 头像图 --> |
|||
<view |
|||
v-if="showAvatar && !imgTitle" |
|||
class="skeleton-avatar" |
|||
v-for="(item, index) in nameRow" |
|||
:key="index" |
|||
:class="[avatarShape]" |
|||
:style="{ width: avatarSize, height: avatarSize}" |
|||
></view> |
|||
<!-- 文字条 --> |
|||
<view class="skeleton-content" v-if="showTitle && !imgTitle"> |
|||
<view class="skeleton-title" :style="{ width: titleWidth }"></view> |
|||
<view class="skeleton-rows"> |
|||
<view |
|||
class="skeleton-row-item" |
|||
v-for="(item, index) in rowList" |
|||
:key="index" |
|||
:style="{ width: item.width }" |
|||
></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-else><slot></slot></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
const DEFAULT_ROW_WIDTH = '100%' |
|||
const DEFAULT_LAST_ROW_WIDTH = '60%' |
|||
|
|||
export default { |
|||
props: { |
|||
loading: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
imgTitle: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
nameRow:{ |
|||
type: Number, |
|||
default: 1, |
|||
}, |
|||
flexType:{ |
|||
type: String, |
|||
default: 'flex-start', // center 居中 √ space-between 两端对齐 √ space-around 子元素拉手分布 √ flex-start 居左 flex-end 居右 |
|||
}, |
|||
showAvatar: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
avatarSize: { |
|||
type: String, |
|||
default: '50px', |
|||
}, |
|||
avatarShape: { |
|||
type: String, |
|||
default: 'round', // square | round |
|||
}, |
|||
showTitle: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
titleWidth: { |
|||
type: String, |
|||
default: '40%', |
|||
}, |
|||
row: { |
|||
type: Number, |
|||
default: 3, |
|||
}, |
|||
animate: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
}, |
|||
data() { |
|||
return {} |
|||
}, |
|||
computed: { |
|||
rowList() { |
|||
let list = [] |
|||
for (let i = 0; i < this.row; i++) { |
|||
list.push({ |
|||
width: i === this.row - 1 && i !== 0 ? DEFAULT_LAST_ROW_WIDTH : DEFAULT_ROW_WIDTH, |
|||
}) |
|||
} |
|||
return list |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.skeleton { |
|||
display: flex; |
|||
margin: 16px; |
|||
--bg-color: #f2f3f5; |
|||
--row-height: 16px; |
|||
--row-margin-top: 16px; |
|||
} |
|||
.skeleton-imgTitle { |
|||
flex-wrap: wrap; |
|||
background: var(--bg-color); |
|||
margin: 10px auto; |
|||
|
|||
} |
|||
.skeleton-avatar { |
|||
flex-shrink: 0; |
|||
background: var(--bg-color); |
|||
margin-right: 8px; |
|||
} |
|||
.skeleton-avatar.round { |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.skeleton-content { |
|||
width: 100%; |
|||
} |
|||
|
|||
.skeleton-title { |
|||
background-color: var(--bg-color); |
|||
height: var(--row-height); |
|||
} |
|||
|
|||
.skeleton-title + .skeleton-rows { |
|||
margin-top: var(--row-margin-top); |
|||
} |
|||
|
|||
.skeleton-rows { |
|||
} |
|||
|
|||
.skeleton-row-item { |
|||
background-color: var(--bg-color); |
|||
height: var(--row-height); |
|||
} |
|||
.skeleton-row-item:not(:first-child) { |
|||
margin-top: var(--row-margin-top); |
|||
} |
|||
|
|||
.skeleton.animate { |
|||
animation: skeleton-blink 1.2s ease-in-out infinite; |
|||
} |
|||
|
|||
@keyframes skeleton-blink { |
|||
0% { |
|||
opacity: 1; |
|||
} |
|||
50% { |
|||
opacity: 0.6; |
|||
} |
|||
100% { |
|||
opacity: 1; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,77 +0,0 @@ |
|||
# skeleton |
|||
感谢原作者 https://ext.dcloud.net.cn/plugin?id=852 |
|||
|
|||
自己项目非常需要骨架,正好原作者发布了1.0 根据自己项目 自己修改了下。 |
|||
|
|||
目前仅支持: |
|||
1.轮播图 |
|||
2.分类栏 |
|||
3.头像 |
|||
4.文章条 |
|||
5.动态心情 |
|||
|
|||
以上是根据自己项目修改的,后续再拓展,或者自己根据自己项目修改,原作者写的还是很灵活的,修改方便! |
|||
|
|||
|
|||
## 属性说明 |
|||
|
|||
|属性名|类型|默认值|说明| |
|||
| -- | -- | --|--| |
|||
| loading | Boolean | true | 是否显示占位图 | |
|||
| flexType | String | flex-start | 排列方式 center 居中 √ space-between 两端对齐 √ space-around 子元素拉手分布 √ flex-start 居左 flex-end 居右 | |
|||
| imgTitle | Boolean | false | 轮播图占位图 | |
|||
| showAvatar | Boolean | true | 是否显示头像占位图 | |
|||
| nameRow | Number | 1 | 显示头像圆1个 | |
|||
| avatarSize | String | 50px | 头像站占位图大小 | |
|||
| avatarShape | String | round | 头像形状,可选值:round, square | |
|||
| showTitle | Boolean | true | 是否显示标题占位图 | |
|||
| titleWidth | String | 40% | 标题占位图宽度 | |
|||
| row | Number| 3 | 标题段落占位图行数 | |
|||
| animate | Boolean | true | 是否开启动画 | |
|||
|
|||
## 使用示例 |
|||
|
|||
```html |
|||
<skeleton |
|||
:loading="loading" |
|||
:avatarSize="skeleton1.avatarSize" |
|||
:row="skeleton1.row" |
|||
:showTitle="skeleton1.showTitle" |
|||
> |
|||
<view class="section-content">我是段落1</view> |
|||
</skeleton> |
|||
``` |
|||
|
|||
```javascript |
|||
import Skeleton from '../components/skeleton/index.vue' |
|||
export default { |
|||
components: { |
|||
Skeleton |
|||
}, |
|||
data() { |
|||
return { |
|||
loading: true, |
|||
skeleton1 : { |
|||
avatarSize: '52px', |
|||
row: 3, |
|||
showTitle: true, |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.reloadData() |
|||
}, |
|||
methods: { |
|||
reloadData() { |
|||
this.loading = true |
|||
setTimeout(() => { |
|||
this.loading = false |
|||
}, 3000) |
|||
}, |
|||
}, |
|||
} |
|||
``` |
|||
|
|||
## 效果图 |
|||
|
|||
 |
|||
@ -1,438 +0,0 @@ |
|||
<template name="aui-dialog"> |
|||
<view class="aui-dialog" v-if="SHOW" :class="{'aui-dialog-in': FADE==1, 'aui-dialog-out': FADE==0}"> |
|||
<view class="aui-mask" v-if="mask" @touchmove.stop.prevent @click.stop="maskTapClose ? hide() : ''"></view> |
|||
<view class="aui-dialog-main" |
|||
:class="{ |
|||
'aui-dialog-main-style-1': theme==1, |
|||
'aui-dialog-main-style-2': theme==2, |
|||
'aui-dialog-main-style-3': theme==3 |
|||
}" |
|||
> |
|||
<view class="aui-dialog-title" v-if="title">{{title}}</view> |
|||
<view class="aui-dialog-content" v-if="msg!=''" :style="{'text-align': msg.length > 15 ? 'left' : 'center'}" v-html="msg"></view> |
|||
<view class="aui-dialog-content" v-if="items.length > 0"> |
|||
<view class="aui-dialog-input-list" v-for="(item, index) in items" :key="index" :data-index="index"> |
|||
<view class="aui-dialog-input-label" v-if="item.label">{{item.label}}</view> |
|||
<view class="aui-dialog-input-list-input"> |
|||
<input :type="item.type ? item.type : 'text'" :value="item.value" :data-index="index" @input="_onInput" :placeholder="item.placeholder" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="aui-dialog-down"> |
|||
<view |
|||
class="aui-dialog-down-btn" |
|||
v-for="(item, index) in btns" |
|||
:class="{'aui-dialog-down-cancel-btn': item.name=='取消', 'aui-dialog-down-delete-btn': item.name=='删除'}" |
|||
:key="index" |
|||
:data-index="index" |
|||
:style="{ |
|||
'color': touchIndex == index ? touchStyle.color : item.color, |
|||
'background': touchIndex == index ? touchStyle.background : '', |
|||
'width': theme==1?'calc(100% / '+ btns.length +')':'' |
|||
}" |
|||
@click.stop="_btnTab($event)" |
|||
@touchstart="_btnTouchStart($event)" |
|||
@touchmove="_btnTouchEnd($event)" |
|||
@touchend="_btnTouchEnd($event)" |
|||
>{{item.name}}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "aui-dialog", |
|||
props: { |
|||
title: { //标题 |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
msg: { //描述内容 |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
mask: { //是否显示遮罩,默认false |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
maskTapClose: { //遮罩层点击是否关闭 |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
btns: { //横向("row")或纵向("col")控制,默认纵向 |
|||
type: Array, |
|||
default (){ |
|||
return [ |
|||
{name: '确定', color: '#197DE0', isTouch: false} |
|||
] |
|||
} |
|||
}, |
|||
items: { |
|||
type: Array, |
|||
default (){ |
|||
return [ |
|||
{label: '', type: 'text', value: '', placeholder: ''} |
|||
] |
|||
} |
|||
}, |
|||
theme: { //主题样式,控制模态弹窗按钮显示风格 |
|||
type: Number, |
|||
default: 1 |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
SHOW: false, |
|||
FADE: -1, |
|||
ITEMS: [], |
|||
touchIndex: -1, //长按时当前索引 |
|||
touchStyle: { //长按时当前样式设置 |
|||
color: '', |
|||
background: '#EFEFEF' |
|||
} |
|||
}; |
|||
}, |
|||
created(){ |
|||
var _this = this; |
|||
|
|||
}, |
|||
onLoad(){ |
|||
|
|||
}, |
|||
methods:{ |
|||
//显示 |
|||
show(){ |
|||
var _this = this; |
|||
return new Promise(function(resolve, reject){ |
|||
_this.SHOW = true; |
|||
var _showtimer = setTimeout(()=>{ |
|||
_this.FADE = 1; |
|||
clearTimeout(_showtimer); |
|||
},50) |
|||
resolve(); |
|||
}); |
|||
}, |
|||
//隐藏 |
|||
hide(){ |
|||
var _this = this; |
|||
return new Promise(function(resolve, reject){ |
|||
_this.FADE = 0; |
|||
var _hidetimer = setTimeout(()=>{ |
|||
_this.SHOW = false; |
|||
_this.FADE = -1; |
|||
clearTimeout(_hidetimer); |
|||
},50) |
|||
resolve(); |
|||
}); |
|||
}, |
|||
//底部按钮点击 |
|||
_btnTab(e){ |
|||
var _this = this, |
|||
index = Number(e.currentTarget.dataset.index); |
|||
_this.hide(); |
|||
var _closetimer = setTimeout(()=>{ |
|||
var data = { |
|||
status: 0, |
|||
msg: _this.btns[index].name, |
|||
index: index |
|||
}; |
|||
_this.$emit("callback", data); |
|||
clearTimeout(_closetimer); |
|||
},100) |
|||
}, |
|||
//输入检测 |
|||
_onInput(e){ |
|||
var _this = this, |
|||
index = Number(e.currentTarget.dataset.index), |
|||
value = e.detail.value; |
|||
if(_this.ITEMS.length <= 0) |
|||
{ |
|||
_this.items.forEach((item, index)=>{ |
|||
_this.ITEMS.push({label: item.label, type: item.type, value: item.value, placeholder: item.placeholder}); |
|||
}); |
|||
} |
|||
_this.$set(_this.ITEMS[index], 'value', value); |
|||
|
|||
}, |
|||
getVal(){ |
|||
var _this = this; |
|||
setTimeout(()=>{ |
|||
_this.ITEMS = []; |
|||
},200) |
|||
return _this.ITEMS; |
|||
}, |
|||
_btnTouchStart(e){ |
|||
var _this = this, |
|||
index = Number(e.currentTarget.dataset.index); |
|||
_this.touchIndex = index; |
|||
}, |
|||
_btnTouchEnd(e){ |
|||
var _this = this, |
|||
index = Number(e.currentTarget.dataset.index); |
|||
_this.touchIndex = -1; |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* dialog 模态弹窗样式 */ |
|||
.aui-dialog{ |
|||
width: 100vw; |
|||
height: 100vh; |
|||
opacity: 0; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 999; |
|||
} |
|||
.aui-dialog-main{ |
|||
min-width: 280px; |
|||
max-width: 300px; |
|||
background: #fff; |
|||
border-radius: 13px; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
-webkit-transition-property: -webkit-transform,opacity; |
|||
transition-property: transform,opacity; |
|||
-webkit-transform: translate3d(-50%,-50%,0) scale(1.185); |
|||
transform: translate3d(-50%,-50%,0) scale(1.185); |
|||
opacity: 0; |
|||
z-index: 999; |
|||
} |
|||
.aui-dialog.aui-dialog-in{ |
|||
-webkit-transition-duration: 100ms; |
|||
transition-duration: 100ms; |
|||
opacity: 1; |
|||
} |
|||
.aui-dialog.aui-dialog-out{ |
|||
-webkit-transition-duration: 100ms; |
|||
transition-duration: 100ms; |
|||
opacity: 0; |
|||
} |
|||
.aui-dialog.aui-dialog-in .aui-mask{ |
|||
-webkit-transition-duration: 200ms; |
|||
transition-duration: 200ms; |
|||
opacity: 1; |
|||
} |
|||
.aui-dialog.aui-dialog-out .aui-mask{ |
|||
-webkit-transition-duration: 200ms; |
|||
transition-duration: 200ms; |
|||
opacity: 0; |
|||
} |
|||
.aui-dialog.aui-dialog-out .aui-dialog-main{ |
|||
-webkit-transition-duration: 200ms; |
|||
transition-duration: 200ms; |
|||
-webkit-transform: translate3d(-50%,-50%,0) scale(0.8); |
|||
transform: translate3d(-50%,-50%,0) scale(0.8); |
|||
opacity: 0 |
|||
} |
|||
.aui-dialog.aui-dialog-in .aui-dialog-main{ |
|||
-webkit-transition-duration: 200ms; |
|||
transition-duration: 200ms; |
|||
-webkit-transform: translate3d(-50%,-50%,0) scale(1); |
|||
transform: translate3d(-50%,-50%,0) scale(1); |
|||
opacity: 1; |
|||
} |
|||
.aui-dialog-title{ |
|||
width: 100%; |
|||
height: 40px; |
|||
line-height: 55px; |
|||
position: relative; |
|||
font-size: 18px; |
|||
/*font-weight: bolder;*/ |
|||
display: inline-block; |
|||
border-top-left-radius: 13px; |
|||
border-top-right-radius: 13px; |
|||
text-align: center; |
|||
color: #333; |
|||
box-sizing: border-box; |
|||
} |
|||
.aui-dialog-content{ |
|||
width: 100%; |
|||
max-height: 70vh; |
|||
line-height: 27px; |
|||
font-size: 16px; |
|||
color: #555555; |
|||
text-align: center; |
|||
display: inline-block; |
|||
overflow-y: scroll; |
|||
padding: 30px 20px 25px 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
.aui-dialog-content::-webkit-scrollbar { |
|||
width: 0px; |
|||
} |
|||
.aui-dialog-down{ |
|||
width: 100%; |
|||
height: 50px; |
|||
text-align: right; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
.aui-dialog-down-btn{ |
|||
width: auto; |
|||
height: 100%; |
|||
display: inline-block; |
|||
font-size: 17px; |
|||
color: #197DE0; |
|||
text-align: center; |
|||
position: relative; |
|||
} |
|||
.aui-dialog-down-btn.active{ |
|||
background: #EFEFEF; |
|||
} |
|||
.aui-dialog-down-cancel-btn{ |
|||
color: #909090; |
|||
} |
|||
.aui-dialog-down-delete-btn{ |
|||
color: #FF0000; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-content{ |
|||
text-align: center; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down{ |
|||
height: 50px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down-btn{ |
|||
line-height: 50px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex: auto; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down:before{ |
|||
content: ''; |
|||
width: 100%; |
|||
height: 1px; |
|||
-ms-transform: scaleY(.3); |
|||
-webkit-transform: scaleY(.3); |
|||
transform: scaleY(.3); |
|||
background: rgba(100,100,100,.3); |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 999; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down-btn:first-child{ |
|||
border-bottom-left-radius: 13px; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down-btn:last-child{ |
|||
border-bottom-right-radius: 13px; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down-btn:after{ |
|||
content: ''; |
|||
width: 1px; |
|||
height: 100%; |
|||
-ms-transform: scaleX(.3); |
|||
-webkit-transform: scaleX(.3); |
|||
transform: scaleX(.3); |
|||
background: rgba(100,100,100,.3); |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
z-index: 999; |
|||
} |
|||
.aui-dialog-main-style-1 .aui-dialog-down-btn:last-child:after{display: none;} |
|||
.aui-dialog-main-style-2{ |
|||
border-radius: 6px; |
|||
} |
|||
.aui-dialog-main-style-2 .aui-dialog-title{ |
|||
padding: 0 15px; |
|||
box-sizing: border-box; |
|||
} |
|||
.aui-dialog-main-style-2 .aui-dialog-down{ |
|||
height: 40px; |
|||
padding: 0 10px 10px 10px; |
|||
box-sizing: border-box; |
|||
} |
|||
.aui-dialog-main-style-2 .aui-dialog-down-btn{ |
|||
height: 30px; |
|||
line-height: 30px; |
|||
padding: 0 10px; |
|||
margin: 0 0 0 10px; |
|||
} |
|||
.aui-dialog-main-style-3 .aui-dialog-down{ |
|||
height: auto; |
|||
} |
|||
.aui-dialog-main-style-3 .aui-dialog-down-btn{ |
|||
width: 100%; |
|||
line-height: 50px; |
|||
} |
|||
.aui-dialog-main-style-3 .aui-dialog-down-btn:before{ |
|||
content: ''; |
|||
width: 100%; |
|||
height: 1px; |
|||
-ms-transform: scaleY(.3); |
|||
-webkit-transform: scaleY(.3); |
|||
transform: scaleY(.3); |
|||
background: rgba(100,100,100,.4); |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 1; |
|||
} |
|||
.aui-dialog-main-style-3 .aui-dialog-down-btn:last-child{ |
|||
border-bottom-left-radius: 13px; |
|||
border-bottom-right-radius: 13px; |
|||
} |
|||
.aui-dialog-main-style-3 .aui-dialog-down-btn:first-child:after{display: none;} |
|||
/*input 输入弹窗样式设置*/ |
|||
.aui-dialog-input-list{ |
|||
width: 100%; |
|||
position: relative; |
|||
text-align: left; |
|||
} |
|||
.aui-dialog-input-list .aui-dialog-input-label{ |
|||
width: 260px; |
|||
height: 40px; |
|||
line-height: 40px; |
|||
display: inline-block; |
|||
font-size: 16px; |
|||
color: #646464; |
|||
} |
|||
.aui-dialog-input-list-input{ |
|||
width: 100%; |
|||
background: #FFFFFF; |
|||
border-radius: 3px; |
|||
border: none; |
|||
box-sizing: border-box; |
|||
padding: 2px; |
|||
margin: 0 0 15px 0; |
|||
color: #515151; |
|||
position: relative; |
|||
} |
|||
.aui-dialog-input-list input{ |
|||
width: 100%; |
|||
height: 40px; |
|||
line-height: 20px; |
|||
border-radius: 3px; |
|||
border: none; |
|||
margin: 0; |
|||
padding: 0 10px; |
|||
box-sizing: border-box; |
|||
font-size: 15px; |
|||
color: #515151; |
|||
position: relative; |
|||
z-index: 1; |
|||
} |
|||
.aui-dialog-input-list-input:after{ |
|||
content: ''; |
|||
width: 200%; |
|||
height: 200%; |
|||
border: 1px solid rgba(100,100,100,.3); |
|||
-ms-transform: scale(.5, .5); |
|||
-webkit-transform: scale(.5, .5); |
|||
transform: scale(.5, .5); |
|||
position: absolute; |
|||
top: -50%; |
|||
left: -50%; |
|||
border-radius: 10px; |
|||
z-index: 0; |
|||
} |
|||
</style> |
|||
@ -1,292 +0,0 @@ |
|||
.aui-content{ |
|||
width: 100%; |
|||
height: 100vh; |
|||
/* #ifndef MP */ |
|||
height: -webkit-calc(100vh - 44px); |
|||
height: calc(100vh - 44px); |
|||
/* #endif */ |
|||
background: #EFEFEF; |
|||
overflow-y: scroll; |
|||
padding: 0; |
|||
box-sizing: border-box; |
|||
position: relative; |
|||
} |
|||
/* 横向分割线 */ |
|||
.row-before{position: relative;} |
|||
.row-before:before{content: ''; width: 100%; height: 1px !important; background: rgba(100,100,100,.3); -ms-transform: scaleY(.3); -webkit-transform: scaleY(.3); transform: scaleY(.3); position: absolute; top: 0; right: 0; left: auto; z-index: 1;} |
|||
.row-after{position: relative;} |
|||
.row-after:after{content: ''; width: 100%; height: 1px !important; background: rgba(100,100,100,.3); -ms-transform: scaleY(.3); -webkit-transform: scaleY(.3); transform: scaleY(.3); position: absolute; bottom: 0; right: 0; left: auto; z-index: 1;} |
|||
/* 纵向分割线 */ |
|||
.col-before{position: relative;} |
|||
.col-before:before{content: ''; width: 1px !important; height: 100%; background: rgba(100,100,100,.3); -ms-transform: scaleX(.3); -webkit-transform: scaleX(.3); transform: scaleX(.3); position: absolute; top: 0; left: 0; z-index: 1;} |
|||
.col-after{position: relative;} |
|||
.col-after:after{content: ''; width: 1px !important; height: 100%; background: rgba(100,100,100,.3); -ms-transform: scaleX(.3); -webkit-transform: scaleX(.3); transform: scaleX(.3); position: absolute; top: 0; right: 0; z-index: 1;} |
|||
/*按钮边框线*/ |
|||
.border{position: relative;} |
|||
.border:after{content: ''; width: -webkit-calc(200% - 2px); width: calc(200% - 2px); height: -webkit-calc(200% - 2px); height: calc(200% - 2px); border-radius: 3px; border: 1px solid rgba(100,100,100,.3); position: absolute; left: -50%; top: -50%; -ms-transform: scale(.5, .5); -webkit-transform: scale(.5, .5); transform: scale(.5, .5); z-index: 1;} |
|||
.aui-lists{ |
|||
width: 100%; |
|||
} |
|||
.aui-list{ |
|||
width: 100%; |
|||
height: 55px; |
|||
font-size: 0; |
|||
padding: 0 15px; |
|||
background: #FFFFFF; |
|||
box-sizing: border-box; |
|||
position: relative; |
|||
z-index: 1; |
|||
} |
|||
.aui-list:after{ |
|||
content: ''; |
|||
width: 100%; |
|||
height: 1px; |
|||
background: rgba(100,100,100,.3); |
|||
-ms-transform: scaleY(.3); |
|||
-webkit-transform: scaleY(.3); |
|||
transform: scaleY(.3); |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
} |
|||
.aui-list:last-child:after{ |
|||
display: none; |
|||
} |
|||
.aui-list-title{ |
|||
width: 100%; |
|||
height: 44px; |
|||
line-height: 44px; |
|||
font-size: 14px; |
|||
padding: 0 15px; |
|||
box-sizing: border-box; |
|||
color: #999; |
|||
display: inline-block; |
|||
} |
|||
.aui-list-left{ |
|||
height: 100%; |
|||
line-height: 55px; |
|||
font-size: 15px; |
|||
color: #333; |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
} |
|||
.aui-list-right{ |
|||
height: 100%; |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
float: right; |
|||
} |
|||
.aui-list-right .aui-btn-right{ |
|||
height: 55px; |
|||
text-align: center; |
|||
line-height: 55px; |
|||
font-size: 14px; |
|||
display: inline-block; |
|||
color: #aaa; |
|||
vertical-align: top; |
|||
|
|||
} |
|||
.aui-btn{ |
|||
width: -webkit-calc(100% - 30px); |
|||
width: calc(100% - 30px); |
|||
height: 50px; |
|||
line-height: 50px; |
|||
text-align: center; |
|||
border: none; |
|||
color: #333; |
|||
font-size: 15px; |
|||
border-radius: 5px; |
|||
margin: 0 15px 15px 15px; |
|||
padding: 0 10px; |
|||
transition: background-color .2s; |
|||
box-sizing: border-box; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
position: relative; |
|||
} |
|||
.aui-btn-blue{ |
|||
background: #197DE0; |
|||
color: #FFF; |
|||
} |
|||
|
|||
/* 遮罩层样式 */ |
|||
.aui-mask{ |
|||
width: 100%; |
|||
height: 100%; |
|||
background: rgba(0,0,0,.6); |
|||
-ms-animation: aui-fade-in .2s ease-out forwards; |
|||
-webkit-animation: aui-fade-in .2s ease-out forwards; |
|||
animation: aui-fade-in .2s ease-out forwards; |
|||
position: fixed; |
|||
top: 0px; |
|||
left: 0px; |
|||
z-index: 998; |
|||
} |
|||
|
|||
/* 动画设计 */ |
|||
/* fade-in */ |
|||
@-ms-keyframes aui-fade-in{ |
|||
0%{opacity: 0;} |
|||
100%{opacity: 1;} |
|||
} |
|||
@-webkit-keyframes aui-fade-in{ |
|||
0%{opacity: 0;} |
|||
100%{opacity: 1;} |
|||
} |
|||
@keyframes aui-fade-in{ |
|||
0%{opacity: 0;} |
|||
100%{opacity: 1;} |
|||
} |
|||
/* fade-out */ |
|||
@-ms-keyframes aui-fade-out{ |
|||
0%{opacity: 1;} |
|||
100%{opacity: 0;} |
|||
} |
|||
@-webkit-keyframes aui-fade-out{ |
|||
0%{opacity: 1;} |
|||
100%{opacity: 0;} |
|||
} |
|||
@keyframes aui-fade-out{ |
|||
0%{opacity: 1;} |
|||
100%{opacity: 0;} |
|||
} |
|||
/* aui-scale-in */ |
|||
@-ms-keyframes aui-scale-in{ |
|||
0%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
@-webkit-keyframes aui-scale-in{ |
|||
0%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
@keyframes aui-scale-in{ |
|||
0%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
/* aui-scale-out */ |
|||
@-ms-keyframes aui-scale-out{ |
|||
0%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
100%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
} |
|||
@-webkit-keyframes aui-scale-out{ |
|||
0%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
100%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
} |
|||
@keyframes aui-scale-out{ |
|||
0%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
100%{-ms-transform: scale(0.8); -webkit-transform: scale(0.8); transform: scale(0.8);} |
|||
} |
|||
|
|||
/* aui-scale-in-tosmall */ |
|||
@-ms-keyframes aui-scale-in-tosmall{ |
|||
0%{-ms-transform: scale(1.2); -webkit-transform: scale(1.2); transform: scale(1.2);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
@-webkit-keyframes aui-scale-in-tosmall{ |
|||
0%{-ms-transform: scale(1.2); -webkit-transform: scale(1.2); transform: scale(1.2);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
@keyframes aui-scale-in-tosmall{ |
|||
0%{-ms-transform: scale(1.2); -webkit-transform: scale(1.2); transform: scale(1.2);} |
|||
100%{-ms-transform: scale(1); -webkit-transform: scale(1); transform: scale(1);} |
|||
} |
|||
|
|||
/* aui-scale-in-tosmall-dialog */ |
|||
@-ms-keyframes aui-scale-in-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1.16); -webkit-transform: translate3d(-50%,-50%,0) scale(1.16); transform: translate3d(-50%,-50%,0) scale(1.16); opacity: 0;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3(-50%,-50%,0) scale(1); opacity: 1;} |
|||
} |
|||
@-webkit-keyframes aui-scale-in-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1.16); -webkit-transform: translate3d(-50%,-50%,0) scale(1.16); transform: translate3d(-50%,-50%,0) scale(1.16); opacity: 0;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3(-50%,-50%,0) scale(1); opacity: 1;} |
|||
} |
|||
@keyframes aui-scale-in-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1.16); -webkit-transform: translate3d(-50%,-50%,0) scale(1.16); transform: translate3d(-50%,-50%,0) scale(1.16); opacity: 0;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3(-50%,-50%,0) scale(1); opacity: 1;} |
|||
} |
|||
/* aui-scale-out-tosmall-dialog */ |
|||
@-ms-keyframes aui-scale-out-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3d(-50%,-50%,0) scale(1); opacity: 1;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(0.8); -webkit-transform: translate3d(-50%,-50%,0) scale(0.8); transform: translate3(-50%,-50%,0) scale(0.8); opacity: 0;} |
|||
} |
|||
@-webkit-keyframes aui-scale-out-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3d(-50%,-50%,0) scale(1); opacity: 1;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(0.8); -webkit-transform: translate3d(-50%,-50%,0) scale(0.8); transform: translate3(-50%,-50%,0) scale(0.8); opacity: 0;} |
|||
} |
|||
@keyframes aui-scale-out-tosmall-dialog{ |
|||
0%{-ms-transform: translate3d(-50%,-50%,0) scale(1); -webkit-transform: translate3d(-50%,-50%,0) scale(1); transform: translate3d(-50%,-50%,0) scale(1); opacity: 1;} |
|||
100%{-ms-transform: translate3d(-50%,-50%,0) scale(0.8); -webkit-transform: translate3d(-50%,-50%,0) scale(0.8); transform: translate3(-50%,-50%,0) scale(0.8); opacity: 0;} |
|||
} |
|||
/* aui-slide-up */ |
|||
@-ms-keyframes aui-slide-up{ |
|||
0%{bottom: -40vh;} |
|||
100%{bottom: 10px;} |
|||
} |
|||
@-webkit-keyframes aui-slide-up{ |
|||
0%{bottom: -40vh;} |
|||
100%{bottom: 10px;} |
|||
} |
|||
@keyframes aui-slide-up{ |
|||
0%{bottom: -40vh;} |
|||
100%{bottom: 10px;} |
|||
} |
|||
|
|||
/* aui-slide-down */ |
|||
@-ms-keyframes aui-slide-down{ |
|||
0%{bottom: 10px;} |
|||
100%{bottom: -40vh;} |
|||
} |
|||
@-webkit-keyframes aui-slide-down{ |
|||
0%{bottom: 10px;} |
|||
100%{bottom: -40vh;} |
|||
} |
|||
@keyframes aui-slide-down{ |
|||
0%{bottom: 10px;} |
|||
100%{bottom: -40vh;} |
|||
} |
|||
|
|||
/* aui-slide-up-screen */ |
|||
@-ms-keyframes aui-slide-up-screen{ |
|||
0%{bottom: -60vh;} |
|||
100%{bottom: 0px;} |
|||
} |
|||
@-webkit-keyframes aui-slide-up-screen{ |
|||
0%{bottom: -60vh;} |
|||
100%{bottom: 0px;} |
|||
} |
|||
@keyframes aui-slide-up-screen{ |
|||
0%{bottom: -60vh;} |
|||
100%{bottom: 0px;} |
|||
} |
|||
|
|||
/* aui-slide-down-screen */ |
|||
@-ms-keyframes aui-slide-down-screen{ |
|||
0%{bottom: 0px;} |
|||
100%{bottom: -60vh;} |
|||
} |
|||
@-webkit-keyframes aui-slide-down-screen{ |
|||
0%{bottom: 0px;} |
|||
100%{bottom: -60vh;} |
|||
} |
|||
@keyframes aui-slide-down-screen{ |
|||
0%{bottom: 0px;} |
|||
100%{bottom: -60vh;} |
|||
} |
|||
|
|||
@-webkit-keyframes aui-slide-up_to_middle { |
|||
0%{opacity: 0; top: -50vh; -ms-transform: translate(0,0); -webkit-transform: translate(0,0); transform: translate(0,0);} |
|||
100%{opacity: 1; top: 45%; -ms-transform: translate(0, -50%); -webkit-transform: translate(0, -50%); transform: translate(0, -50%);} |
|||
} |
|||
@keyframes aui-slide-up_to_middle { |
|||
0%{opacity: 0; top: -50vh; -ms-transform: translate(0,0); -webkit-transform: translate(0,0); transform: translate(0,0);} |
|||
100%{opacity: 1; top: 45%; -ms-transform: translate(0, -50%); -webkit-transform: translate(0, -50%); transform: translate(0, -50%);} |
|||
} |
|||
@-webkit-keyframes aui-slide-middle_to_up { |
|||
0%{opacity: 1; top: 45%; -ms-transform: translate(0, -50%); -webkit-transform: translate(0, -50%); transform: translate(0, -50%);} |
|||
100%{opacity: 0; top: -50vh; -ms-transform: translate(0,0); -webkit-transform: translate(0,0); transform: translate(0,0);} |
|||
} |
|||
@keyframes aui-slide-middle_to_up { |
|||
0%{opacity: 1; top: 45%; -ms-transform: translate(0, -50%); -webkit-transform: translate(0, -50%); transform: translate(0, -50%);} |
|||
100%{opacity: 0; top: -50vh; -ms-transform: translate(0,0); -webkit-transform: translate(0,0); transform: translate(0,0);} |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -1,55 +0,0 @@ |
|||
const aui = { |
|||
console: function(str){ |
|||
console.log(str); |
|||
}, |
|||
/***打开新页面 |
|||
@param {string} url 页面路径 |
|||
@param {Object} opts 参数 {id: ''} |
|||
@example: aui.openWin("index.html", {id: 1}) |
|||
*/ |
|||
openWin(url, opts){ |
|||
var _this = this; |
|||
var str = '?'; |
|||
for(var i in opts){ |
|||
if(_this.isDefine(opts[i])){ |
|||
str += i + '=' + opts[i] + '&'; |
|||
} |
|||
} |
|||
uni.navigateTo({ |
|||
url: _this.isDefine(opts) ? url + str : url |
|||
}) |
|||
}, |
|||
/***关闭页面 |
|||
@example: aui.closeWin() |
|||
*/ |
|||
closeWin(callback){ |
|||
//直接关闭页面,并向后台发送数据
|
|||
if(typeof callback == "function"){ |
|||
if(window.addEventListener) { |
|||
window.addEventListener("beforeunload", callback, false); |
|||
} else { |
|||
window.attachEvent("onbeforeunload", callback, false); |
|||
} |
|||
} |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}); |
|||
}, |
|||
/***判断字符串是否为空 |
|||
@param {string} str 变量 |
|||
@example: aui.isDefine("变量"); |
|||
*/ |
|||
isDefine(str){ |
|||
if (str == null || str == "" || str == "undefined" || str == undefined || str == "null" || str == "(null)" || str == 'NULL' || typeof (str) == 'undefined'){ |
|||
return false; |
|||
}else{ |
|||
str = str + ""; |
|||
str = str.replace(/\s/g, ""); |
|||
if (str == ""){return false;} |
|||
return true; |
|||
} |
|||
}, |
|||
} |
|||
export { |
|||
aui |
|||
} |
|||
@ -1,181 +0,0 @@ |
|||
<template> |
|||
<view class="coupon-item"> |
|||
<view class="coupon-money"> |
|||
<view class="nick" v-if="!types">满{{item.full}}使用</view> |
|||
<view class="layof" :style="{color:theme}">¥{{item.reduce}}</view> |
|||
<view class="end_time">{{item.end_time}}前使用</view> |
|||
<view v-if="!types"> |
|||
<!-- <view class="tit">券号:{{item.ticket}}</view> --> |
|||
<view class="demand">{{item.coupon.name}}</view> |
|||
</view> |
|||
</view> |
|||
<!-- <view class="get-btn" v-if="types" :style="{color:color, borderColor:color, background:solid}">选择使用</view> --> |
|||
<button @tap="jump" class="get-btn" v-if="item.status == 0" :style="{color:color, borderColor:color, background:solid}">立即使用</button> |
|||
<image v-else-if="item.status == 1" class="img" src="../../static/icon-img/used.png" mode=""></image> |
|||
<image v-else-if="item.status == 3" class="img" src="../../static/icon-img/guoqi.png" mode=""></image> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
components: { |
|||
|
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
mounted() { |
|||
|
|||
}, |
|||
props: { |
|||
item: { |
|||
type: Object |
|||
}, |
|||
types: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
theme: { |
|||
type: String, |
|||
default: '#ff9000' |
|||
}, |
|||
solid: { |
|||
type: String, |
|||
default: '#ffffff' |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#ff9000' |
|||
}, |
|||
}, |
|||
methods: { |
|||
jump() { |
|||
uni.switchTab({ |
|||
url: '../../pages/index/index' |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss'> |
|||
.coupon-item { |
|||
width: 100%; |
|||
height: auto; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
border-radius: 10upx; |
|||
padding: 0 20upx; |
|||
margin-top: 22upx; |
|||
border: 1px solid #eeeeee; |
|||
position: relative; |
|||
background: linear-gradient(to bottom right, transparent, #DBA871); |
|||
|
|||
.coupon-money { |
|||
width: 465upx; |
|||
height: auto; |
|||
display: table; |
|||
float: left; |
|||
padding: 26upx 0; |
|||
border-style: none dotted none none; |
|||
border-color: #eeeeee; |
|||
|
|||
.nick { |
|||
width: 100%; |
|||
height: 50upx; |
|||
line-height: 30upx; |
|||
font-size: $font-sm; |
|||
color: $font-color-999; |
|||
} |
|||
|
|||
.tit { |
|||
width: 100%; |
|||
height: 50upx; |
|||
line-height: 50upx; |
|||
font-size: $font-sm; |
|||
color: $font-color-999; |
|||
} |
|||
|
|||
.demand { |
|||
width: 100%; |
|||
height: 30upx; |
|||
line-height: 30upx; |
|||
font-size: $font-sm; |
|||
color: $font-color-999; |
|||
} |
|||
|
|||
.layof { |
|||
width: 100%; |
|||
height: 48upx; |
|||
line-height: 30upx; |
|||
font-size: 44upx; |
|||
color: #ff9000; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.end_time { |
|||
width: 100%; |
|||
height: 30upx; |
|||
line-height: 30upx; |
|||
font-size: $font-sm; |
|||
color: $font-color-999; |
|||
margin-bottom: 10px; |
|||
} |
|||
} |
|||
|
|||
.img { |
|||
position: absolute; |
|||
width: 150rpx; |
|||
height: 150rpx; |
|||
right: 28upx; |
|||
top: 40upx; |
|||
} |
|||
|
|||
.get-btn { |
|||
width: 160upx; |
|||
height: 52upx; |
|||
line-height: 50upx; |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 26upx; |
|||
margin-top: -26upx; |
|||
text-align: center; |
|||
border-radius: 60upx; |
|||
color: #ff9000; |
|||
border: 1px solid #ff9000; |
|||
font-size: $font-sm; |
|||
float: right; |
|||
} |
|||
} |
|||
|
|||
.coupon-item:after { |
|||
width: 40upx; |
|||
height: 20upx; |
|||
position: absolute; |
|||
left: 460upx; |
|||
top: -1px; |
|||
border-radius: 0 0 40upx 40upx; |
|||
content: ""; |
|||
display: block; |
|||
background: #F8F8F8; |
|||
border: 1px solid #eeeeee; |
|||
border-top: 0px; |
|||
} |
|||
|
|||
.coupon-item:before { |
|||
width: 40upx; |
|||
height: 20upx; |
|||
position: absolute; |
|||
left: 460upx; |
|||
bottom: -1px; |
|||
border-radius: 40upx 40upx 0 0; |
|||
content: ""; |
|||
display: block; |
|||
background: #F8F8F8; |
|||
border: 1px solid #eeeeee; |
|||
border-bottom: 0px; |
|||
} |
|||
</style> |
|||
@ -1,234 +0,0 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<fui-button @click="showSuccessModal = true" text="显示成功弹窗"></fui-button> |
|||
<fui-button @click="showConfirmModal = true" text="显示确认弹窗"></fui-button> |
|||
<fui-button @click="showCustomModal = true" text="显示自定义弹窗"></fui-button> |
|||
|
|||
<!-- 成功弹窗示例 --> |
|||
<custom-modal |
|||
:show="showSuccessModal" |
|||
width="600" |
|||
@cancel="handleModalCancel" |
|||
> |
|||
<fui-icon name="checkbox-fill" :size="108" color="#09BE4F"></fui-icon> |
|||
<text class="fui-title">购买成功</text> |
|||
<text class="fui-descr">成功购买一张月卡,可免费阅读30天</text> |
|||
|
|||
<template #buttons> |
|||
<fui-button |
|||
text="我知道了" |
|||
width="240rpx" |
|||
height="72rpx" |
|||
:size="28" |
|||
radius="36rpx" |
|||
background="#FFB703" |
|||
borderWidth="0" |
|||
:margin="['0','0','24rpx']" |
|||
@click="handleButtonClick('success_confirm')" |
|||
/> |
|||
</template> |
|||
</custom-modal> |
|||
|
|||
<!-- 确认弹窗示例 --> |
|||
<custom-modal |
|||
:show="showConfirmModal" |
|||
width="500" |
|||
@cancel="handleModalCancel" |
|||
> |
|||
<fui-icon name="warning-fill" :size="108" color="#FF6B35"></fui-icon> |
|||
<text class="fui-title">确认删除</text> |
|||
<text class="fui-descr">删除后无法恢复,确定要继续吗?</text> |
|||
|
|||
<template #buttons> |
|||
<fui-button |
|||
text="取消" |
|||
width="200rpx" |
|||
height="72rpx" |
|||
:size="28" |
|||
radius="36rpx" |
|||
background="#F5F5F5" |
|||
color="#333" |
|||
borderWidth="0" |
|||
:margin="['0','0','12rpx']" |
|||
@click="handleButtonClick('confirm_cancel')" |
|||
/> |
|||
<fui-button |
|||
text="确定删除" |
|||
width="200rpx" |
|||
height="72rpx" |
|||
:size="28" |
|||
radius="36rpx" |
|||
background="#FF6B35" |
|||
borderWidth="0" |
|||
@click="handleButtonClick('confirm_delete')" |
|||
/> |
|||
</template> |
|||
</custom-modal> |
|||
|
|||
<!-- 自定义内容弹窗示例 --> |
|||
<custom-modal |
|||
:show="showCustomModal" |
|||
width="700" |
|||
:showClose="false" |
|||
@cancel="handleModalCancel" |
|||
> |
|||
<view class="custom-content"> |
|||
<text class="custom-title">支付二维码</text> |
|||
<view class="custom-form"> |
|||
<fui-qrcode :value="qrcode"></fui-qrcode> |
|||
</view> |
|||
</view> |
|||
|
|||
<template #buttons> |
|||
<view class="button-row"> |
|||
<fui-button |
|||
text="发送二维码给用户" |
|||
width="200rpx" |
|||
height="72rpx" |
|||
:size="28" |
|||
radius="36rpx" |
|||
background="#007AFF" |
|||
borderWidth="0" |
|||
@click="handleButtonClick('custom_submit')" |
|||
/> |
|||
</view> |
|||
</template> |
|||
</custom-modal> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import CustomModal from './custom-modal.vue' |
|||
|
|||
export default { |
|||
components: { |
|||
CustomModal |
|||
}, |
|||
data() { |
|||
return { |
|||
showSuccessModal: false, |
|||
showConfirmModal: false, |
|||
showCustomModal: false, |
|||
qrcode: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET', |
|||
formData: { |
|||
name: '', |
|||
phone: '' |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// 处理弹窗取消事件 |
|||
handleModalCancel(type) { |
|||
console.log('弹窗取消:', type) |
|||
this.showSuccessModal = false |
|||
this.showConfirmModal = false |
|||
this.showCustomModal = false |
|||
}, |
|||
|
|||
// 处理按钮点击事件 |
|||
handleButtonClick(action) { |
|||
console.log('按钮点击:', action) |
|||
|
|||
switch(action) { |
|||
case 'success_confirm': |
|||
this.showSuccessModal = false |
|||
uni.showToast({ |
|||
title: '确认成功', |
|||
icon: 'success' |
|||
}) |
|||
break |
|||
|
|||
case 'confirm_cancel': |
|||
this.showConfirmModal = false |
|||
break |
|||
|
|||
case 'confirm_delete': |
|||
this.showConfirmModal = false |
|||
uni.showToast({ |
|||
title: '删除成功', |
|||
icon: 'success' |
|||
}) |
|||
break |
|||
|
|||
case 'custom_cancel': |
|||
this.showCustomModal = false |
|||
break |
|||
|
|||
case 'custom_submit': |
|||
if (!this.formData.name || !this.formData.phone) { |
|||
uni.showToast({ |
|||
title: '请填写完整信息', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
this.showCustomModal = false |
|||
uni.showToast({ |
|||
title: '提交成功', |
|||
icon: 'success' |
|||
}) |
|||
|
|||
// 重置表单 |
|||
this.formData = { |
|||
name: '', |
|||
phone: '' |
|||
} |
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.container { |
|||
padding: 40rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 30rpx; |
|||
} |
|||
|
|||
.fui-title { |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #333; |
|||
text-align: center; |
|||
margin: 20rpx 0; |
|||
} |
|||
|
|||
.fui-descr { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
text-align: center; |
|||
margin: 0 0 30rpx 0; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.custom-content { |
|||
width: 100%; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.custom-title { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #333; |
|||
text-align: center; |
|||
display: block; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.custom-form { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.button-row { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
gap: 40rpx; |
|||
} |
|||
</style> |
|||
@ -1,393 +0,0 @@ |
|||
<template> |
|||
<view @touchmove.stop.prevent> |
|||
<view class="modal-box" :style="{width:width,padding:padding,borderRadius:radius}" :class="[(fadein || show)?'modal-normal':'modal-scale',show?'modal-show':'']"> |
|||
<view v-if="custom"> |
|||
<slot></slot> |
|||
</view> |
|||
<view v-else> |
|||
<view class="modal-title" v-if="title">{{title}}</view> |
|||
<view class="modal-content" :class="[title?'':'mtop']" :style="{color:color,fontSize:size+'rpx'}"> |
|||
<slot></slot> |
|||
</view> |
|||
<view class="modalBtn-box" :class="[button.length!=2?'flex-column':'']"> |
|||
<block v-for="(item,index) in button" :key="index"> |
|||
<button class="modal-btn" |
|||
:class="[ |
|||
''+(item.type || 'primary')+(item.plain?'-outline':''), |
|||
button.length!=2?'btn-width':'', |
|||
button.length>2?'mbtm':'', |
|||
shape=='circle'?'circle-btn':'', |
|||
'btn-' + (item.size || 'default'), |
|||
]" |
|||
:hover-class="''+(item.plain?'outline':(item.type || 'primary'))+'-hover'" :data-index="index" @tap="handleClick">{{item.text || "确定"}}</button> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="modal-mask" :class="[show?'mask-show':'']" @tap="handleClickCancel"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "Modal", |
|||
props: { |
|||
//是否显示 |
|||
show: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
//自定义modal体 |
|||
custom: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
width: { |
|||
type: String, |
|||
default: "80%" |
|||
}, |
|||
padding: { |
|||
type: String, |
|||
default: "30rpx" |
|||
}, |
|||
radius: { |
|||
type: String, |
|||
default: "12rpx" |
|||
}, |
|||
//标题 |
|||
title: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
//内容 |
|||
content: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
//内容字体颜色 |
|||
color: { |
|||
type: String, |
|||
default: "#343434" |
|||
}, |
|||
//内容字体大小 rpx |
|||
size: { |
|||
type: Number, |
|||
default: 28 |
|||
}, |
|||
//形状 circle, square |
|||
shape: { |
|||
type: String, |
|||
default: 'square' |
|||
}, |
|||
button: { |
|||
type: Array, |
|||
default: function() { |
|||
return [{ |
|||
text: "取消", |
|||
type: "red", |
|||
plain: true //是否空心 |
|||
}, { |
|||
text: "确定", |
|||
type: "red", |
|||
plain: false |
|||
}] |
|||
} |
|||
}, |
|||
//点击遮罩 是否可关闭 |
|||
maskClosable: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
//淡入效果,自定义弹框插入input输入框时传true |
|||
fadein: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
}, |
|||
methods: { |
|||
handleClick(e) { |
|||
if (!this.show) return; |
|||
const dataset = e.currentTarget.dataset; |
|||
this.$emit('click', { |
|||
index: Number(dataset.index) |
|||
}); |
|||
}, |
|||
handleClickCancel() { |
|||
if (!this.maskClosable) return; |
|||
this.$emit('cancel'); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.modal-box { |
|||
position: fixed; |
|||
left: 50%; |
|||
top: 50%; |
|||
margin: auto; |
|||
background: #fff; |
|||
z-index: 1000; |
|||
transition: all 0.3s ease-in-out; |
|||
opacity: 0; |
|||
box-sizing: border-box; |
|||
visibility: hidden; |
|||
} |
|||
|
|||
.modal-scale { |
|||
transform: translate(-50%, -50%) scale(0); |
|||
} |
|||
|
|||
.modal-normal { |
|||
transform: translate(-50%, -50%) scale(1); |
|||
} |
|||
|
|||
.modal-show { |
|||
opacity: 1; |
|||
visibility: visible; |
|||
} |
|||
|
|||
.modal-mask { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(0, 0, 0, 0.6); |
|||
z-index: 999; |
|||
transition: all 0.3s ease-in-out; |
|||
opacity: 0; |
|||
visibility: hidden; |
|||
} |
|||
|
|||
.mask-show { |
|||
visibility: visible; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.modal-title { |
|||
text-align: center; |
|||
font-size: 34rpx; |
|||
color: #333; |
|||
padding-top: 20rpx; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.modal-content { |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
padding-top: 20rpx; |
|||
padding-bottom: 60rpx; |
|||
} |
|||
|
|||
.mtop { |
|||
margin-top: 30rpx; |
|||
} |
|||
|
|||
.mbtm { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.modalBtn-box { |
|||
width: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between |
|||
} |
|||
|
|||
.flex-column { |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.modal-btn { |
|||
width: 46%; |
|||
height: 68rpx; |
|||
line-height: 68rpx; |
|||
position: relative; |
|||
border-radius: 60rpx; |
|||
font-size: 28rpx; |
|||
overflow: visible; |
|||
margin-left: 0; |
|||
margin-right: 0; |
|||
|
|||
&.btn-default { |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
&.btn-lg { |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
&.btn-sm { |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
|
|||
.modal-btn::after { |
|||
content: ""; |
|||
position: absolute; |
|||
width: 200%; |
|||
height: 200%; |
|||
-webkit-transform-origin: 0 0; |
|||
transform-origin: 0 0; |
|||
-webkit-transform: scale(0.5, 0.5); |
|||
transform: scale(0.5, 0.5); |
|||
left: 0; |
|||
top: 0; |
|||
border-radius: 60rpx; |
|||
} |
|||
|
|||
.btn-width { |
|||
width: 80% !important; |
|||
} |
|||
|
|||
.primary { |
|||
background: #97AF13; |
|||
color: #fff; |
|||
} |
|||
|
|||
.primary-hover { |
|||
background: #97AF13; |
|||
color: #e5e5e5; |
|||
} |
|||
|
|||
.primary-outline { |
|||
color: #97AF13; |
|||
background: none; |
|||
} |
|||
|
|||
.primary-outline::after { |
|||
border: 1px solid #97AF13; |
|||
} |
|||
|
|||
.danger { |
|||
background: #ed3f14; |
|||
color: #fff; |
|||
} |
|||
|
|||
.danger-hover { |
|||
background: #d53912; |
|||
color: #e5e5e5; |
|||
} |
|||
|
|||
.danger-outline { |
|||
color: #ed3f14; |
|||
background: none; |
|||
} |
|||
|
|||
.danger-outline::after { |
|||
border: 1px solid #ed3f14; |
|||
} |
|||
|
|||
.red { |
|||
background: #e41f19; |
|||
color: #fff; |
|||
} |
|||
|
|||
.red-hover { |
|||
background: #c51a15; |
|||
color: #e5e5e5; |
|||
} |
|||
|
|||
.red-outline { |
|||
color: #e41f19; |
|||
background: none; |
|||
} |
|||
|
|||
.red-outline::after { |
|||
border: 1px solid #e41f19; |
|||
} |
|||
|
|||
.warning { |
|||
background: #ff7900; |
|||
color: #fff; |
|||
} |
|||
|
|||
.warning-hover { |
|||
background: #e56d00; |
|||
color: #e5e5e5; |
|||
} |
|||
|
|||
.warning-outline { |
|||
color: #ff7900; |
|||
background: none; |
|||
} |
|||
|
|||
.warning-outline::after { |
|||
border: 1px solid #ff7900; |
|||
} |
|||
|
|||
.green { |
|||
background: #19be6b; |
|||
color: #fff; |
|||
} |
|||
|
|||
.green-hover { |
|||
background: #16ab60; |
|||
color: #e5e5e5; |
|||
} |
|||
|
|||
.green-outline { |
|||
color: #19be6b; |
|||
background: none; |
|||
} |
|||
|
|||
.green-outline::after { |
|||
border: 1px solid #19be6b; |
|||
} |
|||
|
|||
.white { |
|||
background: #fff; |
|||
color: #333; |
|||
} |
|||
|
|||
.white-hover { |
|||
background: #f7f7f9; |
|||
color: #666; |
|||
} |
|||
|
|||
.white-outline { |
|||
color: #333; |
|||
background: none; |
|||
} |
|||
|
|||
.white-outline::after { |
|||
border: 1px solid #333; |
|||
} |
|||
|
|||
.gray { |
|||
background: #ededed; |
|||
color: #999; |
|||
} |
|||
|
|||
.gray-hover { |
|||
background: #d5d5d5; |
|||
color: #898989; |
|||
} |
|||
|
|||
.gray-outline { |
|||
color: #999; |
|||
background: none; |
|||
} |
|||
|
|||
.gray-outline::after { |
|||
border: 1px solid #999; |
|||
} |
|||
|
|||
.outline-hover { |
|||
opacity: 0.6; |
|||
} |
|||
|
|||
.circle-btn { |
|||
border-radius: 40rpx !important; |
|||
} |
|||
|
|||
.circle-btn::after { |
|||
border-radius: 80rpx !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,517 @@ |
|||
<template> |
|||
<view class="schedule-detail" v-if="visible"> |
|||
<!-- <view class="popup-wrapper">--> |
|||
<!-- <view class="popup-header">--> |
|||
<!-- <text class="popup-title">课次详情</text>--> |
|||
<!-- <view class="close-btn" @click="closePopup">--> |
|||
<!-- <text class="close-icon">×</text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="popup-content" v-if="loading">--> |
|||
<!-- <view class="loading">--> |
|||
<!-- <fui-loading></fui-loading>--> |
|||
<!-- <text class="loading-text">加载中...</text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="popup-content" v-else-if="error">--> |
|||
<!-- <view class="error-message">--> |
|||
<!-- <text>{{ errorMessage }}</text>--> |
|||
<!-- <view class="retry-btn" @click="fetchScheduleDetail">--> |
|||
<!-- <text>重试</text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="popup-content" v-else>--> |
|||
<!-- <view class="course-title">--> |
|||
<!-- <text>{{ scheduleDetail.title || '暂无课程名称' }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="course-time">--> |
|||
<!-- <text>{{ scheduleDetail.course_date || '' }} {{ scheduleDetail.time_slot || '' }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="schedule-info">--> |
|||
<!-- <view class="info-item">--> |
|||
<!-- <text class="info-label">授课教师:</text>--> |
|||
<!-- <text class="info-value">{{ scheduleDetail.coach?.name || '未设置' }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="info-item">--> |
|||
<!-- <text class="info-label">教室:</text>--> |
|||
<!-- <text class="info-value">{{ scheduleDetail.venue?.venue_name || '未设置' }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="info-item">--> |
|||
<!-- <text class="info-label">当前人数:</text>--> |
|||
<!-- <text class="info-value">{{ studentCount }}/{{ scheduleDetail.venue?.capacity || 0 }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="info-item">--> |
|||
<!-- <text class="info-label">课程内容:</text>--> |
|||
<!-- <text class="info-value">{{ scheduleDetail.content || '未设置上课内容' }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="info-item" v-if="scheduleDetail.remark">--> |
|||
<!-- <text class="info-label">备注:</text>--> |
|||
<!-- <text class="info-value">{{ scheduleDetail.remark }}</text>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <!– 学员列表 –>--> |
|||
<!-- <view class="student-list-section">--> |
|||
<!-- <view class="section-title">--> |
|||
<!-- <text>学员列表</text>--> |
|||
<!-- <text class="status-tag" :class="statusClass">{{ statusText }}</text>--> |
|||
<!-- </view>--> |
|||
<!-- <view class="student-list" v-if="scheduleDetail.student_courses && scheduleDetail.student_courses.length > 0">--> |
|||
<!-- <view class="student-item" v-for="(student, index) in scheduleDetail.student_courses" :key="index">--> |
|||
<!-- <view class="student-avatar">--> |
|||
<!-- <image :src="$util.img(student.avatar)" mode="aspectFill"></image>--> |
|||
<!-- </view>--> |
|||
<!-- <view class="student-info">--> |
|||
<!-- <text class="student-name">{{ student.name }}</text>--> |
|||
<!-- <text class="student-status" :class="{'signed': student.status === 'signed'}">--> |
|||
<!-- {{ student.status === 'signed' ? '已签到' : '未签到' }}--> |
|||
<!-- </text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- <view class="empty-list" v-else>--> |
|||
<!-- <text>暂无学员参与此课程</text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
|
|||
<!-- <view class="popup-footer">--> |
|||
<!-- <view class="action-btn adjust-btn" @click="handleAdjustClass">--> |
|||
<!-- <text>调课</text>--> |
|||
<!-- </view>--> |
|||
<!-- <view class="action-btn sign-btn" @click="handleSignIn">--> |
|||
<!-- <text>点名</text>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
<!-- </view>--> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import apiRoute from '@/api/apiRoute.js'; |
|||
|
|||
export default { |
|||
name: 'ScheduleDetail', |
|||
props: { |
|||
visible: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
scheduleId: { |
|||
type: [String, Number], |
|||
default: '' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
loading: false, |
|||
error: false, |
|||
errorMessage: '加载失败,请重试', |
|||
scheduleDetail: {}, |
|||
studentCount: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
// 课程状态文本和样式 |
|||
statusText() { |
|||
if (!this.scheduleDetail.student_courses || !this.scheduleDetail.student_courses[0]) { |
|||
return '未开始'; |
|||
} |
|||
|
|||
const now = new Date(); |
|||
const startDate = this.scheduleDetail.student_courses[0].start_date ? |
|||
new Date(this.scheduleDetail.student_courses[0].start_date) : null; |
|||
const endDate = this.scheduleDetail.student_courses[0].end_date ? |
|||
new Date(this.scheduleDetail.student_courses[0].end_date) : null; |
|||
|
|||
if (startDate && endDate) { |
|||
if (now >= startDate && now <= endDate) { |
|||
return '上课中'; |
|||
} else if (now > endDate) { |
|||
return '已结束'; |
|||
} else if (now < startDate) { |
|||
return '未开始'; |
|||
} |
|||
} |
|||
|
|||
return '未开始'; |
|||
}, |
|||
statusClass() { |
|||
switch (this.statusText) { |
|||
case '上课中': |
|||
return 'status-in-progress'; |
|||
case '已结束': |
|||
return 'status-ended'; |
|||
case '未开始': |
|||
default: |
|||
return 'status-not-started'; |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
// 监听课程ID变化,重新获取数据 |
|||
scheduleId: { |
|||
immediate: true, |
|||
handler(newVal) { |
|||
if (newVal && this.visible) { |
|||
this.fetchScheduleDetail(); |
|||
} |
|||
} |
|||
}, |
|||
// 监听弹窗可见性变化 |
|||
visible(newVal) { |
|||
if (newVal && this.scheduleId) { |
|||
this.fetchScheduleDetail(); |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// 获取课程详情 |
|||
async fetchScheduleDetail() { |
|||
if (!this.scheduleId) { |
|||
this.error = true; |
|||
this.errorMessage = '课程ID不能为空'; |
|||
return; |
|||
} |
|||
|
|||
this.loading = true; |
|||
this.error = false; |
|||
|
|||
try { |
|||
// 使用新接口获取课程安排详情 |
|||
const res = await apiRoute.getCourseScheduleInfo({ |
|||
id: this.scheduleId |
|||
}); |
|||
|
|||
if (res.code === 1 && res.data) { |
|||
this.scheduleDetail = res.data; |
|||
// 计算学生数量 |
|||
this.studentCount = this.scheduleDetail.student_courses ? |
|||
this.scheduleDetail.student_courses.length : 0; |
|||
} else { |
|||
// 如果新接口不可用,尝试使用旧接口 |
|||
const fallbackRes = await apiRoute.courseInfo({ |
|||
id: this.scheduleId |
|||
}); |
|||
|
|||
if (fallbackRes.code === 1 && fallbackRes.data) { |
|||
this.scheduleDetail = fallbackRes.data; |
|||
this.studentCount = this.scheduleDetail.student_courses ? |
|||
this.scheduleDetail.student_courses.length : 0; |
|||
} else { |
|||
throw new Error(res.msg || fallbackRes.msg || '获取课程详情失败'); |
|||
} |
|||
} |
|||
} catch (error) { |
|||
console.error('获取课程详情失败:', error); |
|||
this.error = true; |
|||
this.errorMessage = error.message || '获取课程详情失败,请重试'; |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
}, |
|||
|
|||
// 关闭弹窗 |
|||
closePopup() { |
|||
this.$emit('update:visible', false); |
|||
}, |
|||
|
|||
// 点名功能 |
|||
handleSignIn() { |
|||
// 如果课程已结束,显示提示 |
|||
if (this.statusText === '已结束') { |
|||
uni.showToast({ |
|||
title: '课程已结束,无法点名', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 如果没有学生,显示提示 |
|||
if (!this.scheduleDetail.student_courses || this.scheduleDetail.student_courses.length === 0) { |
|||
uni.showToast({ |
|||
title: '暂无学员,无法点名', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 触发点名事件,由父组件处理 |
|||
this.$emit('sign-in', { |
|||
scheduleId: this.scheduleId, |
|||
scheduleDetail: this.scheduleDetail |
|||
}); |
|||
}, |
|||
|
|||
// 调课功能 |
|||
handleAdjustClass() { |
|||
// 如果课程已结束,显示提示 |
|||
if (this.statusText === '已结束') { |
|||
uni.showToast({ |
|||
title: '课程已结束,无法调课', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 触发调课事件,由父组件处理 |
|||
this.$emit('adjust-class', { |
|||
scheduleId: this.scheduleId, |
|||
scheduleDetail: this.scheduleDetail |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.schedule-detail { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.popup-wrapper { |
|||
width: 90%; |
|||
max-height: 80vh; |
|||
background-color: #434544; |
|||
border-radius: 16rpx; |
|||
overflow: hidden; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.popup-header { |
|||
padding: 30rpx; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
border-bottom: 1px solid #555; |
|||
} |
|||
|
|||
.popup-title { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
|
|||
.close-btn { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.close-icon { |
|||
font-size: 40rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.popup-content { |
|||
flex: 1; |
|||
padding: 30rpx; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.loading, .error-message { |
|||
height: 300rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.loading-text { |
|||
margin-top: 20rpx; |
|||
font-size: 28rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
.error-message { |
|||
color: #ff6b6b; |
|||
font-size: 28rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.retry-btn { |
|||
margin-top: 30rpx; |
|||
padding: 12rpx 30rpx; |
|||
background-color: #29d3b4; |
|||
border-radius: 8rpx; |
|||
color: #fff; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.course-title { |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.course-time { |
|||
font-size: 28rpx; |
|||
color: #FAD24E; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.schedule-info { |
|||
background-color: #333; |
|||
border-radius: 12rpx; |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.info-item { |
|||
display: flex; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.info-label { |
|||
width: 160rpx; |
|||
font-size: 26rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
.info-value { |
|||
flex: 1; |
|||
font-size: 26rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.student-list-section { |
|||
margin-top: 30rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding-bottom: 16rpx; |
|||
border-bottom: 1px solid #555; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.section-title text { |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.status-tag { |
|||
font-size: 24rpx; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
} |
|||
|
|||
.status-in-progress { |
|||
background-color: #FAD24E; |
|||
color: #333; |
|||
} |
|||
|
|||
.status-ended { |
|||
background-color: #e2e2e2; |
|||
color: #333; |
|||
} |
|||
|
|||
.status-not-started { |
|||
background-color: #1cd188; |
|||
color: #fff; |
|||
} |
|||
|
|||
.student-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.student-item { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.student-avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
background-color: #555; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.student-avatar image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.student-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.student-name { |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
margin-bottom: 6rpx; |
|||
} |
|||
|
|||
.student-status { |
|||
font-size: 24rpx; |
|||
color: #ff6b6b; |
|||
} |
|||
|
|||
.student-status.signed { |
|||
color: #1cd188; |
|||
} |
|||
|
|||
.empty-list { |
|||
padding: 40rpx 0; |
|||
text-align: center; |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.popup-footer { |
|||
display: flex; |
|||
padding: 30rpx; |
|||
gap: 20rpx; |
|||
border-top: 1px solid #555; |
|||
} |
|||
|
|||
.action-btn { |
|||
flex: 1; |
|||
height: 80rpx; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border-radius: 8rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.adjust-btn { |
|||
background-color: #555; |
|||
color: #fff; |
|||
} |
|||
|
|||
.sign-btn { |
|||
background-color: #29d3b4; |
|||
color: #fff; |
|||
} |
|||
</style> |
|||
@ -1,96 +0,0 @@ |
|||
export default { |
|||
'contact': '\ue100', |
|||
'person': '\ue101', |
|||
'personadd': '\ue102', |
|||
'contact-filled': '\ue130', |
|||
'person-filled': '\ue131', |
|||
'personadd-filled': '\ue132', |
|||
'phone': '\ue200', |
|||
'email': '\ue201', |
|||
'chatbubble': '\ue202', |
|||
'chatboxes': '\ue203', |
|||
'phone-filled': '\ue230', |
|||
'email-filled': '\ue231', |
|||
'chatbubble-filled': '\ue232', |
|||
'chatboxes-filled': '\ue233', |
|||
'weibo': '\ue260', |
|||
'weixin': '\ue261', |
|||
'pengyouquan': '\ue262', |
|||
'chat': '\ue263', |
|||
'qq': '\ue264', |
|||
'videocam': '\ue300', |
|||
'camera': '\ue301', |
|||
'mic': '\ue302', |
|||
'location': '\ue303', |
|||
'mic-filled': '\ue332', |
|||
'speech': '\ue332', |
|||
'location-filled': '\ue333', |
|||
'micoff': '\ue360', |
|||
'image': '\ue363', |
|||
'map': '\ue364', |
|||
'compose': '\ue400', |
|||
'trash': '\ue401', |
|||
'upload': '\ue402', |
|||
'download': '\ue403', |
|||
'close': '\ue404', |
|||
'redo': '\ue405', |
|||
'undo': '\ue406', |
|||
'refresh': '\ue407', |
|||
'star': '\ue408', |
|||
'plus': '\ue409', |
|||
'minus': '\ue410', |
|||
'circle': '\ue411', |
|||
'checkbox': '\ue411', |
|||
'close-filled': '\ue434', |
|||
'clear': '\ue434', |
|||
'refresh-filled': '\ue437', |
|||
'star-filled': '\ue438', |
|||
'plus-filled': '\ue439', |
|||
'minus-filled': '\ue440', |
|||
'circle-filled': '\ue441', |
|||
'checkbox-filled': '\ue442', |
|||
'closeempty': '\ue460', |
|||
'refreshempty': '\ue461', |
|||
'reload': '\ue462', |
|||
'starhalf': '\ue463', |
|||
'spinner': '\ue464', |
|||
'spinner-cycle': '\ue465', |
|||
'search': '\ue466', |
|||
'plusempty': '\ue468', |
|||
'forward': '\ue470', |
|||
'back': '\ue471', |
|||
'left-nav': '\ue471', |
|||
'checkmarkempty': '\ue472', |
|||
'home': '\ue500', |
|||
'navigate': '\ue501', |
|||
'gear': '\ue502', |
|||
'paperplane': '\ue503', |
|||
'info': '\ue504', |
|||
'help': '\ue505', |
|||
'locked': '\ue506', |
|||
'more': '\ue507', |
|||
'flag': '\ue508', |
|||
'home-filled': '\ue530', |
|||
'gear-filled': '\ue532', |
|||
'info-filled': '\ue534', |
|||
'help-filled': '\ue535', |
|||
'more-filled': '\ue537', |
|||
'settings': '\ue560', |
|||
'list': '\ue562', |
|||
'bars': '\ue563', |
|||
'loop': '\ue565', |
|||
'paperclip': '\ue567', |
|||
'eye': '\ue568', |
|||
'arrowup': '\ue580', |
|||
'arrowdown': '\ue581', |
|||
'arrowleft': '\ue582', |
|||
'arrowright': '\ue583', |
|||
'arrowthinup': '\ue584', |
|||
'arrowthindown': '\ue585', |
|||
'arrowthinleft': '\ue586', |
|||
'arrowthinright': '\ue587', |
|||
'pulldown': '\ue588', |
|||
'closefill': '\ue589', |
|||
'sound': '\ue590', |
|||
'scan': '\ue612' |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,175 @@ |
|||
# 课程安排表组件 |
|||
|
|||
## 功能特性 |
|||
|
|||
### 📅 完整的课程表视图 |
|||
- **时间轴显示**:11:00-17:00 每小时时间段 |
|||
- **周视图**:显示一周7天的课程安排 |
|||
- **双向滚动**:支持水平(日期)和垂直(时间)滚动 |
|||
- **响应式布局**:适配微信小程序端 |
|||
|
|||
### 🔍 智能筛选功能 |
|||
- **顶部快捷筛选**:时间、老师、教室、班级 |
|||
- **详细筛选弹窗**:支持多选条件组合筛选 |
|||
- **实时统计**:显示总课程数和未点名课程数 |
|||
|
|||
### 🎨 主题样式 |
|||
- **暗黑主题**:#292929 背景色 |
|||
- **绿色主色调**:#29d3b4 强调色 |
|||
- **层次化设计**:清晰的视觉层级 |
|||
|
|||
### 📱 交互体验 |
|||
- **日期导航**:支持上一周/下一周切换 |
|||
- **课程类型**:普通课程、私教课程、活动课程区分显示 |
|||
- **点击交互**:支持单元格点击添加课程 |
|||
- **悬浮按钮**:快速添加课程 |
|||
|
|||
## 使用方法 |
|||
|
|||
### 1. 页面导航 |
|||
```javascript |
|||
// 跳转到课程安排表 |
|||
this.$navigateTo({ |
|||
url: '/pages/coach/schedule/schedule_table' |
|||
}) |
|||
``` |
|||
|
|||
### 2. 数据结构 |
|||
|
|||
#### 课程数据格式 |
|||
```javascript |
|||
{ |
|||
id: 1, // 课程ID |
|||
date: '2025-07-02', // 日期 |
|||
time: '11:00', // 时间 |
|||
courseName: '花花-丁颖', // 课程名称 |
|||
students: '小鱼-周子', // 学员 |
|||
teacher: '燕子-符', // 老师 |
|||
status: '燕菜', // 状态 |
|||
type: 'normal', // 类型:normal/private/activity |
|||
duration: 1 // 持续时间(小时) |
|||
} |
|||
``` |
|||
|
|||
#### 筛选选项格式 |
|||
```javascript |
|||
{ |
|||
teacherOptions: [ |
|||
{ id: 1, name: '张老师' } |
|||
], |
|||
classroomOptions: [ |
|||
{ id: 1, name: '教室1' } |
|||
], |
|||
classOptions: [ |
|||
{ id: 1, name: '花花-丁颖' } |
|||
] |
|||
} |
|||
``` |
|||
|
|||
### 3. 自定义配置 |
|||
|
|||
#### 修改时间段 |
|||
```javascript |
|||
// 在 data 中修改 timeSlots |
|||
timeSlots: [ |
|||
{ time: '09:00', value: 9 }, |
|||
{ time: '10:00', value: 10 }, |
|||
// ... 更多时间段 |
|||
] |
|||
``` |
|||
|
|||
#### 修改表格宽度 |
|||
```javascript |
|||
// 调整表格总宽度(rpx) |
|||
tableWidth: 1200 |
|||
``` |
|||
|
|||
### 4. API 接口集成 |
|||
|
|||
#### 获取课程数据 |
|||
```javascript |
|||
async loadCourses() { |
|||
try { |
|||
const res = await apiRoute.getCourseSchedule({ |
|||
startDate: this.currentWeekStart, |
|||
endDate: this.getWeekEndDate() |
|||
}) |
|||
this.courses = res.data |
|||
} catch (error) { |
|||
console.error('加载课程失败:', error) |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 添加课程 |
|||
```javascript |
|||
async addCourse(courseData) { |
|||
try { |
|||
const res = await apiRoute.addCourseSchedule(courseData) |
|||
if (res.code === 1) { |
|||
this.loadCourses() // 重新加载数据 |
|||
} |
|||
} catch (error) { |
|||
console.error('添加课程失败:', error) |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 性能优化建议 |
|||
|
|||
### 1. 数据懒加载 |
|||
- 只加载当前周的数据 |
|||
- 切换周时再加载新数据 |
|||
|
|||
### 2. 滚动优化 |
|||
- 使用 `scroll-view` 组件的惯性滚动 |
|||
- 避免在滚动事件中进行复杂计算 |
|||
|
|||
### 3. 渲染优化 |
|||
- 使用 `v-show` 替代 `v-if` 进行显示隐藏 |
|||
- 合理使用 `key` 属性优化列表渲染 |
|||
|
|||
## 扩展功能 |
|||
|
|||
### 1. 课程详情 |
|||
```javascript |
|||
// 点击课程查看详情 |
|||
handleCourseClick(course) { |
|||
this.$navigateTo({ |
|||
url: `/pages/coach/course/info?id=${course.id}` |
|||
}) |
|||
} |
|||
``` |
|||
|
|||
### 2. 拖拽排课 |
|||
```javascript |
|||
// 可以集成拖拽功能实现课程时间调整 |
|||
// 使用 movable-view 组件或手势事件 |
|||
``` |
|||
|
|||
### 3. 批量操作 |
|||
```javascript |
|||
// 支持批量选择和操作课程 |
|||
// 添加多选模式 |
|||
``` |
|||
|
|||
## 注意事项 |
|||
|
|||
1. **微信小程序兼容性**:使用了 `scroll-view` 组件,确保在微信小程序中正常滚动 |
|||
2. **数据更新**:切换日期时需要重新加载数据 |
|||
3. **内存管理**:大量数据时考虑虚拟滚动优化 |
|||
4. **网络状态**:处理网络异常情况,提供离线缓存 |
|||
|
|||
## troubleshooting |
|||
|
|||
### 滚动不流畅 |
|||
- 检查 `scroll-view` 的高度设置 |
|||
- 减少滚动事件中的计算量 |
|||
|
|||
### 数据不更新 |
|||
- 确保数据是响应式的 |
|||
- 检查 Vue 数据绑定是否正确 |
|||
|
|||
### 样式错位 |
|||
- 检查 flex 布局设置 |
|||
- 确保单元格宽度一致 |
|||
@ -0,0 +1,481 @@ |
|||
<template> |
|||
<view class="schedule-detail-container"> |
|||
<!-- 页面加载状态 --> |
|||
<view class="loading-container" v-if="loading"> |
|||
<fui-loading></fui-loading> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
|
|||
<!-- 错误状态显示 --> |
|||
<view class="error-container" v-else-if="error"> |
|||
<text class="error-text">{{ errorMessage }}</text> |
|||
<view class="retry-btn" @click="fetchScheduleDetail"> |
|||
<text>重试</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="schedule-content" v-else> |
|||
<!-- 课程基本信息 --> |
|||
<view class="schedule-header"> |
|||
<view class="course-title"> |
|||
<text>{{ scheduleDetail.title || '暂无课程名称' }}</text> |
|||
<text class="status-tag" :class="statusClass">{{ statusText }}</text> |
|||
</view> |
|||
<view class="course-time">{{ scheduleDetail.course_date || '' }} {{ scheduleDetail.time_slot || '' }}</view> |
|||
</view> |
|||
|
|||
<!-- 课程详细信息 --> |
|||
<view class="info-card"> |
|||
<view class="info-item"> |
|||
<text class="info-label">授课教师:</text> |
|||
<text class="info-value">{{ scheduleDetail.coach?.name || '未设置' }}</text> |
|||
</view> |
|||
<view class="info-item"> |
|||
<text class="info-label">教室:</text> |
|||
<text class="info-value">{{ scheduleDetail.venue?.venue_name || '未设置' }}</text> |
|||
</view> |
|||
<view class="info-item"> |
|||
<text class="info-label">当前人数:</text> |
|||
<text class="info-value">{{ studentCount }}/{{ scheduleDetail.venue?.capacity || 0 }}</text> |
|||
</view> |
|||
<view class="info-item"> |
|||
<text class="info-label">课程内容:</text> |
|||
<text class="info-value">{{ scheduleDetail.content || '未设置上课内容' }}</text> |
|||
</view> |
|||
<view class="info-item" v-if="scheduleDetail.remark"> |
|||
<text class="info-label">备注:</text> |
|||
<text class="info-value">{{ scheduleDetail.remark }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 学员列表 --> |
|||
<view class="student-list-section"> |
|||
<view class="section-title"> |
|||
<text>学员列表</text> |
|||
</view> |
|||
<view class="student-list" v-if="scheduleDetail.student_courses && scheduleDetail.student_courses.length > 0"> |
|||
<view class="student-item" v-for="(student, index) in scheduleDetail.student_courses" :key="index"> |
|||
<view class="student-avatar"> |
|||
<image :src="$util.img(student.avatar)" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="student-info"> |
|||
<text class="student-name">{{ student.name }}</text> |
|||
<text class="student-status" :class="{'signed': student.status === 'signed'}"> |
|||
{{ student.status === 'signed' ? '已签到' : '未签到' }} |
|||
</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="empty-list" v-else> |
|||
<text>暂无学员参与此课程</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部按钮区域 --> |
|||
<view class="footer-actions"> |
|||
<view class="action-btn adjust-btn" @click="handleAdjustClass"> |
|||
<text>调课</text> |
|||
</view> |
|||
<view class="action-btn sign-btn" @click="handleSignIn"> |
|||
<text>点名</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 引入课程详情组件 --> |
|||
<schedule-detail |
|||
:visible="showDetailPopup" |
|||
:scheduleId="scheduleId" |
|||
@update:visible="showDetailPopup = $event" |
|||
@sign-in="onSignIn" |
|||
@adjust-class="onAdjustClass" |
|||
></schedule-detail> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import apiRoute from '@/api/apiRoute.js' |
|||
import ScheduleDetail from '@/components/schedule/ScheduleDetail.vue' |
|||
|
|||
export default { |
|||
components: { |
|||
ScheduleDetail |
|||
}, |
|||
data() { |
|||
return { |
|||
scheduleId: '', // 课程安排ID |
|||
loading: false, |
|||
error: false, |
|||
errorMessage: '加载失败,请重试', |
|||
scheduleDetail: {}, |
|||
studentCount: 0, |
|||
showDetailPopup: false |
|||
} |
|||
}, |
|||
computed: { |
|||
// 课程状态文本和样式 |
|||
statusText() { |
|||
if (!this.scheduleDetail.student_courses || !this.scheduleDetail.student_courses[0]) { |
|||
return '未开始'; |
|||
} |
|||
|
|||
const now = new Date(); |
|||
const startDate = this.scheduleDetail.student_courses[0].start_date ? |
|||
new Date(this.scheduleDetail.student_courses[0].start_date) : null; |
|||
const endDate = this.scheduleDetail.student_courses[0].end_date ? |
|||
new Date(this.scheduleDetail.student_courses[0].end_date) : null; |
|||
|
|||
if (startDate && endDate) { |
|||
if (now >= startDate && now <= endDate) { |
|||
return '上课中'; |
|||
} else if (now > endDate) { |
|||
return '已结束'; |
|||
} else if (now < startDate) { |
|||
return '未开始'; |
|||
} |
|||
} |
|||
|
|||
return '未开始'; |
|||
}, |
|||
statusClass() { |
|||
switch (this.statusText) { |
|||
case '上课中': |
|||
return 'status-in-progress'; |
|||
case '已结束': |
|||
return 'status-ended'; |
|||
case '未开始': |
|||
default: |
|||
return 'status-not-started'; |
|||
} |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
if (options.id) { |
|||
this.scheduleId = options.id; |
|||
this.fetchScheduleDetail(); |
|||
} else { |
|||
this.error = true; |
|||
this.errorMessage = '未找到课程安排ID'; |
|||
} |
|||
}, |
|||
methods: { |
|||
// 获取课程详情 |
|||
async fetchScheduleDetail() { |
|||
if (!this.scheduleId) { |
|||
this.error = true; |
|||
this.errorMessage = '课程ID不能为空'; |
|||
return; |
|||
} |
|||
|
|||
this.loading = true; |
|||
this.error = false; |
|||
|
|||
try { |
|||
// 使用新接口获取课程安排详情 |
|||
const res = await apiRoute.getCourseScheduleInfo({ |
|||
id: this.scheduleId |
|||
}); |
|||
|
|||
if (res.code === 1 && res.data) { |
|||
this.scheduleDetail = res.data; |
|||
// 计算学生数量 |
|||
this.studentCount = this.scheduleDetail.student_courses ? |
|||
this.scheduleDetail.student_courses.length : 0; |
|||
} else { |
|||
// 如果新接口不可用,尝试使用旧接口 |
|||
const fallbackRes = await apiRoute.courseInfo({ |
|||
id: this.scheduleId |
|||
}); |
|||
|
|||
if (fallbackRes.code === 1 && fallbackRes.data) { |
|||
this.scheduleDetail = fallbackRes.data; |
|||
this.studentCount = this.scheduleDetail.student_courses ? |
|||
this.scheduleDetail.student_courses.length : 0; |
|||
} else { |
|||
throw new Error(res.msg || fallbackRes.msg || '获取课程详情失败'); |
|||
} |
|||
} |
|||
} catch (error) { |
|||
console.error('获取课程详情失败:', error); |
|||
this.error = true; |
|||
this.errorMessage = error.message || '获取课程详情失败,请重试'; |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
}, |
|||
|
|||
// 点名功能 |
|||
handleSignIn() { |
|||
// 如果课程已结束,显示提示 |
|||
if (this.statusText === '已结束') { |
|||
uni.showToast({ |
|||
title: '课程已结束,无法点名', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 如果没有学生,显示提示 |
|||
if (!this.scheduleDetail.student_courses || this.scheduleDetail.student_courses.length === 0) { |
|||
uni.showToast({ |
|||
title: '暂无学员,无法点名', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 显示点名界面或跳转至点名页面 |
|||
uni.navigateTo({ |
|||
url: `/pages/coach/schedule/sign_in?id=${this.scheduleId}` |
|||
}); |
|||
}, |
|||
|
|||
// 调课功能 |
|||
handleAdjustClass() { |
|||
// 如果课程已结束,显示提示 |
|||
if (this.statusText === '已结束') { |
|||
uni.showToast({ |
|||
title: '课程已结束,无法调课', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 跳转至调课页面 |
|||
uni.navigateTo({ |
|||
url: `/pages/coach/schedule/adjust_course?id=${this.scheduleId}` |
|||
}); |
|||
}, |
|||
|
|||
// 从弹窗组件中点名处理 |
|||
onSignIn(data) { |
|||
console.log('处理点名:', data); |
|||
uni.navigateTo({ |
|||
url: `/pages/coach/schedule/sign_in?id=${data.scheduleId}` |
|||
}); |
|||
}, |
|||
|
|||
// 从弹窗组件中调课处理 |
|||
onAdjustClass(data) { |
|||
console.log('处理调课:', data); |
|||
uni.navigateTo({ |
|||
url: `/pages/coach/schedule/adjust_course?id=${data.scheduleId}` |
|||
}); |
|||
}, |
|||
|
|||
// 显示弹窗 |
|||
showPopup() { |
|||
this.showDetailPopup = true; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.schedule-detail-container { |
|||
background-color: #292929; |
|||
min-height: 100vh; |
|||
padding-bottom: 140rpx; // 为底部按钮留出空间 |
|||
} |
|||
|
|||
.loading-container, .error-container { |
|||
height: 100vh; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.loading-text, .error-text { |
|||
margin-top: 30rpx; |
|||
font-size: 28rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
.retry-btn { |
|||
margin-top: 30rpx; |
|||
padding: 12rpx 30rpx; |
|||
background-color: #29d3b4; |
|||
border-radius: 8rpx; |
|||
color: #fff; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.schedule-content { |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.schedule-header { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.course-title { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.status-tag { |
|||
font-size: 24rpx; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
} |
|||
|
|||
.status-in-progress { |
|||
background-color: #FAD24E; |
|||
color: #333; |
|||
} |
|||
|
|||
.status-ended { |
|||
background-color: #e2e2e2; |
|||
color: #333; |
|||
} |
|||
|
|||
.status-not-started { |
|||
background-color: #1cd188; |
|||
color: #fff; |
|||
} |
|||
|
|||
.course-time { |
|||
font-size: 28rpx; |
|||
color: #FAD24E; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.info-card { |
|||
background-color: #434544; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.info-item { |
|||
display: flex; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.info-label { |
|||
width: 160rpx; |
|||
font-size: 26rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
.info-value { |
|||
flex: 1; |
|||
font-size: 26rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.student-list-section { |
|||
background-color: #434544; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding-bottom: 16rpx; |
|||
border-bottom: 1px solid #555; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.section-title text { |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.student-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.student-item { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.student-avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
background-color: #555; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.student-avatar image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.student-info { |
|||
flex: 1; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.student-name { |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.student-status { |
|||
font-size: 24rpx; |
|||
color: #ff6b6b; |
|||
background: rgba(255, 107, 107, 0.1); |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 20rpx; |
|||
} |
|||
|
|||
.student-status.signed { |
|||
color: #1cd188; |
|||
background: rgba(28, 209, 136, 0.1); |
|||
} |
|||
|
|||
.empty-list { |
|||
padding: 40rpx 0; |
|||
text-align: center; |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.footer-actions { |
|||
position: fixed; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
display: flex; |
|||
padding: 30rpx; |
|||
gap: 20rpx; |
|||
background-color: #292929; |
|||
border-top: 1px solid #434544; |
|||
} |
|||
|
|||
.action-btn { |
|||
flex: 1; |
|||
height: 80rpx; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border-radius: 8rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.adjust-btn { |
|||
background-color: #555; |
|||
color: #fff; |
|||
} |
|||
|
|||
.sign-btn { |
|||
background-color: #29d3b4; |
|||
color: #fff; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,263 +0,0 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<view class="header"> |
|||
<text class="title">字典功能测试</text> |
|||
</view> |
|||
|
|||
<view class="test-section"> |
|||
<view class="section-title">1. 测试单个字典获取</view> |
|||
<button class="test-btn" @click="testSingleDict">测试获取单个字典</button> |
|||
<view class="result" v-if="singleResult"> |
|||
<text class="result-title">结果:</text> |
|||
<text class="result-content">{{ JSON.stringify(singleResult, null, 2) }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="test-section"> |
|||
<view class="section-title">2. 测试批量字典获取</view> |
|||
<button class="test-btn" @click="testBatchDict">测试批量获取字典</button> |
|||
<view class="result" v-if="batchResult"> |
|||
<text class="result-title">结果:</text> |
|||
<text class="result-content">{{ JSON.stringify(batchResult, null, 2) }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="test-section"> |
|||
<view class="section-title">3. 测试字典缓存</view> |
|||
<button class="test-btn" @click="testCache">测试缓存机制</button> |
|||
<view class="result" v-if="cacheResult"> |
|||
<text class="result-title">缓存测试结果:</text> |
|||
<text class="result-content">{{ cacheResult }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="test-section"> |
|||
<view class="section-title">4. 测试静默请求</view> |
|||
<button class="test-btn" @click="testQuietRequest">测试静默请求</button> |
|||
<view class="result" v-if="quietResult"> |
|||
<text class="result-title">静默请求结果:</text> |
|||
<text class="result-content">{{ JSON.stringify(quietResult, null, 2) }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="test-section"> |
|||
<view class="section-title">5. 清除缓存</view> |
|||
<button class="test-btn clear" @click="clearAllCache">清除所有缓存</button> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import dictUtil from '@/common/dictUtil.js' |
|||
import axiosQuiet from '@/common/axiosQuiet.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
singleResult: null, |
|||
batchResult: null, |
|||
cacheResult: null, |
|||
quietResult: null |
|||
} |
|||
}, |
|||
methods: { |
|||
// 测试单个字典获取 |
|||
async testSingleDict() { |
|||
try { |
|||
console.log('开始测试单个字典获取') |
|||
const result = await dictUtil.getDict('source') |
|||
this.singleResult = result |
|||
console.log('单个字典获取结果:', result) |
|||
|
|||
uni.showToast({ |
|||
title: '单个字典测试完成', |
|||
icon: 'success' |
|||
}) |
|||
} catch (error) { |
|||
console.error('单个字典获取失败:', error) |
|||
this.singleResult = { error: error.message || '获取失败' } |
|||
|
|||
uni.showToast({ |
|||
title: '单个字典测试失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
// 测试批量字典获取 |
|||
async testBatchDict() { |
|||
try { |
|||
console.log('开始测试批量字典获取') |
|||
const keys = ['source', 'SourceChannel', 'customer_purchasing_power'] |
|||
const result = await dictUtil.getBatchDict(keys) |
|||
this.batchResult = result |
|||
console.log('批量字典获取结果:', result) |
|||
|
|||
uni.showToast({ |
|||
title: '批量字典测试完成', |
|||
icon: 'success' |
|||
}) |
|||
} catch (error) { |
|||
console.error('批量字典获取失败:', error) |
|||
this.batchResult = { error: error.message || '获取失败' } |
|||
|
|||
uni.showToast({ |
|||
title: '批量字典测试失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
// 测试缓存机制 |
|||
async testCache() { |
|||
try { |
|||
console.log('开始测试缓存机制') |
|||
|
|||
// 清除缓存 |
|||
dictUtil.clearCache(['source']) |
|||
|
|||
// 第一次获取(应该从接口获取) |
|||
const start1 = Date.now() |
|||
await dictUtil.getDict('source', true) |
|||
const time1 = Date.now() - start1 |
|||
|
|||
// 第二次获取(应该从缓存获取) |
|||
const start2 = Date.now() |
|||
await dictUtil.getDict('source', true) |
|||
const time2 = Date.now() - start2 |
|||
|
|||
this.cacheResult = `第一次获取: ${time1}ms, 第二次获取: ${time2}ms, 缓存提升: ${Math.round((time1 - time2) / time1 * 100)}%` |
|||
|
|||
uni.showToast({ |
|||
title: '缓存测试完成', |
|||
icon: 'success' |
|||
}) |
|||
} catch (error) { |
|||
console.error('缓存测试失败:', error) |
|||
this.cacheResult = '缓存测试失败: ' + (error.message || '未知错误') |
|||
|
|||
uni.showToast({ |
|||
title: '缓存测试失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
// 测试静默请求 |
|||
async testQuietRequest() { |
|||
try { |
|||
console.log('开始测试静默请求') |
|||
const result = await axiosQuiet.get('/dict/batch', { |
|||
keys: 'source,SourceChannel' |
|||
}) |
|||
this.quietResult = result |
|||
console.log('静默请求结果:', result) |
|||
|
|||
uni.showToast({ |
|||
title: '静默请求测试完成', |
|||
icon: 'success' |
|||
}) |
|||
} catch (error) { |
|||
console.error('静默请求失败:', error) |
|||
this.quietResult = { error: error.message || error.msg || '请求失败' } |
|||
|
|||
uni.showToast({ |
|||
title: '静默请求测试失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
// 清除所有缓存 |
|||
clearAllCache() { |
|||
dictUtil.clearCache() |
|||
this.singleResult = null |
|||
this.batchResult = null |
|||
this.cacheResult = null |
|||
this.quietResult = null |
|||
|
|||
uni.showToast({ |
|||
title: '缓存已清除', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.container { |
|||
padding: 20rpx; |
|||
background: #f5f5f5; |
|||
min-height: 100vh; |
|||
} |
|||
|
|||
.header { |
|||
background: #fff; |
|||
padding: 30rpx; |
|||
border-radius: 12rpx; |
|||
margin-bottom: 20rpx; |
|||
text-align: center; |
|||
|
|||
.title { |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
} |
|||
|
|||
.test-section { |
|||
background: #fff; |
|||
border-radius: 12rpx; |
|||
padding: 30rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.test-btn { |
|||
background: #29d3b4; |
|||
color: #fff; |
|||
border: none; |
|||
border-radius: 8rpx; |
|||
padding: 16rpx 32rpx; |
|||
font-size: 26rpx; |
|||
margin-bottom: 20rpx; |
|||
|
|||
&.clear { |
|||
background: #ff6b6b; |
|||
} |
|||
|
|||
&:active { |
|||
opacity: 0.8; |
|||
} |
|||
} |
|||
|
|||
.result { |
|||
background: #f8f9fa; |
|||
border-radius: 8rpx; |
|||
padding: 20rpx; |
|||
border-left: 4rpx solid #29d3b4; |
|||
|
|||
.result-title { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
display: block; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.result-content { |
|||
font-size: 22rpx; |
|||
color: #333; |
|||
word-break: break-all; |
|||
white-space: pre-wrap; |
|||
font-family: monospace; |
|||
line-height: 1.5; |
|||
} |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue