|
|
<?php
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
use App\Exports\BaseExport;
|
|
|
use App\Helpers\ResponseCode;
|
|
|
use App\Models\AppointmentType;
|
|
|
use App\Models\Article;
|
|
|
use App\Models\Book;
|
|
|
use App\Models\Calendar;
|
|
|
use App\Models\Company;
|
|
|
use App\Models\CourseContentEvaluationAsk;
|
|
|
use App\Models\CourseContentEvaluationForm;
|
|
|
use App\Models\CustomForm;
|
|
|
use App\Models\CustomFormField;
|
|
|
use App\Models\EmailTemplate;
|
|
|
use App\Models\StatisticsConfig;
|
|
|
use App\Models\SupplyDemand;
|
|
|
use App\Models\TimeEvent;
|
|
|
use App\Models\User;
|
|
|
use App\Models\CourseSign;
|
|
|
use App\Models\Course;
|
|
|
use App\Models\CourseType;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
|
use Rap2hpoutre\FastExcel\FastExcel;
|
|
|
|
|
|
class StatisticsConfigController extends BaseController
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
* 构造函数
|
|
|
*/
|
|
|
public function __construct()
|
|
|
{
|
|
|
parent::__construct(new StatisticsConfig());
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* @OA\Get(
|
|
|
* path="/api/admin/statistics-config/index",
|
|
|
* tags={"动态统计"},
|
|
|
* summary="列表",
|
|
|
* description="",
|
|
|
* @OA\Parameter(name="is_export", in="query", @OA\Schema(type="string"), required=false, description="是否导出0否1是"),
|
|
|
* @OA\Parameter(name="export_fields", in="query", @OA\Schema(type="string"), required=false, description="需要导出的字段数组"),
|
|
|
* @OA\Parameter(name="filter", in="query", @OA\Schema(type="string"), required=false, description="查询条件。数组"),
|
|
|
* @OA\Parameter(name="show_relation", in="query", @OA\Schema(type="string"), required=false, description="需要输出的关联关系数组,包括:teacher,courseSettings,coursePeriods"),
|
|
|
* @OA\Parameter(name="page_size", in="query", @OA\Schema(type="string"), required=false, description="每页显示的条数"),
|
|
|
* @OA\Parameter(name="page", in="query", @OA\Schema(type="string"), required=false, description="页码"),
|
|
|
* @OA\Parameter(name="sort_name", in="query", @OA\Schema(type="string"), required=false, description="排序字段名字"),
|
|
|
* @OA\Parameter(name="sort_type", 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="暂无"
|
|
|
* )
|
|
|
* )
|
|
|
*/
|
|
|
public function index()
|
|
|
{
|
|
|
$all = request()->all();
|
|
|
$list = $this->model->where(function ($query) use ($all) {
|
|
|
if (isset($all['filter']) && !empty($all['filter'])) {
|
|
|
foreach ($all['filter'] as $condition) {
|
|
|
$key = $condition['key'] ?? null;
|
|
|
$op = $condition['op'] ?? null;
|
|
|
$value = $condition['value'] ?? null;
|
|
|
if (!isset($key) || !isset($op) || !isset($value)) {
|
|
|
continue;
|
|
|
}
|
|
|
// 等于
|
|
|
if ($op == 'eq') {
|
|
|
$query->where($key, $value);
|
|
|
}
|
|
|
// 不等于
|
|
|
if ($op == 'neq') {
|
|
|
$query->where($key, '!=', $value);
|
|
|
}
|
|
|
// 大于
|
|
|
if ($op == 'gt') {
|
|
|
$query->where($key, '>', $value);
|
|
|
}
|
|
|
// 大于等于
|
|
|
if ($op == 'egt') {
|
|
|
$query->where($key, '>=', $value);
|
|
|
}
|
|
|
// 小于
|
|
|
if ($op == 'lt') {
|
|
|
$query->where($key, '<', $value);
|
|
|
}
|
|
|
// 小于等于
|
|
|
if ($op == 'elt') {
|
|
|
$query->where($key, '<=', $value);
|
|
|
}
|
|
|
// 模糊搜索
|
|
|
if ($op == 'like') {
|
|
|
$query->where($key, 'like', '%' . $value . '%');
|
|
|
}
|
|
|
// 否定模糊搜索
|
|
|
if ($op == 'notlike') {
|
|
|
$query->where($key, 'not like', '%' . $value . '%');
|
|
|
}
|
|
|
// 范围搜索
|
|
|
if ($op == 'range') {
|
|
|
list($from, $to) = explode(',', $value);
|
|
|
if (empty($from) || empty($to)) {
|
|
|
continue;
|
|
|
}
|
|
|
$query->whereBetween($key, [$from, $to]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
|
|
|
if (isset($all['is_export']) && !empty($all['is_export'])) {
|
|
|
$list = $list->get()->toArray();
|
|
|
$export_fields = $all['export_fields'] ?? [];
|
|
|
// 导出文件名字
|
|
|
$tableName = $this->model->getTable();
|
|
|
$filename = (new CustomForm())->getTableComment($tableName);
|
|
|
return Excel::download(new BaseExport($export_fields, $list, $tableName), $filename . date('YmdHis') . '.xlsx');
|
|
|
} else {
|
|
|
// 输出
|
|
|
$list = $list->paginate($all['page_size'] ?? 20);
|
|
|
}
|
|
|
return $this->success($list);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @OA\Get(
|
|
|
* path="/api/admin/statistics-config/show",
|
|
|
* tags={"动态统计"},
|
|
|
* summary="详情",
|
|
|
* description="",
|
|
|
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"),
|
|
|
* @OA\Parameter(name="show_relation", 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="暂无"
|
|
|
* )
|
|
|
* )
|
|
|
*/
|
|
|
public function show()
|
|
|
{
|
|
|
$all = \request()->all();
|
|
|
$messages = [
|
|
|
'id.required' => 'Id必填',
|
|
|
];
|
|
|
$validator = Validator::make($all, [
|
|
|
'id' => 'required'
|
|
|
], $messages);
|
|
|
if ($validator->fails()) {
|
|
|
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
|
|
|
}
|
|
|
$detail = $this->model->find($all['id']);
|
|
|
return $this->success($detail);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @OA\Post(
|
|
|
* path="/api/admin/statistics-config/save",
|
|
|
* tags={"动态统计"},
|
|
|
* summary="保存统计数据配置",
|
|
|
* description="根据传入的id决定是更新现有配置还是新增新的配置。",
|
|
|
* @OA\Parameter(name="id", in="query", @OA\Schema(type="integer"), required=false, description="配置ID(存在则更新,不存在则新增)"),
|
|
|
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), required=true, description="名字"),
|
|
|
* @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=false, description="标识key"),
|
|
|
* @OA\Parameter(name="decimal_places", in="query", @OA\Schema(type="integer"), required=false, description="小数点位数,默认0"),
|
|
|
* @OA\Parameter(name="description", in="query", @OA\Schema(type="string"), required=false, description="描述"),
|
|
|
* @OA\Parameter(name="config_json", in="query", @OA\Schema(type="string"), required=false, description="配置json,包含数据来源、条件设置、统计方式等配置,详见配置文档"),
|
|
|
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
|
|
|
* @OA\Response(
|
|
|
* response="200",
|
|
|
* description="操作成功"
|
|
|
* )
|
|
|
* )
|
|
|
*/
|
|
|
public function save()
|
|
|
{
|
|
|
$all = \request()->all();
|
|
|
DB::beginTransaction();
|
|
|
try {
|
|
|
if (isset($all['id'])) {
|
|
|
$model = $this->model->find($all['id']);
|
|
|
if (empty($model)) {
|
|
|
return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']);
|
|
|
}
|
|
|
} else {
|
|
|
$model = $this->model;
|
|
|
$all['admin_id'] = $this->getUserId();
|
|
|
$all['department_id'] = $this->getUser()->department_id;
|
|
|
}
|
|
|
$original = $model->getOriginal();
|
|
|
$model->fill($all);
|
|
|
$model->save();
|
|
|
DB::commit();
|
|
|
return $this->success($model);
|
|
|
} catch (\Exception $exception) {
|
|
|
DB::rollBack();
|
|
|
return $this->fail([$exception->getCode(), $exception->getMessage()]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @OA\Get(
|
|
|
* path="/api/admin/statistics-config/destroy",
|
|
|
* tags={"动态统计"},
|
|
|
* summary="删除",
|
|
|
* description="",
|
|
|
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"),
|
|
|
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
|
|
|
* @OA\Response(
|
|
|
* response="200",
|
|
|
* description="暂无"
|
|
|
* )
|
|
|
* )
|
|
|
*/
|
|
|
public function destroy()
|
|
|
{
|
|
|
return parent::destroy();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @OA\Get(
|
|
|
* path="/api/admin/statistics-config/calculate",
|
|
|
* tags={"动态统计"},
|
|
|
* summary="根据配置key执行统计",
|
|
|
* description="根据配置的key获取对应的统计配置,然后根据config_json的配置执行统计查询,返回数据列表和统计结果",
|
|
|
* @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="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
|
|
|
* @OA\Response(
|
|
|
* response="200",
|
|
|
* description="返回统计数据"
|
|
|
* )
|
|
|
* )
|
|
|
*/
|
|
|
public function calculate()
|
|
|
{
|
|
|
$all = \request()->all();
|
|
|
$messages = [
|
|
|
'key.required' => 'key必填',
|
|
|
];
|
|
|
$validator = Validator::make($all, [
|
|
|
'key' => 'required'
|
|
|
], $messages);
|
|
|
if ($validator->fails()) {
|
|
|
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);
|
|
|
|
|
|
// 获取列表总数(用于分页)
|
|
|
$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)
|
|
|
];
|
|
|
}
|
|
|
|
|
|
return $this->success([
|
|
|
'sql' => $statisticsSqlFull,
|
|
|
'list' => $data,
|
|
|
'total' => $statisticsResult,
|
|
|
'pagination' => $pagination,
|
|
|
'config' => $config
|
|
|
]);
|
|
|
|
|
|
} 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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|