mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-12 17:08:11 +00:00
feat: 新增任务重复周期
This commit is contained in:
parent
d4d3f1245b
commit
e63e025bda
@ -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] 背景色(子任务不支持)
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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
48
app/Tasks/LoopTask.php
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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.
@ -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"></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"></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: '',
|
||||
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;
|
||||
|
||||
@ -825,6 +825,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.task-detail-loop {
|
||||
> li {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.task-detail {
|
||||
.task-info {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user