lion 7 months ago
commit 68b49945df

@ -43,8 +43,8 @@ class UpdateCompany extends Command
public function handle()
{
$user_id = $this->option('user_id');
$updateLocal = (int)$this->option('address');
$updateMarket = (int)$this->option('market');
$updateLocal = (int) $this->option('address');
$updateMarket = (int) $this->option('market');
// 更新公司信息
$this->compnay($user_id);
// 更新经纬度信息(可选)
@ -227,11 +227,11 @@ class UpdateCompany extends Command
$bar->start();
// 上市代码正则:匹配全球各地上市公司股票代码后缀
// 支持的后缀:.SH,.SZ,.BJ,.TW,.HK,.SG,.US,.DE,.FR,.JP,.KR,.N,.O,.A,.PK,.Q,.TO,.AX,.L,-S,-B,-SB,-P,-Z,-W,-SW,-SWR,-R,-WR,-X,-SS,-RS,.WS,.U,.PR,.B,.DB,.UN,.RT,.WT,.E,.C,.D,.F,.G,.H,.I,.J,.K,.L,.M,.N,.O,.P,.V,.Y,.Z
// 简化匹配:只要字符串中包含分隔符(.或-)+指定后缀,就算上市
// 支持的后缀:.SH,.SZ,.BJ,.TW,.HK,.SG,.US,.DE,.FR,.JP,.KR,.N,.O,.A,.PK,.Q,.TO,.AX,.L,.WS,.U,.PR,.B,.DB,.UN,.RT,.WT,.E,.C,.D,.F,.G,.H,.I,.J,.K,.L,.M,.N,.O,.P,.V,.Y,.Z
// 简化匹配:只要字符串中包含分隔符(.)+指定后缀,就算上市(只匹配.开头的,不匹配-开头的)
// 支持格式688001.SH、AAPG.O、TSLA.US、华兴源创688001.SH
// 后缀按长度从长到短排序,避免短后缀误匹配
$stockCodePattern = '/[.\-](SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i';
$stockCodePattern = '/\.(SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i';
$updatedCount = 0;
foreach ($companies as $company) {

@ -11,6 +11,8 @@ use App\Models\Calendar;
use App\Models\Company;
use App\Models\CourseContentEvaluationAsk;
use App\Models\CourseContentEvaluationForm;
use App\Models\CourseSign;
use App\Models\CourseType;
use App\Models\CustomForm;
use App\Models\CustomFormField;
use App\Models\EmailTemplate;
@ -37,6 +39,8 @@ class CompanyController extends BaseController
* tags={"公司管理"},
* summary="参数",
* description="",
* @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=false, description="开始日期"),
* @OA\Parameter(name="end_date", 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",
@ -46,6 +50,9 @@ class CompanyController extends BaseController
*/
public function config()
{
// 如果提供了开始时间和结束时间,则计算统计
$start_date = request('start_date', CourseType::START_DATE);
$end_date = request('end_date', date('Y-m-d'));
// 企业标签
$companiesTags = Company::where('company_tag', '!=', '')->pluck('company_tag');
$companiesTags = $companiesTags->flatten()->implode(',');
@ -56,7 +63,17 @@ class CompanyController extends BaseController
});
$companiesTags = array_values($companiesTags);
return $this->success(compact('companiesTags'));
// 累计被投企业数(从起始日期到结束日期)
$course_signs_invested = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, null);
// 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量)
$company_invested_after_enrollment_total = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, null);
// 今年范围内被投企业数(在指定时间范围内报名的学员所在公司中,被投时间在年份范围内的公司数量)
$company_invested_year_total = CourseSign::companyInvestedYear($start_date, $end_date, null);
return $this->success(compact('companiesTags', 'course_signs_invested', 'company_invested_after_enrollment_total', 'company_invested_year_total'));
}
@ -88,11 +105,13 @@ class CompanyController extends BaseController
public function index()
{
$all = request()->all();
$list = $this->model->with(['users' => function ($query) use ($all) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
})->with('courseSigns.course');
}])->whereHas('users', function ($query) use ($all) {
$list = $this->model->with([
'users' => function ($query) use ($all) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
})->with('courseSigns.course');
}
])->whereHas('users', function ($query) use ($all) {
if (isset($all['course_type_id'])) {
$query->whereHas('courses', function ($q) use ($all) {
$q->where('type', $all['course_type_id']);
@ -211,11 +230,13 @@ class CompanyController extends BaseController
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$detail = $this->model->with(['users' => function ($query) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
})->with('courseSigns.course');
}])->find($all['id']);
$detail = $this->model->with([
'users' => function ($query) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
})->with('courseSigns.course');
}
])->find($all['id']);
return $this->success($detail);
}

