Browse Source

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

master
于宏哲PHP 9 months ago
parent
commit
150004c557
  1. 2
      .gitignore
  2. 6673
      admin/yarn.lock
  3. 119
      docker-compose.yml
  4. 1
      niucloud/.gitignore
  5. 16
      niucloud/app/api/controller/apiController/CommunicationRecords.php
  6. 26
      niucloud/app/api/controller/apiController/Course.php
  7. 38
      niucloud/app/api/controller/apiController/OrderTable.php
  8. 18
      niucloud/app/api/controller/apiController/Personnel.php
  9. 6
      niucloud/app/api/controller/pay/Pay.php
  10. 8
      niucloud/app/api/route/route.php
  11. 3
      niucloud/app/command/TestCommand.php
  12. 2
      niucloud/app/dict/schedule/schedule.php
  13. 3
      niucloud/app/job/transfer/schedule/PerformanceCalculation.php
  14. 3
      niucloud/app/listener/personnel/Student.php
  15. 8
      niucloud/app/model/student_courses/StudentCourses.php
  16. 9
      niucloud/app/service/admin/pay/PayService.php
  17. 19
      niucloud/app/service/api/apiService/CommunicationRecordsService.php
  18. 135
      niucloud/app/service/api/apiService/CourseService.php
  19. 247
      niucloud/app/service/api/apiService/PersonnelService.php
  20. 268
      start.sh
  21. 15
      uniapp/api/apiRoute.js
  22. 91
      uniapp/common/util.js
  23. 2
      uniapp/components/AQ/AQTabber.vue
  24. 70
      uniapp/components/custom-modal/custom-modal.vue
  25. 234
      uniapp/components/custom-modal/example.vue
  26. 10
      uniapp/pages.json
  27. 147
      uniapp/pages/coach/course/info_list.vue
  28. 301
      uniapp/pages/market/clue/clue_info.vue
  29. 164
      uniapp/pages/market/clue/clue_table.vue
  30. 279
      uniapp/pages/market/clue/order_list.vue
  31. 13
      uniapp/pages/student/login/login.vue

2
.gitignore

@ -9,3 +9,5 @@
/niucloud/vendor
/CLAUDE.md
.claude
node_modules
/docker

6673
admin/yarn.lock

File diff suppressed because it is too large

119
docker-compose.yml

@ -0,0 +1,119 @@
version: '3.8'
services:
# PHP 服务
php:
image: php:8.2-fpm
container_name: niucloud_php
volumes:
- ./niucloud:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf
working_dir: /var/www/html
depends_on:
- mysql
- redis
environment:
- PHP_IDE_CONFIG=serverName=niucloud
command: >
bash -c "
apt-get update &&
apt-get install -y libzip-dev zip unzip git libpng-dev libjpeg-dev libfreetype6-dev &&
docker-php-ext-configure gd --with-freetype --with-jpeg &&
docker-php-ext-install pdo pdo_mysql mysqli zip gd &&
pecl install redis &&
docker-php-ext-enable redis &&
php-fpm
"
networks:
- niucloud_network
# Nginx 服务
nginx:
image: nginx:alpine
container_name: niucloud_nginx
ports:
- "20080:80" # 原本是 80 映射到 20080
- "20081:8080" # 原本是 8080 映射到 20081
volumes:
- ./niucloud:/var/www/html
- ./admin/dist:/var/www/admin
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/conf.d:/etc/nginx/conf.d
- ./docker/logs/nginx:/var/log/nginx
depends_on:
- php
- node
networks:
- niucloud_network
# MySQL 数据库
mysql:
image: mysql:8.0
container_name: niucloud_mysql
ports:
- "23306:3306" # 原本是 3306 映射到 23306
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: niucloud
MYSQL_USER: niucloud
MYSQL_PASSWORD: niucloud123
volumes:
- ./docker/data/mysql:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./docker/logs/mysql:/var/log/mysql
command: --default-authentication-plugin=mysql_native_password
networks:
- niucloud_network
# Redis 缓存
redis:
image: redis:alpine
container_name: niucloud_redis
ports:
- "26379:6379" # 原本是 6379 映射到 26379
volumes:
- ./docker/data/redis:/data
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- niucloud_network
# Node.js 服务 (用于构建前端)
node:
image: node:18-alpine
container_name: niucloud_node
working_dir: /app
volumes:
- ./admin:/app
- ./docker/data/node_modules:/app/node_modules
ports:
- "23000:3000" # 原本是 3000 映射到 23000
command: >
sh -c "
npm config set registry https://registry.npmmirror.com &&
npm install &&
npm run dev
"
networks:
- niucloud_network
# Composer 服务 (用于 PHP 依赖管理)
composer:
image: composer:latest
container_name: niucloud_composer
volumes:
- ./niucloud:/app
working_dir: /app
command: install --ignore-platform-reqs
networks:
- niucloud_network
networks:
niucloud_network:
driver: bridge
volumes:
mysql_data:
redis_data:
node_modules:

1
niucloud/.gitignore

@ -5,3 +5,4 @@ install.lock
/vendor/*
.env
runtime/*
composer.lock

16
niucloud/app/api/controller/apiController/CommunicationRecords.php

@ -46,4 +46,20 @@ class CommunicationRecords extends BaseApiService
}
return success('操作成功');
}
public function edit(Request $request){
$date = date('Y-m-d H:i:s');
$where = [
['id', '=', $request->param('id', '')],
];
$data = [
'remarks' => $request->param('remarks', ''),//备注
'updated_at' => $date
];
$res = (new CommunicationRecordsService())->edit($where,$data);
if(!$res['code']){
return fail('操作失败');
}
return success('操作成功');
}
}

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

@ -124,4 +124,30 @@ class Course extends BaseApiService
return (new CourseService())->schedule_del($data);
}
/**
* 更新学员课程人员配置
* @param Request $request
* @return \think\Response
*/
public function updateStudentCoursePersonnel(Request $request)
{
try {
$params = $request->all();
// 验证必要参数
if (empty($params['student_course_id'])) {
return fail('学员课程ID不能为空');
}
$res = (new CourseService())->updateStudentCoursePersonnel($params);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data'], '更新成功');
} catch (\Exception $e) {
return fail('更新学员课程人员配置失败:' . $e->getMessage());
}
}
}

38
niucloud/app/api/controller/apiController/OrderTable.php

