feat: 新增任务重复周期

This commit is contained in:
kuaifan 2022-06-24 15:28:14 +08:00
parent d4d3f1245b
commit e63e025bda
13 changed files with 320 additions and 1 deletions

View File

@ -1467,6 +1467,7 @@ class ProjectController extends AbstractController
* @apiParam {Number} task_id 任务ID
* @apiParam {String} [name] 任务描述
* @apiParam {Array} [times] 计划时间(格式:开始时间,结束时间2020-01-01 00:00,2020-01-01 23:59
* @apiParam {String} [loop] 重复周期,数字代表天数(子任务不支持)
* @apiParam {Array} [owner] 修改负责人
* @apiParam {String} [content] 任务详情(子任务不支持)
* @apiParam {String} [color] 背景色(子任务不支持)

View File

@ -9,6 +9,7 @@ use App\Module\RandomColor;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteTmpTask;
use App\Tasks\EmailNoticeTask;
use App\Tasks\LoopTask;
use Arr;
use Cache;
use Hhxsv5\LaravelS\Swoole\Task\Task;
@ -191,6 +192,8 @@ class IndexController extends InvokeController
// 删除过期的临时表数据
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
Task::deliver(new DeleteTmpTask('tmp', 24));
// 周期任务
Task::deliver(new LoopTask());
return "success";
}

View File