@ -0,0 +1,209 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Exports\BaseExport;
use App\Helpers\ResponseCode;
use App\Models\CustomForm;
use App\Models\EmployeeParticipation;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
class EmployeeParticipationController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new EmployeeParticipation());
}
/**
* @OA\Get(
* path="/api/admin/employee-participations/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="需要输出的关联关系数组包括courseType"),
* @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/employee-participations/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/employee-participations/save",
* tags={"员工参与"},
* summary="保存",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=false, description="Id(存在更新,不存在新增)"),
* @OA\Parameter(name="type", in="query", @OA\Schema(type="integer"), required=true, description="类型1员工参与数2干部培训数"),
* @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string", format="date"), required=false, description="开始日期"),
* @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string", format="date"), required=false, description="结束日期"),
* @OA\Parameter(name="total", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="数量"),
* @OA\Parameter(name="course_type_id", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="课程类型ID"),
* @OA\Parameter(name="course_name", in="query", @OA\Schema(type="string", nullable=true), description="课程名称"),
* @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;
}
$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/employee-participations/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();
}
}

@ -221,8 +221,7 @@ class OtherController extends CommonController
$end_date = $params['end_date'];
$course_type_id = $params['course_type_id'];
$courses = $params['courses'];
// 被投企业数
$list['course_signs_invested'] = CourseSign::yhInvested($start_date, $end_date, $courses->pluck('id')->toArray());
// 报名人数
$list['course_signs_total'] = CourseSign::courseSignsTotal($start_date, $end_date, null, $courses->pluck('id'));
// 审核通过人数
@ -230,16 +229,33 @@ class OtherController extends CommonController
// 审核通过人数去重
$list['course_signs_pass_unique'] = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), null);
// 开课场次
// 开课场次
$calendar = Calendar::where(function ($query) use ($start_date, $end_date) {
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
})->where(function ($query) use ($course_type_id) {
// 条件1有 course_id 的数据,通过 course.type 匹配课程体系
// 条件2没有 course_id 的数据,直接用 course_type_id 字段匹配
// 两个条件是或关系
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('course_type_id', $course_type_id);
$course_type_id_array = is_array($course_type_id) ? $course_type_id : explode(',', $course_type_id);
// 条件1有 course_id 时,通过关联的 course.type 匹配
$query->where(function ($q) use ($course_type_id_array) {
$q->whereHas('course', function ($subQ) use ($course_type_id_array) {
$subQ->whereIn('type', $course_type_id_array);
});
});
// 条件2没有 course_id 时,直接用 course_type_id 字段匹配(或关系)
$query->orWhere(function ($q) use ($course_type_id_array) {
$q->whereIn('course_type_id', $course_type_id_array);
});
}
});
})->get();
$list['course_total'] = (clone $calendar)->count();
// 开课天数
$list['course_day_total'] = (clone $calendar)->where('is_count_days', 1)->sum('days');
@ -258,9 +274,15 @@ class OtherController extends CommonController
// 入学后上市公司数量(在指定时间范围内报名的学员所在公司中,在入学后上市的公司数量)
$list['company_market_after_enrollment_total'] = CourseSign::companyMarketAfterEnrollment($start_date, $end_date, $course_ids);
// 累计被投企业数
$list['course_signs_invested'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $course_ids);
// 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量)
$list['company_invested_after_enrollment_total'] = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids);
// 今年被投企业数
$list['company_invested_year_total'] = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids);
// 元和员工参与人数
$list['company_join_total'] = CourseSign::companyJoin($start_date, $end_date, $course_ids);
// 全市干部参与企业
@ -290,11 +312,11 @@ class OtherController extends CommonController
$courseTypesSum[] = [
'course_type' => $courseType->name,
// 培养人数
'course_type_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses2->pluck('id')),
'course_type_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses2->pluck('id'), false, false),
// 去重培养人数
'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'), false, false),
'course_name' => $course->name,
'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id]),
'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id], false, false),
// 跟班学员数量
'genban_total' => CourseSign::genban($start_date, $end_date, [$course->id]),
// 被投企业数
@ -305,29 +327,33 @@ class OtherController extends CommonController
}
}
// 附加历史课程数据
$historyCourses = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('type', $course_type_id);
$courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get();
foreach ($courseTypesHistory as $historyCourse) {
$courses3 = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where('type', $historyCourse->id)->get();
foreach ($courses3 as $course) {
$courseTypesSum[] = [
'course_type' => $historyCourse->name,
'course_name' => $course->course_name,
// 培养人数
'course_type_signs_pass' => $course->course_type_signs_pass,
// 去重培养人数
'course_type_signs_pass_unique' => $course->course_type_signs_pass_unique,
// 课程人数
'course_signs_pass' => $course->course_signs_pass,
// 跟班学员数量
'genban_total' => 0,
// 被投企业数
'yh_invested_total' => 0,
// 元禾同事数
'company_join_total' => 0,
];
}
})->get();
foreach ($historyCourses as $historyCourse) {
$courseTypesSum[] = [
'course_type' => $historyCourse->typeDetail->name,
// 培养人数
'course_type_signs_pass' => $historyCourses->where('type', $historyCourse->type)->sum('course_type_signs_pass'),
// 去重培养人数
'course_type_signs_pass_unique' => $historyCourses->where('type', $historyCourse->type)->sum('course_type_signs_pass_unique'),
'course_name' => $historyCourse->course_name,
'course_signs_pass' => $historyCourse->course_signs_pass,
];
}
// 区域明细统计
@ -341,7 +367,7 @@ class OtherController extends CommonController
* tags={"其他"},
* summary="课程统计明细导出",
* description="导出课程统计数据的明细",
* @OA\Parameter(name="export_type", in="query", @OA\Schema(type="string"), required=true, description="导出类型course_signs_invested-被投企业明细, course_signs_total-报名人数明细, course_signs_pass-审核通过人数明细, course_signs_pass_unique-审核通过人数去重明细, courseTypesSum-课程分类明细, areas-区域明细, company_market_total-上市公司明细, ganbu_total-跟班学员明细, company_market_year_total-今年上市公司明细, company_market_after_enrollment_total-入学后上市公司明细, company_invested_after_enrollment_total-入学后被投企业明细, course_total-开课场次明细, course_day_total-开课天数明细, company_join_total-元和员工参与企业明细, company_ganbu_total-全市干部参与企业明细, cover_head_total-苏州头部企业明细, cover_rencai_total-高层次人才明细, cover_stock_total-重点上市公司明细"),
* @OA\Parameter(name="export_type", in="query", @OA\Schema(type="string"), required=true, description="导出类型course_signs_invested-被投企业明细, course_signs_total-报名人数明细, course_signs_pass-审核通过人数明细, course_signs_pass_unique-审核通过人数去重明细, courseTypesSum-课程分类明细, areas-区域明细, company_market_total-上市公司明细, ganbu_total-跟班学员明细, company_market_year_total-今年上市公司明细, company_market_after_enrollment_total-入学后上市公司明细, company_invested_after_enrollment_total-入学后被投企业明细, company_invested_year_total-年份范围内被投企业明细, course_total-开课场次明细, course_day_total-开课天数明细, company_join_total-元和员工参与企业明细, company_ganbu_total-全市干部参与企业明细, cover_head_total-苏州头部企业明细, cover_rencai_total-高层次人才明细, cover_stock_total-重点上市公司明细"),
* @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=false, description="开始日期"),
* @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string"), required=false, description="结束日期"),
* @OA\Parameter(name="course_type_id", in="query", @OA\Schema(type="string"), required=false, description="课程体系id多个英文逗号"),
@ -374,7 +400,7 @@ class OtherController extends CommonController
switch ($export_type) {
case 'course_signs_invested':
// 被投企业明细 - 使用与coursesHome相同的算法
$companies = CourseSign::yhInvested($start_date, $end_date, $course_ids, true);
$companies = CourseSign::yhInvestedTotal($start_date, $end_date, $course_ids, true);
foreach ($companies as $company) {
$data[] = [
'company_name' => $company->company_name,
@ -830,6 +856,92 @@ class OtherController extends CommonController
$filename = '入学后被投企业明细';
break;
case 'company_invested_year_total':
// 年份范围内被投企业明细 - 所有年份范围内被投企业,关联学员、课程信息
// 数据结构:主表是公司,子数据是学员信息
// 导出时:公司信息只在第一行显示,后续行公司信息为空
$companiesData = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids->toArray(), true);
foreach ($companiesData as $item) {
$company = $item['company'];
$users = $item['users'] ?? [];
// 公司基本信息(只在第一行使用)
$companyInfo = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
'company_address' => $company->company_address ?? '',
'company_city' => $company->company_city ?? '',
'company_area' => $company->company_area ?? '',
'company_tag' => $company->company_tag ?? '',
];
if (empty($users)) {
// 如果没有学员报名记录,仍然导出公司基本信息
$data[] = array_merge($companyInfo, [
'user_name' => '',
'course_name' => '',
'course_type' => '',
]);
} else {
// 每个学员一行,多个课程合并显示
$isFirstRow = true;
foreach ($users as $userInfo) {
$courses = $userInfo['courses'] ?? [];
// 合并同一学员的多个课程:格式为"课程体系-课程名称,课程体系-课程名称"
$courseList = [];
foreach ($courses as $courseInfo) {
$courseType = $courseInfo['course_type'] ?? '';
$courseName = $courseInfo['course_name'] ?? '';
if ($courseType && $courseName) {
$courseList[] = $courseType . '-' . $courseName;
} elseif ($courseName) {
$courseList[] = $courseName;
}
}
$courseDisplay = implode("\r\n", $courseList);
if ($isFirstRow) {
// 第一行:显示公司信息
$data[] = array_merge($companyInfo, [
'user_name' => $userInfo['user_name'] ?? '',
'course_name' => $courseDisplay,
'course_type' => '', // 课程类型已合并到课程名称中
]);
$isFirstRow = false;
} else {
// 后续行:公司信息为空
$data[] = [
'company_name' => '',
'company_legal_representative' => '',
'company_date' => '',
'company_address' => '',
'company_city' => '',
'company_area' => '',
'company_tag' => '',
'user_name' => $userInfo['user_name'] ?? '',
'course_name' => $courseDisplay,
];
}
}
}
}
$fields = [
'company_name' => '企业名称',
'company_legal_representative' => '法人',
'company_date' => '成立时间',
'company_address' => '地址',
'company_city' => '所在城市',
'company_area' => '所在区域',
'company_tag' => '企业资质',
'user_name' => '学员姓名',
'course_name' => '课程信息',
];
$filename = '年份范围内被投企业明细';
break;
case 'course_total':
// 开课场次明细 - 与coursesHome算法一致
$calendars = Calendar::whereBetween('date', [$start_date, $end_date])

@ -210,10 +210,10 @@ class UserController extends BaseController
$query->with('course.typeDetail')->orderBy('fee_status', 'desc');
}
])->withCount([
'appointments' => function ($query) {
$query->whereIn('status', [0, 1]);
}
]);
'appointments' => function ($query) {
$query->whereIn('status', [0, 1]);
}
]);
// 是否被投企业
if (isset($all['is_yh_invested'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
@ -287,7 +287,7 @@ class UserController extends BaseController
}
$list = $list->where(function ($query) use ($all) {
if (isset($all['from'])) {
$query->where('from', $all['from']);
$query->where('from', 'like', '%' . $all['from'] . '%');
}
if (isset($all['is_vip'])) {
$query->where('is_vip', $all['is_vip']);
@ -318,9 +318,7 @@ class UserController extends BaseController
$query->where('name', 'like', '%' . $all['name'] . '%');
}
if (isset($all['company_name'])) {
$query->whereHas('company', function ($query) use ($all) {
$query->where('company_name', 'like', '%' . $all['company_name'] . '%');
});
$query->where('company_name', 'like', '%' . $all['company_name'] . '%');
}
if (isset($all['company_position'])) {
$query->where('company_position', $all['company_position']);
@ -540,8 +538,7 @@ class UserController extends BaseController
} else {
if (in_array($k, ['company_type', 'type'])) {
$list[$key][$k] = str_replace('、', ',', $value[$v]);
$list[$key][$k] = str_replace('', ',', $list[$key][$k]);
;
$list[$key][$k] = str_replace('', ',', $list[$key][$k]);;
} else {
$list[$key][$k] = $value[$v];
}

@ -100,7 +100,7 @@ class CourseSign extends SoftDeletesModel
/**
* 指定时间内的报名信息(未去重)
*/
public static function courseSignsTotal($start_date, $end_date, $status = null, $course_ids = null, $retList = false)
public static function courseSignsTotal($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $needHistory = true)
{
$totalQuery = self::getStudentList($start_date, $end_date, $status, $course_ids);
if ($retList) {
@ -110,19 +110,24 @@ class CourseSign extends SoftDeletesModel
// 基础数据
$baseTotal = $totalQuery->count();
// 历史数据
$historyTotal = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('type', $course_type_id);
}
})->sum('course_type_signs_pass');
$historyTotal = 0;
if ($needHistory) {
$historyTotal = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->whereHas('typeDetail', function ($query) {
$query->where('is_history', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('type', $course_type_id);
}
})->sum('course_type_signs_pass');
}
// 返回统计数据
return $historyTotal + $baseTotal;
}
@ -131,7 +136,7 @@ class CourseSign extends SoftDeletesModel
/**
* 指定时间内的报名信息(去重)
*/
public static function courseSignsTotalByUnique($start_date, $end_date, $status = null, $course_ids = null, $retList = false)
public static function courseSignsTotalByUnique($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $needHistory = true)
{
$totalQuery = self::getStudentList($start_date, $end_date, $status, $course_ids);
$user = User::whereIn('id', $totalQuery->get()->pluck('user_id'))->groupBy('mobile')->get();
@ -140,25 +145,82 @@ class CourseSign extends SoftDeletesModel
return $user;
} else {
$baseTotal = $user->count();
// 历史数据
$historyTotal = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('type', $course_type_id);
}
})->sum('course_type_signs_pass_unique');
$historyTotal = 0;
if ($needHistory) {
// 历史数据
$historyTotal = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->whereHas('typeDetail', function ($query) {
$query->where('is_history', 1);
})->where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
})->where(function ($query) {
$course_type_id = request('course_type_id');
if ($course_type_id) {
$course_type_id = explode(',', $course_type_id);
$query->whereIn('type', $course_type_id);
}
})->sum('course_type_signs_pass_unique');
}
// 统计数据
return $baseTotal + $historyTotal;
}
}
/**
* 累计被投企业统计
* @param string|null $start_date 开始日期
* @param string|null $end_date 结束日期
* @param array|null $course_ids 课程ID仅在自定义时间时生效
* @param bool $retList 是否返回列表
*/
public static function yhInvestedTotal($start_date = null, $end_date = null, $course_ids, $retList = false)
{
// 默认时间:获取所有学员,不限制课程
$userIds = self::getStudentList($start_date, $end_date, 1, $course_ids)->get()->pluck('user_id');
// 获取这些学员所在的被投企业
$companies = Company::whereHas('users', function ($query) use ($userIds) {
$query->whereIn('id', $userIds);
})->where('is_yh_invested', 1)->get();
// 自定义时间:需要按被投时间筛选
// 筛选出被投时间在范围内的企业
$filteredCompanies = [];
foreach ($companies as $company) {
$projectUsers = $company->project_users ?? [];
$hasValidInvestDate = false;
$allInvestDatesNull = true;
foreach ($projectUsers as $item) {
$investDate = $item['investDate'] ?? null;
// 检查是否有有效的被投时间
if ($investDate) {
$allInvestDatesNull = false;
// 检查被投时间是否在范围内
if ($investDate <= $end_date) {
$hasValidInvestDate = true;
break; // 只要有一条满足就加入
}
}
}
// 如果有有效的被投时间在范围内或者所有被投时间都是null则加入结果
if ($hasValidInvestDate || $allInvestDatesNull) {
$filteredCompanies[] = $company;
}
}
$companies = collect($filteredCompanies);
// 返回结果
if ($retList) {
return $companies->values();
} else {
return $companies->count();
}
}
/**
* 被投企业统计
* @param string|null $start_date 开始日期
@ -183,12 +245,10 @@ class CourseSign extends SoftDeletesModel
$companies = Company::whereHas('users', function ($query) use ($userIds) {
$query->whereIn('id', $userIds);
})->where('is_yh_invested', 1)->get();
// 自定义时间:需要按被投时间筛选
if (!$isDefaultDate) {
$startDate = substr($start_date, 0, 10);
$endDate = substr($end_date, 0, 10);
// 筛选出被投时间在范围内的企业
$filteredCompanies = [];
foreach ($companies as $company) {
@ -213,6 +273,123 @@ class CourseSign extends SoftDeletesModel
}
}
/**
* 被投企业统计(统计或列表)- 按年份范围统计
* @param string|null $start_date 开始日期
* @param string|null $end_date 结束日期
* @param array|null $course_ids 课程ID数组不传则统计所有课程
* @param bool $retList 是否返回列表false返回数量true返回列表包含学员、课程信息
* @return int|array
*/
public static function companyInvestedYear($start_date = null, $end_date = null, $course_ids = null, $retList = false)
{
$courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids);
$courseSigns = $courseSignsQuery->with(['user.company', 'course.typeDetail'])->get();
// 获取所有被投企业的ID
$companyIds = $courseSigns->pluck('user.company.id')
->filter()
->unique()
->toArray();
// 计算年份范围
$years = [];
if ($start_date && $end_date) {
// 从开始和结束日期中提取年份范围
$startYear = (int)date('Y', strtotime($start_date));
$endYear = (int)date('Y', strtotime($end_date));
// 生成所有年份的数组
for ($year = $startYear; $year <= $endYear; $year++) {
$years[] = $year;
}
} else {
// 如果没有提供日期,使用当前年份
$years[] = (int)date('Y');
}
// 获取这些公司中标记为被投的公司
$allInvestedCompanies = Company::whereIn('id', $companyIds)
->where('is_yh_invested', 1)
->get();
// 筛选出被投时间在年份范围内的企业
$companies = [];
foreach ($allInvestedCompanies as $company) {
$projectUsers = $company->project_users ?? [];
$hasInvestInYears = false;
foreach ($projectUsers as $item) {
$investDate = $item['investDate'] ?? null;
if ($investDate) {
$investYear = (int)date('Y', strtotime($investDate));
if (in_array($investYear, $years)) {
$hasInvestInYears = true;
break;
}
}
}
if ($hasInvestInYears) {
$companies[$company->id] = $company;
}
}
$companies = collect($companies);
if ($retList) {
// 返回详细列表:主表是公司,子数据是学员信息
$result = [];
foreach ($courseSigns as $courseSign) {
if (!$courseSign->user || !$courseSign->user->company) {
continue;
}
$companyId = $courseSign->user->company->id;
// 只处理年份范围内被投企业的记录
if (!isset($companies[$companyId])) {
continue;
}
$company = $companies[$companyId];
// 如果公司还没有在结果中,初始化
if (!isset($result[$companyId])) {
$result[$companyId] = [
'company' => $company,
'users' => [],
];
}
// 按学员分组,收集每个学员的课程信息
$userId = $courseSign->user->id;
if (!isset($result[$companyId]['users'][$userId])) {
$result[$companyId]['users'][$userId] = [
'user' => $courseSign->user,
'user_name' => $courseSign->user->name ?? '',
'mobile' => $courseSign->user->mobile ?? '',
'courses' => [],
];
}
// 添加该学员的课程信息
$result[$companyId]['users'][$userId]['courses'][] = [
'course_name' => $courseSign->course->name ?? '',
'course_type' => $courseSign->course->typeDetail->name ?? '',
'course_sign' => $courseSign,
];
}
// 将 users 转换为数组(去掉 user_id 作为 key
foreach ($result as $companyId => $item) {
$result[$companyId]['users'] = array_values($item['users']);
}
// 转换为数组并返回
return array_values($result);
} else {
// 返回统计数据
return $companies->count();
}
}
/**
* 跟班学员(统计或列表)
* @param string $start_date 开始日期
@ -408,13 +585,8 @@ class CourseSign extends SoftDeletesModel
'信诚管理咨询',
'集成电路公司',
'常州团队',
'国元禾'
'国元禾'
];
// $company = Company::where(function ($query) use ($companyNameKeyword) {
// foreach ($companyNameKeyword as $item) {
// $query->orWhere('company_name', 'like', '%' . $item . '%');
// }
// })->get();
$list = User::whereIn('id', $courseSignByType->pluck('user_id'))
->where(function ($query) use ($companyNameKeyword) {
foreach ($companyNameKeyword as $item) {
@ -442,9 +614,15 @@ class CourseSign extends SoftDeletesModel
public static function ganbu($start_date = null, $end_date = null, $course_ids = null, $retList = false)
{
$courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids);
// 获取需要统计跟班学员的课程
$genbanCourse = Course::whereHas('typeDetail', function ($query) {
$query->where('is_count_genban', 1);
})->get();
$courseSigns = $courseSignsQuery->whereHas('user', function ($query) {
$query->where('from', 'like', '%跟班学员%');
})->get();
})->whereIn('course_id', $genbanCourse->pluck('id'))->get();
if ($retList) {
return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get();
} else {
@ -684,3 +862,4 @@ class CourseSign extends SoftDeletesModel
}

@ -6,7 +6,7 @@ namespace App\Models;
class CourseType extends SoftDeletesModel
{
const START_DATE = '2020-01-01 00:00:00';
const START_DATE = '2000-01-01';
protected $appends = ['is_count_genban_text'];

@ -0,0 +1,34 @@
<?php
namespace App\Models;
class EmployeeParticipation extends CommonModel
{
protected $table = 'employee_participations';
protected $appends = ['type_text'];
public static $intToString = [
'type' => [
1 => '员工参与数',
2 => '干部培训数',
],
];
/**
* 获取类型文本
*/
public function getTypeTextAttribute()
{
return self::$intToString['type'][$this->type] ?? '';
}
/**
* 关联课程类型
*/
public function courseType()
{
return $this->hasOne(CourseType::class, 'id', 'course_type_id');
}
}

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('employee_participations', function (Blueprint $table) {
$table->comment('员工参与表');
$table->increments('id');
$table->tinyInteger('type')->nullable()->comment('类型1员工参与数2干部培训数');
$table->date('start_date')->nullable()->comment('开始日期');
$table->date('end_date')->nullable()->comment('结束日期');
$table->integer('total')->nullable()->comment('数量');
$table->integer('course_type_id')->nullable()->comment('课程类型ID');
$table->string('course_name')->nullable()->comment('课程名称');
$table->dateTime('created_at')->nullable();
$table->dateTime('updated_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('employee_participations');
}
};

@ -263,6 +263,11 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::post('history-courses/save', [\App\Http\Controllers\Admin\HistoryCourseController::class, "save"]);
Route::get('history-courses/destroy', [\App\Http\Controllers\Admin\HistoryCourseController::class, "destroy"]);
// 员工参与
Route::get('employee-participations/index', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "index"]);
Route::get('employee-participations/show', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "show"]);
Route::post('employee-participations/save', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "save"]);
Route::get('employee-participations/destroy', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "destroy"]);
// 统计数据配置管理
Route::get('statistics-configs/index', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "index"]);

