no message

This commit is contained in:
kuaifan 2021-06-15 18:21:21 +08:00
parent e2ae2536b3
commit 8174d4b6b3
9 changed files with 328 additions and 18 deletions

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Models\AbstractModel; use App\Models\AbstractModel;
use App\Models\Project; use App\Models\Project;
use App\Models\ProjectColumn; use App\Models\ProjectColumn;
use App\Models\ProjectLog;
use App\Models\ProjectTask; use App\Models\ProjectTask;
use App\Models\ProjectTaskFile; use App\Models\ProjectTaskFile;
use App\Models\ProjectUser; use App\Models\ProjectUser;
@ -798,4 +799,44 @@ class ProjectController extends AbstractController
// //
return $task->deleteTask(); return $task->deleteTask();
} }
/**
* 获取项目、任务日志
*
* @apiParam {Number} project_id 项目ID
* @apiParam {Number} task_id 任务ID 项目ID 二选一任务ID优先
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:100
*/
public function log__lists()
{
user::auth();
//
$project_id = intval(Request::input('project_id'));
$task_id = intval(Request::input('task_id'));
//
$builder = ProjectLog::with(['user']);
if ($task_id > 0) {
$task = ProjectTask::userTask($task_id);
$builder->whereTaskId($task->id);
} else {
$project = Project::userProject($project_id);
$builder->whereTaskId($project->id);
}
//
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(100, 20));
$list->transform(function (ProjectLog $log) {
$timestamp = Carbon::parse($log->created_at)->timestamp;
$log->time = [
'ymd' => date(date("Y", $timestamp) == date("Y", Base::time()) ? "m-d" : "Y-m-d", $timestamp),
'hi' => date("h:i", $timestamp) ,
'week' => "" . Base::getTimeWeek($timestamp),
'segment' => Base::getTimeDayeSegment($timestamp),
];
return $log;
});
//
return Base::retSuccess('success', $list);
}
} }

View File

@ -4,9 +4,39 @@ namespace App\Models;
/** /**
* Class ProjectLog * Class ProjectLog
*
* @package App\Models * @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $column_id 列表ID
* @property int|null $task_id 项目ID
* @property int|null $userid 会员ID
* @property string|null $detail 详细信息
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereColumnId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereDetail($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUserid($value)
* @mixin \Eloquent
*/ */
class ProjectLog extends AbstractModel class ProjectLog extends AbstractModel
{ {
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function user(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(User::class, 'userid', 'userid');
}
} }

View File

@ -0,0 +1,126 @@
<template>
<div class="project-log">
<ul class="logs-activity">
<li v-for="items in lists">
<div class="logs-date">{{logDate(items)}}</div>
<div class="logs-section">
<Timeline>
<TimelineItem v-for="(item, index) in items.lists" :key="index">
<div slot="dot" class="logs-dot">
<UserAvatar :userid="item.userid" :size="18"/>
</div>
<div class="log-summary">
<span class="log-creator">{{item.user.nickname}}</span>
<span class="log-text">{{$L(item.detail)}}</span>
<span class="log-time">{{item.time.ymd}} {{item.time.segment}} {{item.time.hi}}</span></div>
</TimelineItem>
</Timeline>
</div>
</li>
<li v-if="loadIng > 0" class="logs-loading"><Loading/></li>
<li v-else-if="hasMorePages" class="logs-more" @click="getMore">{{$L('加载更多')}}</li>
<li v-else-if="totalNum == 0" class="logs-none" @click="getLists(true)">{{$L('没有任何动态')}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "ProjectLog",
props: {
projectId: {
type: Number,
default: 0
},
taskId: {
type: Number,
default: 0
},
},
data() {
return {
loadIng: 0,
lists: {},
listPage: 1,
hasMorePages: false,
totalNum: -1,
}
},
mounted() {
this.getLists(true);
},
computed: {
},
watch: {
projectId() {
this.lists = {};
this.getLists(true);
},
taskId() {
this.lists = {};
this.getLists(true);
},
},
methods: {
logDate(items) {
let md = $A.formatDate("m-d");
return md == items.ymd ? (items.ymd + ' ' + this.$L('今天')) : items.key;
},
getLists(resetLoad) {
if (resetLoad === true) {
this.listPage = 1;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'project/log/lists',
data: {
project_id: this.projectId,
task_id: this.taskId,
page: Math.max(this.listPage, 1),
pagesize: this.pagesize,
}
}).then(({data}) => {
this.loadIng--;
if (resetLoad === true) {
this.lists = {};
}
data.data.forEach((item) => {
let time = item.time;
let key = time.ymd + " " + time.week;
if (typeof this.lists[key] !== "object") {
this.$set(this.lists, key, {
key: key,
ymd: time.ymd,
lists: [],
});
}
this.lists[key].lists.push(item);
});
this.hasMorePages = data.hasMorePages;
this.totalNum = data.total;
}).catch(() => {
this.loadIng--;
this.lists = {};
this.hasMorePages = false;
this.totalNum = 0;
});
},
getMore() {
if (!this.hasMorePages) {
return;
}
this.hasMorePages = false;
this.listPage++;
this.getLists();
},
}
}
</script>

