diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 7d71d1fd..9e409149 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -17,10 +17,13 @@ use App\Models\ProjectUser; use App\Models\User; use App\Models\WebSocketDialog; use App\Module\Base; +use App\Module\BillExport; use Carbon\Carbon; use Illuminate\Support\Arr; +use Madzipper; use Request; use Response; +use Session; /** * @apiDefine project @@ -977,6 +980,154 @@ class ProjectController extends AbstractController return Base::retSuccess('success', $list); } + /** + * @api {get} api/project/task/export 18. 导出任务(限管理员) + * + * @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份 + * @apiVersion 1.0.0 + * @apiGroup project + * @apiName task__export + * + * @apiParam {Array} [userid] 指定会员,如:[1, 2] + * @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30'] + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function task__export() + { + $user = User::auth('admin'); + // + $userid = Base::arrayRetainInt(Request::input('userid'), true); + $time = Request::input('time'); + if (empty($userid) || empty($time)) { + return Base::retError('参数错误'); + } + if (count($userid) > 20) { + return Base::retError('导出会员限制最多20个'); + } + if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) { + return Base::retError('时间选择错误'); + } + if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) { + return Base::retError('时间范围限制最大90天'); + } + // + $headings = []; + $headings[] = '任务ID'; + $headings[] = '任务标题'; + $headings[] = '负责人'; + $headings[] = '创建人'; + $headings[] = '是否完成'; + $headings[] = '完成时间'; + $headings[] = '是否归档'; + $headings[] = '归档时间'; + $headings[] = '任务开始时间'; + $headings[] = '任务结束时间'; + $headings[] = '结束剩余'; + $headings[] = '所属项目'; + $headings[] = '父级任务ID'; + $datas = []; + // + $builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid']) + ->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id') + ->where('project_task_users.owner', 1) + ->whereIn('project_task_users.userid', $userid) + ->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay()); + $builder->orderByDesc('project_tasks.id')->chunk(100, function($tasks) use (&$datas) { + /** @var ProjectTask $task */ + foreach ($tasks as $task) { + if ($task->complete_at) { + $a = Carbon::parse($task->complete_at)->timestamp; + $b = Carbon::parse($task->end_at)->timestamp; + if ($b > $a) { + $endSurplus = Base::timeDiff($a, $b); + } else { + $endSurplus = "-" . Base::timeDiff($b, $a); + } + } else { + $endSurplus = '-'; + } + $datas[] = [ + $task->id, + Base::filterEmoji($task->name), + Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})", + Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})", + $task->complete_at ? '已完成' : '-', + $task->complete_at ?: '-', + $task->archived_at ? '已归档' : '-', + $task->archived_at ?: '-', + $task->start_at ?: '-', + $task->end_at ?: '-', + $endSurplus, + Base::filterEmoji($task->project?->name) ?: '-', + $task->parent_id ?: '-', + ]; + } + }); + // + $fileName = User::userid2nickname($userid[0]) ?: $userid[0]; + if (count($userid) > 1) { + $fileName .= "等" . count($userid) . "位成员"; + } + $fileName .= '任务统计_' . Base::time() . '.xls'; + $filePath = "temp/task/export/" . date("Ym", Base::time()); + $res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName); + if ($res != 1) { + return Base::retError('导出失败,' . $fileName . '!'); + } + $xlsPath = storage_path("app/" . $filePath . "/" . $fileName); + $zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls'). ".zip"; + $zipPath = storage_path($zipFile); + if (file_exists($zipPath)) { + Base::deleteDirAndFile($zipPath, true); + } + try { + Madzipper::make($zipPath)->add($xlsPath)->close(); + } catch (\Exception) { } + // + if (file_exists($zipPath)) { + $base64 = base64_encode(Base::array2string([ + 'file' => $zipFile, + ])); + Session::put('task::export:userid', $user->userid); + return Base::retSuccess('success', [ + 'size' => Base::twoFloat(filesize($zipPath) / 1024, true), + 'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)), + ]); + } else { + return Base::retError('打包失败,请稍后再试...'); + } + } + + /** + * @api {get} api/project/task/down 18. 导出任务(限管理员) + * + * @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份 + * @apiVersion 1.0.0 + * @apiGroup project + * @apiName task__down + * + * @apiParam {String} key 通过export接口得到的下载钥匙 + * + * @apiSuccess {File} 文件下载 + */ + public function task__down() + { + $userid = Session::get('task::export:userid'); + if (empty($userid)) { + return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502); + } + // + $array = Base::string2array(base64_decode(urldecode(Request::input('key')))); + $file = $array['file']; + if (empty($file) || !file_exists(storage_path($file))) { + return Base::ajaxError("文件不存在!", [], 0, 502); + } + return response()->download(storage_path($file)); + } + /** * @api {get} api/project/task/one 19. 获取单个任务信息 * diff --git a/app/Module/Base.php b/app/Module/Base.php index 6de39b29..e6e3bd40 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -2967,4 +2967,19 @@ class Base $matrix = array_unique($matrix, SORT_REGULAR); return array_merge($matrix); } + + /** + * 去除emoji表情 + * @param $str + * @return string|string[]|null + */ + public static function filterEmoji($str) + { + return preg_replace_callback( + '/./u', + function (array $match) { + return strlen($match[0]) >= 4 ? '' : $match[0]; + }, + $str); + } } diff --git a/app/Module/BillExport.php b/app/Module/BillExport.php new file mode 100644 index 00000000..c0b21f4c --- /dev/null +++ b/app/Module/BillExport.php @@ -0,0 +1,144 @@ +title = $title; + $this->data = $data; + } + + public static function create($data = [], $title = "Sheet1") { + if (is_string($data)) { + list($title, $data) = [$data, $title]; + } + if (!is_array($data)) { + $data = []; + } + return new BillExport($title, $data); + } + + public function setTitle($title) { + $this->title = $title; + return $this; + } + + public function setHeadings(array $headings) { + $this->headings = $headings; + return $this; + } + + public function setData(array $data) { + $this->data = $data; + return $this; + } + + public function setTypeList(array $typeList, $number = 0) { + $this->typeLists = $typeList; + $this->typeNumber = $number; + return $this; + } + + public function store($fileName = '') { + if (empty($fileName)) { + $fileName = date("YmdHis") . '.xls'; + } + try { + return Excel::store($this, $fileName); + } catch (Exception $e) { + return "导出错误:" . $e->getMessage(); + } catch (\PhpOffice\PhpSpreadsheet\Exception $e) { + return "导出错误:" . $e->getMessage(); + } + } + + public function download($fileName = '') { + if (empty($fileName)) { + $fileName = date("YmdHis") . '.xls'; + } + try { + return Excel::download($this, $fileName); + } catch (Exception $e) { + return "导出错误:" . $e->getMessage(); + } catch (\PhpOffice\PhpSpreadsheet\Exception $e) { + return "导出错误:" . $e->getMessage(); + } + } + + /** + * 导出的文件标题 + * @return string + */ + public function title(): string + { + return $this->title; + } + + /** + * 标题行 + * @return array + */ + public function headings(): array + { + return $this->headings; + } + + /** + * 导出的内容 + * @return \Illuminate\Support\Collection + */ + public function collection() + { + return collect($this->data); + } + + /** + * 设置单元格事件 + * @return array + */ + public function registerEvents(): array + { + return [ + AfterSheet::Class => function (AfterSheet $event) { + $count = count($this->data); + foreach ($this->typeLists AS $cell => $typeList) { + if ($cell && $typeList) { + $p = $this->headings ? 1 : 0; + for ($i = 1 + $p; $i <= max($count, $this->typeNumber) + $p; $i++) { + $validation = $event->sheet->getDelegate()->getCell($cell . $i)->getDataValidation(); + $validation->setType(DataValidation::TYPE_LIST); + $validation->setErrorStyle(DataValidation::STYLE_WARNING); + $validation->setAllowBlank(false); + $validation->setShowDropDown(true); + $validation->setShowInputMessage(true); + $validation->setShowErrorMessage(true); + $validation->setErrorTitle('输入的值不合法'); + $validation->setError('选择的值不在列表中,请选择列表中的值'); + $validation->setPromptTitle('从列表中选择'); + $validation->setPrompt('请选择下拉列表中的值'); + $validation->setFormula1('"' . implode(',', $typeList) . '"'); + } + } + } + } + ]; + } +} diff --git a/app/Module/BillImport.php b/app/Module/BillImport.php new file mode 100644 index 00000000..ed49ae24 --- /dev/null +++ b/app/Module/BillImport.php @@ -0,0 +1,16 @@ + 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/> - {{$L('导出任务记录')}} + {{$L('导出任务统计')}} @@ -229,18 +229,18 @@ - +
- + { this.exportLoadIng--; + this.exportTaskShow = false; $A.downFile(data.url); }).catch(({msg}) => { this.exportLoadIng--;