@ -36,6 +36,8 @@ use Request;
* @property string|null $p_name 优先级名称
* @property string|null $p_color 优先级颜色
* @property int|null $sort 排序(ASC)
* @property string|null $loop 重复周期
* @property string|null $loop_at 下一次重复时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
@ -78,6 +80,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoop($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePLevel($value)
@ -760,11 +764,21 @@ class ProjectTask extends AbstractModel
'change' => [$oldStringAt, $newStringAt]
]);
//修改计划时间需要重置任务邮件提醒日志
// 修改计划时间需要重置任务邮件提醒日志
ProjectTaskMailLog::whereTaskId($this->id)->delete();
}
// 以下紧顶级任务可修改
if ($this->parent_id === 0) {
// 重复周期
if (Arr::exists($data, 'loop')) {
$this->loop = $data['loop'];
if (!$this->refreshLoop()) {
throw new ApiException('重复周期选择错误');
}
} elseif (Arr::exists($data, 'times')) {
// 更新任务时间也要更新重复周期
$this->refreshLoop();
}
// 协助人员
if (Arr::exists($data, 'assist')) {
$array = [];
@ -858,6 +872,105 @@ class ProjectTask extends AbstractModel
return true;
}
/**
* 刷新重复周期时间
* @param bool $save 是否执行保存
* @return bool
*/
public function refreshLoop($save = false)
{
if (!$this->start_at) {
return false;
}
//
$success = true;
$start = Carbon::parse($this->start_at);
if ($start->lt(Carbon::today())) {
// 如果任务开始时间小于今天则重复周期开始时间为今天
$start = Carbon::parse(date("Y-m-d {$start->toTimeString()}"));
}
switch ($this->loop) {
case "day":
$this->loop_at = $start->addDay();
break;
case "weekdays":
$this->loop_at = $start->addWeekday();
break;
case "week":
$this->loop_at = $start->addWeek();
break;
case "twoweeks":
$this->loop_at = $start->addWeeks(2);
break;
case "month":
$this->loop_at = $start->addMonth();
break;
case "year":
$this->loop_at = $start->addYear();
break;
case "never":
$this->loop_at = null;
break;
default:
if (Base::isNumber($this->loop)) {
$this->loop_at = $start->addDays($this->loop);
} else {
$success = false;
}
break;
}
if ($success && $save) {
$this->save();
}
return $success;
}
/**
* 复制任务
* @return self
*/
public function copyTask()
{
if ($this->parent_id > 0) {
throw new ApiException('子任务禁止复制');
}
return AbstractModel::transaction(function() {
// 复制任务
$task = $this->replicate();
$task->dialog_id = 0;
$task->archived_at = null;
$task->archived_userid = 0;
$task->archived_follow = 0;
$task->complete_at = null;
$task->created_at = Carbon::now();
$task->save();
// 复制任务内容
if ($this->content) {
$tmp = $this->content->replicate();
$tmp->task_id = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
// 复制任务附件
foreach ($this->taskFile as $taskFile) {
$tmp = $taskFile->replicate();
$tmp->task_id = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
// 复制任务成员
foreach ($this->taskUser as $taskUser) {
$tmp = $taskUser->replicate();
$tmp->task_id = $task->id;
$tmp->task_pid = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
//
return $task;
});
}
/**
* 同步项目成员至聊天室
*/

48
app/Tasks/LoopTask.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
/**
* 任务重复周期
*/
class LoopTask extends AbstractTask
{
public function __construct()
{
}
public function start()
{
ProjectTask::whereBetween('loop_at', [
Carbon::now()->subMinutes(10),
Carbon::now()
])->chunkById(100, function ($list) {
/** @var ProjectTask $item */
foreach ($list as $item) {
try {
$task = $item->copyTask();
if ($item->start_at) {
$diffSecond = Carbon::parse($item->start_at)->diffInSeconds(Carbon::parse($item->end_at), true);
$task->start_at = Carbon::parse($item->loop_at);
$task->end_at = $task->start_at->addSeconds($diffSecond);
}
$task->refreshLoop(true);
$task->addLog("创建任务来自周期任务ID" . $item->id, [], $item->userid);
//
$item->loop = '';
$item->loop_at = null;
$item->save();
} catch (\Throwable $e) {
$item->addLog("生成重复任务失败:" . $e->getMessage(), [], $item->userid);
}
}
});
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectTasksLoopLoopAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_tasks', function (Blueprint $table) {
if (!Schema::hasColumn('project_tasks', 'loop')) {
$table->timestamp('loop_at')->nullable()->after('sort')->comment('下一次重复时间');
$table->string('loop', 20)->nullable()->default('')->after('sort')->comment('重复周期');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_tasks', function (Blueprint $table) {
$table->dropColumn("loop_at");
$table->dropColumn("loop");
});
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -275,6 +275,29 @@
</li>
</ul>
</FormItem>
<FormItem v-if="(taskDetail.loop && taskDetail.loop != 'never') || loopForce">
<div class="item-label" slot="label">
<i class="taskfont">&#xe68c;</i>{{$L('重复周期')}}
</div>
<ul class="item-content">
<li>
<EDropdown
ref="loop"
trigger="click"
placement="bottom"
@command="updateData('loop', $event)">
<ETooltip :disabled="windowSmall || !taskDetail.loop_at" :content="`${$L('下个周期')}: ${taskDetail.loop_at}`" placement="right">
<span>{{$L(loopLabel(taskDetail.loop))}}</span>
</ETooltip>
<EDropdownMenu slot="dropdown" class="task-detail-loop">
<EDropdownItem v-for="item in loops" :key="item.key" :command="item.key">
{{$L(item.label)}}
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</li>
</ul>
</FormItem>
<FormItem v-if="fileList.length > 0">
<div class="item-label" slot="label">
<i class="taskfont">&#xe6e6;</i>{{$L('附件')}}
@ -490,6 +513,8 @@ export default {
timeValue: [],
timeOptions: {shortcuts:$A.timeOptionShortcuts()},
loopForce: false,
nowTime: $A.Time(),
nowInterval: null,
@ -527,6 +552,17 @@ export default {
dialogDrag: false,
imageAttachment: true,
receiveTaskSubscribe: null,
loops: [
{key: 'never', label: '从不'},
{key: 'day', label: '每天'},
{key: 'weekdays', label: '每个工作日'},
{key: 'week', label: '每周'},
{key: 'twoweeks', label: '每两周'},
{key: 'month', label: '每月'},
{key: 'year', label: '每年'},
{key: 'custom', label: '自定义'},
]
}
},
@ -711,6 +747,13 @@ export default {
name: '截止时间',
});
}
if (!taskDetail.loop || taskDetail.loop == 'never') {
list.push({
command: 'loop',
icon: '&#xe68c;',
name: '重复周期',
});
}
if (this.fileList.length == 0) {
list.push({
command: 'file',
@ -751,6 +794,7 @@ export default {
}
this.timeOpen = false;
this.timeForce = false;
this.loopForce = false;
this.assistForce = false;
this.addsubForce = false;
this.receiveShow = false;
@ -784,6 +828,14 @@ export default {
return $A.Date(taskDetail.end_at, true) < this.nowTime;
},
loopLabel(loop) {
const item = this.loops.find(item => item.key === loop)
if (item) {
return item.label
}
return loop ? `${loop}` : '从不'
},
onNameKeydown(e) {
if (e.keyCode === 13) {
if (!e.shiftKey) {
@ -850,6 +902,14 @@ export default {
this.$set(this.taskDetail, 'times', [params.start_at, params.end_at])
break;
case 'loop':
if (params === 'custom') {
this.customLoop()
return;
}
this.$set(this.taskDetail, 'loop', params)
break;
case 'content':
const content = this.$refs.desc.getContent();
if (content == this.taskContent) {
@ -883,6 +943,51 @@ export default {
})
},
customLoop() {
let value = this.taskDetail.loop || 1
$A.Modal.confirm({
render: (h) => {
return h('div', [
h('div', {
style: {
fontSize: '16px',
fontWeight: '500',
marginBottom: '20px',
}
}, this.$L('重复周期')),
h('Input', {
style: {
width: '160px',
margin: '0 auto',
},
props: {
type: 'number',
value,
maxlength: 3
},
on: {
input: (val) => {
value = $.runNum(val)
}
}
}, [
h('span', {slot: 'prepend'}, this.$L('每')),
h('span', {slot: 'append'}, this.$L('天'))
])
])
},
onOk: _ => {
this.$Modal.remove()
if (value > 0) {
this.updateData('loop', value)
}
},
loading: true,
okText: this.$L('确定'),
cancelText: this.$L('取消'),
});
},
openOwner() {
const list = this.getOwner.map(({userid}) => userid)
this.$set(this.taskDetail, 'owner_userid', list)
@ -1070,6 +1175,13 @@ export default {
})
break;
case 'loop':
this.loopForce = true;
this.$nextTick(() => {
this.$refs.loop.show();
})
break;
case 'file':
this.onUploadClick(true)
break;

View File

@ -825,6 +825,12 @@
}
}
.task-detail-loop {
> li {
text-align: center;
}
}
@media (max-width: 768px) {
.task-detail {
.task-info {