View File

@ -330,24 +330,28 @@
<TaskUpload ref="upload" class="upload"/> <TaskUpload ref="upload" class="upload"/>
</div> </div>
<div class="task-dialog" :style="dialogStyle"> <div class="task-dialog" :style="dialogStyle">
<DialogWrapper v-if="taskDetail.dialog_id > 0" ref="dialog"> <template v-if="taskDetail.dialog_id > 0">
<div slot="head" class="head"> <DialogWrapper ref="dialog">
<Icon class="icon" type="ios-chatbubbles-outline" /> <div slot="head" class="head">
<div class="nav"> <Icon class="icon" type="ios-chatbubbles-outline" />
<p class="active">{{$L('聊天')}}</p> <div class="nav">
<p>{{$L('动态')}}</p> <p :class="{active:navActive=='dialog'}" @click="navActive='dialog'">{{$L('聊天')}}</p>
<p :class="{active:navActive=='log'}" @click="navActive='log'">{{$L('动态')}}</p>
</div>
</div> </div>
</div> </DialogWrapper>
</DialogWrapper> <ProjectLog v-if="navActive=='log'" :task-id="taskDetail.id"/>
</template>
<div v-else> <div v-else>
<div class="head"> <div class="head">
<Icon class="icon" type="ios-chatbubbles-outline" /> <Icon class="icon" type="ios-chatbubbles-outline" />
<div class="nav"> <div class="nav">
<p class="active">{{$L('聊天')}}</p> <p :class="{active:navActive=='dialog'}" @click="navActive='dialog'">{{$L('聊天')}}</p>
<p>{{$L('动态')}}</p> <p :class="{active:navActive=='log'}" @click="navActive='log'">{{$L('动态')}}</p>
</div> </div>
</div> </div>
<div class="no-dialog"> <ProjectLog v-if="navActive=='log'" :task-id="taskDetail.id"/>
<div v-else class="no-dialog">
<div class="no-tip">{{$L('暂无消息')}}</div> <div class="no-tip">{{$L('暂无消息')}}</div>
<div class="no-input"> <div class="no-input">
<Input <Input
@ -374,10 +378,11 @@ import TaskPriority from "./TaskPriority";
import UserInput from "../../../components/UserInput"; import UserInput from "../../../components/UserInput";
import TaskUpload from "./TaskUpload"; import TaskUpload from "./TaskUpload";
import DialogWrapper from "./DialogWrapper"; import DialogWrapper from "./DialogWrapper";
import ProjectLog from "./ProjectLog";
export default { export default {
name: "TaskDetail", name: "TaskDetail",
components: {DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor}, components: {ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
props: { props: {
openTask: { openTask: {
type: Object, type: Object,
@ -417,6 +422,7 @@ export default {
innerHeight: window.innerHeight, innerHeight: window.innerHeight,
msgText: '', msgText: '',
navActive: 'dialog',
taskPlugins: [ taskPlugins: [
'advlist autolink lists link image charmap print preview hr anchor pagebreak imagetools', 'advlist autolink lists link image charmap print preview hr anchor pagebreak imagetools',

View File

@ -390,7 +390,7 @@ export default {
}); });
if (data.id == state.projectOpenTask.id) { if (data.id == state.projectOpenTask.id) {
state.projectOpenTask = Object.assign({}, state.projectOpenTask, data); state.projectOpenTask = Object.assign({}, state.projectOpenTask, data);
} else if (data.parent_id == state.projectOpenTask.id) { } else if (data.parent_id == state.projectOpenTask.id && state.projectOpenTask.sub_task) {
let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id); let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id);
if (index > -1) { if (index > -1) {
state.projectOpenTask.sub_task.splice(index, 1, Object.assign(state.projectOpenTask.sub_task[index], data)) state.projectOpenTask.sub_task.splice(index, 1, Object.assign(state.projectOpenTask.sub_task[index], data))
@ -654,7 +654,7 @@ export default {
} }
if (data.id == state.projectOpenTask.id) { if (data.id == state.projectOpenTask.id) {
state.projectOpenTask = Object.assign({}, state.projectOpenTask, {_show: false}); state.projectOpenTask = Object.assign({}, state.projectOpenTask, {_show: false});
} else if (data.parent_id == state.projectOpenTask.id) { } else if (data.parent_id == state.projectOpenTask.id && state.projectOpenTask.sub_task) {
let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id); let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id);
if (index > -1) { if (index > -1) {
state.projectOpenTask.sub_task.splice(index, 1) state.projectOpenTask.sub_task.splice(index, 1)

View File

@ -1,6 +1,7 @@
@import "dialog-wrapper"; @import "dialog-wrapper";
@import "project-dialog"; @import "project-dialog";
@import "project-list"; @import "project-list";
@import "project-log";
@import "task-add"; @import "task-add";
@import "task-add-simple"; @import "task-add-simple";
@import "task-detail"; @import "task-detail";

View File

@ -29,7 +29,7 @@
align-items: center; align-items: center;
line-height: 22px; line-height: 22px;
> h2 { > h2 {
font-size: 18px; font-size: 17px;
font-weight: 600; font-weight: 600;
} }
> em { > em {
@ -43,7 +43,6 @@
flex-shrink: 0; flex-shrink: 0;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
margin-top: 2px;
color: #aaaaaa; color: #aaaaaa;
&.pointer { &.pointer {
cursor: pointer; cursor: pointer;

View File

@ -0,0 +1,79 @@
.project-log {
.logs-activity {
position: relative;
word-break: break-all;
padding: 12px 12px;
> li {
list-style: none;
padding-top: 0;
&.logs-loading,
&.logs-more,
&.logs-none {
height: 22px;
line-height: 22px;
}
&.logs-loading {
display: flex;
.common-loading {
margin: 0;
}
}
&.logs-more {
cursor: pointer;
&:hover {
color: #048be0;
}
}
&.logs-none {
cursor: pointer;
color: #bbbbbb;
}
.logs-date {
color: rgba(0, 0, 0, .36);
padding-bottom: 14px;
}
.logs-section {
margin: 4px;
.ivu-timeline {
> li {
padding-bottom: 8px;
&:last-child {
padding-bottom: 0;
}
.ivu-timeline-item-head-custom {
margin-top: 8px;
}
}
}
}
.logs-dot {
width: 18px;
height: 18px;
margin-left: 10px;
.avatar-box {
> em {
transform-origin: bottom right;
}
}
}
.log-summary {
> span,
> a {
padding-right: 6px;
word-wrap: break-word;
word-break: break-word;
}
.log-creator {
color:rgba(0, 0, 0, 0.85)
}
.log-text {
color: rgba(0,0,0,.54);
}
.log-time {
color: rgba(0,0,0,.3);
font-size: 12px;
}
}
}
}
}

View File

@ -348,11 +348,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 24px; margin-right: 24px;
cursor: pointer;
&.active { &.active {
margin-top: -2px; margin-top: -2px;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #555555; color: #555555;
cursor: default;
} }
} }
} }
@ -381,6 +383,22 @@
} }
} }
} }
.project-log {
margin-left: 36px;
.logs-activity {
padding: 22px 0 0;
> li {
list-style: none;
padding-top: 0;
&.logs-loading,
&.logs-more,
&.logs-none {
height: 59px;
line-height: 59px;
}
}
}
}
} }
&.open-dialog { &.open-dialog {
@ -419,9 +437,7 @@
padding-left: 0; padding-left: 0;
> p { > p {
margin-right: 28px; margin-right: 28px;
cursor: pointer;
&.active { &.active {
cursor: default;
font-size: 18px; font-size: 18px;
} }
} }
@ -445,6 +461,18 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
.project-log {
position: absolute;
top: 40px;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
padding: 8px 8px;
margin-left: 8px;
background-color: #ffffff;
overflow: auto;
}
} }
} }