You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1204 lines
58 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Http\Controllers\Admin;
use App\Exports\BaseExport;
use App\Exports\CommonExport;
use App\Helpers\ResponseCode;
use App\Helpers\StarterResponseCode;
use App\Models\Course;
use App\Models\CourseAppointmentTotal;
use App\Models\CourseSign;
use App\Models\CourseType;
use App\Models\CustomForm;
use App\Models\CustomFormField;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Rap2hpoutre\FastExcel\FastExcel;
class UserController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new User());
}
/**
* @OA\Get(
* path="/api/admin/users/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="需要输出的关联关系数组包括coursescourseSignsteachercourseSettingscoursePeriods"),
* @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="has_course", in="query", @OA\Schema(type="string"), required=false, description="是否有课程0否1是"),
* @OA\Parameter(name="keyword", 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",
* description="暂无"
* )
* )
*/
public function index()
{
$all = request()->all();
$list = $this->model->with(underlineToHump($all['show_relation'] ?? []))
->with([
'courseSigns' => function ($query) use ($all) {
$query->where('status', 1)->with('course.teacher', 'course.typeDetail');
}
])->where(function ($query) use ($all) {
if (isset($all['keyword'])) {
$query->whereHas('courses', function ($q) use ($all) {
$q->where('name', 'like', '%' . $all['keyword'] . '%');
})->orWhere('name', 'like', '%' . $all['keyword'] . '%');
}
if (isset($all['has_course']) && $all['has_course'] == 1) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
});
}
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 . '%');
}
// null搜索
if ($op == 'null') {
$query->whereNull($key);
}
// notnull搜索
if ($op == 'notnull') {
$query->whereNotNull($key);
}
// 范围搜索
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/users/study",
* tags={"用户信息"},
* summary="学员管理(参与了课程的用户包含统计数据)",
* description="",
* @OA\Parameter(name="is_export", in="query", @OA\Schema(type="string"), required=false, description="是否导出0否1是"),
* @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="file_name", 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="course_id", in="query", @OA\Schema(type="string"), required=false, description="课程id"),
* @OA\Parameter(name="course_name", in="query", @OA\Schema(type="string"), required=false, description="课程名称"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), required=false, description="名字"),
* @OA\Parameter(name="company_name", in="query", @OA\Schema(type="string"), required=false, description="公司名字"),
* @OA\Parameter(name="company_position", in="query", @OA\Schema(type="string"), required=false, description="职务"),
* @OA\Parameter(name="company_area", in="query", @OA\Schema(type="string"), required=false, description="所在区域多个英文逗号分隔like 匹配关联 company 的 company_areaor 关系)"),
* @OA\Parameter(name="company_type", in="query", @OA\Schema(type="string"), required=false, description="企业性质"),
* @OA\Parameter(name="company_industry", in="query", @OA\Schema(type="string"), required=false, description="所属行业"),
* @OA\Parameter(name="courses_start_date", in="query", @OA\Schema(type="string"), required=false, description="课程开始时间"),
* @OA\Parameter(name="courses_end_date", in="query", @OA\Schema(type="string"), required=false, description="课程结束时间"),
* @OA\Parameter(name="is_vip", in="query", @OA\Schema(type="string"), required=false, description="是否vip0否1是"),
* @OA\Parameter(name="courses_ing", in="query", @OA\Schema(type="string"), required=false, description="是否课程进行中0否1是"),
* @OA\Parameter(name="is_chart", in="query", @OA\Schema(type="string"), required=false, description="课程是否参与统计0否1是"),
* @OA\Parameter(name="is_schoolmate", in="query", @OA\Schema(type="string"), required=false, description="是否校友0否1是"),
* @OA\Parameter(name="mobile", in="query", @OA\Schema(type="string"), required=false, description="手机号"),
* @OA\Parameter(name="status", in="query", @OA\Schema(type="string"), required=false, description="审核状态0待审核1通过2不通过"),
* @OA\Parameter(name="course_type", in="query", @OA\Schema(type="string"), required=false, description="课程体系ID"),
* @OA\Parameter(name="company_has_share", in="query", @OA\Schema(type="string"), required=false, description="是否有股份0否1是"),
* @OA\Parameter(name="keyword", in="query", @OA\Schema(type="string"), required=false, description="关键词(搜索学校、专业、介绍)"),
* @OA\Parameter(name="start_company_date", in="query", @OA\Schema(type="string"), required=false, description="开始成立日期"),
* @OA\Parameter(name="end_company_date", in="query", @OA\Schema(type="string"), required=false, description="结束成立日期"),
* @OA\Parameter(name="start_birthday", in="query", @OA\Schema(type="string"), required=false, description="开始出生日期"),
* @OA\Parameter(name="end_birthday", in="query", @OA\Schema(type="string"), required=false, description="结束出生日期"),
* @OA\Parameter(name="sign_start_date", in="query", @OA\Schema(type="string"), required=false, description="报名开始时间"),
* @OA\Parameter(name="sign_end_date", in="query", @OA\Schema(type="string"), required=false, description="报名结束时间"),
* @OA\Parameter(name="company_need_fund", in="query", @OA\Schema(type="string"), required=false, description="是否需要融资0否1是"),
* @OA\Parameter(name="is_fee", in="query", @OA\Schema(type="string"), required=false, description="是否缴费0否1是"),
* @OA\Parameter(name="has_openid", in="query", @OA\Schema(type="string"), required=false, description="是否绑定小程序0否1是"),
* @OA\Parameter(name="year", in="query", @OA\Schema(type="string"), required=false, description="年份,默认为当前年份"),
* @OA\Parameter(name="is_black", in="query", @OA\Schema(type="string"), required=false, description="是否黑名单0否1是"),
* @OA\Parameter(name="is_yh_invested", in="query", @OA\Schema(type="string"), required=false, description="是否元和已投企业0否1是"),
* @OA\Parameter(name="is_company_market", in="query", @OA\Schema(type="string"), required=false, description="是否上市公司0否1是"),
* @OA\Parameter(name="company_tag", in="query", @OA\Schema(type="string"), required=false, description="企业标签"),
* @OA\Parameter(name="talent_tags", in="query", @OA\Schema(type="string"), required=false, description="人才标签,多个英文逗号分隔"),
* @OA\Parameter(name="address", in="query", @OA\Schema(type="string"), required=false, description="公司地址,模糊匹配关联 company 的 company_address 或 company_city"),
* @OA\Parameter(name="is_rencai", in="query", @OA\Schema(type="string"), required=false, description="是否人才1=是时筛选;满足任一条即为人才:报名过人才培训课程(typeDetail.name=人才培训) 或 user.type/talent_tags 含「人才」"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function study()
{
$all = request()->all();
$year = request('year', date('Y'));
$start_date = $year . '-01-01';
$end_date = $year . '-12-31';
$list = $this->model->with(
'appointments',
'companyIndustryDetail',
'companyPositionDetail',
'companyAreaDetail',
'company'
)
->with([
'courseSigns' => function ($query) {
$query->with('course.typeDetail')->orderBy('fee_status', 'desc');
}
])->withCount([
'appointments' => function ($query) {
$query->whereIn('status', [0, 1]);
}
]);
// 是否被投企业
if (isset($all['is_yh_invested'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
if ($all['is_yh_invested'] == 1) {
$query->where('is_yh_invested', 1);
} else {
$query->where('is_yh_invested', 0);
}
});
}
// 是否上市公司
if (isset($all['is_company_market'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
if ($all['is_company_market'] == 1) {
$query->where('company_market', 1);
} else {
$query->where('company_market', 0);
}
});
}
// company_tag等价于company_type新旧数据兼容
if (isset($all['company_type'])) {
$all['company_tag'] = $all['company_type'];
}
// 新数据
if (isset($all['company_tag'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
$string = explode(',', $all['company_tag']);
$query->where(function ($q) use ($string) {
foreach ($string as $index => $v) {
if ($index === 0) {
$q->where('company_tag', 'like', '%' . trim($v) . '%');
} else {
$q->orWhere('company_tag', 'like', '%' . trim($v) . '%');
}
}
});
});
}
// 榜单标签查询
if (isset($all['ranking_tag'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
$string = explode(',', $all['ranking_tag']);
$query->where(function ($q) use ($string) {
foreach ($string as $index => $v) {
$trimmed = trim($v);
if (!empty($trimmed)) {
if ($index === 0) {
$q->where('ranking_tag', 'like', '%' . $trimmed . '%');
} else {
$q->orWhere('ranking_tag', 'like', '%' . $trimmed . '%');
}
}
}
});
});
}
$list = $list->whereHas('courseSigns', function ($query) use ($all) {
if (isset($all['course_id'])) {
$query->where('course_id', $all['course_id']);
}
if (isset($all['status'])) {
$query->where('status', $all['status']);
}
// 报名时间筛选
if (isset($all['sign_start_date']) && isset($all['sign_end_date'])) {
$query->whereBetween('created_at', [$all['sign_start_date'], $all['sign_end_date']]);
} elseif (isset($all['sign_start_date'])) {
$query->where('created_at', '>=', $all['sign_start_date']);
} elseif (isset($all['sign_end_date'])) {
$query->where('created_at', '<=', $all['sign_end_date']);
}
$query->whereHas('course', function ($q) use ($all) {
if (isset($all['year'])) {
$q->where('year', $all['year']);
}
if (isset($all['is_fee'])) {
$q->where('is_fee', $all['is_fee']);
};
if (isset($all['course_type'])) {
$q->where('type', $all['course_type']);
};
if (isset($all['course_name'])) {
$q->where('name', 'like', '%' . $all['course_name'] . '%');
}
// 课程日期与统计一致start_date 或 end_date 在区间内即计入whereBetween 重叠逻辑)
if (!empty($all['courses_start_date']) && !empty($all['courses_end_date'])) {
$q->where(function ($query) use ($all) {
$query->whereBetween('start_date', [$all['courses_start_date'], $all['courses_end_date']])
->orWhereBetween('end_date', [$all['courses_start_date'], $all['courses_end_date']]);
});
} else {
if (!empty($all['courses_start_date'])) {
$q->where('start_date', '>=', $all['courses_start_date']);
}
if (!empty($all['courses_end_date'])) {
$q->where('end_date', '<=', $all['courses_end_date']);
}
}
if (isset($all['courses_ing']) && $all['courses_ing'] == 1) {
$q->where(function ($query) use ($all) {
$query->where('start_date', '<=', date('Y-m-d'))->where('end_date', '>=', date('Y-m-d'));
});
}
// 课程是否参与统计0否 1是
if (isset($all['is_chart'])) {
$q->where('is_chart', $all['is_chart']);
}
// from=跟班学员 时与 ganbu_total 口径一致:仅统计 is_count_genban=1 的课程类型
if (!empty($all['from']) && strpos((string) $all['from'], '跟班学员') !== false) {
$q->whereHas('typeDetail', function ($q) {
$q->where('is_count_genban', 1);
});
}
});
});
// 不通过的需要全部不通过
if (isset($all['status']) && $all['status'] == 2) {
// 筛选不通过的
$list = $list->whereDoesntHave('courseSigns', function ($query) {
$query->where('status', 1);
});
}
$list = $list->where(function ($query) use ($all) {
if (isset($all['from'])) {
$query->where('from', 'like', '%' . $all['from'] . '%');
}
if (isset($all['is_vip'])) {
$query->where('is_vip', $all['is_vip']);
}
if (isset($all['is_black'])) {
$query->where('is_black', $all['is_black']);
}
if (isset($all['type'])) {
$type = explode(',', $all['type']);
$query->where(function ($q) use ($type) {
foreach ($type as $v) {
$q->orWhereRaw('FIND_IN_SET(?, type)', [$v]);
}
});
}
if (isset($all['has_openid'])) {
if ($all['has_openid'] == 1) {
$query->where(function ($q) {
$q->whereNotNull('openid')->where('openid', '!=', '');
});
} else {
$query->where(function ($q) {
$q->whereNull('openid')->orWhere('openid', '');
});
}
}
if (isset($all['name'])) {
$query->where('name', 'like', '%' . $all['name'] . '%');
}
if (isset($all['company_name'])) {
$query->where('company_name', 'like', '%' . $all['company_name'] . '%');
}
if (isset($all['company_position'])) {
$query->where('company_position', $all['company_position']);
}
// 所在区域like 匹配关联 company 的 company_area多个英文逗号分隔为 or
if (isset($all['company_area']) && $all['company_area'] !== '') {
$query->whereHas('company', function ($c) use ($all) {
$c->filterByArea($all['company_area']);
});
}
// 公司地址:模糊匹配关联 company 的 company_address 或 company_city
if (isset($all['address']) && $all['address'] !== '') {
$query->whereHas('company', function ($c) use ($all) {
$c->where('company_address', 'like', '%' . $all['address'] . '%')
->orWhere('company_city', 'like', '%' . $all['address'] . '%');
});
}
if (isset($all['company_industry'])) {
$company_industry = explode(',', $all['company_industry']);
$query->where(function ($q) use ($company_industry) {
foreach ($company_industry as $v) {
$q->orWhereRaw('FIND_IN_SET(?, company_industry)', [$v]);
}
});
}
if (isset($all['is_schoolmate'])) {
$query->where('is_schoolmate', $all['is_schoolmate']);
}
if (isset($all['year'])) {
$query->where('created_at', 'like', '%' . $all['year'] . '%');
}
if (isset($all['company_need_fund'])) {
$query->where('company_need_fund', $all['company_need_fund']);
}
if (isset($all['mobile'])) {
$query->where('mobile', 'like', '%' . $all['mobile'] . '%');
}
if (isset($all['education'])) {
$education = explode(',', $all['education']);
$query->whereIn('education', $education);
}
if (isset($all['company_has_share'])) {
$query->where('company_has_share', $all['company_has_share']);
}
if (isset($all['school'])) {
$query->where('school', 'like', '%' . $all['school'] . '%');
}
if (isset($all['start_company_date']) && isset($all['end_company_date'])) {
$query->whereBetween('company_date', [$all['start_company_date'], $all['end_company_date']]);
}
if (isset($all['start_birthday']) && isset($all['end_birthday'])) {
$query->whereBetween('birthday', [$all['start_birthday'], $all['end_birthday']]);
}
if (isset($all['keyword'])) {
$query->where('school', 'like', '%' . $all['keyword'] . '%')
->orWhere('speciality', 'like', '%' . $all['keyword'] . '%')
->orWhere('introduce', 'like', '%' . $all['keyword'] . '%');
}
if (isset($all['talent_tags'])) {
$talentTags = explode(',', $all['talent_tags']);
$query->where(function ($q) use ($talentTags) {
foreach ($talentTags as $tag) {
$q->orWhereRaw('FIND_IN_SET(?, talent_tags)', [trim($tag)]);
}
});
}
// 是否人才(与 CourseSign::rencai 口径一致1=是时筛选。任一条即视为人才:报名过人才培训课程 或 type/talent_tags 含「人才」。
// 人才培训仅在本次查询的课程与时间范围内courses_start/end、is_chart 等)认定,与 cover_rencai_total 一致。
if (isset($all['is_rencai']) && (int) $all['is_rencai'] === 1) {
$query->where(function ($q) use ($all) {
$q->whereHas('courseSigns', function ($cs) use ($all) {
$cs->where('status', 1);
if (isset($all['course_id'])) {
$cs->where('course_id', $all['course_id']);
}
if (isset($all['sign_start_date']) && isset($all['sign_end_date'])) {
$cs->whereBetween('created_at', [$all['sign_start_date'], $all['sign_end_date']]);
} elseif (isset($all['sign_start_date'])) {
$cs->where('created_at', '>=', $all['sign_start_date']);
} elseif (isset($all['sign_end_date'])) {
$cs->where('created_at', '<=', $all['sign_end_date']);
}
$cs->whereHas('course', function ($c) use ($all) {
if (isset($all['year'])) {
$c->where('year', $all['year']);
}
if (isset($all['is_fee'])) {
$c->where('is_fee', $all['is_fee']);
}
if (isset($all['course_type'])) {
$c->where('type', $all['course_type']);
}
if (isset($all['course_name'])) {
$c->where('name', 'like', '%' . $all['course_name'] . '%');
}
if (!empty($all['courses_start_date']) && !empty($all['courses_end_date'])) {
$c->where(function ($q2) use ($all) {
$q2->whereBetween('start_date', [$all['courses_start_date'], $all['courses_end_date']])
->orWhereBetween('end_date', [$all['courses_start_date'], $all['courses_end_date']]);
});
} else {
if (!empty($all['courses_start_date'])) {
$c->where('start_date', '>=', $all['courses_start_date']);
}
if (!empty($all['courses_end_date'])) {
$c->where('end_date', '<=', $all['courses_end_date']);
}
}
if (isset($all['courses_ing']) && $all['courses_ing'] == 1) {
$c->where(function ($q2) {
$q2->where('start_date', '<=', date('Y-m-d'))->where('end_date', '>=', date('Y-m-d'));
});
}
if (isset($all['is_chart'])) {
$c->where('is_chart', $all['is_chart']);
}
if (!empty($all['from']) && strpos((string) $all['from'], '跟班学员') !== false) {
$c->whereHas('typeDetail', function ($t) {
$t->where('is_count_genban', 1);
});
}
$c->whereHas('typeDetail', function ($t) {
$t->where('name', '人才培训');
});
});
})->orWhere('type', 'like', '%人才%')->orWhere('talent_tags', 'like', '%人才%');
});
}
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
if (isset($all['is_export']) && !empty($all['is_export'])) {
$list = $list->limit(5000)->get()->toArray();
return Excel::download(new CommonExport($list, $all['export_fields'] ?? ''), $all['file_name'] ?? '' . date('YmdHis') . '.xlsx');
} else {
// 累计总数
$total = CourseSign::courseSignsTotalByUnique(CourseType::START_DATE, date('Y-m-d'));
// 报名人数
$year_total = CourseSign::courseSignsTotalByUnique($start_date, $end_date);
// 年度培养学员
$year_training_total = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1);
// 累计培养学员
$training_total = CourseSign::courseSignsTotalByUnique(CourseType::START_DATE, date('Y-m-d'), 1);
$list = $list->paginate($all['page_size'] ?? 20);
}
return $this->success(['list' => $list, 'year_total' => $year_total, 'total' => $total, 'year_training_total' => $year_training_total, 'training_total' => $training_total]);
}
/**
* @OA\Get(
* path="/api/admin/users/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/users/save",
* tags={"时间段设置"},
* summary="更新或新增",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="integer"), required=true, description="课程ID存在则更新不存在则新增"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="访问令牌"),
* @OA\Parameter(name="openid", in="query", @OA\Schema(type="string"), description="用户openid"),
* @OA\Parameter(name="sex", in="query", @OA\Schema(type="string"), description="性别男/女"),
* @OA\Parameter(name="nickname", in="query", @OA\Schema(type="string"), description="昵称"),
* @OA\Parameter(name="mobile", in="query", @OA\Schema(type="string"), description="手机号"),
* @OA\Parameter(name="country", in="query", @OA\Schema(type="string"), description="国家"),
* @OA\Parameter(name="province", in="query", @OA\Schema(type="string"), description="省份"),
* @OA\Parameter(name="city", in="query", @OA\Schema(type="string"), description="城市"),
* @OA\Parameter(name="headimgurl", in="query", @OA\Schema(type="string"), description="头像URL"),
* @OA\Parameter(name="username", in="query", @OA\Schema(type="string"), description="用户名"),
* @OA\Parameter(name="password", in="query", @OA\Schema(type="string"), description="密码"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), description="名字"),
* @OA\Parameter(name="birthday", in="query", @OA\Schema(type="string"), description="生日"),
* @OA\Parameter(name="email", in="query", @OA\Schema(type="string"), description="邮箱"),
* @OA\Parameter(name="education", in="query", @OA\Schema(type="integer"), description="学历"),
* @OA\Parameter(name="school", in="query", @OA\Schema(type="string"), description="学校"),
* @OA\Parameter(name="speciality", in="query", @OA\Schema(type="string"), description="专业"),
* @OA\Parameter(name="honour", in="query", @OA\Schema(type="string"), description="荣誉"),
* @OA\Parameter(name="introduce", in="query", @OA\Schema(type="string"), description="介绍"),
* @OA\Parameter(name="company_name", in="query", @OA\Schema(type="string"), description="公司名称"),
* @OA\Parameter(name="company_position", in="query", @OA\Schema(type="string"), description="个人职务"),
* @OA\Parameter(name="company_has_share", in="query", @OA\Schema(type="string"), description="是否有股份0否1是"),
* @OA\Parameter(name="company_build_date", in="query", @OA\Schema(type="string"), description="公司成立日期"),
* @OA\Parameter(name="company_area", in="query", @OA\Schema(type="string"), description="公司区域-数据字典"),
* @OA\Parameter(name="company_type", in="query", @OA\Schema(type="string"), description="公司性质-数据字典"),
* @OA\Parameter(name="company_industry", in="query", @OA\Schema(type="string"), description="公司所属行业-数据字典"),
* @OA\Parameter(name="company_business", in="query", @OA\Schema(type="string"), description="公司主营业务"),
* @OA\Parameter(name="company_fund", in="query", @OA\Schema(type="string"), description="公司融资情况0否1是"),
* @OA\Parameter(name="company_need_fund", in="query", @OA\Schema(type="integer"), description="公司是否需要融资0否1是"),
* @OA\Parameter(name="sign_from", in="query", @OA\Schema(type="string"), description="报名信息来源"),
* @OA\Parameter(name="remark", in="query", @OA\Schema(type="string"), description="备注"),
* @OA\Parameter(name="is_black", in="query", @OA\Schema(type="string"), description="是否黑名单0否1是"),
* @OA\Parameter(name="has_appointment_total", in="query", @OA\Schema(type="string"), description="预约剩余次数"),
* @OA\Parameter(name="from", in="query", @OA\Schema(type="string"), description="来源"),
* @OA\Response(
* response=200,
* description="操作成功"
* )
* )
*/
public function save()
{
return parent::save();
}
/**
* @OA\Get(
* path="/api/admin/users/destroy",
* tags={"用户信息"},
* summary="删除",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function destroy()
{
return parent::destroy();
}
/**
* @OA\Post(
* path="/api/admin/users/excel-show",
* tags={"用户信息"},
* summary="导入预览",
* description="",
* @OA\Parameter(name="file", 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",
* description="暂无"
* )
* )
*/
public function excelShow()
{
$file = \request()->file('file');
$data = \request('data', []);
//判断文件是否有效
if (!(\request()->hasFile('file') && $file->isValid())) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '文件不存在或无效']);
}
//获取文件大小
$img_size = floor($file->getSize() / 1024);
if ($img_size >= 50 * 1024) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '文件必须小于50M']);
}
//过滤文件后缀
$ext = $file->getClientOriginalExtension();
if (!in_array($ext, ['xls', 'xlsx', 'csv'])) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '仅支持xls/xlsx/csv格式']);
}
$tempFile = $file->getRealPath();
$dataArray = (new FastExcel)->import($tempFile)->toArray();
// 数据过滤,只能导入数据表有的字段
$tableName = $this->model->getTable();
$rowTableFieldByComment = (new CustomFormField)->getRowTableFieldsByComment($tableName);
$list = [];
$statusList = CourseSign::$intToString['status'];
$statusList = array_flip($statusList);
foreach ($dataArray as $key => $value) {
// 获取课程id
$course = Course::where('name', $value['课程名称'])->first();
$list[$key]['course_id'] = $course->id ?? 0;
$list[$key]['course_name'] = $value['课程名称'] ?? '';
// 默认没有付费
$list[$key]['fee_status'] = 0;
$list[$key]['status'] = $list[$key]['status_name'] = null;
if (isset($value['审核状态'])) {
// 获取审核状态
$list[$key]['status'] = $statusList[$value['审核状态']];
$list[$key]['status_name'] = $value['审核状态'];
if ($list[$key]['status'] == 1) {
// 审核通过,则付费情况根据课程情况定
$list[$key]['fee_status'] = $course->is_fee ?? 0;
}
}
foreach ($rowTableFieldByComment as $k => $v) {
if (isset($value[$v])) {
// 日期格式
if ($value[$v] instanceof \DateTimeImmutable) {
$list[$key][$k] = Carbon::parse($value[$v])->toDateString();
} else {
if (in_array($k, ['company_type', 'type'])) {
$list[$key][$k] = str_replace('、', ',', $value[$v]);
$list[$key][$k] = str_replace('', ',', $list[$key][$k]);
;
} else {
$list[$key][$k] = $value[$v];
}
}
}
}
}
return $this->success($list);
}
/**
* @OA\Post(
* path="/api/admin/users/import",
* tags={"用户信息"},
* summary="导入",
* description="",
* @OA\Parameter(name="data", 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",
* description="暂无"
* )
* )
*/
public function import()
{
return parent::import();
}
/**
* @OA\Post(
* path="/api/admin/users/import-study",
* tags={"用户信息"},
* summary="导入学员信息(旧的校友库导入)",
* description="",
* @OA\Parameter(name="data", 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",
* description="暂无"
* )
* )
*/
public function importStudy()
{
$all = \request()->all();
$messages = [
'data.required' => '数据必填',
];
$validator = Validator::make($all, [
'data' => 'required',
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$filteredRecords = $all['data'];
$suc = 0;
DB::beginTransaction();
try {
// 一个个新增因为需要设置关联表的id
foreach ($filteredRecords as $record) {
if (empty($record['mobile'])) {
return $this->fail([ResponseCode::ERROR_BUSINESS, ($record['姓名'] ?? '') . '手机号不存在']);
}
$record['is_import'] = 1;
$record['username'] = $record['name'];
$record['is_vip'] = $record['fee_status'] == 1 ? 1 : 0;
$where = ['mobile' => $record['mobile']];
// 去除空值更新
$record = array_filter($record, function ($value) {
return $value != '';
});
// 所有数据通过追加的形式更新
$user = $this->model->where($where)->first();
if ($user) {
// 更新,所有$record里的数组通过追加在原来数据后面的形式更新
foreach ($record as $k => &$v) {
if (!in_array($k, User::$coverFields)) {
// 追加更新
$tempArray = explode(',', $user->$k . ',' . $v);
$tempArray = array_unique(array_filter($tempArray));
$v = implode(',', $tempArray);
}
}
$user->fill($record);
$user->save();
} else {
// 新增
$user = $this->model->create($record);
}
// 写入报名表
if (isset($record['course_id']) && !empty($record['course_id'])) {
$whereSign = ['course_id' => $record['course_id'], 'user_id' => $user->id];
$dataSign = [
'course_id' => $record['course_id'],
'user_id' => $user->id,
'is_import' => 1,
'status' => $record['status'] ?? 1,
'fee_status' => $record['fee_status']
];
$courseSign = CourseSign::updateOrCreate($whereSign, $dataSign);
// 加导入次数,加预约次数
if ($courseSign->status == 1) {
CourseAppointmentTotal::add($courseSign->user_id, $courseSign->id);
}
}
$suc++;
}
DB::commit();
return $this->success(['total' => count($filteredRecords), 'suc' => $suc]);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Post(
* path="/api/admin/users/batch-update-schoolmate",
* tags={"用户信息"},
* summary="设置取消校友库",
* description="",
* @OA\Parameter(name="ids", in="query", @OA\Schema(type="string"), required=true, description="英文逗号分隔的id数组"),
* @OA\Parameter(name="is_schoolmate", in="query", @OA\Schema(type="string"), required=true, description="是否校友库-0否1是"),
* @OA\Parameter(name="is_black", in="query", @OA\Schema(type="string"), required=true, description="是否黑名单-0否1是"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function batchUpdateSchoolmate()
{
$all = \request()->all();
$messages = [
'ids.required' => '编号必填',
];
$validator = Validator::make($all, [
'ids' => 'required',
], $messages);
if ($validator->fails()) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$idsArray = array_filter(array_map('trim', explode(',', $all['ids'])));
if (empty($idsArray)) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, '编号不能为空']);
}
DB::beginTransaction();
try {
$updatedCount = 0;
if (isset($all['is_schoolmate'])) {
if ($all['is_schoolmate'] == 1) {
// 改为逐条 save(),确保触发审计日志
$users = $this->model->whereIn('id', $idsArray)
->whereRaw('COALESCE(is_schoolmate, 0) != 1')
->get();
foreach ($users as $user) {
$user->is_schoolmate = 1;
$user->schoolmate_time = now();
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
} else {
// 改为逐条 save(),确保触发审计日志
$users = $this->model->whereIn('id', $idsArray)->get();
foreach ($users as $user) {
$user->is_schoolmate = $all['is_schoolmate'];
$user->schoolmate_time = null;
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
}
}
DB::commit();
return $this->success('批量更新成功,共更新 ' . $updatedCount . ' 条记录');
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Post(
* path="/api/admin/users/batch-update",
* tags={"用户信息"},
* summary="批量更新用户信息",
* description="",
* @OA\Parameter(name="ids", in="query", @OA\Schema(type="string"), required=true, description="英文逗号分隔的用户id"),
* @OA\Parameter(name="data", in="query", @OA\Schema(type="object"), required=true, description="需要更新的字段对象,键为字段名,值为字段值,例如:{is_vip:1,is_schoolmate:1,talent_tags:标签1,标签2}"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function batchUpdate()
{
$all = \request()->all();
$messages = [
'ids.required' => '用户ID必填',
'data.required' => '更新数据必填',
];
$validator = Validator::make($all, [
'ids' => 'required',
'data' => 'required|array',
], $messages);
if ($validator->fails()) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
// 获取可更新的字段列表fillable字段
$fillableFields = $this->model->getFillable();
// 构建更新数据只保留fillable中的字段
$data = [];
foreach ($all['data'] as $field => $value) {
// 只允许更新fillable中的字段
if (in_array($field, $fillableFields)) {
$data[$field] = $value;
}
}
if (empty($data)) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, '没有可更新的有效字段']);
}
if (array_key_exists('is_schoolmate', $data)) {
$data['schoolmate_time'] = ($data['is_schoolmate'] == 1) ? now() : null;
}
// 解析用户ID
$idsArray = explode(',', $all['ids']);
$idsArray = array_filter(array_map('trim', $idsArray));
if (empty($idsArray)) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, '用户ID不能为空']);
}
DB::beginTransaction();
try {
$users = $this->model->whereIn('id', $idsArray)->get();
$updatedCount = 0;
foreach ($users as $user) {
// 改为逐条 save(),确保触发审计日志
$user->fill($data);
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
DB::commit();
return $this->success('批量更新成功,共更新 ' . $updatedCount . ' 条记录');
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Post(
* path="/api/admin/users/excel-show-special",
* tags={"用户信息"},
* summary="特殊导入规则预览(通过姓名、公司、职位匹配,仅更新不创建)",
* description="通过姓名、公司名称、职位三个字段匹配现有用户,未匹配到的用户会在返回结果中提示",
* @OA\Parameter(name="file", 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",
* description="返回匹配结果,包含未匹配用户列表"
* )
* )
*/
public function excelShowSpecial()
{
$file = \request()->file('file');
//判断文件是否有效
if (!(\request()->hasFile('file') && $file->isValid())) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '文件不存在或无效']);
}
//获取文件大小
$img_size = floor($file->getSize() / 1024);
if ($img_size >= 50 * 1024) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '文件必须小于50M']);
}
//过滤文件后缀
$ext = $file->getClientOriginalExtension();
if (!in_array($ext, ['xls', 'xlsx', 'csv'])) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '仅支持xls/xlsx/csv格式']);
}
$tempFile = $file->getRealPath();
$dataArray = (new FastExcel)->import($tempFile)->toArray();
// 数据过滤,只能导入数据表有的字段
$tableName = $this->model->getTable();
$rowTableFieldByComment = (new CustomFormField)->getRowTableFieldsByComment($tableName);
// 获取 Excel 文件中的表头(从第一行数据中提取)
$excelHeaders = [];
if (!empty($dataArray)) {
$firstRow = reset($dataArray);
$excelHeaders = array_keys($firstRow);
}
// 只保留 Excel 中存在的字段的表头信息
$filteredHeaders = [];
foreach ($rowTableFieldByComment as $fieldName => $comment) {
if (in_array($comment, $excelHeaders)) {
$filteredHeaders[$fieldName] = $comment;
}
}
$list = [];
$unmatchedUsers = []; // 记录未匹配到的用户
foreach ($dataArray as $key => $value) {
// 获取姓名、公司名称、职位
$name = $value['姓名'] ?? $value['name'] ?? '';
$companyName = $value['公司'] ?? $value['company_name'] ?? '';
$companyPosition = $value['职务'] ?? $value['company_position'] ?? '';
// 通过姓名、公司名称、职位匹配用户
$matchedUser = null;
if (!empty($name) && !empty($companyName) && !empty($companyPosition)) {
$matchedUser = $this->model->where('username', $name)
->where('company_name', $companyName)
->where('company_position', $companyPosition)
->first();
}
// 记录未匹配到的用户
if (!$matchedUser) {
$unmatchedUsers[] = [
'row' => $key + 1, // Excel行号从1开始
'name' => $name,
'company_name' => $companyName,
'company_position' => $companyPosition,
];
}
// 构建返回数据使用ID作为唯一标识
$list[$key] = [
'name' => $name,
'company_name' => $companyName,
'company_position' => $companyPosition,
'matched' => $matchedUser ? true : false,
'id' => $matchedUser ? $matchedUser->id : null, // 使用ID作为唯一标识
'existing_data' => $matchedUser ? [
'id' => $matchedUser->id,
'username' => $matchedUser->username,
'name' => $matchedUser->name,
'company_name' => $matchedUser->company_name,
'company_position' => $matchedUser->company_position,
] : null,
];
// 处理其他字段
foreach ($rowTableFieldByComment as $k => $v) {
if (isset($value[$v])) {
// 日期格式
if ($value[$v] instanceof \DateTimeImmutable) {
$list[$key][$k] = Carbon::parse($value[$v])->toDateString();
} else {
if (in_array($k, ['company_type', 'type'])) {
$list[$key][$k] = str_replace('、', ',', $value[$v]);
$list[$key][$k] = str_replace('', ',', $list[$key][$k]);
} else {
$list[$key][$k] = $value[$v];
}
}
}
}
}
// 构建返回结果
$result = [
'headers' => $filteredHeaders, // 表头信息,只包含 Excel 中存在的字段,键为字段名,值为中文注释
'list' => $list,
'matched_count' => count($list) - count($unmatchedUsers),
'unmatched_count' => count($unmatchedUsers),
'unmatched_users' => $unmatchedUsers,
];
// 如果有未匹配到的用户,添加提示信息
if (count($unmatchedUsers) > 0) {
$unmatchedNames = array_map(function ($user) {
return "{$user['row']}行:{$user['name']}{$user['company_name']} - {$user['company_position']}";
}, $unmatchedUsers);
$result['message'] = '以下用户未匹配到,请检查数据:' . implode('', $unmatchedNames);
}
return $this->success($result);
}
/**
* @OA\Post(
* path="/api/admin/users/import-special",
* tags={"用户信息"},
* summary="特殊导入规则导入(通过姓名、公司、职位匹配,仅更新不创建)",
* description="仅更新已匹配到的用户,如果存在未匹配到的用户,将返回错误并回滚事务",
* @OA\Parameter(name="data", 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",
* description="返回更新结果"
* )
* )
*/
public function importSpecial()
{
$all = \request()->all();
$messages = [
'data.required' => '数据必填',
];
$validator = Validator::make($all, [
'data' => 'required',
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$filteredRecords = $all['data'];
$suc = 0;
$updateCount = 0;
$failedRecords = []; // 记录导入失败的记录
DB::beginTransaction();
try {
foreach ($filteredRecords as $index => $record) {
// 获取匹配的用户ID如果预览时已匹配到
$userId = $record['id'] ?? null;
// 如果没有ID记录失败并跳过
if (!$userId) {
$failedRecords[] = [
'row' => $index + 1,
'name' => $record['name'] ?? '',
'company_name' => $record['company_name'] ?? '',
'company_position' => $record['company_position'] ?? '',
'reason' => '缺少用户ID'
];
continue;
}
// 去除匹配相关的字段,避免更新到数据库
unset($record['matched'], $record['existing_data'], $record['id']);
// 去除空值
$record = array_filter($record, function ($value) {
return $value != '';
});
// 通过ID查找并更新用户
$user = $this->model->find($userId);
if (!$user) {
// 用户不存在,记录失败并跳过
$failedRecords[] = [
'row' => $index + 1,
'name' => $record['name'] ?? '',
'company_name' => $record['company_name'] ?? '',
'company_position' => $record['company_position'] ?? '',
'reason' => '用户不存在ID: ' . $userId . ''
];
continue;
}
// 设置username如果name字段存在
if (isset($record['name'])) {
$record['username'] = $record['name'];
}
// 直接覆盖更新
$user->fill($record);
$user->save();
$updateCount++;
$suc++;
}
DB::commit();
// 构建返回结果
$result = [
'total' => count($filteredRecords),
'suc' => $suc,
'update_count' => $updateCount,
'failed_count' => count($failedRecords),
'failed_records' => $failedRecords
];
// 如果有失败的记录,添加提示信息
if (count($failedRecords) > 0) {
$failedNames = array_map(function ($record) {
return "{$record['row']}行:{$record['name']}{$record['company_name']} - {$record['company_position']}- {$record['reason']}";
}, $failedRecords);
$result['message'] = '以下记录导入失败:' . implode('', $failedNames);
}
return $this->success($result);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
}