@ -72,21 +72,42 @@ class OrderTable extends BaseApiService
//订单-创建
public function add(Request $request)
{
// 获取当前登录的员工ID
$staff_id = $request->memberId() ?? 0;
$params = $request->params([
["payment_type", ""], // 付款类型必填验证
["course_id", ""], // 课程ID必填验证
["class_id", ""], // 班级ID必填验证
["staff_id", ""], // 员工ID必填验证
["staff_id", ""], // 员工ID(可选)
["resource_id", ""], // 客户资源表ID必填验证
["order_type", ""], // 客户资源表ID必填验证
]);
foreach($params as $k=>$v){
if(empty($v)){
return fail('缺少参数');
// 验证必要参数
if(empty($params['payment_type']) || empty($params['course_id']) ||
empty($params['class_id']) || empty($params['resource_id'])) {
return fail('缺少必要参数');
}
// 如果前端没提供员工ID,使用当前登录的员工ID
if(empty($params['staff_id'])) {
if(empty($staff_id)) {
return fail('无法获取员工信息');
}
$params['staff_id'] = $staff_id;
}
// 获取班级信息以查询campus_id
$class = \app\model\class_grade\ClassGrade::where('id', $params['class_id'])->find();
if (!$class) {
return fail('班级不存在');
}
$class = $class->toArray();
$campus_id = $class['campus_id'] ?? 0;
if(empty($campus_id)) {
return fail('班级没有关联校区');
}
$course = \app\model\course\Course::where('id', $params['course_id'])->find();
if (!$course) {
@ -95,7 +116,6 @@ class OrderTable extends BaseApiService
$course = $course->toArray();
$order_amount = $course['price'];//课程的价格
$data = [
'payment_type' => $params['payment_type'],//付款类型: cash-现金支付, scan_code-扫码支付, subscription-订阅支付
'order_amount' => $order_amount,//订单金额
@ -103,12 +123,10 @@ class OrderTable extends BaseApiService
'class_id' => $params['class_id'],//班级ID
'staff_id' => $params['staff_id'],//员工表ID
'resource_id' => $params['resource_id'],//客户资源表id
'campus_id' => $campus_id,//校区ID
'order_type' => $params['order_type'],
];
$res = (new OrderTableService())->addData($data);
if (!$res['code']) {

18
niucloud/app/api/controller/apiController/Personnel.php

@ -262,4 +262,22 @@ class Personnel extends BaseApiService
}
}
/**
* 获取教练数据列表
* @param Request $request
* @return \think\Response
*/
public function getCoachList(Request $request)
{
try {
$res = (new PersonnelService())->getCoachList();
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取教练列表失败:' . $e->getMessage());
}
}
}

6
niucloud/app/api/controller/pay/Pay.php

@ -101,11 +101,9 @@ class Pay extends BaseApiController
*/
public function getQrcode(){
$data = $this->request->params([
['trade_type', ''],
['trade_id', ''],
['type', ''],
['order_id', ''],
]);
return success('SUCCESS',(new PayService())->getQrcode($data['type'], $data['trade_type'], $data['trade_id']));
return success('SUCCESS',(new \app\service\admin\pay\PayService())->order_pay($data));
}
public function qrcodeNotify(int $order_id)

8
niucloud/app/api/route/route.php

@ -226,6 +226,8 @@ Route::group(function () {
//员工端-获取全部人员列表
Route::get('personnel/getPersonnelAll', 'apiController.Personnel/getPersonnelAll');
//员工端-获取教练数据列表
Route::get('personnel/getCoachList', 'apiController.Personnel/getCoachList');
//员工端-添加新员工信息
Route::post('personnel/add', 'apiController.Personnel/add');
@ -255,6 +257,7 @@ Route::group(function () {
//沟通记录-添加
Route::post('communicationRecords/add', 'apiController.CommunicationRecords/add');
Route::post('communicationRecords/edit', 'apiController.CommunicationRecords/edit');
//校区-获取员工下的全部校区
Route::get('campus/getPersonnelCampus', 'apiController.Campus/getPersonnelCampus');
@ -286,6 +289,9 @@ Route::group(function () {
//员工端-订单管理-创建
Route::post('orderTable/add', 'apiController.OrderTable/add');
//员工端-更新学员课程人员配置
Route::post('updateStudentCoursePersonnel', 'apiController.Course/updateStudentCoursePersonnel');
@ -371,6 +377,8 @@ Route::group(function () {
Route::get('personnel/serviceLogDetail', 'apiController.Personnel/serviceLogDetail');
Route::post('personnel/updateServiceRemark', 'apiController.Personnel/updateServiceRemark');
Route::get('getQrcode', 'pay.Pay/getQrcode');
})->middleware(ApiChannel::class)
->middleware(ApiPersonnelCheckToken::class, true)

3
niucloud/app/command/TestCommand.php

@ -4,6 +4,7 @@ declare (strict_types = 1);
namespace app\command;
use app\job\transfer\schedule\CourseScheduleJob;
use app\job\transfer\schedule\PerformanceCalculation;
use app\job\transfer\schedule\ResourceAutoAllocation;
use think\console\Command;
use think\console\Input;
@ -23,7 +24,7 @@ class TestCommand extends Command
protected function execute(Input $input, Output $output)
{
// 指令输出
$obj = new CourseScheduleJob();
$obj = new ResourceAutoAllocation();
$obj->doJob();
$output->writeln('testcommand');
}

2
niucloud/app/dict/schedule/schedule.php

@ -11,7 +11,7 @@ return [
'hour' => 0,
'min' => 5
],
'class' => 'app\job\transfer\schedule\CheckFinish',
'class' => 'app\job\transfer\schedule\ResourceAutoAllocation',
'function' => ''
],
[

3
niucloud/app/job/transfer/schedule/PerformanceCalculation.php

@ -235,8 +235,7 @@ class PerformanceCalculation extends BaseJob
$orders = OrderTable::with(['course', 'personnel'])
->where('order_status', 'completed') // 假设只计算已完成的订单
->where(function ($query) {
$query->where('performance_calculated', 0) // 未计算过绩效的订单
->whereOr('accounting_time', null); // 或者核算时间为空的订单
$query->whereNull('accounting_time'); // 或者核算时间为空的订单
})
->select()
->toArray();

3
niucloud/app/listener/personnel/Student.php

@ -91,7 +91,8 @@ class Student
'gift_hours' => $course_info['gift_session_count'],
'start_date' => date("Y-m-d"),
'end_date' => date("Y-m-d", strtotime("+30 days")),
'single_session_count' => $course_info['single_session_count']
'single_session_count' => $course_info['single_session_count'],
'resource_id'=> $order_info['resource_id']
]);

8
niucloud/app/model/student_courses/StudentCourses.php

@ -71,16 +71,12 @@ class StudentCourses extends BaseModel
}
public function student(){
return $this->hasOne(Student::class, 'id', 'student_id')->joinType('left')->withField('name,id')->bind(['student_id_name'=>'name']);
return $this->hasOne(Student::class, 'id', 'student_id');
}
public function course(){
return $this->hasOne(Course::class, 'id', 'course_id')->joinType('left')->withField('course_name,id')->bind(['course_id_name'=>'course_name']);
return $this->hasOne(Course::class, 'id', 'course_id');
}
}

9
niucloud/app/service/admin/pay/PayService.php

@ -225,7 +225,8 @@ class PayService extends BaseAdminService
return $pay_type_list;
}
public function order_pay($data){
public function order_pay($data)
{
$out_trade_no = 'sm' . date("YmdHis") . time();
$order = new OrderTable();
$order_info = $order->where(['id' => $data['order_id']])->find();
@ -267,13 +268,13 @@ class PayService extends BaseAdminService
'channel' => '微信扫码支付'
]);
return ['qrcode_url' => getCurrentDomain().$path,'out_trade_no'=>$out_trade_no];
return ['qrcode_url' => getCurrentDomain().$path,'out_trade_no'=>$out_trade_no,'code_url'=> $url['code_url']];
}
public function check_payment_status($data){
public function check_payment_status($data)
{
$order = new OrderTable();
$order_status = $order->where(['payment_id' => $data['out_trade_no']])->value("order_status");
return ['order_status' => $order_status];
}
}

19
niucloud/app/service/api/apiService/CommunicationRecordsService.php

@ -86,4 +86,23 @@ class CommunicationRecordsService extends BaseApiService
];
}
public function edit(array $where, array $data)
{
$model = $this->model;
$edit = $model->where($where)->update($data);
if ($edit) {
$res = [
'code' => 1,
'msg' => '操作成功',
'data' => []
];
} else {
$res = [
'code' => 0,
'msg' => '操作失败',
'data' => []
];
}
return $res;
}
}

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

@ -200,17 +200,66 @@ class CourseService extends BaseApiService
$PersonCourseSchedule = new PersonCourseSchedule();
$student_arr = $PersonCourseSchedule->where('schedule_id',$id)->column('student_id');
$query = $StudentCourses->where('course_id', $id);
// 获取当前时间
$now = date('Y-m-d H:i:s');
// 查询end_date大于当前时间的课程记录
$query = $StudentCourses->where('course_id', $id)
->where('end_date', '>', $now);
if (!empty($student_arr)) {
$query->whereNotIn('student_id', $student_arr);
}
$res = $query->field('student_id as value')->select()->toArray();
$Student = new Student();
foreach ($res as $k => &$v) {
$StudentName = $Student->where('id',$v['value'])->value('name');
$v['text'] = $StudentName;
$studentCourses = $query->field('student_id as value, resource_id')->select()->toArray();
// 如果没有符合条件的记录,直接返回空数组
if (empty($studentCourses)) {
return [];
}
return $res;
// 收集所有的resource_id
$resourceIds = array_column($studentCourses, 'resource_id');
$resourceIds = array_filter($resourceIds); // 过滤掉空值
if (empty($resourceIds)) {
return [];
}
// 查询客户资源表获取详细信息
$resources = Db::name('customer_resources')
->whereIn('id', $resourceIds)
->field('id, name, source_channel, source, age')
->select()
->toArray();
// 将资源信息与学生ID关联
$result = [];
foreach ($resources as $resource) {
// 查找对应的学生ID
foreach ($studentCourses as $course) {
if ($course['resource_id'] == $resource['id']) {
// 源和来源渠道转换为字典值
$source = get_dict_value("source", $resource['source']);
$sourceChannel = get_dict_value("source_channel", $resource['source_channel']);
$result[] = [
'value' => $course['value'], // 学生ID
'resource_id' => $resource['id'],
'text' => $resource['name'], // 学生姓名
'name' => $resource['name'],
'age' => $resource['age'],
'source' => $source,
'source_channel' => $sourceChannel,
'source_raw' => $resource['source'],
'source_channel_raw' => $resource['source_channel']
];
break;
}
}
}
return $result;
}
public function addStudent($data)
@ -377,5 +426,77 @@ class CourseService extends BaseApiService
}
}
/**
* 更新学员课程人员配置
* @param array $data
* @return array
*/
public function updateStudentCoursePersonnel(array $data)
{
$res = [
'code' => 0,
'msg' => '更新失败',
'data' => []
];
try {
// 开启事务
Db::startTrans();
$studentCourseId = $data['student_course_id'];
$mainCoachId = $data['main_coach_id'] ?? null;
$educationId = $data['education_id'] ?? null;
$assistantIds = $data['assistant_ids'] ?? '';
// 更新学员课程表
$updateData = [
'updated_at' => date('Y-m-d H:i:s')
];
if ($mainCoachId !== null) {
$updateData['main_coach_id'] = $mainCoachId;
}
if ($educationId !== null) {
$updateData['education_id'] = $educationId;
}
if ($assistantIds !== '') {
$updateData['assistant_ids'] = $assistantIds;
}
// 更新学员课程表
$updateResult = Db::name('student_courses')
->where('id', $studentCourseId)
->update($updateData);
if (!$updateResult) {
Db::rollback();
$res['msg'] = '更新学员课程配置失败';
return $res;
}
// 提交事务
Db::commit();
$res = [
'code' => 1,
'msg' => '更新成功',
'data' => [
'student_course_id' => $studentCourseId,
'main_coach_id' => $mainCoachId,
'education_id' => $educationId,
'assistant_ids' => $assistantIds
]
];
} catch (\Exception $e) {
Db::rollback();
$res['msg'] = '更新学员课程人员配置异常:' . $e->getMessage();
}
return $res;
}
}

