Merge branch 'master' of ssh://47.101.48.251:/data/git/wx.sstbc.com

master
lion 3 months ago
commit 46bc6d1819

@ -15,10 +15,13 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// 发送消息
$schedule->command("sms:notification")->everyMinute();
// 更新课程状态
$schedule->command('update_sign_status')->everyMinute();
// 更新首字母
$schedule->command('update_letter')->everyMinute();
// $schedule->command('update_appointment')->everyMinute();
// 更新可预约次数
$schedule->command('update_appointment_total')->dailyAt('1:00');
// 生日检测
$schedule->command('check_birthday')->dailyAt('09:00');
@ -31,7 +34,7 @@ class Kernel extends ConsoleKernel
// 更新课程校友资格
$schedule->command('auto_schoolmate')->everyThirtyMinutes();
// 更新公司信息
$schedule->command('update_company')->everyTenMinutes();
$schedule->command('update_company')->everyFiveMinutes();
// 全量同步公司信息(每天凌晨执行,不同步经纬度和地址)
$schedule->command('sync:company')->dailyAt('02:00');
// 同步老师课程方向

@ -51,7 +51,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
const HISTORY_COURSES_SUB_COLUMNS = [
'hc_course_type' => '课程体系',
'hc_course_name' => '课程名称',
'hc_signs_pass' => '培养人未去重',
'hc_signs_pass' => '培养人未去重',
'hc_signs_pass_unique' => '培养人数去重',
'hc_course_signs_pass' => '课程培养人数',
];
@ -292,7 +292,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$endCol = $this->getColumnLetter($index + count(self::USERS_SUB_COLUMNS) - 1);
$sheet->mergeCells("{$startCol}1:{$endCol}1");
$sheet->setCellValue("{$startCol}1", $label);
// 确保第二行表头正确设置所有子列(包括缺失的列)
$subIndex = 0;
foreach (self::USERS_SUB_COLUMNS as $subField => $subLabel) {
@ -301,7 +301,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$sheet->setCellValue("{$col}2", $subLabel);
$subIndex++;
}
$index += count(self::USERS_SUB_COLUMNS);
} elseif (str_contains($field, 'project_users')) {
// 项目经理信息列:合并第一行的多个单元格
@ -309,7 +309,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$endCol = $this->getColumnLetter($index + count(self::PROJECT_USERS_SUB_COLUMNS) - 1);
$sheet->mergeCells("{$startCol}1:{$endCol}1");
$sheet->setCellValue("{$startCol}1", $label);
// 确保第二行表头正确设置所有子列
$subIndex = 0;
foreach (self::PROJECT_USERS_SUB_COLUMNS as $subField => $subLabel) {
@ -317,7 +317,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$sheet->setCellValue("{$col}2", $subLabel);
$subIndex++;
}
$index += count(self::PROJECT_USERS_SUB_COLUMNS);
} elseif (str_contains($field, 'history_courses')) {
// 历史课程信息列:合并第一行的多个单元格
@ -325,7 +325,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$endCol = $this->getColumnLetter($index + count(self::HISTORY_COURSES_SUB_COLUMNS) - 1);
$sheet->mergeCells("{$startCol}1:{$endCol}1");
$sheet->setCellValue("{$startCol}1", $label);
// 确保第二行表头正确设置所有子列
$subIndex = 0;
foreach (self::HISTORY_COURSES_SUB_COLUMNS as $subField => $subLabel) {
@ -333,7 +333,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
$sheet->setCellValue("{$col}2", $subLabel);
$subIndex++;
}
$index += count(self::HISTORY_COURSES_SUB_COLUMNS);
} else {
// 其他列:合并第一行和第二行

@ -80,7 +80,7 @@ class CalendarsController extends BaseController
if (isset($all['month'])) {
$query->where('start_time', 'like', $all['month'] . '%');
}
})->count();
})->where('is_count_days', 1)->count();
// 本年开课场次
$yearCourseCount = Calendar::where(function ($query) use ($all) {
if (isset($all['month'])) {
@ -88,7 +88,7 @@ class CalendarsController extends BaseController
$year = date('Y', strtotime($all['month']));
$query->where('start_time', 'like', $year . '%');
}
})->count();
})->where('is_count_days', 1)->count();
return $this->success(compact('list', 'monthDayCalendar', 'yearDayCalendar', 'monthCourseCount', 'yearCourseCount'));
}
@ -142,7 +142,7 @@ class CalendarsController extends BaseController
* @OA\Parameter(name="is_publish", in="query", @OA\Schema(type="string"), required=true, description="是否向用户发布0否1是"),
* @OA\Parameter(name="address", in="query", @OA\Schema(type="string"), required=true, description="地址"),
* @OA\Parameter(name="days", in="query", @OA\Schema(type="string"), required=true, description="天数"),
* @OA\Parameter(name="history_courses", in="query", @OA\Schema(type="array", @OA\Items(type="object")), required=false, description="历史课程数组每项包含type(课程体系ID), course_name(课程名称), course_type_signs_pass(培养人未去重), course_type_signs_pass_unique(培养人数去重), course_signs_pass(课程培养人数), start_time(开始时间), end_time(结束时间)"),
* @OA\Parameter(name="history_courses", in="query", @OA\Schema(type="array", @OA\Items(type="object")), required=false, description="历史课程数组每项包含type(课程体系ID), course_name(课程名称), course_type_signs_pass(培养人未去重), course_type_signs_pass_unique(培养人数去重), course_signs_pass(课程培养人数), start_time(开始时间), end_time(结束时间)"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
* @OA\Response(
* response="200",

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
use App\Exports\BaseExport;
use App\Helpers\ResponseCode;
use App\Models\Course;
use App\Models\CustomForm;
use App\Models\CustomFormField;
use App\Models\EmployeeParticipation;
@ -157,6 +158,7 @@ class EmployeeParticipationController extends BaseController
* @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_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="company_name", in="query", @OA\Schema(type="string", nullable=true), description="公司名字"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string", nullable=true), description="姓名"),
@ -244,27 +246,58 @@ class EmployeeParticipationController extends BaseController
$tempFile = $file->getRealPath();
$dataArray = (new FastExcel)->import($tempFile)->toArray();
// 固定的字段映射Excel表头中文 => 数据库字段名)
$fieldMapping = [
'类型' => 'type',
'开始日期' => 'start_date',
'结束日期' => 'end_date',
'数量' => 'total',
'课程类型ID' => 'course_type_id',
'课程名称' => 'course_name',
'公司名字' => 'company_name',
'姓名' => 'name',
'部门' => 'department',
// 类型映射:中文 => 数字
$typeMapping = [
'元禾员工参与' => 1,
'干部培训' => 2,
];
// 类型名称映射:数字 => 中文
$typeNameMapping = [
1 => '元禾员工参与',
2 => '干部培训',
];
$list = [];
foreach ($dataArray as $key => $value) {
$list[$key] = [];
// 根据固定字段映射转换数据
foreach ($fieldMapping as $excelHeader => $dbField) {
if (isset($value[$excelHeader])) {
$list[$key][$dbField] = $value[$excelHeader];
}
// 只读取 Excel 中的字段:类型、课程名称、公司名称、姓名、部门
$courseName = $value['课程名称'] ?? null;
$typeText = $value['类型'] ?? null;
// 转换类型:中文 -> 数字
$type = null;
if ($typeText) {
$type = $typeMapping[$typeText] ?? null;
}
// 根据课程名称查找课程信息
$course = null;
if ($courseName) {
$course = Course::where('name', $courseName)->first();
}
// 填充基础字段
$list[$key]['type'] = $type;
$list[$key]['type_name'] = $type ? ($typeNameMapping[$type] ?? '') : '';
$list[$key]['course_name'] = $courseName;
$list[$key]['company_name'] = $value['公司名称'] ?? null;
$list[$key]['name'] = $value['姓名'] ?? null;
$list[$key]['department'] = $value['部门'] ?? null;
// 根据课程信息填充其他字段
if ($course) {
$list[$key]['start_date'] = $course->start_date;
$list[$key]['end_date'] = $course->end_date;
$list[$key]['course_type_id'] = $course->type;
$list[$key]['course_id'] = $course->id;
} else {
// 如果找不到课程,字段设为 null
$list[$key]['start_date'] = null;
$list[$key]['end_date'] = null;
$list[$key]['course_type_id'] = null;
$list[$key]['course_id'] = null;
}
}
return $this->success($list);

@ -163,7 +163,7 @@ class HistoryCourseController extends BaseController
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=false, description="Id(存在更新,不存在新增)"),
* @OA\Parameter(name="type", in="query", @OA\Schema(type="string"), required=true, description="课程体系id"),
* @OA\Parameter(name="course_type_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人未去重"),
* @OA\Parameter(name="course_type_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人未去重"),
* @OA\Parameter(name="course_type_signs_pass_unique", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人数去重"),
* @OA\Parameter(name="course_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="课程培养人数"),
* @OA\Parameter(name="course_name", in="query", @OA\Schema(type="string", nullable=true), description="课程名称"),

@ -134,8 +134,8 @@ class OtherController extends CommonController
// 校友总数
$list['schoolmate_total'] = User::where('is_schoolmate', 1)->count();
// 今年新增校友数
$list['schoolmate_year'] = User::where('is_schoolmate', 1)->where('created_at', 'like', '%' . date('Y') . '%')->count();
// 今年新增校友数根据schoolmate_time字段统计
$list['schoolmate_year'] = User::where('is_schoolmate', 1)->whereYear('schoolmate_time', date('Y'))->count();
// 投后企业
$list['company_invested_total'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, date('Y-m-d'), null);
// 元和员工参与人数
@ -165,17 +165,13 @@ class OtherController extends CommonController
->where('start_time', 'like', '%' . date('Y-m') . '%')
->get();
// 苏州区域数据
$suzhouArea = Company::approvedStudents()->where('company_city', '苏州市')->groupBy('company_area')
->whereNotNull('company_area')
->get(['company_area']);
// 苏州区域数据 - 使用 CourseSign::area 方法实现数据一致
$areasData = CourseSign::area($start_date, $end_date, null, null, true, true);
$suzhou = [];
foreach ($suzhouArea as $item) {
foreach ($areasData as $item) {
$suzhou[] = [
'area' => $item->company_area,
'total' => User::whereHas('company', function ($query) use ($item) {
$query->where('company_area', $item->company_area);
})->where('is_schoolmate', 1)->count()
'area' => $item['area'],
'total' => $item['total_unique'] ?? $item['total']
];
}
@ -258,18 +254,28 @@ class OtherController extends CommonController
$courseType->course_signs_total = $courseType->history_course_signs_total + $courseType->now_course_signs_total;
}
// 统计 is_chart=0 的课程类型数据,组成"其他"统计项
$otherCourseType = CourseType::getOtherStatistics($configStartDate, $configEndDate);
// 将"其他"添加到 courseTypes 集合中
$courseTypes->push($otherCourseType);
// 将统计数据直接组合到配置对象中
$config->courseTypes = $courseTypes;
// 总期数
// 总期数(包含"其他"
$config->course_periods_total = $courseTypes->sum('course_periods_total');
// 总去重人数
// 总去重人数(包含"其他"
$coursesAll = Course::whereIn('type', $allCourseTypes->pluck('id'))
->where('is_chart', 1)
->where(function ($query) use ($configStartDate, $configEndDate) {
$query->whereBetween('start_date', [$configStartDate, $configEndDate])
->orWhereBetween('end_date', [$configStartDate, $configEndDate]);
})->get();
$config->course_signs_unique_total = $courseTypes->sum('history_course_signs_total') + CourseSign::courseSignsTotalByUnique($configStartDate, $configEndDate, 1, $coursesAll->pluck('id'), false, false);
// 获取"其他"课程类型的课程ID列表
$otherCourseIds = CourseType::getOtherCourseIds($configStartDate, $configEndDate);
$config->course_signs_unique_total = $courseTypes->sum('history_course_signs_total') + CourseSign::courseSignsTotalByUnique($configStartDate, $configEndDate, 1, $coursesAll->pluck('id')->merge($otherCourseIds), false, false);
}
return $this->success(compact('list', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article', 'yearConfigs'));
@ -318,20 +324,25 @@ class OtherController extends CommonController
// 跟班学员数在指定时间范围内报名的学员中from为'跟班学员'的数量)
$list['ganbu_total'] = CourseSign::genban($start_date, $end_date, $course_ids);
// 今年上市公司数量从stock_companys表获取stock_date在今年
$currentYear = date('Y');
$list['company_market_year_total'] = StockCompany::whereYear('stock_date', $currentYear)->count();
// 当前上市公司数量从stock_companys表获取stock_date在指定时间范围内
$list['company_market_year_total'] = StockCompany::getByDateRange($start_date, $end_date);
// 入学后上市公司数量从stock_companys表获取is_after_enrollment = 1
$list['company_market_after_enrollment_total'] = StockCompany::where('is_after_enrollment', 1)->count();
// 入学后上市公司数量从stock_companys表获取is_after_enrollment = 1stock_date在指定时间范围内
$list['company_market_after_enrollment_total'] = StockCompany::getAfterEnrollment($start_date, $end_date);
// 累计被投企业数
// 累计被投企业数,被投企业覆盖数
$list['course_signs_invested'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $course_ids);
// 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量)
// 当前被投企业覆盖数(根据传入的开始结束时间统计)
$list['course_signs_invested_current'] = CourseSign::yhInvestedTotal($start_date, $end_date, $course_ids);
// 当前入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量)
$list['company_invested_after_enrollment_total'] = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids);
// 今年被投企业数
// 累计入学后被投企业数量从CourseType::START_DATE开始的入学后被投企业数量
$list['company_invested_after_enrollment_total_cumulative'] = CourseSign::companyInvestedAfterEnrollment(CourseType::START_DATE, $end_date, $course_ids);
// 当前被投企业数
$list['company_invested_year_total'] = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids);
// 元和员工参与人数
@ -373,7 +384,7 @@ class OtherController extends CommonController
// 被投企业数
'yh_invested_total' => CourseSign::yhInvested($start_date, $end_date, [$course->id]),
// 元禾同事数
'company_join_total' => CourseSign::companyJoin($start_date, $end_date, [$course->id], false, false, false),
'company_join_total' => CourseSign::companyJoin($start_date, $end_date, [$course->id], false, false),
];
}
}
@ -410,10 +421,13 @@ class OtherController extends CommonController
// 区域明细统计
$areas = CourseSign::area($start_date, $end_date, 1, $courses->pluck('id'), true);
// 校友的区域明细统计
$areas_schoolmate = CourseSign::area($start_date, $end_date, 1, $courses->pluck('id'), true, true);
// 获取统计项元数据
$statistics_metadata = Course::getStatisticsMetadata();
return $this->success(compact('list', 'courseTypesSum', 'areas', 'statistics_metadata'));
return $this->success(compact('list', 'courseTypesSum', 'areas', 'areas_schoolmate', 'statistics_metadata'));
}
/**
@ -457,107 +471,119 @@ class OtherController extends CommonController
// 被投企业明细 - 使用与coursesHome相同的算法
// 确保 $course_ids 是数组格式
$courseIdsArray = $course_ids ? (is_array($course_ids) ? $course_ids : $course_ids->toArray()) : null;
$companies = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $courseIdsArray, true);
foreach ($companies as $company) {
// 获取该公司的学员信息
// 使用与yhInvestedTotal相同的筛选逻辑status=1筛选course_ids和日期范围
// 直接通过公司ID查询学员
$userIds = User::where('company_id', $company->id)->pluck('id')->toArray();
// 公司基本信息
$companyInfo = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
'company_address' => $company->company_address ?? '',
'business_scope' => $company->business_scope ?? '',
'contact_phone' => $company->contact_phone ?? '',
'contact_mail' => $company->contact_mail ?? '',
'company_tag' => $company->company_tag ?? '',
// 'credit_code' => ($company->credit_code ? ' ' . $company->credit_code : ''),
];
if (empty($userIds)) {
// 如果没有学员,仍然导出公司基本信息
$data[] = array_merge($companyInfo, [
'user_name' => '',
'course_names' => '',
'course_types' => '',
'course_count' => 0,
]);
} else {
$userCourseSigns = CourseSign::getStudentList(CourseType::START_DATE, $end_date, 1, $courseIdsArray)
->whereIn('user_id', $userIds)
->with(['user', 'course.typeDetail'])
->get();
// 辅助函数:构建被投企业数据
$buildInvestedCompanyData = function ($start_date, $end_date, $courseIdsArray) {
$data = [];
$companies = CourseSign::yhInvestedTotal($start_date, $end_date, $courseIdsArray, true);
foreach ($companies as $company) {
// 获取该公司的学员信息
$userIds = User::where('company_id', $company->id)->pluck('id')->toArray();
// 公司基本信息
$companyInfo = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
'company_address' => $company->company_address ?? '',
'business_scope' => $company->business_scope ?? '',
'contact_phone' => $company->contact_phone ?? '',
'contact_mail' => $company->contact_mail ?? '',
'company_tag' => $company->company_tag ?? '',
];
// 按学员分组
$usersData = [];
foreach ($userCourseSigns as $sign) {
if (!$sign->user) {
continue;
if (empty($userIds)) {
// 如果没有学员,仍然导出公司基本信息
$data[] = array_merge($companyInfo, [
'user_name' => '',
'course_names' => '',
'course_types' => '',
'course_count' => 0,
]);
} else {
$userCourseSigns = CourseSign::getStudentList($start_date, $end_date, 1, $courseIdsArray)
->whereIn('user_id', $userIds)
->with(['user', 'course.typeDetail'])
->get();
// 按学员分组
$usersData = [];
foreach ($userCourseSigns as $sign) {
if (!$sign->user) {
continue;
}
$userId = $sign->user_id;
if (!isset($usersData[$userId])) {
$usersData[$userId] = [
'user' => $sign->user,
'courseSigns' => [],
];
}
$usersData[$userId]['courseSigns'][] = $sign;
}
$userId = $sign->user_id;
if (!isset($usersData[$userId])) {
$usersData[$userId] = [
'user' => $sign->user,
'courseSigns' => [],
];
// 每个学员一行,公司信息只在第一行显示,后续行公司信息为空
$isFirstRow = true;
foreach ($usersData as $userData) {
$user = $userData['user'];
$courseSigns = collect($userData['courseSigns']);
if ($courseSigns->isNotEmpty()) {
// 获取课程名称列表,用中文顿号分隔
$courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->implode('、');
// 获取课程体系列表,用中文顿号分隔
$courseTypes = $courseSigns->pluck('course.typeDetail.name')
->filter()
->unique()
->values()
->implode('、');
// 报名课程数
$courseCount = $courseSigns->count();
if ($isFirstRow) {
// 第一行:显示公司信息
$data[] = array_merge($companyInfo, [
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
]);
$isFirstRow = false;
} else {
// 后续行:公司信息为空
$data[] = [
'company_name' => '',
'company_legal_representative' => '',
'company_date' => '',
'company_address' => '',
'business_scope' => '',
'contact_phone' => '',
'contact_mail' => '',
'company_tag' => '',
'credit_code' => '',
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
];
}
}
}
$usersData[$userId]['courseSigns'][] = $sign;
}
}
// 每个学员一行,公司信息只在第一行显示,后续行公司信息为空
$isFirstRow = true;
foreach ($usersData as $userData) {
$user = $userData['user'];
$courseSigns = collect($userData['courseSigns']);
return $data;
};
if ($courseSigns->isNotEmpty()) {
// 获取课程名称列表,用中文顿号分隔
$courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->implode('、');
// 第一个 sheet累计被投企业覆盖数从 CourseType::START_DATE 开始)
$cumulativeData = $buildInvestedCompanyData(CourseType::START_DATE, $end_date, $courseIdsArray);
// 获取课程体系列表,用中文顿号分隔
$courseTypes = $courseSigns->pluck('course.typeDetail.name')
->filter()
->unique()
->values()
->implode('、');
// 第二个 sheet当前被投企业覆盖数从传入的开始日期开始
$currentData = $buildInvestedCompanyData($start_date, $end_date, $courseIdsArray);
// 报名课程数
$courseCount = $courseSigns->count();
if ($isFirstRow) {
// 第一行:显示公司信息
$data[] = array_merge($companyInfo, [
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
]);
$isFirstRow = false;
} else {
// 后续行:公司信息为空
$data[] = [
'company_name' => '',
'company_legal_representative' => '',
'company_date' => '',
'company_address' => '',
'business_scope' => '',
'contact_phone' => '',
'contact_mail' => '',
'company_tag' => '',
'credit_code' => '',
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
];
}
}
}
}
}
$fields = [
'company_name' => '企业名称',
'company_legal_representative' => '法人',
@ -567,13 +593,25 @@ class OtherController extends CommonController
'contact_phone' => '联系电话',
'contact_mail' => '联系邮箱',
'company_tag' => '企业资质',
// 'credit_code' => '统一社会信用代码',
'user_name' => '学员姓名',
'course_names' => '课程名称',
'course_types' => '课程体系',
'course_count' => '报名课程数',
];
// 创建多 sheet 导出
$sheets = [
new SheetExport($cumulativeData, $fields, '被投企业覆盖数'),
new SheetExport($currentData, $fields, '当前被投企业覆盖数'),
];
$filename = '被投企业明细';
// 直接返回多 sheet 导出
return Excel::download(
new MultiSheetExport($sheets),
$filename . '_' . date('YmdHis') . '.xlsx'
);
break;
case 'course_signs_total':
@ -668,7 +706,7 @@ class OtherController extends CommonController
'course_name' => '课程名称',
'start_time' => '开始时间',
'end_time' => '结束时间',
'course_type_signs_pass' => '培养人未去重',
'course_type_signs_pass' => '培养人未去重',
'course_type_signs_pass_unique' => '培养人数去重',
'course_signs_pass' => '课程培养人数',
];
@ -806,7 +844,7 @@ class OtherController extends CommonController
'course_name' => '课程名称',
'start_time' => '开始时间',
'end_time' => '结束时间',
'course_type_signs_pass' => '培养人未去重',
'course_type_signs_pass' => '培养人未去重',
'course_type_signs_pass_unique' => '培养人数去重',
'course_signs_pass' => '课程培养人数',
];
@ -838,7 +876,7 @@ class OtherController extends CommonController
$query->whereBetween('start_date', [$start_date, $end_date])
->orWhereBetween('end_date', [$start_date, $end_date]);
}
})->orderBy('start_date', 'asc')->get();
})->where('is_chart', 1)->orderBy('start_date', 'asc')->get();
foreach ($courses2 as $course) {
$data[] = [
'course_type' => $courseType->name,
@ -1101,8 +1139,7 @@ class OtherController extends CommonController
case 'company_market_year_total':
// 今年上市公司明细 - 从stock_companys表获取与coursesHome统计逻辑保持一致
$currentYear = date('Y');
$stockCompanies = StockCompany::with('company')->whereYear('stock_date', $currentYear)->get();
$stockCompanies = StockCompany::getByDateRange($start_date, $end_date, true)->load('company');
foreach ($stockCompanies as $stockCompany) {
// 通过company_id关联到Company表获取详细信息
@ -1222,7 +1259,7 @@ class OtherController extends CommonController
case 'company_market_after_enrollment_total':
// 入学后上市公司明细 - 从stock_companys表获取与coursesHome统计逻辑保持一致
$stockCompanies = StockCompany::with('company')->where('is_after_enrollment', 1)->get();
$stockCompanies = StockCompany::getAfterEnrollment($start_date, $end_date, true)->load('company');
foreach ($stockCompanies as $stockCompany) {
// 通过company_id关联到Company表获取详细信息
@ -1345,23 +1382,36 @@ class OtherController extends CommonController
case 'company_invested_after_enrollment_total':
// 入学后被投企业明细 - 使用模型方法
$companiesAfterEnrollment = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids, true);
// 辅助函数:构建入学后被投企业数据
$buildAfterEnrollmentData = function ($start_date, $end_date, $course_ids) {
$data = [];
$companiesAfterEnrollment = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids, true);
foreach ($companiesAfterEnrollment as $item) {
$company = $item['company'];
$userNames = collect($item['users'])->pluck('name')->filter()->unique()->implode("\n\r");
$data[] = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
'invest_date' => $item['invest_date'] ?? '',
'first_sign_date' => $item['first_enrollment_date'] ?? '',
'user_names' => $userNames,
'company_address' => $company->company_address ?? '',
'company_city' => $company->company_city ?? '',
'company_area' => $company->company_area ?? '',
];
}
return $data;
};
// 第一个 sheet当前入学后被投企业从传入的开始日期开始
$currentData = $buildAfterEnrollmentData($start_date, $end_date, $course_ids);
// 第二个 sheet累计入学后被投企业从 CourseType::START_DATE 开始)
$cumulativeData = $buildAfterEnrollmentData(CourseType::START_DATE, $end_date, $course_ids);
foreach ($companiesAfterEnrollment as $item) {
$company = $item['company'];
$userNames = collect($item['users'])->pluck('name')->filter()->unique()->implode("\n\r");
$data[] = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
'invest_date' => $item['invest_date'] ?? '',
'first_sign_date' => $item['first_enrollment_date'] ?? '',
'user_names' => $userNames,
'company_address' => $company->company_address ?? '',
'company_city' => $company->company_city ?? '',
'company_area' => $company->company_area ?? '',
];
}
$fields = [
'company_name' => '企业名称',
'company_legal_representative' => '法人',
@ -1373,7 +1423,20 @@ class OtherController extends CommonController
'company_city' => '所在城市',
'company_area' => '所在区域',
];
// 创建多 sheet 导出
$sheets = [
new SheetExport($currentData, $fields, '当前入学后被投企业'),
new SheetExport($cumulativeData, $fields, '累计入学后被投企业'),
];
$filename = '入学后被投企业明细';
// 直接返回多 sheet 导出
return Excel::download(
new MultiSheetExport($sheets),
$filename . '_' . date('YmdHis') . '.xlsx'
);
break;
case 'company_invested_year_total':

@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\ResponseCode;
use App\Models\StatisticsMetadata;
use Illuminate\Support\Facades\DB;
class StatisticsMetadataController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new StatisticsMetadata());
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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="需要输出的关联关系数组"),
* @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()
{
return parent::index();
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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()
{
return parent::show();
}
/**
* @OA\Post(
* path="/api/admin/statistics-metadata/save",
* tags={"统计指标管理"},
* summary="保存(新增/更新)",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=false, description="id有id则为更新无id则为新增"),
* @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=true, description="统计项标识"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), required=true, description="统计项名称"),
* @OA\Parameter(name="from", in="query", @OA\Schema(type="string"), required=false, description="统计逻辑说明"),
* @OA\Parameter(name="verify", in="query", @OA\Schema(type="string"), required=false, description="验证方法说明"),
* @OA\Parameter(name="remark", 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 save()
{
$all = \request()->all();
DB::beginTransaction();
try {
// 验证必填字段
if (empty($all['key'])) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项标识不能为空']);
}
if (empty($all['name'])) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项名称不能为空']);
}
if (isset($all['id'])) {
$model = $this->model->find($all['id']);
if (empty($model)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']);
}
// 更新时检查 key 是否重复(排除自己)
$exists = StatisticsMetadata::where('key', $all['key'])
->where('id', '!=', $all['id'])
->first();
if ($exists) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']);
}
} else {
$model = $this->model;
// 新增时检查 key 是否重复
$exists = StatisticsMetadata::where('key', $all['key'])->first();
if ($exists) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']);
}
}
$original = $model->getOriginal();
$model->fill($all);
$model->save();
DB::commit();
// 记录日志
$this->saveLogs($original, $model);
return $this->success($model);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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();
}
}

@ -87,6 +87,26 @@ class Calendar extends SoftDeletesModel
});
}
/**
* 获取开课统计(场次数量或天数总和)
* @param string|null $start_date 开始日期
* @param string|null $end_date 结束日期
* @param array|null $course_type_id 课程体系ID数组
* @param string $type 统计类型:'count' 统计次数,'sum' 统计天数总和
* @return int|float
*/
public static function getCourseStatistics($start_date = null, $end_date = null, $course_type_id = null, $type = 'count')
{
$query = self::getCalendarsByDateRange($start_date, $end_date, $course_type_id)
->where('is_count_days', 1);
if ($type === 'sum') {
return $query->sum('days');
}
return $query->count();
}
/**
* 获取开课场次数量
* @param string|null $start_date 开始日期
@ -96,7 +116,7 @@ class Calendar extends SoftDeletesModel
*/
public static function getCourseTotal($start_date = null, $end_date = null, $course_type_id = null)
{
return self::getCalendarsByDateRange($start_date, $end_date, $course_type_id)->count();
return self::getCourseStatistics($start_date, $end_date, $course_type_id, 'count');
}
/**
@ -108,9 +128,7 @@ class Calendar extends SoftDeletesModel
*/
public static function getCourseDayTotal($start_date = null, $end_date = null, $course_type_id = null)
{
return self::getCalendarsByDateRange($start_date, $end_date, $course_type_id)
->where('is_count_days', 1)
->sum('days');
return self::getCourseStatistics($start_date, $end_date, $course_type_id, 'sum');
}
}

