'待联系', 1 => '已发邮件', 2 => '已回复', 3 => '已投稿', 4 => '退信/无效', 5 => '黑名单(退订)', ]; public function __construct(\think\Request $request = null) { parent::__construct($request); } /** * 专家列表(支持多条件筛选 + 分页) * * 参数: * keyword - 搜索姓名/邮箱/单位 * field - 按领域关键词筛选 * major_id - 按学科ID筛选 * state - 状态筛选 (0-5, 传-1或不传则不过滤) * source - 来源筛选 * pageIndex - 页码 (默认1) * pageSize - 每页条数 (默认20) */ public function getList() { $data = $this->request->param(); $keyword = trim(isset($data['keyword']) ? $data['keyword'] : ''); $field = trim(isset($data['field']) ? $data['field'] : ''); $majorId = intval(isset($data['major_id']) ? $data['major_id'] : 0); $state = isset($data['state']) ? $data['state'] : '-1'; $source = trim(isset($data['source']) ? $data['source'] : ''); $page = max(1, intval(isset($data['pageIndex']) ? $data['pageIndex'] : 1)); $pageSize = max(1, intval(isset($data['pageSize']) ? $data['pageSize'] : 20)); $query = Db::name('expert')->alias('e'); $needJoin = ($field !== '' || $majorId > 0); if ($needJoin) { $query->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner'); if ($field !== '') { $query->where('ef.field', 'like', '%' . $field . '%'); } if ($majorId > 0) { $query->where('ef.major_id', $majorId); } $query->group('e.expert_id'); } if ($state !== '-1' && $state !== '') { $query->where('e.state', intval($state)); } if ($keyword !== '') { $query->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%'); } if ($source !== '') { $query->where('e.source', $source); } $countQuery = clone $query; $total = $countQuery->distinct('e.expert_id')->count(); $list = $query ->field('e.*') ->order('e.ctime desc') ->page($page, $pageSize) ->select(); foreach ($list as &$item) { $item['fields'] = Db::name('expert_field') ->where('expert_id', $item['expert_id']) ->where('state', 0) ->select(); $item['state_text'] = isset($this->stateMap[$item['state']]) ? $this->stateMap[$item['state']] : '未知'; $item['ctime_text'] = $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : ''; $item['ltime_text'] = $item['ltime'] ? date('Y-m-d H:i:s', $item['ltime']) : ''; } return jsonSuccess([ 'list' => $list, 'total' => $total, 'pageIndex' => $page, 'pageSize' => $pageSize, 'totalPages' => $total > 0 ? ceil($total / $pageSize) : 0, ]); } /** * 获取专家详情(含所有领域) */ public function getDetail() { $expertId = intval($this->request->param('expert_id', 0)); if (!$expertId) { return jsonError('expert_id is required'); } $expert = Db::name('expert')->where('expert_id', $expertId)->find(); if (!$expert) { return jsonError('专家不存在'); } $expert['fields'] = Db::name('expert_field') ->where('expert_id', $expertId) ->where('state', 0) ->select(); $expert['state_text'] = isset($this->stateMap[$expert['state']]) ? $this->stateMap[$expert['state']] : '未知'; $expert['ctime_text'] = $expert['ctime'] ? date('Y-m-d H:i:s', $expert['ctime']) : ''; $expert['ltime_text'] = $expert['ltime'] ? date('Y-m-d H:i:s', $expert['ltime']) : ''; return jsonSuccess($expert); } /** * 添加专家 */ public function addExpert() { $data = $this->request->post(); $name = trim(isset($data['name']) ? $data['name'] : ''); $email = trim(isset($data['email']) ? $data['email'] : ''); if ($name === '' || $email === '') { return jsonError('name和email不能为空'); } $exists = Db::name('expert')->where('email', $email)->find(); if ($exists) { return jsonError('该邮箱已存在,expert_id=' . $exists['expert_id']); } $insert = [ 'name' => $name, 'email' => $email, 'affiliation' => trim(isset($data['affiliation']) ? $data['affiliation'] : ''), 'source' => trim(isset($data['source']) ? $data['source'] : 'manual'), 'ctime' => time(), 'ltime' => 0, 'state' => 0, ]; $expertId = Db::name('expert')->insertGetId($insert); if (!empty($data['fields'])) { $this->saveExpertFields($expertId, $data['fields']); } return jsonSuccess(['expert_id' => $expertId]); } /** * 编辑专家 */ public function editExpert() { $data = $this->request->post(); $expertId = intval(isset($data['expert_id']) ? $data['expert_id'] : 0); if (!$expertId) { return jsonError('expert_id is required'); } $expert = Db::name('expert')->where('expert_id', $expertId)->find(); if (!$expert) { return jsonError('专家不存在'); } $update = []; if (isset($data['name'])) $update['name'] = trim($data['name']); if (isset($data['email'])) $update['email'] = trim($data['email']); if (isset($data['affiliation'])) $update['affiliation'] = trim($data['affiliation']); if (isset($data['source'])) $update['source'] = trim($data['source']); if (isset($data['state'])) $update['state'] = intval($data['state']); if (!empty($update)) { Db::name('expert')->where('expert_id', $expertId)->update($update); } if (isset($data['fields'])) { Db::name('expert_field')->where('expert_id', $expertId)->where('state', 0)->update(['state' => 1]); if (!empty($data['fields'])) { $this->saveExpertFields($expertId, $data['fields']); } } return jsonSuccess(['expert_id' => $expertId]); } /** * 批量修改状态 * * 参数: * expert_ids - 逗号分隔的ID列表 "1,2,3" * state - 目标状态 0-5 */ public function updateState() { $data = $this->request->post(); $expertIds = isset($data['expert_ids']) ? $data['expert_ids'] : ''; $state = intval(isset($data['state']) ? $data['state'] : -1); if (empty($expertIds)) { return jsonError('expert_ids is required'); } if ($state < 0 || $state > 5) { return jsonError('state取值范围0-5'); } $ids = array_map('intval', explode(',', $expertIds)); $count = Db::name('expert')->where('expert_id', 'in', $ids)->update(['state' => $state]); return jsonSuccess(['updated' => $count]); } /** * 删除专家(软删除,设为黑名单状态5) * * 参数: * expert_ids - 逗号分隔的ID列表 * hard - 传1则物理删除 */ public function deleteExpert() { $data = $this->request->post(); $expertIds = isset($data['expert_ids']) ? $data['expert_ids'] : ''; $hard = intval(isset($data['hard']) ? $data['hard'] : 0); if (empty($expertIds)) { return jsonError('expert_ids is required'); } $ids = array_map('intval', explode(',', $expertIds)); if ($hard) { Db::name('expert_field')->where('expert_id', 'in', $ids)->delete(); $count = Db::name('expert')->where('expert_id', 'in', $ids)->delete(); } else { $count = Db::name('expert')->where('expert_id', 'in', $ids)->update(['state' => 5]); } return jsonSuccess(['affected' => $count]); } /** * 给专家添加领域 */ public function addField() { $data = $this->request->post(); $expertId = intval(isset($data['expert_id']) ? $data['expert_id'] : 0); $majorId = intval(isset($data['major_id']) ? $data['major_id'] : 0); $field = trim(isset($data['field']) ? $data['field'] : ''); if (!$expertId || $field === '') { return jsonError('expert_id和field不能为空'); } $exists = Db::name('expert_field') ->where('expert_id', $expertId) ->where('field', $field) ->where('state', 0) ->find(); if ($exists) { return jsonError('该领域已存在'); } $id = Db::name('expert_field')->insertGetId([ 'expert_id' => $expertId, 'major_id' => $majorId, 'field' => $field, 'state' => 0, ]); return jsonSuccess(['expert_field_id' => $id]); } /** * 删除领域(软删除) */ public function removeField() { $efId = intval($this->request->param('expert_field_id', 0)); if (!$efId) { return jsonError('expert_field_id is required'); } Db::name('expert_field')->where('expert_field_id', $efId)->update(['state' => 1]); return jsonSuccess([]); } /** * 获取所有不重复的领域列表(用于筛选下拉框) */ public function getFieldOptions() { $list = Db::name('expert_field') ->where('state', 0) ->group('field') ->column('field'); return jsonSuccess($list); } /** * 获取所有来源列表(用于筛选下拉框) */ public function getSourceOptions() { $list = Db::name('expert') ->where('source', '<>', '') ->group('source') ->column('source'); return jsonSuccess($list); } /** * 导出某个领域的专家为Excel * * 参数: * field - 领域关键词(必填) * major_id - 学科ID(可选) * state - 状态筛选(可选,默认不过滤) * keyword - 搜索姓名/邮箱/单位 * source - 来源筛选 */ public function exportExcel() { $data = $this->request->param(); $field = trim(isset($data['field']) ? $data['field'] : ''); $majorId = intval(isset($data['major_id']) ? $data['major_id'] : 0); $state = isset($data['state']) ? $data['state'] : '-1'; $keyword = trim(isset($data['keyword']) ? $data['keyword'] : ''); $source = trim(isset($data['source']) ? $data['source'] : ''); $query = Db::name('expert')->alias('e'); if ($field !== '' || $majorId > 0) { $query->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner'); if ($field !== '') { $query->where('ef.field', 'like', '%' . $field . '%'); } if ($majorId > 0) { $query->where('ef.major_id', $majorId); } $query->group('e.expert_id'); } if ($state !== '-1' && $state !== '') { $query->where('e.state', intval($state)); } if ($keyword !== '') { $query->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%'); } if ($source !== '') { $query->where('e.source', $source); } $list = $query->field('e.*')->order('e.ctime desc')->select(); if (empty($list)) { return jsonError('没有符合条件的数据可导出'); } $expertIds = array_column($list, 'expert_id'); $allFields = Db::name('expert_field') ->where('expert_id', 'in', $expertIds) ->where('state', 0) ->select(); $fieldMap = []; foreach ($allFields as $f) { $fieldMap[$f['expert_id']][] = $f['field']; } vendor("PHPExcel.PHPExcel"); $objPHPExcel = new \PHPExcel(); $sheet = $objPHPExcel->getActiveSheet(); $sheet->setTitle('Expert List'); $headers = [ 'A' => '#', 'B' => 'Name', 'C' => 'Email', 'D' => 'Affiliation', 'E' => 'Source', 'F' => 'Fields', 'G' => 'State', 'H' => 'Add Time', 'I' => 'Last Promotion', ]; foreach ($headers as $col => $header) { $sheet->setCellValue($col . '1', $header); } $headerStyle = [ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['type' => \PHPExcel_Style_Fill::FILL_SOLID, 'startcolor' => ['rgb' => '4472C4']], 'alignment' => ['horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER], ]; $sheet->getStyle('A1:I1')->applyFromArray($headerStyle); foreach ($list as $i => $item) { $row = $i + 2; $fields = isset($fieldMap[$item['expert_id']]) ? implode(', ', $fieldMap[$item['expert_id']]) : ''; $stateText = isset($this->stateMap[$item['state']]) ? $this->stateMap[$item['state']] : '未知'; $sheet->setCellValue('A' . $row, $i + 1); $sheet->setCellValue('B' . $row, $item['name']); $sheet->setCellValueExplicit('C' . $row, $item['email'], \PHPExcel_Cell_DataType::TYPE_STRING); $sheet->setCellValue('D' . $row, $item['affiliation']); $sheet->setCellValue('E' . $row, $item['source']); $sheet->setCellValue('F' . $row, $fields); $sheet->setCellValue('G' . $row, $stateText); $sheet->setCellValue('H' . $row, $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : ''); $sheet->setCellValue('I' . $row, $item['ltime'] ? date('Y-m-d H:i:s', $item['ltime']) : ''); } $sheet->getColumnDimension('A')->setWidth(6); $sheet->getColumnDimension('B')->setWidth(25); $sheet->getColumnDimension('C')->setWidth(35); $sheet->getColumnDimension('D')->setWidth(40); $sheet->getColumnDimension('E')->setWidth(15); $sheet->getColumnDimension('F')->setWidth(50); $sheet->getColumnDimension('G')->setWidth(15); $sheet->getColumnDimension('H')->setWidth(20); $sheet->getColumnDimension('I')->setWidth(20); $label = $field !== '' ? preg_replace('/[^a-zA-Z0-9_\x{4e00}-\x{9fa5}]/u', '_', $field) : 'all'; $filename = 'expert_' . $label . '_' . date('Ymd_His') . '.xlsx'; $dir = ROOT_PATH . 'public' . DS . 'exports'; if (!is_dir($dir)) { mkdir($dir, 0777, true); } $filepath = $dir . DS . $filename; $writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007'); $writer->save($filepath); return jsonSuccess([ 'file_url' => '/exports/' . $filename, 'file_name' => $filename, 'count' => count($list), ]); } /** * 批量保存专家领域 * @param int $expertId * @param array $fields [{"major_id":1,"field":"xxx"}, ...] */ private function saveExpertFields($expertId, $fields) { if (is_string($fields)) { $fields = json_decode($fields, true); } if (!is_array($fields)) { return; } foreach ($fields as $f) { $majorId = intval(isset($f['major_id']) ? $f['major_id'] : 0); $fieldName = trim(isset($f['field']) ? $f['field'] : ''); if ($fieldName === '') continue; $exists = Db::name('expert_field') ->where('expert_id', $expertId) ->where('field', $fieldName) ->where('state', 0) ->find(); if ($exists) continue; Db::name('expert_field')->insert([ 'expert_id' => $expertId, 'major_id' => $majorId, 'field' => $fieldName, 'state' => 0, ]); } } }