no message
This commit is contained in:
parent
e2ae2536b3
commit
8174d4b6b3
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
126
resources/assets/js/pages/manage/components/ProjectLog.vue
Normal file
126
resources/assets/js/pages/manage/components/ProjectLog.vue
Normal 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>
|
@ -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">
|
||||||
|
<DialogWrapper ref="dialog">
|
||||||
<div slot="head" class="head">
|
<div slot="head" 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>
|
||||||
</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',
|
||||||
|
4
resources/assets/js/store/actions.js
vendored
4
resources/assets/js/store/actions.js
vendored
@ -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)
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
79
resources/assets/sass/pages/components/project-log.scss
vendored
Normal file
79
resources/assets/sass/pages/components/project-log.scss
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user