@ -188,33 +188,6 @@ class CourseSign extends SoftDeletesModel
$companies = Company::approvedStudents()->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) {
@ -248,25 +221,6 @@ class CourseSign extends SoftDeletesModel
$companies = Company::approvedStudents()->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) {
$projectUsers = $company->project_users ?? [];
foreach ($projectUsers as $item) {
$investDate = $item['investDate'] ?? null;
// 检查被投时间是否在范围内
if ($investDate && $investDate >= $startDate && $investDate <= $endDate) {
$filteredCompanies[] = $company;
break; // 只要有一条满足就加入
}
}
}
$companies = collect($filteredCompanies);
}
// 返回结果
if ($retList) {
@ -416,7 +370,16 @@ class CourseSign extends SoftDeletesModel
if ($retList) {
return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get();
} else {
return User::whereIn('id', $courseSigns->pluck('user_id'))->count();
$baseCount = User::whereIn('id', $courseSigns->pluck('user_id'))->count();
// 额外数据:从 TraineeStudent 模型中统计
$traineeStudentTotal = TraineeStudent::where(function ($query) use ($start_date, $end_date) {
// 开始结束日期的筛选。or查询
if ($start_date && $end_date) {
$query->whereBetween('start_date', [$start_date, $end_date])
->orWhereBetween('end_date', [$start_date, $end_date]);
}
})->sum('total');
return $baseCount + $traineeStudentTotal;
}
}
@ -473,63 +436,84 @@ class CourseSign extends SoftDeletesModel
}
/**
* 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量)
* 当前入学后被投企业数量(被投时间在指定时间范围内,且学员入学时间小于等于被投时间的公司数量)
* @param string $start_date 开始日期
* @param string $end_date 结束日期
* @param array|null $course_ids 课程ID数组,不传则统计所有课程
* @param array|null $course_ids 课程ID数组(已废弃,保留以兼容)
* @param bool $retList 是否返回列表false返回数量true返回列表
* @return int|array
*/
public static function companyInvestedAfterEnrollment($start_date, $end_date, $course_ids = null, $retList = false)
{
$courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids);
$courseSignsForInvest = $courseSignsQuery->with(['user.company', 'course'])->get();
// 获取所有被投企业
$companies = Company::approvedStudents()->where('is_yh_invested', 1)->get();
$companiesAfterEnrollment = [];
foreach ($courseSignsForInvest as $sign) {
if ($sign->user && $sign->user->company && $sign->user->company->is_yh_invested == 1) {
// 入学时间使用参与课程的课程开始时间course->start_date不使用报名时间created_at
$enrollmentDate = null;
if ($sign->course && $sign->course->start_date) {
$enrollmentDate = \Carbon\Carbon::parse($sign->course->start_date)->format('Y-m-d');
}
// 如果没有课程开始时间,跳过这条记录
if (!$enrollmentDate) {
continue;
}
foreach ($companies as $company) {
$projectUsers = $company->project_users ?? [];
// 从 project_users 中检查是否有任何一个被投时间 >= 课程开始时间(入学时间)
$projectUsers = $sign->user->company->project_users;
$hasInvestAfterEnrollment = false;
$investDate = null;
if (!empty($projectUsers) && is_array($projectUsers)) {
foreach ($projectUsers as $projectUser) {
if (!empty($projectUser['investDate'])) {
$currentInvestDate = $projectUser['investDate'];
// 只要有一个被投时间 >= 课程开始时间(入学时间),就满足条件
if ($currentInvestDate >= $enrollmentDate) {
$hasInvestAfterEnrollment = true;
// 记录满足条件的被投时间(如果有多个,记录最早的)
if ($investDate === null || $currentInvestDate < $investDate) {
$investDate = $currentInvestDate;
// 遍历被投时间,找到在指定时间范围内的被投时间
foreach ($projectUsers as $projectUser) {
$investDate = $projectUser['investDate'] ?? null;
// 检查被投时间是否在指定时间范围内
if ($investDate && $investDate >= $start_date && $investDate <= $end_date) {
// 获取该公司的所有学员
$users = $company->users()->get();
// 检查是否有学员的入学时间(课程开始时间)小于等于被投时间
$hasValidEnrollment = false;
$firstEnrollmentDate = null;
$matchedUsers = [];
foreach ($users as $user) {
// 获取该学员的所有审核通过的课程报名记录
$courseSigns = self::where('user_id', $user->id)
->where('status', 1)
->with('course')
->get();
foreach ($courseSigns as $courseSign) {
if ($courseSign->course && $courseSign->course->start_date) {
$enrollmentDate = \Carbon\Carbon::parse($courseSign->course->start_date)->format('Y-m-d');
// 检查入学时间是否小于等于被投时间
if ($enrollmentDate <= $investDate) {
$hasValidEnrollment = true;
// 记录最早的入学时间
if ($firstEnrollmentDate === null || $enrollmentDate < $firstEnrollmentDate) {
$firstEnrollmentDate = $enrollmentDate;
}
if ($retList) {
$matchedUsers[] = $user;
}
break; // 只要有一个课程满足条件即可
}
}
}
}
}
// 只要有一个被投时间 >= 课程开始时间(入学时间),说明是入学后被投
if ($hasInvestAfterEnrollment && $investDate) {
$companyId = $sign->user->company->id;
if (!isset($companiesAfterEnrollment[$companyId])) {
$companiesAfterEnrollment[$companyId] = [
'company' => $sign->user->company,
'first_enrollment_date' => $enrollmentDate, // 首次入学时间(课程开始时间)
'invest_date' => $investDate,
'users' => [],
];
}
if ($retList) {
$companiesAfterEnrollment[$companyId]['users'][] = $sign->user;
// 如果有学员的入学时间小于等于被投时间,则计入统计
if ($hasValidEnrollment) {
$companyId = $company->id;
if (!isset($companiesAfterEnrollment[$companyId])) {
$companiesAfterEnrollment[$companyId] = [
'company' => $company,
'first_enrollment_date' => $firstEnrollmentDate,
'invest_date' => $investDate,
'users' => [],
];
}
if ($retList) {
// 合并用户列表(去重)
$existingUserIds = array_column($companiesAfterEnrollment[$companyId]['users'], 'id');
foreach ($matchedUsers as $user) {
if (!in_array($user->id, $existingUserIds)) {
$companiesAfterEnrollment[$companyId]['users'][] = $user;
}
}
}
break; // 只要有一个被投时间满足条件即可
}
}
}
@ -548,20 +532,55 @@ class CourseSign extends SoftDeletesModel
* @param string $end_date 结束日期
* @param array|null $course_ids 课程ID数组不传则统计所有课程
* @param bool $retList 是否返回列表false返回详情true返回列表
* @param bool $is_schoolmate 是否只统计校友false统计所有true只统计校友
*/
public static function area($start_date, $end_date, $status = null, $course_ids = null, $retList = false)
public static function area($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $is_schoolmate = false)
{
$courseSignList = self::getStudentList($start_date, $end_date, $status, $course_ids);
// 地区
$suzhouArea = Company::approvedStudents()->where('company_city', '苏州市')->pluck('company_area')->unique();
// 如果只统计校友,添加校友筛选条件
if ($is_schoolmate) {
$courseSignList->whereHas('user', function ($query) {
$query->where('is_schoolmate', 1);
});
}
// 地区 - 从数据字典获取
$suzhouAreas = ParameterDetail::where('parameter_id', 5)
->where('status', 1)
->orderBy('sort', 'asc')
->pluck('value')
->toArray();
$list = [];
foreach ($suzhouArea as $area) {
$sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) use ($area) {
$query->whereHas('company', function ($query) use ($area) {
$query->where('company_area', $area);
});
})->get();
foreach ($suzhouAreas as $area) {
// 特殊处理:高新区需要统计高新区+虎丘区
if ($area === '高新区') {
$sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) {
$query->whereHas('company', function ($query) {
$query->where('company_city', '苏州市')
->where(function ($q) {
$q->where('company_area', '高新区')
->orWhere('company_area', '虎丘区');
});
});
})->get();
} elseif ($area === '苏州市外') {
// 苏州市外:统计除了苏州之外的所有数据
$sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) {
$query->whereHas('company', function ($query) {
$query->where('company_city', '!=', '苏州市');
});
})->get();
} else {
// 其他区域:正常统计
$sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) use ($area) {
$query->whereHas('company', function ($query) use ($area) {
$query->where('company_city', '苏州市')
->where('company_area', 'like', '%' . $area . '%');
});
})->get();
}
$list[] = [
'area' => $area,
@ -570,19 +589,7 @@ class CourseSign extends SoftDeletesModel
// 已去重
'total_unique' => User::whereIn('id', $sourseSignList2->pluck('user_id'))->groupBy('mobile')->get()->count(),
];
}
$courseSignList3 = (clone $courseSignList)->whereHas('user', function ($query) {
$query->whereHas('company', function ($query) {
$query->where('company_city', '!=', '苏州市');
});
})->get();
$list[] = [
'area' => '苏州市外',
'total' => $courseSignList3->count(),
// 已去重
'total_unique' => User::groupBy('mobile')->whereIn('id', $courseSignList3->pluck('user_id'))->get()->count(),
];
if ($retList) {
// 返回列表
@ -601,24 +608,35 @@ class CourseSign extends SoftDeletesModel
$courseSignsQuery = self::getStudentList($start_date, $end_date, null, $course_ids);
$courseSignByType = $courseSignsQuery->get();
// 检测关键词
$companyNameKeyword = [
'元禾控股',
'元禾原点',
'元禾厚望',
'元禾重元',
'元禾璞华',
'元禾谷风',
'元禾绿柳',
'元禾辰坤',
'元禾沙湖',
'禾裕集团',
'苏州科服',
'信诚管理咨询',
'集成电路公司',
'常州团队',
'国器元禾'
];
// 从 config 表中获取公司名称关键词
$companyNameKeyword = [];
$configValue = Config::getValueByKey('yuanhe_company');
if ($configValue) {
$configData = json_decode($configValue, true);
if (is_array($configData)) {
foreach ($configData as $item) {
// 提取 company_name 和 short_company_name过滤空值
if (!empty($item['company_name'])) {
$companyNameKeyword[] = $item['company_name'];
}
if (!empty($item['short_company_name'])) {
$companyNameKeyword[] = $item['short_company_name'];
}
}
// 去重关键词并重新索引
$companyNameKeyword = array_values(array_unique($companyNameKeyword));
}
}
// 如果 config 中没有数据,直接返回空
if (empty($companyNameKeyword)) {
if ($retList) {
return collect([]);
} else {
return 0;
}
}
$list = User::whereIn('id', $courseSignByType->pluck('user_id'))
->where(function ($query) use ($companyNameKeyword) {
foreach ($companyNameKeyword as $item) {
@ -692,10 +710,7 @@ class CourseSign extends SoftDeletesModel
*/
public static function isSuzhou($user = null, $company = null)
{
// 判断是否为苏州地区user表的company_address包含"苏州"或关联公司的company_address包含"苏州"或company_city包含"苏州"
if ($user && $user->company_address && strpos($user->company_address, '苏州') !== false) {
return true;
}
// 判断是否为苏州地区关联公司的company_address包含"苏州"或company_city包含"苏州"
if ($company && $company->company_address && strpos($company->company_address, '苏州') !== false) {
return true;
}

@ -23,5 +23,94 @@ class CourseType extends SoftDeletesModel
return $this->hasMany(Course::class, 'type', 'id');
}
/**
* 获取"其他"统计项is_chart=0 的课程类型合并统计)
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @return CourseType
*/
public static function getOtherStatistics($startDate, $endDate)
{
// 获取所有 is_chart=0 的课程类型
$otherCourseTypes = self::where('is_chart', 0)
->where('is_history', 0)
->get();
$otherCourseTypeIds = $otherCourseTypes->pluck('id')->toArray();
// 如果没有"其他"课程类型,返回空统计
if (empty($otherCourseTypeIds)) {
$otherCourseType = new self();
$otherCourseType->id = 0;
$otherCourseType->name = '其他';
$otherCourseType->history_course_periods_total = 0;
$otherCourseType->now_course_periods_total = 0;
$otherCourseType->history_course_signs_total = 0;
$otherCourseType->now_course_signs_total = 0;
$otherCourseType->course_periods_total = 0;
$otherCourseType->course_signs_total = 0;
return $otherCourseType;
}
// 历史课程数据(所有 is_chart=0 的课程类型)
$otherHistoryCourse = HistoryCourse::whereIn('type', $otherCourseTypeIds)
->where(function ($query) use ($startDate, $endDate) {
$query->whereBetween('start_time', [$startDate, $endDate])
->orWhereBetween('end_time', [$startDate, $endDate]);
})->get();
// 实际课程数据(所有 is_chart=0 的课程类型下的课程)
$otherCourses = Course::whereIn('type', $otherCourseTypeIds)->where('is_chart', 1)
->where(function ($query) use ($startDate, $endDate) {
$query->whereBetween('start_date', [$startDate, $endDate])
->orWhereBetween('end_date', [$startDate, $endDate]);
})->get();
$otherHistoryCoursePeriodsTotal = $otherHistoryCourse->count();
$otherNowCoursePeriodsTotal = $otherCourses->count();
$otherHistoryCourseSignsTotal = $otherHistoryCourse->sum('course_type_signs_pass_unique');
$otherNowCourseSignsTotal = CourseSign::courseSignsTotalByUnique($startDate, $endDate, 1, $otherCourses->pluck('id'), false, false);
// 创建"其他"统计项
$otherCourseType = new self();
$otherCourseType->id = 0;
$otherCourseType->name = '其他';
$otherCourseType->history_course_periods_total = $otherHistoryCoursePeriodsTotal;
$otherCourseType->now_course_periods_total = $otherNowCoursePeriodsTotal;
$otherCourseType->history_course_signs_total = $otherHistoryCourseSignsTotal;
$otherCourseType->now_course_signs_total = $otherNowCourseSignsTotal;
$otherCourseType->course_periods_total = $otherCourseType->now_course_periods_total + $otherCourseType->history_course_periods_total;
$otherCourseType->course_signs_total = $otherCourseType->history_course_signs_total + $otherCourseType->now_course_signs_total;
return $otherCourseType;
}
/**
* 获取"其他"课程类型的课程ID列表用于计算总去重人数
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @return \Illuminate\Support\Collection
*/
public static function getOtherCourseIds($startDate, $endDate)
{
// 获取所有 is_chart=0 的课程类型ID
$otherCourseTypeIds = self::where('is_chart', 0)
->where('is_history', 0)
->pluck('id')->toArray();
if (empty($otherCourseTypeIds)) {
return collect([]);
}
// 获取"其他"课程类型的课程数据
$otherCoursesAll = Course::whereIn('type', $otherCourseTypeIds)
->where('is_chart', 1)
->where(function ($query) use ($startDate, $endDate) {
$query->whereBetween('start_date', [$startDate, $endDate])
->orWhereBetween('end_date', [$startDate, $endDate]);
})->get();
return $otherCoursesAll->pluck('id');
}
}

@ -6,6 +6,14 @@ class StatisticsMetadata extends SoftDeletesModel
{
protected $table = 'statistics_metadata';
protected $fillable = [
'key',
'name',
'from',
'verify',
'remark',
];
/**
* 根据 key 获取统计元数据
* @param string $key

@ -27,5 +27,46 @@ class StockCompany extends SoftDeletesModel
{
return $this->belongsTo(Company::class, 'company_id', 'id');
}
/**
* 获取指定时间范围内的上市公司数量(统计或列表)
* @param string|null $start_date 开始日期
* @param string|null $end_date 结束日期
* @param bool $retList 是否返回列表false返回数量true返回列表
* @return int|\Illuminate\Database\Eloquent\Collection
*/
public static function getByDateRange($start_date = null, $end_date = null, $retList = false)
{
$query = self::when($start_date && $end_date, function ($query) use ($start_date, $end_date) {
$query->whereBetween('stock_date', [$start_date, $end_date]);
});
if ($retList) {
return $query->get();
} else {
return $query->count();
}
}
/**
* 获取入学后上市的公司数量(统计或列表)
* @param string|null $start_date 开始日期
* @param string|null $end_date 结束日期
* @param bool $retList 是否返回列表false返回数量true返回列表
* @return int|\Illuminate\Database\Eloquent\Collection
*/
public static function getAfterEnrollment($start_date = null, $end_date = null, $retList = false)
{
$query = self::where('is_after_enrollment', 1)
->when($start_date && $end_date, function ($query) use ($start_date, $end_date) {
$query->whereBetween('stock_date', [$start_date, $end_date]);
});
if ($retList) {
return $query->get();
} else {
return $query->count();
}
}
}

@ -15,7 +15,7 @@ return new class extends Migration {
Schema::create('history_courses', function (Blueprint $table) {
$table->id();
$table->string('type')->comment('课程体系ID');
$table->integer('course_type_signs_pass')->default(0)->comment('培养人未去重');
$table->integer('course_type_signs_pass')->default(0)->comment('培养人未去重');
$table->integer('course_type_signs_pass_unique')->default(0)->comment('培养人数去重');
$table->integer('course_signs_pass')->default(0)->comment('课程培养人数');
// 课程名称

@ -0,0 +1,31 @@
<?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::table('employee_participations', function (Blueprint $table) {
$table->integer('course_id')->nullable()->comment('课程ID')->after('course_type_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('employee_participations', function (Blueprint $table) {
$table->dropColumn('course_id');
});
}
};

@ -242,6 +242,12 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::post('company/save', [\App\Http\Controllers\Admin\CompanyController::class, "save"]);
Route::get('company/destroy', [\App\Http\Controllers\Admin\CompanyController::class, "destroy"]);
// 统计指标管理
Route::get('statistics-metadata/index', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "index"]);
Route::get('statistics-metadata/show', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "show"]);
Route::post('statistics-metadata/save', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "save"]);
Route::get('statistics-metadata/destroy', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "destroy"]);
// 上市公司管理
Route::get('stock-company/index', [\App\Http\Controllers\Admin\StockCompanyController::class, "index"]);
Route::get('stock-company/show', [\App\Http\Controllers\Admin\StockCompanyController::class, "show"]);

@ -1,295 +0,0 @@
-- ============================================
-- 数据库索引优化建议
-- 基于 coursesHome 统计逻辑分析
-- ============================================
-- ============================================
-- 1. course_signs 表索引
-- ============================================
-- 1.1 核心查询索引status + course_id最常用
-- 用于courseSignsTotal, courseSignsTotalByUnique, getStudentList
-- 查询条件status, course_id, whereHas('course')
ALTER TABLE `course_signs`
ADD INDEX `idx_status_course_id` (`status`, `course_id`);
-- 1.2 用户去重查询索引user_id + status
-- 用于courseSignsTotalByUnique按手机号去重
ALTER TABLE `course_signs`
ADD INDEX `idx_user_id_status` (`user_id`, `status`);
-- 1.3 排除状态索引status用于 whereNotIn status [4,5,6]
-- 注意:如果 status 字段选择性不高,可考虑与 course_id 组合
ALTER TABLE `course_signs`
ADD INDEX `idx_status_not_in` (`status`);
-- 1.4 复合索引status + course_id + user_id用于关联查询优化
ALTER TABLE `course_signs`
ADD INDEX `idx_status_course_user` (`status`, `course_id`, `user_id`);
-- ============================================
-- 2. courses 表索引
-- ============================================
-- 2.1 图表统计索引is_chart + 日期范围
-- 用于getStudentList 中的 whereHas('course')
-- 查询条件is_chart=1, start_date/end_date BETWEEN
ALTER TABLE `courses`
ADD INDEX `idx_is_chart_dates` (`is_chart`, `start_date`, `end_date`);
-- 2.2 课程体系索引type + is_chart
-- 用于:按课程体系筛选课程
ALTER TABLE `courses`
ADD INDEX `idx_type_is_chart` (`type`, `is_chart`);
-- 2.3 复合索引type + is_chart + start_date + end_date
-- 用于:课程分类明细统计
ALTER TABLE `courses`
ADD INDEX `idx_type_chart_dates` (`type`, `is_chart`, `start_date`, `end_date`);
-- ============================================
-- 3. calendars 表索引
-- ============================================
-- 3.1 日期范围查询索引start_time + end_time
-- 用于getCourseTotal, getCourseDayTotal
-- 查询条件start_time/end_time BETWEEN
ALTER TABLE `calendars`
ADD INDEX `idx_dates_range` (`start_time`, `end_time`);
-- 3.2 课程体系索引course_type_id + 日期
-- 用于:按课程体系筛选日历
ALTER TABLE `calendars`
ADD INDEX `idx_course_type_dates` (`course_type_id`, `start_time`, `end_time`);
-- 3.3 统计天数索引is_count_days + 日期
-- 用于getCourseDayTotal开课天数统计
ALTER TABLE `calendars`
ADD INDEX `idx_count_days_dates` (`is_count_days`, `start_time`, `end_time`);
-- 3.4 课程关联索引course_id + course_type_id
-- 用于:通过 course.type 匹配课程体系
ALTER TABLE `calendars`
ADD INDEX `idx_course_type_id` (`course_id`, `course_type_id`);
-- ============================================
-- 4. companies 表索引
-- ============================================
-- 4.1 上市公司索引company_market
-- 用于shangshi, suzhouStock上市公司统计
ALTER TABLE `companies`
ADD INDEX `idx_company_market` (`company_market`);
-- 4.2 被投企业索引is_yh_invested
-- 用于yhInvestedTotal, companyInvestedYear被投企业统计
ALTER TABLE `companies`
ADD INDEX `idx_is_yh_invested` (`is_yh_invested`);
-- 4.3 企业标签索引company_tag用于 LIKE 查询)
-- 用于toubuqiye高新技术企业筛选
-- 注意LIKE '%高新技术企业%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `companies`
ADD INDEX `idx_company_tag` (`company_tag`(100));
-- 4.4 城市区域索引company_city + company_area
-- 用于area区域统计, isSuzhou苏州筛选
ALTER TABLE `companies`
ADD INDEX `idx_city_area` (`company_city`, `company_area`);
-- 4.5 地址索引company_address用于 LIKE 查询)
-- 用于isSuzhou苏州筛选
-- 注意LIKE '%苏州%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `companies`
ADD INDEX `idx_company_address` (`company_address`(100));
-- 4.6 复合索引company_market + company_city用于苏州上市公司
ALTER TABLE `companies`
ADD INDEX `idx_market_city` (`company_market`, `company_city`);
-- ============================================
-- 5. users 表索引
-- ============================================
-- 5.1 公司关联索引company_id
-- 用于:通过用户关联公司
ALTER TABLE `users`
ADD INDEX `idx_company_id` (`company_id`);
-- 5.2 跟班学员索引from用于 LIKE 查询)
-- 用于genban, ganbu跟班学员统计
-- 注意LIKE '%跟班学员%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `users`
ADD INDEX `idx_from` (`from`(50));
-- 5.3 手机号索引mobile用于去重
-- 用于courseSignsTotalByUnique按手机号去重
ALTER TABLE `users`
ADD INDEX `idx_mobile` (`mobile`);
-- 5.4 公司名称索引company_name用于 LIKE 查询)
-- 用于companyJoin元和员工筛选
-- 注意LIKE '%元禾控股%' 等无法使用索引
ALTER TABLE `users`
ADD INDEX `idx_company_name` (`company_name`(100));
-- 5.5 用户地址索引company_address用于 LIKE 查询)
-- 用于isSuzhou苏州筛选
ALTER TABLE `users`
ADD INDEX `idx_user_company_address` (`company_address`(100));
-- 5.6 复合索引company_id + from用于跟班学员筛选
ALTER TABLE `users`
ADD INDEX `idx_company_from` (`company_id`, `from`(50));
-- ============================================
-- 6. stock_companies 表索引
-- ============================================
-- 6.1 上市日期索引stock_date
-- 用于company_market_year_total今年上市公司数量
ALTER TABLE `stock_companys`
ADD INDEX `idx_stock_date` (`stock_date`);
-- 6.2 入学后上市索引is_after_enrollment
-- 用于company_market_after_enrollment_total
ALTER TABLE `stock_companys`
ADD INDEX `idx_after_enrollment` (`is_after_enrollment`);
-- 6.3 公司关联索引company_id
-- 用于:关联 companies 表
ALTER TABLE `stock_companys`
ADD INDEX `idx_company_id` (`company_id`);
-- 6.4 复合索引is_after_enrollment + stock_date
ALTER TABLE `stock_companys`
ADD INDEX `idx_enrollment_date` (`is_after_enrollment`, `stock_date`);
-- ============================================
-- 7. history_courses 表索引
-- ============================================
-- 7.1 日期范围索引start_time + end_time
-- 用于:历史课程统计
ALTER TABLE `history_courses`
ADD INDEX `idx_history_dates` (`start_time`, `end_time`);
-- 7.2 课程类型索引type + 日期
-- 用于:按课程体系筛选历史课程
ALTER TABLE `history_courses`
ADD INDEX `idx_type_dates` (`type`, `start_time`, `end_time`);
-- 7.3 日历关联索引calendar_id
-- 用于whereHas('calendar', is_count_people=1)
ALTER TABLE `history_courses`
ADD INDEX `idx_calendar_id` (`calendar_id`);
-- 7.4 复合索引type + calendar_id + 日期
ALTER TABLE `history_courses`
ADD INDEX `idx_type_calendar_dates` (`type`, `calendar_id`, `start_time`, `end_time`);
-- ============================================
-- 8. course_types 表索引
-- ============================================
-- 8.1 历史课程索引is_history
-- 用于:筛选历史课程类型
ALTER TABLE `course_types`
ADD INDEX `idx_is_history` (`is_history`);
-- 8.2 跟班学员统计索引is_count_genban
-- 用于genban筛选需要统计跟班学员的课程
ALTER TABLE `course_types`
ADD INDEX `idx_is_count_genban` (`is_count_genban`);
-- ============================================
-- 9. 关联查询优化索引
-- ============================================
-- 9.1 course_signs 关联 users 优化
-- 已通过 user_id 索引优化
-- 9.2 users 关联 course_signs 优化
-- 需要在 course_signs 表已有 user_id 索引
-- 9.3 companies 关联 users 优化
-- 需要在 users 表已有 company_id 索引
-- 9.4 courses 关联 course_types 优化
-- 需要在 courses 表已有 type 索引
-- ============================================
-- 10. 特殊查询优化建议
-- ============================================
-- 10.1 JSON 字段查询优化
-- companies.project_users 字段JSON无法直接建立索引
-- 建议:如果 investDate 查询频繁,考虑单独建立 invest_dates 表或字段
-- 10.2 LIKE 查询优化
-- 对于 '%关键词%' 类型的 LIKE 查询,无法使用普通索引
-- 建议:
-- 1. 如果可能,改为前缀匹配 '关键词%' 可以使用索引
-- 2. 考虑使用全文索引FULLTEXT
ALTER TABLE `companies`
ADD FULLTEXT INDEX `ft_company_tag` (`company_tag`);
ALTER TABLE `users`
ADD FULLTEXT INDEX `ft_company_name` (`company_name`);
ALTER TABLE `users`
ADD FULLTEXT INDEX `ft_from` (`from`);
-- 10.3 日期范围查询优化
-- 对于 start_date/end_date BETWEEN 查询,确保日期字段有索引
-- 对于 orWhereBetween 查询MySQL 可能无法同时使用两个索引
-- 建议:如果性能问题,考虑拆分为两个查询 UNION
-- ============================================
-- 11. 索引使用说明
-- ============================================
-- 11.1 索引创建顺序
-- 建议按照表的数据量和查询频率,优先创建高频查询的索引
-- 11.2 索引维护
-- 定期使用 EXPLAIN 分析查询计划,确认索引被正确使用
-- 示例EXPLAIN SELECT * FROM course_signs WHERE status=1 AND course_id IN (1,2,3);
-- 11.3 索引监控
-- 使用以下查询监控索引使用情况:
-- SELECT * FROM sys.schema_unused_indexes;
-- SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage;
-- 11.4 注意事项
-- 1. 索引会占用存储空间,增加写入成本
-- 2. 不要过度索引,每个表建议不超过 5-7 个索引
-- 3. 复合索引的顺序很重要,将选择性高的字段放在前面
-- 4. 定期分析表更新统计信息ANALYZE TABLE table_name;
-- ============================================
-- 12. 性能优化建议
-- ============================================
-- 12.1 查询优化
-- 1. 避免在 WHERE 子句中使用函数
-- 2. 使用 EXISTS 替代 IN当子查询结果集较大时
-- 3. 合理使用 JOIN避免过度嵌套
-- 12.2 分页优化
-- 对于大数据量分页,考虑使用游标分页替代 OFFSET
-- 12.3 缓存策略
-- 对于统计类查询,考虑使用 Redis 缓存结果
-- ============================================
-- 索引创建脚本执行顺序建议
-- ============================================
-- 1. 先创建核心表索引course_signs, courses, calendars
-- 2. 再创建关联表索引companies, users
-- 3. 最后创建辅助表索引stock_companies, history_courses
-- 执行前请备份数据库!
-- 建议在业务低峰期执行索引创建操作