247
niucloud/app/service/api/apiService/PersonnelService.php

@ -189,52 +189,51 @@ class PersonnelService extends BaseApiService
$model = $this->model;
//存在员工id的时候
if ((!empty($where['personnel_id']) || isset($where['personnel_id'])) && $where['personnel_id'] !== '') {
//查询这个员工的校区id
$campus_id = CampusPersonRole::where('person_id', $where['personnel_id'])
->distinct(true)
->column('campus_id');
if ($campus_id[0]) {
$person_id_arr = CampusPersonRole::whereIn('campus_id', $campus_id)
->where(['dept_id' => 3])
->distinct(true)
->column('person_id');
if ($person_id_arr) {
//根据校区id获取校区下的全部员工
$model = $model->whereIn('id', $person_id_arr);
}
}else{
echo 123;die;
$person_id_arr = CampusPersonRole::whereIn('campus_id', $campus_id)
->where(['dept_id' => 3])
->distinct(true)
->column('person_id');
if ($person_id_arr) {
//根据校区id获取校区下的全部员工
$model = $model->whereIn('id', $person_id_arr);
}
}
}
if (empty($where['account_type'])) {
$model = $model->where('account_type', $where['account_type']);
}
if (!empty($where['campus'])) {
// $model = $model->where('campus', $where['campus']);
$person_id_arr = CampusPersonRole::whereIn('campus_id', $where['campus'])
->distinct(true)
->where(['dept_id' => 3])
->column('person_id');
if ($person_id_arr) {
//根据校区id获取校区下的全部员工
$model = $model->whereIn('id', $person_id_arr);
}
}
// if ((!empty($where['personnel_id']) || isset($where['personnel_id'])) && $where['personnel_id'] !== '') {
// //查询这个员工的校区id
// $campus_id = CampusPersonRole::where('person_id', $where['personnel_id'])
// ->distinct(true)
// ->column('campus_id');
// if ($campus_id[0]) {
//
// $person_id_arr = CampusPersonRole::whereIn('campus_id', $campus_id)
// ->where(['dept_id' => 3])
// ->distinct(true)
// ->column('person_id');
// if ($person_id_arr) {
// //根据校区id获取校区下的全部员工
// $model = $model->whereIn('id', $person_id_arr);
// }
// }else{
// $person_id_arr = CampusPersonRole::whereIn('campus_id', $campus_id)
// ->where(['dept_id' => 3])
// ->distinct(true)
// ->column('person_id');
// if ($person_id_arr) {
// //根据校区id获取校区下的全部员工
// $model = $model->whereIn('id', $person_id_arr);
// }
// }
// }
//
// if (empty($where['account_type'])) {
// $model = $model->where('account_type', $where['account_type']);
// }
//
// if (!empty($where['campus'])) {
//// $model = $model->where('campus', $where['campus']);
//
// $person_id_arr = CampusPersonRole::whereIn('campus_id', $where['campus'])
// ->distinct(true)
// ->where(['dept_id' => 3])
// ->column('person_id');
//
// if ($person_id_arr) {
// //根据校区id获取校区下的全部员工
// $model = $model->whereIn('id', $person_id_arr);
// }
//
// }
$res = $model->field($field)
@ -573,4 +572,160 @@ class PersonnelService extends BaseApiService
return $prefix . $date . str_pad($sequence, 4, '0', STR_PAD_LEFT);
}
/**
* 获取教练数据列表
* @return array
*/
public function getCoachList()
{
$res = [
'code' => 0,
'msg' => '获取教练列表失败',
'data' => []
];
try {
// 查询dept_id=2的所有人员关系
$campusPersonRoles = CampusPersonRole::where('dept_id', 2)
->field('person_id')
->select()
->toArray();
if (empty($campusPersonRoles)) {
$res = [
'code' => 1,
'msg' => '暂无教练数据',
'data' => [
'coach_list' => [],
'education_list' => [],
'assistant_list' => []
]
];
return $res;
}
// 提取所有person_id
$personIds = array_unique(array_column($campusPersonRoles, 'person_id'));
// 根据person_id查询Personnel表获取姓名、头像、手机号
$personnelList = $this->model
->whereIn('id', $personIds)
->field('id as person_id, name, head_img, phone')
->select()
->toArray();
if (empty($personnelList)) {
$res = [
'code' => 1,
'msg' => '暂无教练数据',
'data' => [
'coach_list' => [],
'education_list' => [],
'assistant_list' => []
]
];
return $res;
}
// 查询每个人员的具体角色信息
$coachList = [];
$educationList = [];
$assistantList = [];
foreach ($personnelList as $personnel) {
// 查询该人员的角色信息
$roles = CampusPersonRole::where('person_id', $personnel['person_id'])
->where('dept_id', 2)
->with(['sysRole' => function ($query) {
$query->field('role_id, role_name, role_key');
}])
->select()
->toArray();
$roleNames = [];
$roleKeys = [];
foreach ($roles as $role) {
if (!empty($role['sys_role'])) {
$roleNames[] = $role['sys_role']['role_name'];
$roleKeys[] = $role['sys_role']['role_key'];
}
}
$roleNameStr = implode(',', $roleNames);
$roleKeyStr = implode(',', $roleKeys);
// 根据角色名称分类
$personnelData = [
'id' => $personnel['person_id'],
'person_id' => $personnel['person_id'], // 确保person_id字段存在
'name' => $personnel['name'],
'head_img' => $personnel['head_img'],
'phone' => $personnel['phone'],
'role_names' => $roleNameStr,
'role_keys' => $roleKeyStr
];
// 根据角色进行分类 - 使用更灵活的匹配规则
$isCoach = false;
$isEducation = false;
$isAssistant = false;
// 检查是否为教练类角色
if (stripos($roleNameStr, '教练') !== false ||
stripos($roleNameStr, '教师') !== false ||
stripos($roleNameStr, '主教') !== false ||
stripos($roleKeyStr, 'coach') !== false ||
stripos($roleKeyStr, 'teacher') !== false) {
$isCoach = true;
}
// 检查是否为教务类角色
if (stripos($roleNameStr, '教务') !== false ||
stripos($roleKeyStr, 'education') !== false ||
stripos($roleKeyStr, 'academic') !== false) {
$isEducation = true;
}
// 检查是否为助教类角色
if (stripos($roleNameStr, '助教') !== false ||
stripos($roleNameStr, '助理') !== false ||
stripos($roleKeyStr, 'assistant') !== false) {
$isAssistant = true;
}
// 如果没有明确的角色分类,则作为通用人员添加到所有列表
if (!$isCoach && !$isEducation && !$isAssistant) {
$isCoach = $isEducation = $isAssistant = true;
}
// 根据分类结果添加到对应列表
if ($isCoach) {
$coachList[] = $personnelData;
}
if ($isEducation) {
$educationList[] = $personnelData;
}
if ($isAssistant) {
$assistantList[] = $personnelData;
}
}
$res = [
'code' => 1,
'msg' => '获取成功',
'data' => [
'coach_list' => $coachList,
'education_list' => $educationList,
'assistant_list' => $assistantList,
'all_personnel' => $personnelList // 返回所有人员数据,前端可以根据需要自行分类
]
];
} catch (\Exception $e) {
$res['msg'] = '获取教练列表异常:' . $e->getMessage();
}
return $res;
}
}

268
start.sh