@ -1,550 +0,0 @@
# 用户统计数据配置 JSON 结构说明
## 概述
`user_statistics_configs` 表的 `config_json` 字段用于存储动态统计配置,包含三个主要部分:数据来源、条件设置、统计方式。
## JSON 结构
```json
{
"data_source": {
"main_model": "user|company|course_sign|course|course_type",
"relations": ["user", "company", "course_sign", "course", "course_type"]
},
"conditions": {
"logic": "and|or",
"items": [
{
"key": "字段名",
"operator": "操作类型",
"value": "值"
}
]
},
"statistics": {
"type": "sum|max|min|count|count_distinct",
"field": "统计字段sum/max/min 时使用,可选)",
"distinct_field": "去重字段count_distinct 时使用,可选)",
"group_by": "分组字段(可选,不设置则不分组)",
"order_by": {
"field": "排序字段(可选)",
"direction": "asc|desc"
}
}
}
```
---
## 一、数据来源data_source
### 1.1 主模型main_model
**说明**:指定统计数据的主要来源模型。
**可选值**
- `user` - 用户模型
- `company` - 公司模型
- `course_sign` - 报名模型
- `course` - 课程模型
- `course_type` - 课程分类模型
**示例**
```json
{
"main_model": "user"
}
```
### 1.2 关联模型relations
**说明**:指定需要关联的其他模型,可以关联多个模型。
**可选值**(数组):
- `user` - 用户模型
- `company` - 公司模型
- `course_sign` - 报名模型
- `course` - 课程模型
- `course_type` - 课程分类模型
**注意**
- 关联模型不能包含主模型本身
- 可以关联多个模型
- 数组可以为空
**示例**
```json
{
"relations": ["company", "course_sign"]
}
```
---
## 二、条件设置conditions
### 2.1 逻辑关系logic
**说明**:指定多个条件之间的逻辑关系。
**可选值**
- `and` - 所有条件都必须满足AND
- `or` - 至少一个条件满足OR
**示例**
```json
{
"logic": "and"
}
```
### 2.2 条件项items
**说明**:条件数组,每个条件包含键名、操作类型和值。
**条件项结构**
```json
{
"key": "字段名",
"operator": "操作类型",
"value": "值"
}
```
#### 字段说明
- **key**(字符串):要查询的字段名
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `company.name`
- **operator**(字符串):操作类型
- `eq` - 等于
- `neq` - 不等于
- `gt` - 大于
- `egt` - 大于等于
- `lt` - 小于
- `elt` - 小于等于
- `like` - 模糊匹配
- `notlike` - 不匹配
- `in` - 在范围内(值为逗号分隔的字符串)
- `notin` - 不在范围内
- `between` - 在范围内(值为逗号分隔的两个值)
- `notbetween` - 不在范围内
- `isnull` - 为空value 可省略)
- `isnotnull` - 不为空value 可省略)
- **value**(字符串/数字/数组):条件值
- 根据操作类型不同,值的形式也不同
- `in` 操作:值为逗号分隔的字符串,如 `"1,2,3"`
- `between` 操作:值为逗号分隔的两个值,如 `"2024-01-01,2024-12-31"`
- `isnull``isnotnull` 操作value 可以省略
**示例**
```json
{
"logic": "and",
"items": [
{
"key": "is_schoolmate",
"operator": "eq",
"value": "1"
},
{
"key": "company.is_yh_invested",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
}
```
---
## 三、统计方式statistics
### 3.1 统计类型type
**说明**:指定统计的方式。
**可选值**
- `sum` - 求和(需要指定 `field` 字段)
- `max` - 最大值(需要指定 `field` 字段)
- `min` - 最小值(需要指定 `field` 字段)
- `count` - 统计总数量(不需要指定 `field` 字段)
- `count_distinct` - 统计去重数量(需要指定 `distinct_field` 字段)
**示例**
```json
{
"type": "count"
}
```
### 3.2 统计字段field
**说明**:当统计类型为 `sum`、`max` 或 `min` 时,指定要统计的字段名。
**注意**
- `type``sum`、`max`、`min` 时必须指定 `field`
- `type``count` 时可以省略 `field`
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `company.company_fund`
**示例**
```json
{
"type": "sum",
"field": "company_fund"
}
```
```json
{
"type": "max",
"field": "company.company_fund"
}
```
```json
{
"type": "min",
"field": "created_at"
}
```
### 3.3 去重字段distinct_field
**说明**:当统计类型为 `count_distinct` 时,指定要去重的字段名。
**注意**
- `type``count_distinct` 时必须指定 `distinct_field`
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `user.mobile`
- **可以与 `group_by` 同时使用**:可以按某个字段分组,然后统计每个分组的去重数量
**示例1不分组去重统计**
```json
{
"type": "count_distinct",
"distinct_field": "mobile"
}
```
**示例2关联模型字段去重**
```json
{
"type": "count_distinct",
"distinct_field": "user.mobile"
}
```
**示例3分组 + 去重统计(组合使用)**
```json
{
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type"
}
```
### 3.4 分组字段group_by
**说明**:指定按哪个字段进行分组统计。这是一个可选配置,可以选择不分组或选择具体的分组字段。
**配置选项**
- **不分组**:不设置 `group_by` 字段,或设置为 `null`,将返回所有符合条件的记录列表
- **按字段分组**:设置具体的分组字段,将按该字段进行分组统计
**分组字段格式**
- 可以是主模型的字段(如:`company_area`
- 可以是关联模型的字段(使用点号分隔,如 `company.company_area`
**示例1不分组统计**
```json
{
"statistics": {
"type": "count"
// 不设置 group_by表示不分组
}
}
```
**示例2按主模型字段分组**
```json
{
"statistics": {
"type": "count",
"group_by": "company_area"
}
}
```
**示例3按关联模型字段分组**
```json
{
"statistics": {
"type": "count",
"group_by": "company.company_area"
}
}
```
**示例4分组 + 去重统计(组合使用)**
```json
{
"statistics": {
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type"
}
}
```
### 3.4 排序方式order_by
**说明**:指定结果的排序方式。
**结构**
```json
{
"field": "排序字段",
"direction": "asc|desc"
}
```
**字段说明**
- **field**(字符串):排序字段名
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔)
- 可以是统计结果字段(如 `total`、`count`
- **direction**(字符串):排序方向
- `asc` - 升序
- `desc` - 降序
**示例**
```json
{
"order_by": {
"field": "total",
"direction": "desc"
}
}
```
---
## 完整示例
### 示例1统计各区域的校友人数
```json
{
"data_source": {
"main_model": "user",
"relations": ["company"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "is_schoolmate",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
},
"statistics": {
"type": "count",
"group_by": "company.company_area",
"order_by": {
"field": "count",
"direction": "desc"
}
}
}
```
### 示例2统计各课程类型的报名人数
```json
{
"data_source": {
"main_model": "course_sign",
"relations": ["course", "user"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "status",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
},
"statistics": {
"type": "count",
"group_by": "course.type",
"order_by": {
"field": "count",
"direction": "desc"
}
}
}
```
### 示例3统计各公司的融资总额
```json
{
"data_source": {
"main_model": "company",
"relations": ["user"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "is_yh_invested",
"operator": "eq",
"value": "1"
},
{
"key": "company_fund",
"operator": "isnotnull"
}
]
},
"statistics": {
"type": "sum",
"field": "company_fund",
"group_by": "company_area",
"order_by": {
"field": "total",
"direction": "desc"
}
}
}
```
### 示例4统计审核通过或待审核的报名人数
```json
{
"data_source": {
"main_model": "course_sign",
"relations": []
},
"conditions": {
"logic": "or",
"items": [
{
"key": "status",
"operator": "eq",
"value": "0"
},
{
"key": "status",
"operator": "eq",
"value": "1"
}
]
},
"statistics": {
"type": "count",
"order_by": {
"field": "created_at",
"direction": "desc"
}
}
}
```
### 示例5统计各课程类型的去重培养人数按手机号去重
```json
{
"data_source": {
"main_model": "course_sign",
"relations": ["user", "course"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "status",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2020-01-01," . date('Y-m-d')
}
]
},
"statistics": {
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type",
"order_by": {
"field": "total",
"direction": "desc"
}
}
}
```
---
## 注意事项
1. **字段引用**
- 主模型字段直接使用字段名
- 关联模型字段使用 `模型名.字段名` 格式
- 例如:`company.name`、`course.type`
2. **数据类型**
- 所有值在 JSON 中都存储为字符串
- 系统会根据字段类型自动转换
3. **条件逻辑**
- `and` 表示所有条件都必须满足
- `or` 表示至少一个条件满足
- 条件数组可以为空(表示无条件)
4. **统计字段**
- `sum` 类型必须指定 `field`
- `count` 类型不需要 `field`
- 分组字段可以为空(表示不分组)
5. **排序字段**
- 可以按任意字段排序
- 可以按统计结果字段排序(如 `total`、`count`
- 排序字段可以为空(使用默认排序)
---
## 文档版本
- **创建日期**2025-11-19
- **最后更新**2025-11-19
Loading…
Cancel
Save