feat: 导出任务功能
This commit is contained in:
parent
f17009a988
commit
cad253b85f
@ -17,10 +17,13 @@ use App\Models\ProjectUser;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\WebSocketDialog;
|
use App\Models\WebSocketDialog;
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
|
use App\Module\BillExport;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Madzipper;
|
||||||
use Request;
|
use Request;
|
||||||
use Response;
|
use Response;
|
||||||
|
use Session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @apiDefine project
|
* @apiDefine project
|
||||||
@ -977,6 +980,154 @@ class ProjectController extends AbstractController
|
|||||||
return Base::retSuccess('success', $list);
|
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. 获取单个任务信息
|
* @api {get} api/project/task/one 19. 获取单个任务信息
|
||||||
*
|
*
|
||||||
|
@ -2967,4 +2967,19 @@ class Base
|
|||||||
$matrix = array_unique($matrix, SORT_REGULAR);
|
$matrix = array_unique($matrix, SORT_REGULAR);
|
||||||
return array_merge($matrix);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
144
app/Module/BillExport.php
Normal file
144
app/Module/BillExport.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Module;
|
||||||
|
|
||||||
|
use Excel;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||||
|
use Maatwebsite\Excel\Events\AfterSheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Exception;
|
||||||
|
|
||||||
|
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
|
||||||
|
{
|
||||||
|
public $title;
|
||||||
|
public $headings = [];
|
||||||
|
public $data = [];
|
||||||
|
public $typeLists = [];
|
||||||
|
public $typeNumber = 0;
|
||||||
|
|
||||||
|
public function __construct($title, array $data)
|
||||||
|
{
|
||||||
|
$this->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) . '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
16
app/Module/BillImport.php
Normal file
16
app/Module/BillImport.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Module;
|
||||||
|
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Concerns\ToArray;
|
||||||
|
|
||||||
|
class BillImport implements ToArray
|
||||||
|
{
|
||||||
|
public function Array(Array $tables)
|
||||||
|
{
|
||||||
|
return $tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -39,7 +39,7 @@
|
|||||||
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
|
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
|
||||||
</div>
|
</div>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem name="exportTask">{{$L('导出任务记录')}}</DropdownItem>
|
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<!-- 主题皮肤 -->
|
<!-- 主题皮肤 -->
|
||||||
@ -229,18 +229,18 @@
|
|||||||
<TaskAdd ref="addTask" v-model="addTaskShow"/>
|
<TaskAdd ref="addTask" v-model="addTaskShow"/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!--导出任务记录-->
|
<!--导出任务统计-->
|
||||||
<Modal
|
<Modal
|
||||||
v-model="exportTaskShow"
|
v-model="exportTaskShow"
|
||||||
:title="$L('导出任务记录')"
|
:title="$L('导出任务统计')"
|
||||||
:mask-closable="false">
|
:mask-closable="false">
|
||||||
<Form ref="exportTask" :model="exportData" label-width="auto" @submit.native.prevent>
|
<Form ref="exportTask" :model="exportData" label-width="auto" @submit.native.prevent>
|
||||||
<FormItem :label="$L('导出会员')">
|
<FormItem :label="$L('导出会员')">
|
||||||
<UserInput v-model="exportData.userids" :multiple-max="20" :placeholder="$L('请选择会员')"/>
|
<UserInput v-model="exportData.userid" :multiple-max="20" :placeholder="$L('请选择会员')"/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem :label="$L('时间范围')">
|
<FormItem :label="$L('时间范围')">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-model="exportData.times"
|
v-model="exportData.time"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
format="yyyy/MM/dd"
|
format="yyyy/MM/dd"
|
||||||
style="width:100%"
|
style="width:100%"
|
||||||
@ -359,8 +359,8 @@ export default {
|
|||||||
exportTaskShow: false,
|
exportTaskShow: false,
|
||||||
exportLoadIng: 0,
|
exportLoadIng: 0,
|
||||||
exportData: {
|
exportData: {
|
||||||
userids: [],
|
userid: [],
|
||||||
times: [],
|
time: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
dialogMsgSubscribe: null,
|
dialogMsgSubscribe: null,
|
||||||
@ -904,6 +904,7 @@ export default {
|
|||||||
data: this.exportData,
|
data: this.exportData,
|
||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
this.exportLoadIng--;
|
this.exportLoadIng--;
|
||||||
|
this.exportTaskShow = false;
|
||||||
$A.downFile(data.url);
|
$A.downFile(data.url);
|
||||||
}).catch(({msg}) => {
|
}).catch(({msg}) => {
|
||||||
this.exportLoadIng--;
|
this.exportLoadIng--;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user