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_area(or 关系)"), * @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, '编号不能为空']); } if (isset($all['is_schoolmate'])) { if ($all['is_schoolmate'] == 1) { // 仅当 非校友→校友 时写入 schoolmate_time;已是校友的不更新,避免覆盖 $this->model->whereIn('id', $idsArray) ->whereRaw('COALESCE(is_schoolmate, 0) != 1') ->update(['is_schoolmate' => 1, 'schoolmate_time' => now()]); } else { $this->model->whereIn('id', $idsArray)->update([ 'is_schoolmate' => $all['is_schoolmate'], 'schoolmate_time' => null, ]); } } return $this->success('批量更新成功'); } /** * @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 { $this->model->whereIn('id', $idsArray)->update($data); DB::commit(); return $this->success('批量更新成功,共更新 ' . count($idsArray) . ' 条记录'); } 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()]); } } }