From 4e25b102e8370e8262a3f3c6b23dcc595f55cd36 Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Fri, 21 Nov 2025 09:43:54 +0800 Subject: [PATCH] update --- .../Controllers/Admin/OtherController.php | 410 +++---------- .../Admin/StatisticsConfigController.php | 558 +---------------- app/Models/StatisticsConfig.php | 575 ++++++++++++++++++ 3 files changed, 668 insertions(+), 875 deletions(-) diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index ef14eae..9b8ca3b 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -27,8 +27,6 @@ use Illuminate\Support\Facades\Validator; use App\Models\Course; use EasyWeChat\Factory; use Illuminate\Filesystem\Filesystem; -use Maatwebsite\Excel\Facades\Excel; -use App\Exports\CommonExport; class OtherController extends CommonController { @@ -121,6 +119,40 @@ class OtherController extends CommonController */ public function homeV2() { + // 校友总数 + $list['schoolmate_total'] = User::where('is_schoolmate', 1)->count(); + // 今年新增校友数 + $list['schoolmate_year'] = User::where('is_schoolmate', 1)->where('created_at', 'like', '%' . date('Y') . '%')->count(); + // 投后企业 + $list['company_invested_total'] = Company::where('is_yh_invested', 1)->count(); + // 元和员工参与 + // 元和员工参与企业 + $companyNameKeyword = ['元禾控股', '元禾原点', '元禾厚望', '元禾重元', '元禾璞华', '元禾谷风', '元禾绿柳', '元禾辰坤', '元禾沙湖', '禾裕集团', '苏州科服', '信诚管理咨询', '集成电路公司', '常州团队', '国企元禾']; + // 获取公司名字包含$companyNameKeyword任意数据的公司,需要模糊匹配 + $list['company_join_total'] = Company::where(function ($query) use ($companyNameKeyword) { + foreach ($companyNameKeyword as $item) { + $query->orWhere('company_name', 'like', '%' . $item . '%'); + } + })->count(); + // 全市干部参与企业 + $list['company_ganbu_total'] = Company::whereHas('users', function ($query) { + $query->where('from', '跟班学员'); + })->count(); + // 三个全覆盖 + // 苏州头部企业 + $list['cover_head_total'] = Company::where('company_tag', 'like', '%' . '高新技术企业' . '%')->count(); + // 高层次人才 + // 获取人才培训课程 + $renCaiCourseIds = Course::whereHas('typeDetail', function ($query) { + $query->where('name', '人才培训'); + })->pluck('id'); + $list['cover_rencai_total'] = CourseSign::whereIn('course_id', $renCaiCourseIds)->where('status', 1)->count(); + // 重点上市公司 + $list['cover_stock_total'] = Company::where('company_market', 1)->count(); + // 本月课程 + $monthCourses = Calendar::with('course.teacher')->where('type', 1) + ->where('date', 'like', '%' . date('Y-m') . '%') + ->get(); // 课程统计 $courseTypes = CourseType::where('is_chart', 1)->get(); $start_date = CourseType::START_DATE; @@ -131,7 +163,7 @@ class OtherController extends CommonController // 已开设期数 $courseType->course_periods_total = Course::where('type', $courseType->id)->count(); // 培养人数去重 - $courseType->course_signs_total = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), null); + $courseType->course_signs_total = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), null, $userIds); } // 苏州区域数据 $suzhouArea = Company::where('company_city', '苏州市')->groupBy('company_area') @@ -148,7 +180,8 @@ class OtherController extends CommonController } // 全国数据 - $countryArea = Company::groupBy('company_city')->whereNotNull('company_city')->get(['company_city']); + $countryArea = Company::groupBy('company_city')->whereNotNull('company_city') + ->get(['company_city']); $country = []; foreach ($countryArea as $item) { $country[] = [ @@ -158,15 +191,14 @@ class OtherController extends CommonController })->where('is_schoolmate', 1)->count() ]; } - // 本月课程 - $monthCourses = Course::with('teacher')->where('start_date', 'like', '%' . date('Y-m') . '%')->get(); + // 时间轴 - $time_axis = TimeEvent::orderBy('sort', 'asc')->orderBy('id', 'desc')->get(); + $time_axis = TimeEvent::orderBy('sort', 'asc')->get(); // 动态信息 - $article['xiaoyou'] = Article::where('type', 1)->limit(6)->orderBy('sort', 'asc')->get(); - $article['yejie'] = Article::where('type', 2)->limit(6)->orderBy('sort', 'asc')->get(); - $article['supply_demands'] = SupplyDemand::limit(6)->orderBy('created_at', 'desc')->get(); - return $this->success(compact('courseTypes', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article')); + $article['xiaoyou'] = Article::where('type', 1)->limit(7)->orderBy('sort', 'desc')->get(); + $article['yejie'] = Article::where('type', 2)->limit(7)->orderBy('sort', 'desc')->get(); + $article['supply_demands'] = SupplyDemand::limit(7)->orderBy('created_at', 'desc')->get(); + return $this->success(compact('list', 'courseTypes', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article')); } /** @@ -187,344 +219,70 @@ class OtherController extends CommonController */ public function coursesHome() { - $params = $this->getCoursesHomeParams(); - $courses = $this->getCoursesByTypeIds($params['course_type_id']); - // 报名人数 - $course_signs_total = CourseSign::courseSignsTotal($params['start_date'], $params['end_date']); - // 审核通过人数 - $course_signs_pass = CourseSign::courseSignsTotal($params['start_date'], $params['end_date'], 1); - // 审核通过人数去重 - $course_signs_pass_unique = CourseSign::courseSignsTotalByUnique($params['start_date'], $params['end_date'], 1, null, null); - // 开课场次 - $calendar = $this->getCalendarsByCourses($courses, $params['start_date'], $params['end_date']); - $course_total = $calendar->count(); - // 开课天数 - $course_day_total = $calendar->sum('days'); - // 课程分类明细统计 - $courseTypesSum = $this->getCourseTypesSum($params['start_date'], $params['end_date'], $params['course_type_id']); - // 区域明细统计 - $areas = $this->getAreasStatistics($params['start_date'], $params['end_date'], $courses); - - return $this->success(compact( - 'course_signs_total', - 'course_signs_pass', - 'course_signs_pass_unique', - 'course_total', - 'course_day_total', - 'courseTypesSum', - 'areas' - )); - } - - /** - * @OA\Get( - * path="/api/admin/other/courses-home-export", - * tags={"其他"}, - * summary="课程统计数据明细导出", - * description="", - * @OA\Parameter(name="export_type", in="query", @OA\Schema(type="string"), required=true, description="导出类型:course_signs_total-报名人数明细, course_signs_pass-审核通过人数明细, course_signs_pass_unique-审核通过人数去重明细, course_total-开课场次明细, course_type_detail-课程分类明细, area_detail-区域明细"), - * @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=true, description="开始日期"), - * @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string"), required=true, description="结束日期"), - * @OA\Parameter(name="course_type_id", in="query", @OA\Schema(type="string"), required=false, description="课程体系id,多个英文逗号"), - * @OA\Parameter(name="course_id", in="query", @OA\Schema(type="string"), required=false, description="课程id,用于课程分类明细"), - * @OA\Parameter(name="area", in="query", @OA\Schema(type="string"), required=false, description="区域,用于区域明细"), - * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), - * @OA\Response( - * response="200", - * description="导出Excel文件" - * ) - * ) - */ - public function coursesHomeExport() - { - $export_type = request('export_type'); - $params = $this->getCoursesHomeParams(); - $course_id = request('course_id', ''); - $area = request('area', ''); - - // 验证导出类型 - $allowed_types = [ - 'course_signs_total', - 'course_signs_pass', - 'course_signs_pass_unique', - 'course_total', - 'course_type_detail', - 'area_detail' - ]; - if (!in_array($export_type, $allowed_types)) { - return $this->fail([ResponseCode::ERROR_PARAMETER, '导出类型不正确']); - } - - $courses = $this->getCoursesByTypeIds($params['course_type_id']); - $data = []; - $filename = ''; - $error = ''; - - switch ($export_type) { - case 'course_signs_total': - $list = CourseSign::courseSignsTotal($params['start_date'], $params['end_date'], null, null, null, true); - $list->load(['user', 'course']); - $data = $this->formatCourseSignsData($list); - $filename = '报名人数明细'; - break; - - case 'course_signs_pass': - $list = CourseSign::courseSignsTotal($params['start_date'], $params['end_date'], 1, null, null, true); - $list->load(['user', 'course']); - $data = $this->formatCourseSignsData($list); - $filename = '审核通过人数明细'; - break; - - case 'course_signs_pass_unique': - $list = CourseSign::courseSignsTotalByUnique($params['start_date'], $params['end_date'], 1, null, null, true); - $data = $this->formatUsersData($list); - $filename = '审核通过人数去重明细'; - break; - - case 'course_total': - $list = $this->getCalendarsByCourses($courses, $params['start_date'], $params['end_date']); - $list->load('course'); - $data = $this->formatCalendarData($list); - $filename = '开课场次明细'; - break; - - case 'course_type_detail': - if (!$course_id) { - return $this->fail([ResponseCode::ERROR_PARAMETER, '课程分类明细需要提供course_id参数']); - } - $course_ids = explode(',', $course_id); - $list = CourseSign::courseSignsTotal($params['start_date'], $params['end_date'], 1, $course_ids, null, true); - $list->load(['user', 'course']); - $data = $this->formatCourseSignsData($list); - $filename = '课程分类明细'; - break; - - case 'area_detail': - if (!$area) { - return $this->fail([ResponseCode::ERROR_PARAMETER, '区域明细需要提供area参数']); - } - $list = CourseSign::courseSignsTotal($params['start_date'], $params['end_date'], 1, $courses->pluck('id'), $area, true); - $list->load(['user', 'course']); - $data = $this->formatCourseSignsData($list); - $filename = '区域明细_' . $area; - break; - } - - if (empty($data)) { - return $this->fail([ResponseCode::ERROR_PARAMETER, '没有可导出的数据']); - } - - // 定义导出字段 - $exportFields = $this->getExportFields($export_type); - - // 导出Excel - return Excel::download( - new CommonExport($data, $exportFields), - $filename . '_' . date('YmdHis') . '.xlsx' - ); - } - - /** - * 获取课程统计参数 - */ - private function getCoursesHomeParams() - { + $start_date = request('start_date', '2020-01-01'); + $end_date = request('end_date', date('Y-m-d')); $course_type_id = request('course_type_id', ''); if ($course_type_id) { + // 部分 $course_type_id = explode(',', $course_type_id); } else { + // 全部 $course_type_id = CourseType::pluck('id')->toArray(); } - - return [ - 'start_date' => request('start_date', CourseType::START_DATE), - 'end_date' => request('end_date', date('Y-m-d')), - 'course_type_id' => $course_type_id, - ]; - } - - /** - * 根据课程体系ID获取课程列表 - */ - private function getCoursesByTypeIds($course_type_id) - { - return Course::whereIn('type', $course_type_id)->get(); - } - - /** - * 根据课程列表获取日历数据 - */ - private function getCalendarsByCourses($courses, $start_date, $end_date) - { - return Calendar::whereIn('course_id', $courses->pluck('id')) - ->whereBetween('date', [$start_date, $end_date]) + // 课程 + $courses = Course::whereIn('type', $course_type_id) + // ->where('start_date', '<=', $end_date) + // ->where('start_date', '>=', $start_date) ->get(); - } - - /** - * 获取课程分类明细统计 - */ - private function getCourseTypesSum($start_date, $end_date, $course_type_id) - { + // 被投企业数 + $list['course_signs_invested'] = CourseSign::yhInvested($start_date, $end_date); + // 报名人数 + $list['course_signs_total'] = CourseSign::courseSignsTotal($start_date, $end_date); + // 审核通过人数 + $list['course_signs_pass'] = CourseSign::courseSignsTotal($start_date, $end_date, 1); + // 审核通过人数去重 + $list['course_signs_pass_unique'] = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, null, null, $userIds); + // 开课场次 + $calendar = Calendar::whereIn('course_id', $courses->pluck('id'))->whereBetween('date', [$start_date, $end_date])->get(); + $list['course_total'] = $calendar->count(); + // 开课天数 + $list['course_day_total'] = $calendar->sum(function ($course) { + $start = Carbon::parse($course->start_time); + $end = Carbon::parse($course->end_time); + return $end->diffInDays($start) + 1; // 包含起始和结束日期 + }); + // 课程分类明细统计 $courseTypesSum = []; $courseTypes = CourseType::whereIn('id', $course_type_id)->get(); foreach ($courseTypes as $courseType) { - $courses2 = Course::where('type', $courseType->id)->get(); + // 获取课程 + $courses2 = Course::where('type', $courseType->id) + // ->where('start_date', '<=', $end_date) + // ->where('start_date', '>=', $start_date) + ->get(); foreach ($courses2 as $course) { $courseTypesSum[] = [ 'course_type' => $courseType->name, + // 培养人数 'course_type_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses2->pluck('id')), - 'course_type_signs_pass_unique' => CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses2->pluck('id'), null), + // 去重培养人数 + 'course_type_signs_pass_unique' => CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses2->pluck('id'), null, $userIds), + 'user_ids' => $userIds, 'course_name' => $course->name, 'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id]), ]; } } - return $courseTypesSum; - } - - /** - * 获取区域明细统计 - */ - private function getAreasStatistics($start_date, $end_date, $courses) - { + // 区域明细统计 $areas = ParameterDetail::where('parameter_id', 5)->get(); foreach ($areas as $area) { $area->course_signs_pass = CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses->pluck('id'), $area->value); - $area->course_signs_pass_unique = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), $area->value); + $area->course_signs_pass_unique = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), $area->value, $userIds); + $area->user_ids = $userIds; } - return $areas; - } - - /** - * 格式化课程报名数据 - */ - private function formatCourseSignsData($list) - { - $data = []; - foreach ($list as $item) { - $user = $item->user; - $course = $item->course; - $data[] = [ - 'name' => $user ? ($user->name ?? $user->username ?? '') : '', - 'mobile' => $user->mobile ?? '', - 'company_name' => $user->company_name ?? '', - 'company_position' => $user->company_position ?? '', - 'company_area' => $user->company_area ?? '', - 'course_name' => $course->name ?? '', - 'status_text' => $item->status_text ?? '', - 'fee_status_text' => $item->fee_status_text ?? '', - 'created_at' => $item->created_at ?? '', - ]; - } - return $data; - } - - /** - * 格式化用户数据(去重) - */ - private function formatUsersData($list) - { - $data = []; - foreach ($list as $user) { - $data[] = [ - 'name' => $user->name ?? $user->username ?? '', - 'mobile' => $user->mobile ?? '', - 'company_name' => $user->company_name ?? '', - 'company_position' => $user->company_position ?? '', - 'company_area' => $user->company_area ?? '', - 'email' => $user->email ?? '', - ]; - } - return $data; - } - - /** - * 格式化日历数据 - */ - private function formatCalendarData($list) - { - $data = []; - foreach ($list as $item) { - $course = $item->course; - $data[] = [ - 'course_name' => $course ? ($course->name ?? '') : '', - 'date' => $item->date ?? '', - 'start_time' => $item->start_time ?? '', - 'end_time' => $item->end_time ?? '', - 'type_text' => $item->type_text ?? '', - 'is_publish_text' => $item->is_publish_text ?? '', - ]; - } - return $data; - } - - /** - * 获取导出字段配置 - */ - private function getExportFields($export_type) - { - $fields = [ - 'course_signs_total' => [ - 'name' => '姓名', - 'mobile' => '手机号', - 'company_name' => '公司名称', - 'company_position' => '职务', - 'company_area' => '公司区域', - 'course_name' => '课程名称', - 'status_text' => '审核状态', - 'fee_status_text' => '缴费状态', - 'created_at' => '报名时间', - ], - 'course_signs_pass' => [ - 'name' => '姓名', - 'mobile' => '手机号', - 'company_name' => '公司名称', - 'company_position' => '职务', - 'company_area' => '公司区域', - 'course_name' => '课程名称', - 'fee_status_text' => '缴费状态', - 'created_at' => '报名时间', - ], - 'course_signs_pass_unique' => [ - 'name' => '姓名', - 'mobile' => '手机号', - 'company_name' => '公司名称', - 'company_position' => '职务', - 'company_area' => '公司区域', - 'email' => '邮箱', - ], - 'course_total' => [ - 'course_name' => '课程名称', - 'date' => '日期', - 'start_time' => '开始时间', - 'end_time' => '结束时间', - 'type_text' => '类型', - 'is_publish_text' => '是否发布', - ], - 'course_type_detail' => [ - 'name' => '姓名', - 'mobile' => '手机号', - 'company_name' => '公司名称', - 'company_position' => '职务', - 'company_area' => '公司区域', - 'course_name' => '课程名称', - 'fee_status_text' => '缴费状态', - 'created_at' => '报名时间', - ], - 'area_detail' => [ - 'name' => '姓名', - 'mobile' => '手机号', - 'company_name' => '公司名称', - 'company_position' => '职务', - 'company_area' => '公司区域', - 'course_name' => '课程名称', - 'fee_status_text' => '缴费状态', - 'created_at' => '报名时间', - ], - ]; - - return $fields[$export_type] ?? []; + $area_course_signs_pass_total = collect($areas)->sum('course_signs_pass'); + $area_course_signs_pass_unique_total = collect($areas)->sum('course_signs_pass_unique'); + return $this->success(compact('list', 'courseTypesSum', 'areas', 'area_course_signs_pass_total', 'area_course_signs_pass_unique_total')); } /** diff --git a/app/Http/Controllers/Admin/StatisticsConfigController.php b/app/Http/Controllers/Admin/StatisticsConfigController.php index d3ef441..65446a6 100644 --- a/app/Http/Controllers/Admin/StatisticsConfigController.php +++ b/app/Http/Controllers/Admin/StatisticsConfigController.php @@ -232,6 +232,7 @@ class StatisticsConfigController extends BaseController * @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=true, description="配置的key标识"), * @OA\Parameter(name="page", in="query", @OA\Schema(type="integer"), required=false, description="页码,默认1"), * @OA\Parameter(name="page_size", in="query", @OA\Schema(type="integer"), required=false, description="每页显示的条数,默认10"), + * @OA\Parameter(name="show_type", in="query", @OA\Schema(type="string"), required=false, description="显示类型:statistics-统计数据,list-列表数据,默认statistics"), * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), * @OA\Response( * response="200", @@ -252,568 +253,27 @@ class StatisticsConfigController extends BaseController return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } - // 获取分页参数 - $page = isset($all['page']) ? (int) $all['page'] : 1; - $pageSize = isset($all['page_size']) ? (int) $all['page_size'] : 10; - // 根据key查找配置 $config = $this->model->where('key', $all['key'])->first(); if (empty($config)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '配置不存在']); } - $configJson = $config->config_json; - if (empty($configJson)) { - return $this->fail([ResponseCode::ERROR_BUSINESS, '配置数据为空']); - } - - // 如果 config_json 是字符串,尝试解析 - if (is_string($configJson)) { - $configJson = json_decode($configJson, true); - if (json_last_error() !== JSON_ERROR_NONE) { - return $this->fail([ResponseCode::ERROR_BUSINESS, '配置 JSON 格式错误:' . json_last_error_msg()]); - } - } - try { - // 检查数据结构 - if (!isset($configJson['data_source'])) { - return $this->fail([ResponseCode::ERROR_BUSINESS, '配置数据缺少 data_source 字段,当前配置键:' . implode(', ', array_keys($configJson))]); - } - - // 获取主模型 - $mainModelName = $configJson['data_source']['main_model'] ?? ''; - if (empty($mainModelName)) { - $dataSourceKeys = isset($configJson['data_source']) ? implode(', ', array_keys($configJson['data_source'])) : '无'; - return $this->fail([ResponseCode::ERROR_BUSINESS, '主模型名称未配置,data_source 中的键:' . $dataSourceKeys]); - } - $mainModel = $this->getModel($mainModelName); - if (empty($mainModel)) { - return $this->fail([ResponseCode::ERROR_BUSINESS, '主模型不存在:' . $mainModelName . ',可用的模型:user, company, course_sign, course, course_type']); - } - - $query = $mainModel::query(); - - // 获取主模型表名 - $tableName = $mainModel::make()->getTable(); - - // 加载关联模型 - $relations = $configJson['data_source']['relations'] ?? []; - if (!empty($relations)) { - foreach ($relations as $relation) { - $query->with($relation); - } - } - - // 应用条件 - $conditions = $configJson['conditions'] ?? []; - if (!empty($conditions['items'])) { - $logic = $conditions['logic'] ?? 'and'; - if ($logic === 'or') { - $query->where(function ($q) use ($conditions, $tableName) { - foreach ($conditions['items'] as $index => $item) { - if ($index === 0) { - $this->applyCondition($q, $item, 'and', 0, $tableName); - } else { - $q->orWhere(function ($subQ) use ($item, $tableName) { - $this->applyCondition($subQ, $item, 'and', 0, $tableName); - }); - } - } - }); - } else { - foreach ($conditions['items'] as $item) { - $this->applyCondition($query, $item, 'and', 0, $tableName); - } - } - } - - // 执行统计 - $statistics = $configJson['statistics'] ?? []; - $statisticsType = $statistics['type'] ?? 'count'; - $groupBy = $statistics['group_by'] ?? null; - // 确保空字符串也被视为不分组 - $groupBy = !empty($groupBy) ? $groupBy : null; - - // 保存原始查询用于获取列表 - $listQuery = clone $query; - - if ($groupBy) { - // 分组统计 - $groupParts = explode('.', $groupBy); - - if (count($groupParts) > 1) { - // 关联模型字段分组,需要 join - $relationName = $groupParts[0]; - $fieldName = $groupParts[1]; - $relationModel = $this->getRelationModel($mainModel, $relationName); - if ($relationModel) { - $relationTable = $relationModel::make()->getTable(); - $relationKey = $this->getRelationKey($mainModel, $relationName); - $query->join($relationTable, $tableName . '.' . $relationKey, '=', $relationTable . '.id'); - $selectFields = [$relationTable . '.' . $fieldName . ' as group_value']; - } else { - $selectFields = [$groupBy . ' as group_value']; - } - } else { - // 主模型字段分组 - $selectFields = [$tableName . '.' . $groupBy . ' as group_value']; - } - - // 根据统计类型构建 SQL - $statisticsField = $statistics['field'] ?? null; - if ($statisticsType === 'sum' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - // 如果还没有 join,需要 join - $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $selectFields[] = DB::raw('SUM(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); - } else { - $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); - } - } else { - $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); - } - } elseif ($statisticsType === 'max' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - // 如果还没有 join,需要 join - $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $selectFields[] = DB::raw('MAX(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); - } else { - $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); - } - } else { - $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); - } - } elseif ($statisticsType === 'min' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - // 如果还没有 join,需要 join - $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $selectFields[] = DB::raw('MIN(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); - } else { - $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); - } - } else { - $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); - } - } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { - // 去重数量统计 - $distinctField = $statistics['distinct_field']; - // 处理关联模型字段 - $fieldParts = explode('.', $distinctField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - // 如果还没有 join,需要 join - $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $selectFields[] = DB::raw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); - } else { - $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); - } - } else { - $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); - } - } else { - // count 统计总数量 - $selectFields[] = DB::raw('COUNT(*) as total'); - } - - $query->select($selectFields); - - if (count($groupParts) > 1) { - $query->groupBy($relationTable . '.' . $fieldName); - } else { - $query->groupBy($tableName . '.' . $groupBy); - } - } else { - // 不分组统计,获取所有数据列表 - // 列表查询保持原样,获取所有符合条件的记录 - } - - // 排序(分组统计时对分组结果排序,不分组时对列表排序) - if (!empty($statistics['order_by'])) { - $orderField = $statistics['order_by']['field'] ?? ($groupBy ? 'total' : 'id'); - $orderDirection = $statistics['order_by']['direction'] ?? 'desc'; - $query->orderBy($orderField, $orderDirection); - if (!$groupBy) { - // 不分组时,列表查询也需要排序 - $listQuery->orderBy($orderField, $orderDirection); - } - } else { - // 没有指定排序时,不分组情况使用默认排序 - if (!$groupBy) { - $listQuery->orderBy('id', 'desc'); - } - } - - // 获取统计查询的 SQL 语句(在执行查询前) - $statisticsSql = $query->toSql(); - $statisticsBindings = $query->getBindings(); - $statisticsSqlFull = $this->getFullSql($statisticsSql, $statisticsBindings); - - // 获取分组统计结果列表(分页) - // 先获取所有结果用于计算统计结果 - $allResults = $query->get(); - - // 对结果进行分页处理 - $totalCount = $allResults->count(); - $pagedResults = $allResults->slice(($page - 1) * $pageSize, $pageSize); - - // 格式化分组统计结果 - $data = []; - foreach ($pagedResults as $item) { - $row = [ - 'group_value' => $item->group_value ?? null, - 'total' => round($item->total, $config->decimal_places) - ]; - $data[] = $row; - } - - // 计算统计结果 - $statisticsResult = null; - $pagination = null; - - if ($groupBy) { - // 分组统计:根据统计类型计算最终结果 - $statisticsField = $statistics['field'] ?? null; - if ($statisticsType === 'sum' && $statisticsField) { - // 分组求和时,统计结果是所有分组的总和 - $allTotalValue = 0; - foreach ($allResults as $item) { - $allTotalValue += $item->total; - } - $statisticsResult = round($allTotalValue, $config->decimal_places); - } elseif ($statisticsType === 'max' && $statisticsField) { - // 分组最大值时,统计结果是所有分组中的最大值 - $maxValue = null; - foreach ($allResults as $item) { - if ($maxValue === null || $item->total > $maxValue) { - $maxValue = $item->total; - } - } - $statisticsResult = $maxValue !== null ? round($maxValue, $config->decimal_places) : 0; - } elseif ($statisticsType === 'min' && $statisticsField) { - // 分组最小值时,统计结果是所有分组中的最小值 - $minValue = null; - foreach ($allResults as $item) { - if ($minValue === null || $item->total < $minValue) { - $minValue = $item->total; - } - } - $statisticsResult = $minValue !== null ? round($minValue, $config->decimal_places) : 0; - } else { - // 分组计数时,统计结果是所有分组的计数总和 - $allTotalValue = 0; - foreach ($allResults as $item) { - $allTotalValue += $item->total; - } - $statisticsResult = $allTotalValue; - } - - // 分页信息 - $pagination = [ - 'current_page' => $page, - 'page_size' => $pageSize, - 'total' => $totalCount, - 'total_pages' => ceil($totalCount / $pageSize) - ]; - } else { - // 不分组统计:先获取 SQL,再执行查询 - // 克隆查询用于获取 SQL(避免执行后无法获取) - $calcQuery = clone $listQuery; - $listQueryForData = clone $listQuery; - - // 根据统计类型计算统计值 - $statisticsField = $statistics['field'] ?? null; - if ($statisticsType === 'sum' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $statisticsResult = round($calcQuery->sum($fieldRelationTable . '.' . $fieldFieldName), $config->decimal_places); - } else { - $statisticsResult = round($listQuery->sum($statisticsField), $config->decimal_places); - } - } else { - $statisticsResult = round($listQuery->sum($statisticsField), $config->decimal_places); - } - } elseif ($statisticsType === 'max' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $statisticsResult = round($calcQuery->max($fieldRelationTable . '.' . $fieldFieldName), $config->decimal_places); - } else { - $statisticsResult = round($listQuery->max($statisticsField), $config->decimal_places); - } - } else { - $statisticsResult = round($listQuery->max($statisticsField), $config->decimal_places); - } - } elseif ($statisticsType === 'min' && $statisticsField) { - // 处理关联模型字段 - $fieldParts = explode('.', $statisticsField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $statisticsResult = round($calcQuery->min($fieldRelationTable . '.' . $fieldFieldName), $config->decimal_places); - } else { - $statisticsResult = round($listQuery->min($statisticsField), $config->decimal_places); - } - } else { - $statisticsResult = round($listQuery->min($statisticsField), $config->decimal_places); - } - } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { - // 去重数量统计 - $distinctField = $statistics['distinct_field']; - // 处理关联模型字段 - $fieldParts = explode('.', $distinctField); - if (count($fieldParts) > 1) { - $fieldRelationName = $fieldParts[0]; - $fieldFieldName = $fieldParts[1]; - $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); - if ($fieldRelationModel) { - $fieldRelationTable = $fieldRelationModel::make()->getTable(); - $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); - $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); - $statisticsResult = $calcQuery->selectRaw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total')->value('total') ?? 0; - } else { - $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $distinctField . ') as total')->value('total') ?? 0; - } - } else { - $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total')->value('total') ?? 0; - } - } else { - // count 统计总数量 - $statisticsResult = $listQuery->count(); - } - - // 获取统计查询的 SQL(在计算完统计值之后,确保包含所有修改) - $statisticsSqlForCalc = $calcQuery->toSql(); - $statisticsBindingsForCalc = $calcQuery->getBindings(); - $statisticsSqlFull = $this->getFullSql($statisticsSqlForCalc, $statisticsBindingsForCalc); + // 调用模型中的统计方法 + $params = [ + 'page' => isset($all['page']) ? (int) $all['page'] : 1, + 'page_size' => isset($all['page_size']) ? (int) $all['page_size'] : 10, + 'show_type' => isset($all['show_type']) ? $all['show_type'] : 'statistics', + ]; - // 获取列表总数(用于分页) - $listTotalCount = $listQueryForData->count(); - - // 不分组时,获取分页数据作为列表 - $listResults = $listQueryForData->skip(($page - 1) * $pageSize)->take($pageSize)->get(); - $data = []; - foreach ($listResults as $item) { - $data[] = $item->toArray(); - } - - // 分页信息 - $pagination = [ - 'current_page' => $page, - 'page_size' => $pageSize, - 'total' => $listTotalCount, - 'total_pages' => ceil($listTotalCount / $pageSize) - ]; - } + $result = $config->calculateStatistics($params); - return $this->success([ - 'sql' => $statisticsSqlFull, - 'list' => $data, - 'total' => $statisticsResult, - 'pagination' => $pagination, - 'config' => $config - ]); + return $this->success($result); } catch (\Exception $exception) { return $this->fail([$exception->getCode(), $exception->getMessage()]); } } - /** - * 获取模型实例 - */ - private function getModel($modelName) - { - $models = [ - 'user' => User::class, - 'company' => Company::class, - 'course_sign' => CourseSign::class, - 'course' => Course::class, - 'course_type' => CourseType::class, - ]; - return $models[$modelName] ?? null; - } - - /** - * 应用查询条件 - */ - private function applyCondition($query, $condition, $logic, $index, $tableName = null) - { - $key = $condition['key'] ?? ''; - $operator = $condition['operator'] ?? 'eq'; - $value = $condition['value'] ?? ''; - - if (empty($key)) { - return; - } - - // 处理关联模型的字段 - $keyParts = explode('.', $key); - if (count($keyParts) > 1) { - // 关联模型字段,使用 whereHas - $relationName = $keyParts[0]; - $fieldName = $keyParts[1]; - $query->whereHas($relationName, function ($q) use ($fieldName, $operator, $value) { - $this->applyOperator($q, $fieldName, $operator, $value); - }); - return; - } - - // 主模型字段,添加表名前缀避免字段歧义 - $qualifiedKey = $tableName ? $tableName . '.' . $key : $key; - $this->applyOperator($query, $qualifiedKey, $operator, $value); - } - - /** - * 获取关联模型 - */ - private function getRelationModel($mainModel, $relationName) - { - $model = new $mainModel(); - if (method_exists($model, $relationName)) { - $relation = $model->$relationName(); - return get_class($relation->getRelated()); - } - return null; - } - - /** - * 获取关联键名 - */ - private function getRelationKey($mainModel, $relationName) - { - $model = new $mainModel(); - if (method_exists($model, $relationName)) { - $relation = $model->$relationName(); - return $relation->getForeignKeyName(); - } - return 'id'; - } - - /** - * 获取完整的 SQL 语句(包含绑定参数) - */ - private function getFullSql($sql, $bindings) - { - if (empty($bindings)) { - return $sql; - } - - $fullSql = $sql; - foreach ($bindings as $binding) { - $value = is_numeric($binding) ? $binding : "'" . addslashes($binding) . "'"; - $fullSql = preg_replace('/\?/', $value, $fullSql, 1); - } - - return $fullSql; - } - - /** - * 应用操作符 - */ - private function applyOperator($query, $key, $operator, $value) - { - switch ($operator) { - case 'eq': - $query->where($key, $value); - break; - case 'neq': - $query->where($key, '!=', $value); - break; - case 'gt': - $query->where($key, '>', $value); - break; - case 'egt': - $query->where($key, '>=', $value); - break; - case 'lt': - $query->where($key, '<', $value); - break; - case 'elt': - $query->where($key, '<=', $value); - break; - case 'like': - $query->where($key, 'like', '%' . $value . '%'); - break; - case 'notlike': - $query->where($key, 'not like', '%' . $value . '%'); - break; - case 'in': - $array = explode(',', $value); - $query->whereIn($key, $array); - break; - case 'notin': - $array = explode(',', $value); - $query->whereNotIn($key, $array); - break; - case 'between': - list($from, $to) = explode(',', $value); - if (!empty($from) && !empty($to)) { - $query->whereBetween($key, [$from, $to]); - } - break; - case 'notbetween': - list($from, $to) = explode(',', $value); - if (!empty($from) && !empty($to)) { - $query->whereNotBetween($key, [$from, $to]); - } - break; - case 'isnull': - $query->whereNull($key); - break; - case 'isnotnull': - $query->whereNotNull($key); - break; - } - } - } diff --git a/app/Models/StatisticsConfig.php b/app/Models/StatisticsConfig.php index 62cc6a7..24fec25 100644 --- a/app/Models/StatisticsConfig.php +++ b/app/Models/StatisticsConfig.php @@ -2,6 +2,8 @@ namespace App\Models; +use Illuminate\Support\Facades\DB; + class StatisticsConfig extends SoftDeletesModel { @@ -9,4 +11,577 @@ class StatisticsConfig extends SoftDeletesModel 'config_json' => 'json', ]; + /** + * 根据配置执行统计计算 + * + * @param array $params 参数数组,包含:page, page_size, show_type + * @return array 返回统计结果数组 + * @throws \Exception + */ + public function calculateStatistics($params = []) + { + $page = isset($params['page']) ? (int) $params['page'] : 1; + $pageSize = isset($params['page_size']) ? (int) $params['page_size'] : 10; + $showType = isset($params['show_type']) ? $params['show_type'] : 'statistics'; + if (!in_array($showType, ['statistics', 'list'])) { + $showType = 'statistics'; + } + + $configJson = $this->config_json; + if (empty($configJson)) { + throw new \Exception('配置数据为空'); + } + + // 如果 config_json 是字符串,尝试解析 + if (is_string($configJson)) { + $configJson = json_decode($configJson, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception('配置 JSON 格式错误:' . json_last_error_msg()); + } + } + + // 检查数据结构 + if (!isset($configJson['data_source'])) { + throw new \Exception('配置数据缺少 data_source 字段,当前配置键:' . implode(', ', array_keys($configJson))); + } + + // 获取主模型 + $mainModelName = $configJson['data_source']['main_model'] ?? ''; + if (empty($mainModelName)) { + $dataSourceKeys = isset($configJson['data_source']) ? implode(', ', array_keys($configJson['data_source'])) : '无'; + throw new \Exception('主模型名称未配置,data_source 中的键:' . $dataSourceKeys); + } + $mainModel = $this->getModel($mainModelName); + if (empty($mainModel)) { + throw new \Exception('主模型不存在:' . $mainModelName . ',可用的模型:user, company, course_sign, course, course_type'); + } + + $query = $mainModel::query(); + + // 获取主模型表名 + $tableName = $mainModel::make()->getTable(); + + // 加载关联模型 + $relations = $configJson['data_source']['relations'] ?? []; + if (!empty($relations)) { + foreach ($relations as $relation) { + $query->with($relation); + } + } + + // 应用条件 + $conditions = $configJson['conditions'] ?? []; + if (!empty($conditions['items'])) { + $logic = $conditions['logic'] ?? 'and'; + if ($logic === 'or') { + $query->where(function ($q) use ($conditions, $tableName) { + foreach ($conditions['items'] as $index => $item) { + if ($index === 0) { + $this->applyCondition($q, $item, 'and', 0, $tableName); + } else { + $q->orWhere(function ($subQ) use ($item, $tableName) { + $this->applyCondition($subQ, $item, 'and', 0, $tableName); + }); + } + } + }); + } else { + foreach ($conditions['items'] as $item) { + $this->applyCondition($query, $item, 'and', 0, $tableName); + } + } + } + + // 执行统计 + $statistics = $configJson['statistics'] ?? []; + $statisticsType = $statistics['type'] ?? 'count'; + $groupBy = $statistics['group_by'] ?? null; + // 确保空字符串也被视为不分组 + $groupBy = !empty($groupBy) ? $groupBy : null; + + // 保存原始查询用于获取列表 + $listQuery = clone $query; + + if ($groupBy) { + // 分组统计 + $groupParts = explode('.', $groupBy); + + if (count($groupParts) > 1) { + // 关联模型字段分组,需要 join + $relationName = $groupParts[0]; + $fieldName = $groupParts[1]; + $relationModel = $this->getRelationModel($mainModel, $relationName); + if ($relationModel) { + $relationTable = $relationModel::make()->getTable(); + $relationKey = $this->getRelationKey($mainModel, $relationName); + $query->join($relationTable, $tableName . '.' . $relationKey, '=', $relationTable . '.id'); + $selectFields = [$relationTable . '.' . $fieldName . ' as group_value']; + } else { + $selectFields = [$groupBy . ' as group_value']; + } + } else { + // 主模型字段分组 + $selectFields = [$tableName . '.' . $groupBy . ' as group_value']; + } + + // 根据统计类型构建 SQL + $statisticsField = $statistics['field'] ?? null; + if ($statisticsType === 'sum' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + // 如果还没有 join,需要 join + $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $selectFields[] = DB::raw('SUM(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); + } else { + $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); + } + } else { + $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); + } + } elseif ($statisticsType === 'max' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + // 如果还没有 join,需要 join + $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $selectFields[] = DB::raw('MAX(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); + } else { + $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); + } + } else { + $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); + } + } elseif ($statisticsType === 'min' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + // 如果还没有 join,需要 join + $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $selectFields[] = DB::raw('MIN(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); + } else { + $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); + } + } else { + $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); + } + } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { + // 去重数量统计 + $distinctField = $statistics['distinct_field']; + // 处理关联模型字段 + $fieldParts = explode('.', $distinctField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + // 如果还没有 join,需要 join + $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $selectFields[] = DB::raw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); + } else { + $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); + } + } else { + $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); + } + } else { + // count 统计总数量 + $selectFields[] = DB::raw('COUNT(*) as total'); + } + + $query->select($selectFields); + + if (count($groupParts) > 1) { + $query->groupBy($relationTable . '.' . $fieldName); + } else { + $query->groupBy($tableName . '.' . $groupBy); + } + } else { + // 不分组统计,获取所有数据列表 + // 列表查询保持原样,获取所有符合条件的记录 + } + + // 排序(分组统计时对分组结果排序,不分组时对列表排序) + if (!empty($statistics['order_by'])) { + $orderField = $statistics['order_by']['field'] ?? ($groupBy ? 'total' : 'id'); + $orderDirection = $statistics['order_by']['direction'] ?? 'desc'; + $query->orderBy($orderField, $orderDirection); + if (!$groupBy) { + // 不分组时,列表查询也需要排序 + $listQuery->orderBy($orderField, $orderDirection); + } + } else { + // 没有指定排序时,不分组情况使用默认排序 + if (!$groupBy) { + $listQuery->orderBy('id', 'desc'); + } + } + + // 获取统计查询的 SQL 语句(在执行查询前) + $statisticsSql = $query->toSql(); + $statisticsBindings = $query->getBindings(); + $statisticsSqlFull = $this->getFullSql($statisticsSql, $statisticsBindings); + + // 获取分组统计结果列表(分页) + // 先获取所有结果用于计算统计结果 + $allResults = $query->get(); + + // 对结果进行分页处理 + $totalCount = $allResults->count(); + $pagedResults = $allResults->slice(($page - 1) * $pageSize, $pageSize); + + // 格式化分组统计结果 + $data = []; + foreach ($pagedResults as $item) { + $row = [ + 'group_value' => $item->group_value ?? null, + 'total' => round($item->total, $this->decimal_places) + ]; + $data[] = $row; + } + + // 计算统计结果 + $statisticsResult = null; + $pagination = null; + + if ($groupBy) { + // 分组统计:根据统计类型计算最终结果 + $statisticsField = $statistics['field'] ?? null; + if ($statisticsType === 'sum' && $statisticsField) { + // 分组求和时,统计结果是所有分组的总和 + $allTotalValue = 0; + foreach ($allResults as $item) { + $allTotalValue += $item->total; + } + $statisticsResult = round($allTotalValue, $this->decimal_places); + } elseif ($statisticsType === 'max' && $statisticsField) { + // 分组最大值时,统计结果是所有分组中的最大值 + $maxValue = null; + foreach ($allResults as $item) { + if ($maxValue === null || $item->total > $maxValue) { + $maxValue = $item->total; + } + } + $statisticsResult = $maxValue !== null ? round($maxValue, $this->decimal_places) : 0; + } elseif ($statisticsType === 'min' && $statisticsField) { + // 分组最小值时,统计结果是所有分组中的最小值 + $minValue = null; + foreach ($allResults as $item) { + if ($minValue === null || $item->total < $minValue) { + $minValue = $item->total; + } + } + $statisticsResult = $minValue !== null ? round($minValue, $this->decimal_places) : 0; + } else { + // 分组计数时,统计结果是所有分组的计数总和 + $allTotalValue = 0; + foreach ($allResults as $item) { + $allTotalValue += $item->total; + } + $statisticsResult = $allTotalValue; + } + + // 分页信息 + $pagination = [ + 'current_page' => $page, + 'page_size' => $pageSize, + 'total' => $totalCount, + 'total_pages' => ceil($totalCount / $pageSize) + ]; + } else { + // 不分组统计:先获取 SQL,再执行查询 + // 克隆查询用于获取 SQL(避免执行后无法获取) + $calcQuery = clone $listQuery; + $listQueryForData = clone $listQuery; + + // 根据统计类型计算统计值 + $statisticsField = $statistics['field'] ?? null; + if ($statisticsType === 'sum' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $statisticsResult = round($calcQuery->sum($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); + } else { + $statisticsResult = round($listQuery->sum($statisticsField), $this->decimal_places); + } + } else { + $statisticsResult = round($listQuery->sum($statisticsField), $this->decimal_places); + } + } elseif ($statisticsType === 'max' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $statisticsResult = round($calcQuery->max($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); + } else { + $statisticsResult = round($listQuery->max($statisticsField), $this->decimal_places); + } + } else { + $statisticsResult = round($listQuery->max($statisticsField), $this->decimal_places); + } + } elseif ($statisticsType === 'min' && $statisticsField) { + // 处理关联模型字段 + $fieldParts = explode('.', $statisticsField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $statisticsResult = round($calcQuery->min($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); + } else { + $statisticsResult = round($listQuery->min($statisticsField), $this->decimal_places); + } + } else { + $statisticsResult = round($listQuery->min($statisticsField), $this->decimal_places); + } + } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { + // 去重数量统计 + $distinctField = $statistics['distinct_field']; + // 处理关联模型字段 + $fieldParts = explode('.', $distinctField); + if (count($fieldParts) > 1) { + $fieldRelationName = $fieldParts[0]; + $fieldFieldName = $fieldParts[1]; + $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); + if ($fieldRelationModel) { + $fieldRelationTable = $fieldRelationModel::make()->getTable(); + $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); + $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); + $statisticsResult = $calcQuery->selectRaw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total')->value('total') ?? 0; + } else { + $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $distinctField . ') as total')->value('total') ?? 0; + } + } else { + $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total')->value('total') ?? 0; + } + } else { + // count 统计总数量 + $statisticsResult = $listQuery->count(); + } + + // 获取统计查询的 SQL(在计算完统计值之后,确保包含所有修改) + $statisticsSqlForCalc = $calcQuery->toSql(); + $statisticsBindingsForCalc = $calcQuery->getBindings(); + $statisticsSqlFull = $this->getFullSql($statisticsSqlForCalc, $statisticsBindingsForCalc); + + // 获取列表总数(用于分页) + $listTotalCount = $listQueryForData->count(); + + // 不分组时,获取分页数据作为列表 + $listResults = $listQueryForData->skip(($page - 1) * $pageSize)->take($pageSize)->get(); + $data = []; + foreach ($listResults as $item) { + $data[] = $item->toArray(); + } + + // 分页信息 + $pagination = [ + 'current_page' => $page, + 'page_size' => $pageSize, + 'total' => $listTotalCount, + 'total_pages' => ceil($listTotalCount / $pageSize) + ]; + } + + // 根据显示类型决定返回的数据 + if ($showType === 'statistics') { + // 只返回统计数据 + return [ + 'total' => $statisticsResult, + 'sql' => $statisticsSqlFull + ]; + } else { + // 只返回列表数据 + return [ + 'list' => $data, + 'pagination' => $pagination, + 'sql' => $statisticsSqlFull + ]; + } + } + + /** + * 获取模型实例 + */ + private function getModel($modelName) + { + $models = [ + 'user' => User::class, + 'company' => Company::class, + 'course_sign' => CourseSign::class, + 'course' => Course::class, + 'course_type' => CourseType::class, + ]; + return $models[$modelName] ?? null; + } + + /** + * 应用查询条件 + */ + private function applyCondition($query, $condition, $logic, $index, $tableName = null) + { + $key = $condition['key'] ?? ''; + $operator = $condition['operator'] ?? 'eq'; + $value = $condition['value'] ?? ''; + + if (empty($key)) { + return; + } + + // 处理关联模型的字段 + $keyParts = explode('.', $key); + if (count($keyParts) > 1) { + // 关联模型字段,使用 whereHas + $relationName = $keyParts[0]; + $fieldName = $keyParts[1]; + $query->whereHas($relationName, function ($q) use ($fieldName, $operator, $value) { + $this->applyOperator($q, $fieldName, $operator, $value); + }); + return; + } + + // 主模型字段,添加表名前缀避免字段歧义 + $qualifiedKey = $tableName ? $tableName . '.' . $key : $key; + $this->applyOperator($query, $qualifiedKey, $operator, $value); + } + + /** + * 获取关联模型 + */ + private function getRelationModel($mainModel, $relationName) + { + $model = new $mainModel(); + if (method_exists($model, $relationName)) { + $relation = $model->$relationName(); + return get_class($relation->getRelated()); + } + return null; + } + + /** + * 获取关联键名 + */ + private function getRelationKey($mainModel, $relationName) + { + $model = new $mainModel(); + if (method_exists($model, $relationName)) { + $relation = $model->$relationName(); + return $relation->getForeignKeyName(); + } + return 'id'; + } + + /** + * 获取完整的 SQL 语句(包含绑定参数) + */ + private function getFullSql($sql, $bindings) + { + if (empty($bindings)) { + return $sql; + } + + $fullSql = $sql; + foreach ($bindings as $binding) { + $value = is_numeric($binding) ? $binding : "'" . addslashes($binding) . "'"; + $fullSql = preg_replace('/\?/', $value, $fullSql, 1); + } + + return $fullSql; + } + + /** + * 应用操作符 + */ + private function applyOperator($query, $key, $operator, $value) + { + switch ($operator) { + case 'eq': + $query->where($key, $value); + break; + case 'neq': + $query->where($key, '!=', $value); + break; + case 'gt': + $query->where($key, '>', $value); + break; + case 'egt': + $query->where($key, '>=', $value); + break; + case 'lt': + $query->where($key, '<', $value); + break; + case 'elt': + $query->where($key, '<=', $value); + break; + case 'like': + $query->where($key, 'like', '%' . $value . '%'); + break; + case 'notlike': + $query->where($key, 'not like', '%' . $value . '%'); + break; + case 'in': + $array = explode(',', $value); + $query->whereIn($key, $array); + break; + case 'notin': + $array = explode(',', $value); + $query->whereNotIn($key, $array); + break; + case 'between': + list($from, $to) = explode(',', $value); + if (!empty($from) && !empty($to)) { + $query->whereBetween($key, [$from, $to]); + } + break; + case 'notbetween': + list($from, $to) = explode(',', $value); + if (!empty($from) && !empty($to)) { + $query->whereNotBetween($key, [$from, $to]); + } + break; + case 'isnull': + $query->whereNull($key); + break; + case 'isnotnull': + $query->whereNotNull($key); + break; + } + } + }