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 @@
-
+