master
cody 5 months ago
parent 865afaccf8
commit 94c8e4f00b

@ -0,0 +1,218 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Exports\BaseExport;
use App\Helpers\ResponseCode;
use App\Models\AppointmentType;
use App\Models\Article;
use App\Models\Book;
use App\Models\Calendar;
use App\Models\Company;
use App\Models\CourseContentEvaluationAsk;
use App\Models\CourseContentEvaluationForm;
use App\Models\CustomForm;
use App\Models\CustomFormField;
use App\Models\EmailTemplate;
use App\Models\SupplyDemand;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Rap2hpoutre\FastExcel\FastExcel;
class ArticleController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new Article());
}
/**
* @OA\Get(
* path="/api/admin/article/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="需要输出的关联关系数组包括teachercourseSettingscoursePeriods"),
* @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/article/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/article/save",
* tags={"文章"},
* summary="保存",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=true, description="Id(存在更新,不存在新增)"),
* @OA\Parameter(name="title", in="query", @OA\Schema(type="string", nullable=true), description="标题"),
* @OA\Parameter(name="content", in="query", @OA\Schema(type="string", nullable=true), description="内容"),
* @OA\Parameter(name="type", in="query", @OA\Schema(type="string", nullable=true), description="类型1校友动态2业界动态"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
* @OA\Response(
* response="200",
* description="操作成功"
* )
* )
*/
public function save()
{
$all = \request()->all();
DB::beginTransaction();
try {
if (isset($all['id'])) {
$model = $this->model->find($all['id']);
if (empty($model)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']);
}
} else {
$model = $this->model;
$all['admin_id'] = $this->getUserId();
$all['department_id'] = $this->getUser()->department_id;
}
$original = $model->getOriginal();
$model->fill($all);
$model->save();
DB::commit();
return $this->success($model);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Get(
* path="/api/admin/article/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();
}
}

@ -115,6 +115,7 @@ class CalendarsController extends BaseController
* @OA\Parameter(name="end_time", in="query", @OA\Schema(type="string", format="date-time"), required=false, description="结束时间YYYY-MM-DD HH:MM:SS"),
* @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="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
* @OA\Response(
* response="200",

@ -0,0 +1,306 @@
# 课程统计数据说明文档
## 一、文档说明
本文档用于说明课程统计系统中各项数据的含义和统计方式,帮助您更好地理解和使用统计数据。
---
## 二、如何查询统计数据
### 2.1 查询方式
通过系统后台的"课程统计"功能,您可以查询以下信息:
- **时间范围**:选择需要统计的开始日期和结束日期
- **课程类型**:可以选择全部课程类型,也可以选择特定的课程类型进行统计
### 2.2 统计时间说明
- 统计数据基于**报名记录的创建时间**,而不是课程开课时间
- 例如选择2024年1月1日到2024年12月31日会统计在这个时间段内创建的所有报名记录
---
## 三、核心统计数据说明
### 3.1 被投企业数
**含义**:在统计时间段内,有多少家元禾已投资的企业有员工报名参加了课程。
**统计方式**
- 统计所有在指定时间段内报名的用户
- 查看这些用户所在的公司
- 统计其中被元禾投资的公司数量
- **重要**:如果一家公司有多个员工报名,这家公司只统计一次
**使用场景**:了解元禾投资企业的参与情况
---
### 3.2 报名人数
**含义**:在统计时间段内,总共有多少条报名记录。
**统计方式**
- 统计所有在指定时间段内创建的报名记录
- 包括所有状态的报名(待审核、审核通过、审核不通过等)
- 不包括已取消和主动放弃的报名
- **重要**:如果同一个人报名了多个课程,会按报名次数分别计算
**使用场景**:了解课程的整体报名热度
**举例**
- 张三报名了3个课程 → 统计为3人
- 李四报名了1个课程 → 统计为1人
- 总计4人
---
### 3.3 审核通过人数
**含义**:在统计时间段内,有多少条报名记录通过了审核。
**统计方式**
- 只统计状态为"审核通过"的报名记录
- 不包括已取消和主动放弃的报名
- **重要**:如果同一个人报名了多个课程且都通过了,会按通过次数分别计算
**使用场景**:了解实际可以参加课程的人数
**举例**
- 张三报名了3个课程都通过了 → 统计为3人
- 李四报名了1个课程通过了 → 统计为1人
- 总计4人
---
### 3.4 审核通过人数(去重)
**含义**:在统计时间段内,有多少个不同的用户通过了审核。
**统计方式**
- 只统计状态为"审核通过"的报名记录
- 根据用户的手机号进行去重
- 如果同一个手机号报名了多个课程,只统计一次
- **重要**:这是真实的培养人数,不会重复计算同一个人
**使用场景**:了解实际培养了多少个不同的学员
**举例**
- 张三手机号13800138000报名了3个课程都通过了 → 统计为1人
- 李四手机号13900139000报名了1个课程通过了 → 统计为1人
- 总计2人去重后
---
### 3.5 开课场次
**含义**:在统计时间段内,总共开了多少场课程。
**统计方式**
- 统计指定课程类型下,在指定时间范围内的所有开课记录
- 统计数据源来自于课程日历上的数据
- **重要**:统计的是开课场次,不是课程数量
**使用场景**:了解课程开展的频率和规模
---
### 3.6 开课天数
**含义**:在统计时间段内,所有课程总共开了多少天。
**统计方式**
- 对每场课程计算开课天数(从开始日期到结束日期)
- 包含开始日期和结束日期
- 将所有场次的天数加起来
- 统计数据源来自于课程日历上的数据
**使用场景**:了解课程的总时长
**举例**
- 某场课程从2024年1月1日到1月3日 → 3天包含1日、2日、3日
- 某场课程从2024年2月10日到2月12日 → 3天
- 总计6天
---
## 四、课程分类明细统计说明
### 4.1 统计内容
系统会按照每个课程类型,详细统计以下信息:
#### 课程类型名称
显示课程体系的名称,例如:"高研班"、"初创班"等。
#### 培养人数(未去重,按课程类型)
**含义**:该课程类型下所有课程的审核通过报名人数总和。
**说明**
- 如果同一个学员在该课程类型下报名了多个课程,会按报名次数分别计算
- 例如:张三在"高研班"类型下报名了2个课程都通过了 → 统计为2人
#### 培养人数(去重,按课程类型)
**含义**:该课程类型下有多少个不同的学员通过了审核。
**说明**
- 根据学员的手机号去重
- 同一个手机号在该课程类型下只统计一次
- 例如张三手机号13800138000在"高研班"类型下报名了2个课程都通过了 → 统计为1人
#### 课程名称
显示具体课程的名称,例如:"2024年第一期高研班"。
#### 培养人数(按单个课程)
**含义**:该单个课程的审核通过报名人数。
**说明**
- 只统计该课程的审核通过人数
- 不包括其他课程的数据
---
## 五、区域明细统计说明
### 5.1 统计内容
系统会按照每个区域,详细统计以下信息:
#### 区域名称
显示区域名称,例如:"工业园区"、"高新区"、"相城区"等。
**特殊区域**"苏州市外"表示不在苏州所有区域内的用户。
#### 审核通过人数(未去重,按区域)
**含义**:该区域有多少条审核通过的报名记录。
**说明**
- 根据学员所在公司的区域进行统计
- 如果同一个学员在该区域报名了多个课程,会按报名次数分别计算
- 例如张三公司位于工业园区报名了2个课程都通过了 → 统计为2人
#### 审核通过人数(去重,按区域)
**含义**:该区域有多少个不同的学员通过了审核。
**说明**
- 根据学员的手机号去重
- 同一个手机号在该区域只统计一次
- 例如张三手机号13800138000公司位于工业园区报名了2个课程都通过了 → 统计为1人
---
## 七、数据关系说明
### 7.1 人数关系
通常情况下,数据之间存在以下关系:
**报名人数** ≥ **审核通过人数** ≥ **审核通过人数(去重)**
**解释**
- 报名人数最多,因为包括所有状态的报名
- 审核通过人数次之,因为只包括审核通过的
- 去重人数最少,因为同一个学员只统计一次
**举例**
- 报名人数500人包括待审核、审核通过、审核不通过等
- 审核通过人数450人只包括审核通过的
- 审核通过人数去重380人同一个学员只统计一次
### 7.2 区域汇总与全局去重的关系
**区域汇总** 可能大于 **全局去重人数**
**原因**
- 区域汇总时,每个区域是分别统计的
- 而全局去重是按所有区域统一去重的
---
## 八、报名状态说明
### 8.1 报名状态类型
报名记录有以下几种状态:
| 状态名称 | 说明 | 是否参与统计 |
|---------|------|------------|
| 待审核 | 报名已提交,等待审核 | 参与"报名人数"统计,不参与"审核通过人数"统计 |
| 审核通过 | 报名已通过审核,可以参加课程 | 参与所有统计 |
| 审核不通过 | 报名未通过审核 | 参与"报名人数"统计,不参与"审核通过人数"统计 |
| 备选 | 报名作为备选 | 参与"报名人数"统计,不参与"审核通过人数"统计 |
| 已取消 | 报名已取消 | 不参与任何统计 |
| 主动放弃 | 学员主动放弃报名 | 不参与任何统计 |
| 黑名单 | 学员在黑名单中 | 参与"报名人数"统计,不参与"审核通过人数"统计 |
### 8.2 统计规则
- **报名人数**:统计除"已取消"和"主动放弃"外的所有状态
- **审核通过人数**:只统计"审核通过"状态的记录
- **所有统计**:都不包括"已取消"和"主动放弃"的记录
---
## 九、常见问题解答
### Q1: 为什么"报名人数"比"审核通过人数"多?
**A**: 因为"报名人数"包括所有状态的报名记录(待审核、审核通过、审核不通过、备选等),而"审核通过人数"只包括审核通过的记录。所以报名人数会更多。
### Q2: 为什么"审核通过人数"比"审核通过人数(去重)"多?
**A**: 因为"审核通过人数"是报名记录数,如果同一个学员报名了多个课程,会按报名次数分别计算。而"审核通过人数(去重)"是按学员手机号去重的,同一个学员只统计一次。
### Q4: "被投企业数"是如何统计的?
**A**: 先统计所有报名的学员,然后查看这些学员所在的公司,统计其中被元禾投资的公司数量。如果一家公司有多个员工报名,这家公司只统计一次。
### Q5: "开课天数"是如何计算的?
**A**: 对每场课程计算从开始日期到结束日期的天数包含开始日期和结束日期然后将所有场次的天数加起来。例如某场课程从1月1日到1月3日算3天。
### Q6: 统计时间范围是什么意思?
**A**: 统计时间范围是指报名记录的创建时间不是课程的开课时间。例如选择2024年1月1日到12月31日会统计在这个时间段内创建的所有报名记录即使这些课程可能在2025年才开课。
### Q7: 为什么选择不同的课程类型,统计数据会不同?
**A**: 因为统计数据是基于选择的课程类型进行筛选的。如果选择全部课程类型,会统计所有课程的数据;如果选择特定的课程类型,只统计该类型下的课程数据。
---
## 十、使用建议
### 10.1 查看整体情况
- 使用"报名人数"了解课程的整体报名热度
- 使用"审核通过人数(去重)"了解实际培养了多少个不同的学员
- 使用"开课场次"和"开课天数"了解课程开展的频率和时长
### 10.2 查看分类情况
- 使用"课程分类明细统计"了解不同课程类型的培养情况
- 使用"区域明细统计"了解不同区域的参与情况
### 10.3 数据对比
- 对比"报名人数"和"审核通过人数",了解审核通过率
- 对比"审核通过人数"和"审核通过人数(去重)",了解学员的参与深度
- 对比不同课程类型的数据,了解各类课程的受欢迎程度
---
## 十一、注意事项
1. **时间范围选择**:统计数据基于报名记录的创建时间,不是课程开课时间
2. **去重逻辑**:去重统计使用学员的手机号作为依据
3. **区域统计**:区域统计基于学员所在公司的区域
4. **数据关系**:不同统计数据之间可能存在包含关系,请注意理解
5. **汇总数据**:区域汇总可能包含跨区域的重复学员,请注意区分

@ -0,0 +1,32 @@
<?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('calendars', function (Blueprint $table) {
$table->decimal('days', 10, 1)->nullable()->comment('天数')->after('end_time');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('calendars', function (Blueprint $table) {
$table->dropColumn('days');
});
}
};

@ -240,6 +240,12 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::get('course-content-check/show', [\App\Http\Controllers\Admin\CourseContentCheckController::class, "show"]);
Route::post('course-content-check/save', [\App\Http\Controllers\Admin\CourseContentCheckController::class, "save"]);
Route::get('course-content-check/destroy', [\App\Http\Controllers\Admin\CourseContentCheckController::class, "destroy"]);
// 文章管理
Route::get('article/index', [\App\Http\Controllers\Admin\ArticleController::class, "index"]);
Route::get('article/show', [\App\Http\Controllers\Admin\ArticleController::class, "show"]);
Route::post('article/save', [\App\Http\Controllers\Admin\ArticleController::class, "save"]);
Route::get('article/destroy', [\App\Http\Controllers\Admin\ArticleController::class, "destroy"]);
});
});
@ -295,7 +301,7 @@ Route::group(["namespace" => "Mobile", "prefix" => "mobile"], function () {
Route::get('course/my-course', [\App\Http\Controllers\Mobile\CourseController::class, "myCourse"]);
Route::get('course/my-course-content', [\App\Http\Controllers\Mobile\CourseController::class, "myCourseContent"]);
// Route::post('course/course-form', [\App\Http\Controllers\Mobile\CourseController::class, "courseForm"]);
// Route::post('course/course-form', [\App\Http\Controllers\Mobile\CourseController::class, "courseForm"]);
Route::get('course/get-sign', [\App\Http\Controllers\Mobile\CourseController::class, "getSign"]);
Route::post('course/update-sign', [\App\Http\Controllers\Mobile\CourseController::class, "updateSign"]);
Route::post('course/course-content-form', [\App\Http\Controllers\Mobile\CourseController::class, "courseContentForm"]);

Loading…
Cancel
Save