@ -0,0 +1,268 @@
#!/bin/bash
# 颜色输出函数
print_message() {
echo -e "\033[1;32m[INFO] $1\033[0m"
}
print_warning() {
echo -e "\033[1;33m[WARNING] $1\033[0m"
}
print_error() {
echo -e "\033[1;31m[ERROR] $1\033[0m"
}
# 设置项目名称
PROJECT_NAME="MyApp"
# 检查Docker和Docker Compose是否安装
check_docker() {
if ! command -v docker &> /dev/null; then
print_error "Docker未安装,请先安装Docker"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
print_error "Docker Compose未安装,请先安装Docker Compose"
exit 1
fi
}
# 创建必要的目录结构
create_directories() {
print_message "创建必要的目录结构..."
mkdir -p docker/{logs/{nginx,mysql},data/{mysql,redis,node_modules},nginx/conf.d,php,mysql,redis}
}
# 检查并生成配置文件
check_config_files() {
print_message "检查必要的配置文件..."
# Nginx 主配置
if [ ! -f "docker/nginx/nginx.conf" ]; then
print_warning "Nginx主配置文件不存在,创建默认配置..."
cat > docker/nginx/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
client_max_body_size 20M;
include /etc/nginx/conf.d/*.conf;
}
EOF
fi
# Nginx 站点配置
if [ ! -f "docker/nginx/conf.d/default.conf" ]; then
print_warning "Nginx站点配置文件不存在,创建默认配置..."
cat > docker/nginx/conf.d/default.conf << 'EOF'
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
server {
listen 8080;
server_name localhost;
root /var/www/admin;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
EOF
fi
# PHP 配置
if [ ! -f "docker/php/php.ini" ]; then
print_warning "PHP配置文件不存在,创建默认配置..."
cat > docker/php/php.ini << 'EOF'
[PHP]
memory_limit = 256M
upload_max_filesize = 20M
post_max_size = 20M
max_execution_time = 300
date.timezone = Asia/Shanghai
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
EOF
fi
# PHP-FPM 配置
if [ ! -f "docker/php/www.conf" ]; then
print_warning "PHP-FPM配置文件不存在,创建默认配置..."
cat > docker/php/www.conf << 'EOF'
[www]
user = www-data
group = www-data
listen = 9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOF
fi
# MySQL 配置
if [ ! -f "docker/mysql/my.cnf" ]; then
print_warning "MySQL配置文件不存在,创建默认配置..."
cat > docker/mysql/my.cnf << 'EOF'
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-authentication-plugin = mysql_native_password
max_allowed_packet = 64M
sql_mode = ''
[client]
default-character-set = utf8mb4
EOF
fi
# Redis 配置
if [ ! -f "docker/redis/redis.conf" ]; then
print_warning "Redis配置文件不存在,创建默认配置..."
cat > docker/redis/redis.conf << 'EOF'
requirepass niucloud123456
appendonly yes
EOF
fi
}
# 启动服务
start_services() {
print_message "启动 ${PROJECT_NAME} 服务..."
docker-compose up -d
}
# 停止服务
stop_services() {
print_message "停止 ${PROJECT_NAME} 服务..."
docker-compose down
}
# 重启服务
restart_services() {
print_message "重启 ${PROJECT_NAME} 服务..."
docker-compose restart
}
# 查看状态
check_status() {
print_message "${PROJECT_NAME} 服务状态:"
docker-compose ps
}
# 查看日志
view_logs() {
service=$1
if [ -z "$service" ]; then
print_message "查看所有服务日志..."
docker-compose logs
else
print_message "查看 $service 服务日志..."
docker-compose logs $service
fi
}
# 初始化项目
init_project() {
print_message "初始化 ${PROJECT_NAME} 项目..."
# 检查是否存在 docker-compose.yml
if [ ! -f "docker-compose.yml" ]; then
print_error "缺少 docker-compose.yml 文件,请先创建!"
exit 1
fi
create_directories
check_config_files
print_message "拉取Docker镜像..."
docker-compose pull
print_message "项目初始化完成,现在可以启动服务了"
}
# 显示帮助信息
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " start 启动所有服务"
echo " stop 停止所有服务"
echo " restart 重启所有服务"
echo " status 查看服务状态"
echo " logs 查看所有服务日志"
echo " logs <服务名> 查看指定服务日志"
echo " init 初始化项目"
echo " help 显示此帮助信息"
}
# 主程序入口
main() {
check_docker
case "$1" in
start)
start_services
;;
stop)
stop_services
;;
restart)
restart_services
;;
status)
check_status
;;
logs)
view_logs "$2"
;;
init)
init_project
;;
help|*)
show_help
;;
esac
}
main "$@"

15
uniapp/api/apiRoute.js

@ -33,6 +33,10 @@ export default {
async common_getPersonnelAll(data = {}) {
return await http.get('/personnel/getPersonnelAll', data);
},
//公共端-获取教练数据列表
async common_getCoachList(data = {}) {
return await http.get('/personnel/getCoachList', data);
},
//公共端-获取全部课程列表
async common_getCourseAll(data = {}) {
return await http.get('/common/getCourseAll', data);
@ -206,6 +210,10 @@ export default {
async xs_communicationRecordsAdd(data = {}) {
return await http.post('/communicationRecords/add', data);
},
//销售端-沟通记录-编辑
async xs_communicationRecordsEdit(data = {}) {
return await http.post('/communicationRecords/edit', data);
},
//销售端-获取好友关系绑定详情
async xs_chatGetChatFriendsInfo(data = {}) {
return await http.get('/chat/getChatFriendsInfo', data);
@ -387,7 +395,10 @@ export default {
//更新学生课程人员配置
async updateStudentCoursePersonnel(data = {}) {
return await http.post('/updateStudentCoursePersonnel', data);
}
},
// 获取订单支付二维码
async getOrderPayQrcode(data = {}) {
return await http.get('/getQrcode', data);
},
}

91
uniapp/common/util.js

@ -226,63 +226,102 @@ function loginOut() {
})
}
// 全局内存字典缓存
// 使用模块级的变量保存缓存,在整个应用生命周期内都有效
const dictMemoryCache = {};
/**
* 获取字典值带1小时缓存
* 获取字典值内存和存储双重缓存
* @param {String} dictKey 字典key
* @returns {Promise<Array|Object>}
*/
async function getDict(dictKey) {
console.log(`util.getDict - 开始获取字典: ${dictKey}`);
console.log(`getDict - 开始获取字典: ${dictKey}`);
const cacheKey = `dict_${dictKey}`;
const cache = uni.getStorageSync(cacheKey);
// 先从内存缓存中获取
const now = Date.now();
if (dictMemoryCache[dictKey] && dictMemoryCache[dictKey].expire > now) {
console.log(`getDict - 使用内存缓存: ${dictKey}`);
return dictMemoryCache[dictKey].data;
}
// 检查缓存是否有效
if (cache && cache.data && cache.expire > now) {
console.log(`util.getDict - 使用缓存数据: ${dictKey}, 数据条数: ${cache.data.length}`);
return cache.data;
// 内存缓存不存在或已过期,尝试从存储中获取
const cacheKey = `dict_${dictKey}`;
try {
const storageCache = uni.getStorageSync(cacheKey);
if (storageCache && storageCache.data && storageCache.expire > now) {
console.log(`getDict - 使用存储缓存: ${dictKey}`);
// 更新内存缓存
dictMemoryCache[dictKey] = storageCache;
return storageCache.data;
}
} catch (e) {
console.error(`getDict - 读取存储缓存异常: ${dictKey}`, e);
}
console.log(`util.getDict - 缓存无效,请求接口: ${dictKey}`);
console.log(`getDict - 缓存无效,请求接口: ${dictKey}`);
// 缓存无效,请求接口
// 缓存不存在或已过期,从服务器获取
try {
console.log(`util.getDict - 调用API: common_Dictionary, key=${dictKey}`);
// 添加超时处理
// 添加超时处理和重试机制
let retryCount = 0;
const maxRetries = 2;
const fetchWithRetry = async () => {
try {
// 超时控制
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`字典加载超时: ${dictKey}`)), 3000);
setTimeout(() => reject(new Error(`字典请求超时: ${dictKey}`)), 3000);
});
// 实际API请求
const apiPromise = marketApi.common_Dictionary({ key: dictKey });
// 使用Promise.race来实现超时控制
const res = await Promise.race([apiPromise, timeoutPromise]);
return await Promise.race([apiPromise, timeoutPromise]);
} catch (error) {
if (retryCount < maxRetries) {
retryCount++;
console.log(`getDict - 重试第${retryCount}次: ${dictKey}`);
return await fetchWithRetry();
}
throw error;
}
};
console.log(`util.getDict - API响应:`, res);
const res = await fetchWithRetry();
if (res && res.code === 1) {
// 处理接口返回的数据,确保返回格式一致
// 接口返回的是 [{name: "抖音", value: "1", sort: 0, memo: ""}, ...]
// 处理接口返回的数据
const formattedData = Array.isArray(res.data) ? res.data : [];
console.log(`util.getDict - 保存缓存: ${dictKey}, 数据条数: ${formattedData.length}`);
uni.setStorageSync(cacheKey, {
if (formattedData.length > 0) {
// 设置缓存过期时间,默认1小时
const cacheData = {
data: formattedData,
expire: now + 3600 * 1000
});
console.log(`util.getDict - 字典获取成功: ${dictKey}`);
};
// 同时更新内存缓存和存储缓存
dictMemoryCache[dictKey] = cacheData;
try {
uni.setStorageSync(cacheKey, cacheData);
} catch (e) {
console.error(`getDict - 存储缓存异常: ${dictKey}`, e);
}
console.log(`getDict - 字典获取成功: ${dictKey}, 条数: ${formattedData.length}`);
} else {
console.warn(`getDict - 字典数据为空: ${dictKey}`);
}
return formattedData;
} else {
console.error(`util.getDict - API请求失败: ${dictKey}`, res);
// 返回空数组而不是抛出异常,避免阻塞流程
console.error(`getDict - API请求失败: ${dictKey}`, res);
return [];
}
} catch (error) {
console.error(`util.getDict - 异常: ${dictKey}`, error);
// 返回空数组,避免阻塞流程
console.error(`getDict - 异常: ${dictKey}`, error);
return [];
}
}