@ -1,87 +0,0 @@
{
"course_signs_total": {
"name": "报名人数",
"from": "1、统计指定时间范围和课程范围内的所有报名记录数量包括所有状态的报名待审核、已通过、已拒绝等2、加上额外添加的课程数据中的人数统计3、注意一个学员报名多个课程会计算多次。",
"verify": ""
},
"course_signs_pass": {
"name": "培养人次(未去重)",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录数量2、统计额外添加的课程数据关联的分类是历史课程关联的日历是统计人数选项里满足时间范围内的课程课程开始或结束时间在时间范围内培养人数未去重的数据3、将两部分数据相加得到总数。",
"verify": "报名管理栏目,累加统计对比"
},
"course_signs_pass_unique": {
"name": "培养人数(已去重)",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录数量按照手机号去重2、统计额外添加的课程数据关联的分类是历史课程关联的日历是统计人数选项里满足时间范围内的课程课程开始或结束时间在时间范围内培养人数去重的数据3、将两部分数据相加得到总数。",
"verify": ""
},
"course_total": {
"name": "开课场次",
"from": "1、统计指定时间范围内、指定课程体系范围内的日历里记录数量",
"verify": "日历里的数据导出对比"
},
"course_day_total": {
"name": "开课天数",
"from": "1、在开课场次的基础上2、获取设置了统计天数的记录3、计算天数总和。",
"verify": "日历里的数据导出对比"
},
"company_market_total": {
"name": "重点上市公司数",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户总数2、如果一个用户报名多个课程只算一个有效用户3、筛选出关联的公司是上市公司的用户数量。",
"verify": "企业资质中包含了上市代码的就识别成上市公司"
},
"ganbu_total": {
"name": "跟班学员数",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户总数2、如果一个用户报名多个课程只算一个有效用户3、筛选出有\"跟班学员\"标签的用户总数。",
"verify": "学员中心,学员标签筛选对比"
},
"company_market_year_total": {
"name": "今年上市公司数量",
"from": "1、从自定义的上市公司数据里2、统计上市年份在今年的公司数量。",
"verify": "自定义上市公司数据对比"
},
"company_market_after_enrollment_total": {
"name": "入学后上市公司数量",
"from": "1、直接从上市公司表中查询2、统计所有标记为\"入学后上市\"的公司数量3、注意这个统计不依赖学员报名记录统计的是自定义的上市的公司。",
"verify": "自定义上市公司数据对比"
},
"course_signs_invested": {
"name": "累计被投企业数",
"from": "1、指定课程体系下从2000-01-01到结束日期的所有课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户关联的公司总数2、筛选被投企业标签的公司3、再次筛选存在被投日期在时间范围内的公司总数。",
"verify": ""
},
"company_invested_after_enrollment_total": {
"name": "入学后被投企业数量",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户关联的公司总数2、筛选被投企业标签的公司3、再次筛选存在被投日期并且被投日期在用户报名的课程的开始时间之后的公司数量。",
"verify": ""
},
"company_invested_year_total": {
"name": "今年被投企业数",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户关联的公司总数2、筛选出有被投企业标签的公司3、再次筛选存在被投日期并且被投日期是今年的公司数量。",
"verify": ""
},
"company_join_total": {
"name": "元和员工参与人数",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下报名记录的用户总数如果一个用户报名多个课程只算一个有效用户2、筛选自己填写的公司名称包含以下关键词的用户总数元禾控股、元禾原点、元禾厚望、元禾重元、元禾璞华、元禾谷风、元禾绿柳、元禾辰坤、元禾沙湖、禾裕集团、苏州科服、信诚管理咨询、集成电路公司、常州团队、国器元禾3、加上自定义的元禾员工/干部培训数据里满足时间范围内的元禾员工总和4、将两部分数据相加得到总数。",
"verify": "公司管理,筛选公司名字加起来对比"
},
"company_ganbu_total": {
"name": "全市干部参与企业",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户总数如果一个用户报名多个课程只算一个有效用户2、加上\"元禾员工/干部培训\"栏目里满足时间范围内的\"元禾员工参与\"记录的参与数量的总和3、将两部分数据相加得到总数。",
"verify": "学员中心,学员标签筛选对比"
},
"cover_head_total": {
"name": "苏州头部企业",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户2、筛选关联的公司包含\"高新技术企业\"字符串的公司并且用户填写的公司地址或者关联的企查查公司地址包含苏州的公司3、统计公司数量。",
"verify": ""
},
"cover_rencai_total": {
"name": "苏州高层次人才",
"from": "1、统计指定课程体系下的指定时间范围内的课程课程开始或结束时间在时间范围内下审核通过的报名记录的用户2、筛选满足以下条件之一的用户参加过\"人才培训\"课程类型的用户,或填写的所有报名表单里个人荣誉字段不包含\"其他\"的用户剩下的选项是国家人才、市级人才等。并且用户填写的公司地址或者关联的企查查公司地址包含苏州的公司3、取两者的并集得到用户总数。",
"verify": ""
},
"cover_stock_total": {
"name": "苏州重点上市公司",
"from": "1、统计在指定时间范围内、指定课程范围内报名的学员2、通过学员关联到其所在公司3、筛选出上市公司4、并且用户填写的公司地址或者关联的企查查公司地址包含苏州的公司",
"verify": ""
}
}
Loading…
Cancel
Save