2
uniapp/components/AQ/AQTabber.vue

@ -142,7 +142,7 @@
},
{
text: "数据",
urlPath: '/pages/market/data/statistics', //
urlPath: '/pages/market/clue/clue_table', //
iconPath: util.img('/uniapp_src/static/images/tabbar/timetable.png'),
selectedIconPath: util.img('/uniapp_src/static/images/tabbar/timetables.png'),
},

70
uniapp/components/custom-modal/custom-modal.vue

@ -0,0 +1,70 @@
<template>
<fui-modal :buttons="[]" :width="width" :show="show" :maskClose="maskClose">
<!-- 默认内容插槽 -->
<slot>
<!-- 默认内容 -->
</slot>
<!-- 按钮插槽 -->
<view v-if="$slots.buttons" class="button-section">
<slot name="buttons"></slot>
</view>
<!-- 关闭按钮 -->
<view v-if="showClose" class="fui-icon__close" @tap="handleClose">
<fui-icon name="close" color="#B2B2B2" :size="48"></fui-icon>
</view>
</fui-modal>
</template>
<script>
export default {
name: 'CustomModal',
props: {
//
show: {
type: Boolean,
default: false
},
//
width: {
type: [String, Number],
default: 600
},
//
showClose: {
type: Boolean,
default: true
},
//
maskClose: {
type: Boolean,
default: true
}
},
methods: {
//
handleClose() {
this.$emit('cancel', 'close')
}
}
}
</script>
<style lang="less" scoped>
.button-section {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 20rpx;
margin-top: 40rpx;
}
.fui-icon__close {
position: absolute;
right: 24rpx;
top: 20rpx;
z-index: 1000;
}
</style>

234
uniapp/components/custom-modal/example.vue

@ -0,0 +1,234 @@
<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>

10
uniapp/pages.json

@ -177,7 +177,7 @@
{
"path": "pages/common/im_chat_info",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "消息",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
@ -673,6 +673,14 @@
"navigationBarBackgroundColor": "#007ACC",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/market/clue/clue_table",
"style": {
"navigationBarTitleText": "数据统计",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
}

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

@ -146,11 +146,32 @@
</view>
</view>
<fui-picker :options="StudentList" :linkage="true" :show="show" :layer="1" @change="change"
<fui-modal :show="show" title="选择学员" @click="handleModalClick" :buttons="[]" @cancel="cancel">
<view class="student-list">
<view class="student-item" v-for="(item, index) in StudentList" :key="index" @click="change(item)">
<view class="student-info">
<view class="student-name">{{item.text}}</view>
<view class="student-details">
<text>年龄: {{item.age || '未知'}}</text>
<text>来源: {{item.source || '未知'}}</text>
<text>渠道: {{item.source_channel || '未知'}}</text>
</view>
</view>
</view>
<view class="empty-list" v-if="StudentList.length === 0">
<text>暂无可添加的学员</text>
</view>
</view>
</fui-modal>
<fui-picker :options="StudentList" :linkage="false" :show="false" :layer="1" @change="change"
@cancel="cancel"></fui-picker>
<fui-actionsheet :show="written_show" :tips="written_tips" :isCancel="isCancel"
:itemList="itemList" @cancel="written_cancel" @click="written_click"></fui-actionsheet>
<uni-popup ref="popup" type="center">
<view class="popup-content">{{ tipContent }}</view>
</uni-popup>
<AQTabber />
</view>
</template>
@ -315,41 +336,27 @@
})
return
} else {
//
this.show = true
}
// 使
// this.StudentList = [
// {
// text: '',
// value: '1001'
// },
// {
// text: '',
// value: '1002'
// },
// {
// text: '',
// value: '1003'
// },
// {
// text: '',
// value: '1004'
// },
// {
// text: '',
// value: '1005'
// }
// ];
// this.show = true;
},
async change(e) {
//
handleModalClick(e) {
//
console.log('模态框点击事件', e)
},
async change(student) {
this.show = false
// student_idresource_id
let studentId = student.value
let resourceId = student.resource_id
let res = await apiRoute.addStudent({
student_id: e.value,
student_id: studentId,
schedule_id: this.course_id,
time_slot: this.courseInfo.time_slot,
course_date: this.courseInfo.course_date
course_date: this.courseInfo.course_date,
resource_id: resourceId // resource_id
})
if (res.code != 1) {
uni.showToast({
@ -358,36 +365,14 @@
})
return
}
this.init()
// 使
// const selectedStudent = this.StudentList.find(student => student.value === e.value);
// if (!selectedStudent) return;
// //
// if (!this.courseInfo.student_courses) {
// this.courseInfo.student_courses = [];
// }
// //
// const now = new Date();
// const futureDate = new Date();
// futureDate.setMonth(futureDate.getMonth() + 3); // 3
// //
// this.courseInfo.student_courses.push({
// student_id: e.value,
// name: selectedStudent.text,
// avatar: '', //
// start_date: now.toISOString().split('T')[0],
// end_date: futureDate.toISOString().split('T')[0],
// status: 'pending' //
// });
uni.showToast({
title: '添加学员成功',
icon: 'success'
})
// uni.showToast({
// title: '',
// icon: 'success'
// });
//
this.init()
},
cancel() {
this.show = false
@ -496,6 +481,54 @@
}
}
/* 学员选择列表样式 */
.student-list {
max-height: 600rpx;
overflow-y: auto;
}
.student-item {
padding: 20rpx 30rpx;
border-bottom: 1px solid #eee;
background-color: #f8f8f8;
margin-bottom: 10rpx;
border-radius: 8rpx;
}
.student-item:active {
background-color: #e0e0e0;
}
.student-info {
display: flex;
flex-direction: column;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.student-details {
display: flex;
flex-direction: column;
font-size: 24rpx;
color: #666;
}
.student-details text {
margin-bottom: 6rpx;
}
.empty-list {
padding: 40rpx;
text-align: center;
color: #999;
font-size: 28rpx;
}
.main_section {
min-height: 100vh;
background: #292929 100%;

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

@ -194,7 +194,7 @@
size="mini"
class="remark-btn"
@click="openAddRemark(v)">
添加备注
{{v.remarks ? '修改备注' : '添加备注'}}
</button>
</view>
<view class="call-remark">
@ -216,11 +216,11 @@
<!-- 添加备注弹窗 -->
<uni-popup ref="remarkPopup" type="dialog">
<uni-popup-dialog
:title="currentRecord ? `添加备注 (${currentRecord.name || '未知'})` : '添加备注'"
:before-close="true"
@confirm="confirmRemark"
@close="closeRemark">
<view class="custom-popup-dialog">
<view class="dialog-header">
<text class="dialog-title">{{currentRecord && currentRecord.remarks ? '修改备注' : '添加备注'}}</text>
</view>
<view class="dialog-content">
<view class="remark-textarea-container">
<textarea
class="remark-textarea"
@ -230,7 +230,12 @@
></textarea>
<text class="remark-count">{{remark_content.length}}/200</text>
</view>
</uni-popup-dialog>
</view>
<view class="dialog-footer">
<view class="dialog-btn cancel-btn" @click="closeRemark">取消</view>
<view class="dialog-btn confirm-btn" @click="confirmRemark">确定</view>
</view>
</view>
</uni-popup>
<!-- 教练配置编辑弹窗 -->
@ -243,45 +248,48 @@
<view class="course-edit-container">
<view class="edit-section">
<view class="section-title">主教练单选</view>
<view v-if="coachList.length === 0" class="empty-tip">暂无主教练数据</view>
<view class="coach-list">
<view
v-for="coach in coachList"
:key="coach.id"
:key="coach.person_id"
class="coach-item"
:class="{selected: selectedMainCoach === coach.id}"
@click="selectMainCoach(coach.id)">
:class="{selected: selectedMainCoach === coach.person_id}"
@click="selectMainCoach(coach.person_id)">
<view class="coach-name">{{ coach.name }}</view>
<view class="coach-check" v-if="selectedMainCoach === coach.id"></view>
<view class="coach-check" v-if="selectedMainCoach === coach.person_id"></view>
</view>
</view>
</view>
<view class="edit-section">
<view class="section-title">教务单选</view>
<view v-if="educationList.length === 0" class="empty-tip">暂无教务数据</view>
<view class="coach-list">
<view
v-for="education in educationList"
:key="education.id"
:key="education.person_id"
class="coach-item"
:class="{selected: selectedEducation === education.id}"
@click="selectEducation(education.id)">
:class="{selected: selectedEducation === education.person_id}"
@click="selectEducation(education.person_id)">
<view class="coach-name">{{ education.name }}</view>
<view class="coach-check" v-if="selectedEducation === education.id"></view>
<view class="coach-check" v-if="selectedEducation === education.person_id"></view>
</view>
</view>
</view>
<view class="edit-section">
<view class="section-title">助教多选</view>
<view v-if="assistantList.length === 0" class="empty-tip">暂无助教数据</view>
<view class="coach-list">
<view
v-for="assistant in assistantList"
:key="assistant.id"
:key="assistant.person_id"
class="coach-item"
:class="{selected: selectedAssistants.includes(assistant.id)}"
@click="toggleAssistant(assistant.id)">
:class="{selected: selectedAssistants.includes(assistant.person_id)}"
@click="toggleAssistant(assistant.person_id)">
<view class="coach-name">{{ assistant.name }}</view>
<view class="coach-check" v-if="selectedAssistants.includes(assistant.id)"></view>
<view class="coach-check" v-if="selectedAssistants.includes(assistant.person_id)"></view>
</view>
</view>
</view>
@ -304,7 +312,6 @@
components: {
//
uniPopup: () => import('@/components/uni-popup/uni-popup.vue'),
uniPopupDialog: () => import('@/components/uni-popup/uni-popup-dialog.vue'),
},
data() {
return {
@ -617,7 +624,7 @@
//
openAddRemark(record) {
this.remark_content = '';
this.remark_content = record.remarks || ''; //
this.currentRecord = record;
this.$refs.remarkPopup.open();
},
@ -632,55 +639,57 @@
return;
}
//
if (!this.currentRecord || !this.currentRecord.id) {
uni.showToast({
title: '未选中通话记录',
icon: 'none'
});
return;
}
try {
uni.showLoading({
title: '提交中...',
mask: true
});
// 使
const params = {
staff_id: this.userInfo.id, // id
resource_id: this.clientInfo.resource_id, // ID
resource_type: '', //
communication_type: 'note', // : phone-, note-
communication_result: 'success', //
remarks: this.remark_content, //
tag: null //
id: this.currentRecord.id, // ID
remarks: this.remark_content //
};
// ID
if (this.currentRecord && this.currentRecord.id) {
params.related_record_id = this.currentRecord.id; // ID
console.log('添加备注到通话记录:', this.currentRecord);
}
console.log('更新通话记录备注:', params);
const res = await apiRoute.xs_communicationRecordsAdd(params);
//
const res = await apiRoute.xs_communicationRecordsEdit(params);
if (res.code !== 1) {
uni.showToast({
title: res.msg || '添加备注失败',
title: res.msg || '更新备注失败',
icon: 'none'
});
return;
}
uni.showToast({
title: '添加备注成功',
title: '备注更新成功',
icon: 'success'
});
//
this.getListCallUp();
await this.getListCallUp();
} catch (error) {
console.error('添加备注失败:', error);
console.error('更新备注失败:', error);
uni.showToast({
title: '添加备注失败,请重试',
title: '更新备注失败,请重试',
icon: 'none'
});
} finally {
uni.hideLoading();
this.$refs.remarkPopup.close();
this.closeRemark();
}
},
@ -711,12 +720,11 @@
return;
}
let member_id = this.clientInfo.customerResource.member_id;
let member_name = this.clientInfo.customerResource.name || '';
let from_id = uni.getStorageSync('userInfo').id //id
let to_id = this.clientInfo.customerResource.member_id //ID
this.$navigateTo({
url: `/pages/market/clue/chat?member_id=${member_id}&member_name=${member_name}`
});
url: `/pages/common/im_chat_info?from_id=${from_id}&to_id=${to_id}`
})
},
//
@ -817,61 +825,67 @@
//
async getPersonnelList() {
try {
// 使API
try {
const res = await apiRoute.getPersonnelList();
if (res.code === 1 && res.data) {
const personnel = res.data || [];
// -
this.coachList = personnel.filter(p => {
return p.role_name && (
p.role_name.includes('教练') ||
p.role_name.includes('教师') ||
(p.roles && p.roles.some(role => role.includes('教练') || role.includes('教师')))
);
});
console.log('getPersonnelList - 开始获取教练数据');
this.educationList = personnel.filter(p => {
return p.role_name && (
p.role_name.includes('教务') ||
(p.roles && p.roles.some(role => role.includes('教务')))
);
uni.showLoading({
title: '获取教练数据...'
});
this.assistantList = personnel.filter(p => {
return p.role_name && (
p.role_name.includes('助教') ||
(p.roles && p.roles.some(role => role.includes('助教')))
);
});
const res = await apiRoute.common_getCoachList();
console.log('getPersonnelList - API响应:', res);
uni.hideLoading();
console.log('getPersonnelList - 人员列表获取成功');
if (res.code === 1 && res.data) {
// 使
this.coachList = res.data.coach_list || [];
this.educationList = res.data.education_list || [];
this.assistantList = res.data.assistant_list || [];
console.log('getPersonnelList - 教练数据获取成功');
console.log('教练列表:', this.coachList);
console.log('教务列表:', this.educationList);
console.log('助教列表:', this.assistantList);
return true;
} else {
console.warn('API返回错误:', res.msg);
throw new Error(res.msg);
}
} catch (apiError) {
console.warn('使用API获取人员列表失败,使用模拟数据:', apiError);
console.warn('API返回错误:', res);
// API
uni.showToast({
title: res.msg || '获取教练数据失败',
icon: 'none'
});
// API使
this.coachList = this.getMockCoachList();
this.educationList = this.getMockEducationList();
this.assistantList = this.getMockAssistantList();
console.log('getPersonnelList - 使用模拟人员数据');
console.log('getPersonnelList - 使用模拟教练数据');
console.log('模拟教练列表:', this.coachList);
console.log('模拟教务列表:', this.educationList);
console.log('模拟助教列表:', this.assistantList);
return true;
}
} catch (error) {
console.error('getPersonnelList - 获取人员列表异常:', error);
console.error('getPersonnelList - 获取教练列表异常:', error);
uni.hideLoading();
//
uni.showToast({
title: '网络错误,使用模拟数据',
icon: 'none'
});
//
this.coachList = this.getMockCoachList();
this.educationList = this.getMockEducationList();
this.assistantList = this.getMockAssistantList();
console.log('getPersonnelList - 使用模拟教练数据(异常情况)');
console.log('模拟教练列表:', this.coachList);
console.log('模拟教务列表:', this.educationList);
console.log('模拟助教列表:', this.assistantList);
return false;
}
},
@ -879,60 +893,86 @@
//
getMockCoachList() {
return [
{ id: 1, name: '张教练' },
{ id: 5, name: '陈教练' },
{ id: 7, name: '刘教练' }
{ id: 1, person_id: 1, name: '张教练', head_img: '', phone: '' },
{ id: 5, person_id: 5, name: '陈教练', head_img: '', phone: '' },
{ id: 7, person_id: 7, name: '刘教练', head_img: '', phone: '' }
];
},
//
getMockEducationList() {
return [
{ id: 2, name: '李教务' },
{ id: 8, name: '周教务' }
{ id: 2, person_id: 2, name: '李教务', head_img: '', phone: '' },
{ id: 8, person_id: 8, name: '周教务', head_img: '', phone: '' }
];
},
//
getMockAssistantList() {
return [
{ id: 3, name: '王助教' },
{ id: 4, name: '赵助教' },
{ id: 6, name: '孙助教' },
{ id: 9, name: '钱助教' }
{ id: 3, person_id: 3, name: '王助教', head_img: '', phone: '' },
{ id: 4, person_id: 4, name: '赵助教', head_img: '', phone: '' },
{ id: 6, person_id: 6, name: '孙助教', head_img: '', phone: '' },
{ id: 9, person_id: 9, name: '钱助教', head_img: '', phone: '' }
];
},
//
openCourseEditDialog(course) {
async openCourseEditDialog(course) {
this.currentCourse = course;
//
//
console.log('打开教练配置弹窗,检查教练数据:', {
coachList: this.coachList,
educationList: this.educationList,
assistantList: this.assistantList
});
//
if (!this.coachList.length && !this.educationList.length && !this.assistantList.length) {
console.log('教练数据为空,重新获取...');
await this.getPersonnelList();
}
// 使person_id
this.selectedMainCoach = course.main_coach_id;
this.selectedEducation = course.education_id;
this.selectedAssistants = course.assistant_ids ? course.assistant_ids.split(',').map(Number) : [];
console.log('打开教练配置弹窗:', {
course: course,
selectedMainCoach: this.selectedMainCoach,
selectedEducation: this.selectedEducation,
selectedAssistants: this.selectedAssistants,
coachListLength: this.coachList.length,
educationListLength: this.educationList.length,
assistantListLength: this.assistantList.length
});
this.$refs.courseEditPopup.open();
},
//
selectMainCoach(coachId) {
this.selectedMainCoach = coachId;
selectMainCoach(personId) {
this.selectedMainCoach = personId;
console.log('选择主教练:', personId);
},
//
selectEducation(educationId) {
this.selectedEducation = educationId;
selectEducation(personId) {
this.selectedEducation = personId;
console.log('选择教务:', personId);
},
//
toggleAssistant(assistantId) {
const index = this.selectedAssistants.indexOf(assistantId);
toggleAssistant(personId) {
const index = this.selectedAssistants.indexOf(personId);
if (index > -1) {
this.selectedAssistants.splice(index, 1);
} else {
this.selectedAssistants.push(assistantId);
this.selectedAssistants.push(personId);
}
console.log('切换助教选择:', personId, '当前选中:', this.selectedAssistants);
},
//
@ -945,9 +985,9 @@
const params = {
student_course_id: this.currentCourse.id,
main_coach_id: this.selectedMainCoach,
education_id: this.selectedEducation,
assistant_ids: this.selectedAssistants.join(',')
main_coach_id: this.selectedMainCoach, // 使person_id
education_id: this.selectedEducation, // 使person_id
assistant_ids: this.selectedAssistants.join(',') // 使person_id
};
console.log('准备保存教练配置:', params);
@ -978,22 +1018,22 @@
const courseIndex = this.courseInfo.findIndex(c => c.id === this.currentCourse.id);
if (courseIndex > -1) {
//
const mainCoach = this.coachList.find(c => c.id === this.selectedMainCoach);
const mainCoach = this.coachList.find(c => c.person_id === this.selectedMainCoach);
if (mainCoach) {
this.courseInfo[courseIndex].main_coach_id = this.selectedMainCoach;
this.courseInfo[courseIndex].main_coach_name = mainCoach.name;
}
//
const education = this.educationList.find(e => e.id === this.selectedEducation);
const education = this.educationList.find(e => e.person_id === this.selectedEducation);
if (education) {
this.courseInfo[courseIndex].education_id = this.selectedEducation;
this.courseInfo[courseIndex].education_name = education.name;
}
//
const assistantNames = this.selectedAssistants.map(id => {
const assistant = this.assistantList.find(a => a.id === id);
const assistantNames = this.selectedAssistants.map(personId => {
const assistant = this.assistantList.find(a => a.person_id === personId);
return assistant ? assistant.name : '';
}).filter(name => name).join(', ');
@ -1227,6 +1267,52 @@
padding: 0 30rpx;
}
//
.custom-popup-dialog {
width: 650rpx;
border-radius: 16rpx;
background-color: #fff;
overflow: hidden;
}
.dialog-header {
padding: 30rpx;
text-align: center;
border-bottom: 1px solid #eee;
}
.dialog-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.dialog-content {
padding: 30rpx;
}
.dialog-footer {
display: flex;
border-top: 1px solid #eee;
}
.dialog-btn {
flex: 1;
padding: 25rpx 0;
text-align: center;
font-size: 30rpx;
}
.cancel-btn {
color: #666;
border-right: 1px solid #eee;
}
.confirm-btn {
color: #29D3B4;
font-weight: bold;
}
//
.remark-textarea-container {
width: 100%;
@ -1236,7 +1322,7 @@
.remark-textarea {
width: 100%;
height: 200rpx;
height: 240rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 20rpx;
@ -1705,4 +1791,11 @@
width: 40rpx;
text-align: center;
}
.empty-tip {
color: #999;
font-size: 28rpx;
text-align: center;
padding: 40rpx 0;
}
</style>

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

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

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

@ -14,6 +14,7 @@
class="item"
v-for="(v,k) in tableList"
:key="k"
@click="handleOrderClick(v)"
>
<view class="top">
<view class="title">订单状态{{ v.order_status == 'pending' ? '待支付' : '已支付' }}</view>
@ -97,7 +98,7 @@
<!--付款类型-->
<fui-form-item
label="选择付款类型"
label="付款类型"
asterisk
asteriskPosition="right"
labelSize='26'
@ -181,7 +182,35 @@
@cancel="cancelClassId">
</fui-picker>
</fui-form-item>
<!--订单类型-->
<fui-form-item
label="订单类型"
asterisk
asteriskPosition="right"
labelSize='26'
prop=""
background='#fff'
labelColor='#000'
:bottomBorder='true'
>
<fui-radio-group name="radio" v-model="formData.order_type">
<view style="display: flex;justify-content: flex-end">
<fui-label>
<view class="fui-align__center">
<fui-radio value="1" checked></fui-radio>
<text class="fui-text">新报名</text>
</view>
</fui-label>
<fui-label :margin="['0','0','0','40rpx']">
<view class="fui-align__center">
<fui-radio value="2"></fui-radio>
<text class="fui-text">续费</text>
</view>
</fui-label>
</view>
</fui-radio-group>
</fui-form-item>
<!--订单金额-->
<fui-form-item
label="订单金额"
@ -206,8 +235,12 @@
</fui-form-item>
</view>
<view class="button_box">
<fui-button background="#fff" color="#414141" borderColor="#465CFF" btnSize="small" @click="closeOrderShow">取消</fui-button>
<fui-button background="#fff" color="#465CFF" borderColor="#465CFF" btnSize="small" @click="clickOrder({index:1})">确定</fui-button>
<fui-button background="#fff" color="#414141" borderColor="#465CFF" btnSize="small" @click="closeOrderShow">
取消
</fui-button>
<fui-button background="#fff" color="#465CFF" borderColor="#465CFF" btnSize="small"
@click="clickOrder({index:1})">确定
</fui-button>
</view>
</fui-form>
@ -215,21 +248,56 @@
<fui-icon name="close" color="#B2B2B2" :size="48"></fui-icon>
</view>
</fui-modal>
<!-- 自定义内容弹窗示例 -->
<custom-modal
:show="showCustomModal"
width="700"
:showClose="true"
:maskClose="true"
@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="300rpx"
height="72rpx"
:size="28"
radius="36rpx"
background="#007AFF"
borderWidth="0"
@click="handleButtonClick"
/>
</view>
</template>
</custom-modal>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
import apiRoute from '@/api/apiRoute.js'
import FuiRadioGroup from '../../../components/firstui/fui-radio-group/fui-radio-group.vue'
import CustomModal from '../../../components/custom-modal/custom-modal.vue'
export default {
components: {
CustomModal,
FuiRadioGroup,
},
data() {
return {
loading: false,//
lowerThreshold: 100,//
isReachedBottom: false,//|true=|false=
showCustomModal: false,
qrcode: 'https://www.baidu.com',
//
filteredData: {
page: 1,//
@ -257,6 +325,7 @@ export default {
resource_id_name: '',//ID
money: '',//|
order_type: '1',
},
order_show: false,//|true=,false=
@ -296,7 +365,7 @@ export default {
if (!options || !options.resource_id) {
uni.showToast({
title: '缺少必要参数',
icon: 'none'
icon: 'none',
})
return
}
@ -330,12 +399,12 @@ export default {
await this.getClassList()
//
await this.getList();
await this.getList()
} catch (error) {
console.error('初始化失败:', error)
uni.showToast({
title: '初始化数据失败',
icon: 'none'
icon: 'none',
})
}
},
@ -344,19 +413,21 @@ export default {
loadMoreData() {
//
if (!this.isReachedBottom) {
this.isReachedBottom = true;//
this.getList();
this.isReachedBottom = true//
this.getList()
}
},
//
async resetFilteredData() {
this.isReachedBottom = false; // 便
this.isReachedBottom = false // 便
this.filteredData.page = 1//
this.filteredData.limit = 10//
this.filteredData.total = 10//
},
handleModalCancel() {
this.showCustomModal = false
},
//
async getList() {
this.loading = true
@ -368,7 +439,7 @@ export default {
this.loading = false
uni.showToast({
title: '暂无更多',
icon: 'none'
icon: 'none',
})
return
}
@ -379,16 +450,16 @@ export default {
let res = await apiRoute.xs_orderTableList(params)//
this.loading = false
this.isReachedBottom = false;
this.isReachedBottom = false
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
icon: 'none',
})
return
}
this.tableList = this.tableList.concat(res.data.data); // 使 concat
this.tableList = this.tableList.concat(res.data.data) // 使 concat
// this.tableList.unshift(...res.data.data); //
console.log('列表', this.tableList)
@ -403,7 +474,7 @@ export default {
if (res.code != 1) {
uni.showToast({
title: res.msg || '获取付款类型失败',
icon: 'none'
icon: 'none',
})
return
}
@ -431,7 +502,7 @@ export default {
if (res.code != 1) {
uni.showToast({
title: res.msg || '获取课程列表失败',
icon: 'none'
icon: 'none',
})
return
}
@ -466,7 +537,7 @@ export default {
if (res.code != 1) {
uni.showToast({
title: res.msg || '获取班级列表失败',
icon: 'none'
icon: 'none',
})
return
}
@ -491,8 +562,6 @@ export default {
},
//
//
openOrderShow() {
@ -532,42 +601,36 @@ export default {
if (!param.resource_id) {
uni.showToast({
title: '客户信息缺失',
icon: 'none'
icon: 'none',
})
return
}
if (!param.class_id) {
uni.showToast({
title: '请选择班级',
icon: 'none'
icon: 'none',
})
return
}
if (!param.course_id) {
uni.showToast({
title: '请选择课程',
icon: 'none'
icon: 'none',
})
return
}
if (!param.payment_type) {
uni.showToast({
title: '请选择付款类型',
icon: 'none'
})
return
}
if(!param.staff_id){
uni.showToast({
title: '员工信息缺失',
icon: 'none'
icon: 'none',
})
return
}
// ID
if (!param.money) {
uni.showToast({
title: '订单金额为空',
icon: 'none'
icon: 'none',
})
return
}
@ -576,7 +639,7 @@ export default {
this.closeOrderShow()
uni.showLoading({
title: '提交中...'
title: '提交中...',
})
let res = await apiRoute.xs_orderTableAdd(param)
@ -585,27 +648,27 @@ export default {
if (res.code != 1) {
uni.showToast({
title: res.msg || '创建订单失败',
icon: 'none'
icon: 'none',
})
return
}
uni.showToast({
title: '创建订单成功',
icon: 'success'
icon: 'success',
})
//1s
setTimeout(() => {
this.resetFilteredData()//
this.getList();//
this.getList()//
}, 1500)
} catch (error) {
uni.hideLoading()
console.error('提交订单失败:', error)
uni.showToast({
title: '创建订单失败,请重试',
icon: 'none'
icon: 'none',
})
}
},
@ -618,7 +681,7 @@ export default {
if (!e || !e.value) {
uni.showToast({
title: '请选择有效的付款类型',
icon: 'none'
icon: 'none',
})
return
}
@ -631,7 +694,7 @@ export default {
if (!this.payment_type_options || this.payment_type_options.length === 0) {
uni.showToast({
title: '付款类型数据加载中,请稍后再试',
icon: 'none'
icon: 'none',
})
this.getPaymentTypeList() //
return
@ -650,7 +713,7 @@ export default {
if (!e || !e.value) {
uni.showToast({
title: '请选择有效的课程',
icon: 'none'
icon: 'none',
})
return
}
@ -673,7 +736,7 @@ export default {
if (!this.course_id_options || this.course_id_options.length === 0) {
uni.showToast({
title: '课程数据加载中,请稍后再试',
icon: 'none'
icon: 'none',
})
this.getCourseList() //
return
@ -692,7 +755,7 @@ export default {
if (!e || !e.value) {
uni.showToast({
title: '请选择有效的班级',
icon: 'none'
icon: 'none',
})
return
}
@ -705,7 +768,7 @@ export default {
if (!this.class_id_options || this.class_id_options.length === 0) {
uni.showToast({
title: '班级数据加载中,请稍后再试',
icon: 'none'
icon: 'none',
})
this.getClassList() //
return
@ -717,19 +780,99 @@ export default {
this.class_id_show = false
},
//
handleOrderClick(orderData) {
console.log('点击订单:', orderData)
//
if (orderData.order_status === 'pending') {
this.initiatePayment(orderData)
} else {
//
console.log('订单已支付,无需再次支付')
uni.showToast({
title: '订单已支付',
icon: 'none',
})
}
},
//
initiatePayment(orderData) {
console.log('发起支付,订单信息:', orderData)
console.log('支付类型:', orderData.payment_type)
//
switch (orderData.payment_type) {
case 'cash':
this.handleCashPayment(orderData)
break
case 'scan_code':
this.handleScanCodePayment(orderData)
break
case 'subscription':
this.handleSubscriptionPayment(orderData)
break
default:
uni.showToast({
title: '未知支付类型',
icon: 'none',
})
break
}
},
//
handleCashPayment(orderData) {
console.log('处理现金支付:', orderData)
//
//
uni.showToast({
title: '现金支付处理中...',
icon: 'loading',
})
//
// this.confirmCashPayment(orderData)
},
//
handleScanCodePayment(orderData) {
console.log('处理扫码支付:', orderData)
//
apiRoute.getOrderPayQrcode({ order_id: orderData.id }).then(res => {
this.qrcode = res.data.code_url
this.showCustomModal = true
})
},
//
handleSubscriptionPayment(orderData) {
console.log('处理订阅支付:', orderData)
//
//
uni.showToast({
title: '订阅支付处理中...',
icon: 'loading',
})
//
// this.initiateSubscriptionPayment(orderData)
},
//
async downloadFile(fileUrl) {
if (!fileUrl) {
this.$util.showToast({
title: '暂无电子发票'
});
return false;
title: '暂无电子发票',
})
return false
}
uni.downloadFile({
url: fileUrl,
success: function(res) {
console.log('下载成功');
console.log('下载成功')
// uni.openDocument({
// filePath: res.tempFilePath,
// fileType: 'pdf',
@ -737,12 +880,14 @@ export default {
// console.log('');
// }
// });
}
});
},
})
}
},
handleButtonClick(){
}
},
}
</script>
@ -751,17 +896,19 @@ export default {
background: #292929;
}
//
.navbar_section {
.custom-content {
width: 100%;
padding: 20rpx;
display: flex;
justify-content: center;
align-items: center;
background: #29d3b4;
flex-wrap: wrap;
.title {
padding: 20rpx 0;
font-size: 30rpx;
color: #315d55;
.custom-title {
width: 100%;
text-align: center;
font-size: 32rpx;
font-weight: 600;
margin-bottom: 20rpx;
}
}
@ -794,31 +941,38 @@ export default {
border-radius: 14rpx;
background-color: #434544;
color: #fff;
.top {
font-size: 28rpx;
display: flex;
justify-content: space-between;
.title {
font-size: 30rpx;
}
.btn {
display: flex;
align-items: center;
color: #29D3B4;
}
}
.bottom {
font-size: 26rpx;
margin-top: 25rpx;
display: flex;
flex-direction: column;
gap: 15rpx;
.box {
display: flex;
justify-content: space-between;
.title {
width: 180rpx;
}
.content {
width: 100%;
}
@ -832,6 +986,7 @@ export default {
display: flex;
justify-content: center;
align-items: center;
.btn {
border-radius: 10rpx;
padding: 15rpx 0;
@ -843,7 +998,6 @@ export default {
}
}
.describe {
@ -876,8 +1030,11 @@ export default {
.input-style {
text-align: right !important;
.input-title{}
.input-title {
}
}
.button_box {
margin-top: 30rpx;
padding: 20rpx;

13
uniapp/pages/student/login/login.vue

@ -72,14 +72,14 @@
value: '3',
text: '销售登陆'
},
{
value: '2',
text: '教务登陆'
},
{
value: '4',
text: '学员登陆'
},
{
value: '5',
text: '教务登陆'
}
],
picker_show_loginType: false, //
@ -161,7 +161,6 @@
//
res = await apiRoute.xy_login(params);
}
console.log('登录响应:', res);
if (res && res.code === 1) { // 1
//
@ -175,6 +174,10 @@
if (res.data.expires_time) {
uni.setStorageSync("expires_time", res.data.expires_time);
}
//
if (res.data.userinfo){
uni.setStorageSync('userInfo',res.data.userinfo)
}
}
uni.showToast({

Loading…
Cancel
Save