work_admin/src/components/project/taskDetail.vue
weipengfei b2770044e0 更新
2024-01-05 19:01:49 +08:00

2439 lines
130 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="task-detail" id="task-detail">
<a-spin class="task-detail-spin" :spinning="loading">
<div class="task-header" :class="{'disabled': task.deleted}">
<span class="head-title" v-if="!task.deleted">
<span v-if="task.parentTask">
<span class="muted">属于任务:</span>
<!--<a class="text-default" @click="init(task.pcode)">{{task.parentTask.name}}</a>-->
<a-breadcrumb separator=">" class="breadcrumb text-default">
<a-breadcrumb-item v-for="parent in task.parentTasks" :key="parent.code">
<a class="text-default" @click="init(parent.code)">{{parent.name}}</a>
</a-breadcrumb-item>
</a-breadcrumb>
</span>
<span v-else>{{task.projectName}} · {{task.stageName}}</span>
</span>
<span class="head-title muted" v-else>
<a-icon type="delete"/>&nbsp;
<span>任务已在回收站中,不可修改</span>
</span>
<span class="header-action text-right">
<template v-if="!task.deleted">
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>复制任务链接</span>
</template>
<a class="action-item muted" v-clipboard="taskLink" @success="copyLink"><a-icon
type="link"/></a>
</a-tooltip>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>点个赞</span>
</template>
<a class="action-item muted" :class="{'active': task.liked}" @click="like(!task.liked)">
<a-icon type="like"/>
<span v-show="task.like">{{task.like}}</span>
</a>
</a-tooltip>
<a-dropdown :trigger="['click']" placement="bottomCenter" v-model="visibleTaskMenu">
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>打开菜单</span>
</template>
<a class="action-item muted"><a-icon type="ellipsis"/></a>
</a-tooltip>
<a-menu @click="doTask" class="field-right-menu"
slot="overlay">
<a-menu-item key="copy">
<a-icon type="copy"/>
<span>复制任务 *</span>
</a-menu-item>
<a-menu-item key="move">
<a-icon type="snippets"/>
<span>移动任务 *</span>
</a-menu-item>
<a-menu-item key="star">
<a-icon type="star"></a-icon>
<span v-if="task.stared">取消收藏</span>
<span v-else>收藏任务</span>
</a-menu-item>
<a-menu-item key="recycle">
<a-icon type="delete"></a-icon>
<span>移到回收站</span>
</a-menu-item>
<a-menu-item key="open">
<a-icon type="book"/>
<span>以新标签页打开</span>
</a-menu-item>
<a-menu-divider/>
<a-menu-item key="private">
<a-icon :type="task.private ? 'lock' : 'unlock'"/>
<span>隐私模式</span>
<a class="menu-action text-primary">
<span v-if="task.private">已开启</span>
<span v-else>已关闭</span>
</a>
<div class="menu-desc muted">
<span v-if="task.private">仅参与者可见</span>
<span v-else>所有成员可见</span>
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template v-else>
<a class="action-item muted" @click="recoveryTask">
<a-icon type="undo"/> <span>恢复内容</span>
</a>
<a class="action-item muted" @click="deleteTask">
<a-icon type="delete"/> <span>彻底删除</span>
</a>
</template>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>关闭面板</span>
</template>
<a class="action-item muted" @click="detailClose"><a-icon type="close"/></a>
</a-tooltip>
</span>
</div>
<div class="task-wrap">
<div class="task-content">
<div class="content-left">
<vue-scroll :ops="scrollOps">
<div class="task-title" :class="{'disabled': task.deleted}">
<a-input ref="inputTitle" auto-focus @blur="doName" v-model="task.name" size="large"
v-show="showEditName"/>
<a-tooltip :mouseEnterDelay="0.5" v-if="!task.deleted">
<template slot="title">
<span>点击即可编辑</span>
</template>
<div class="title-text" @click="editTitle" v-show="!showEditName">
{{task.name}}
</div>
</a-tooltip>
<div v-else class="title-text" v-show="!showEditName">
{{task.name}}
</div>
</div>
<div class="task-basic-attrs-view muted">
<div class="field-list" :class="{'disabled': task.deleted}">
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="check-square"/>
<span class="field-name">完成状态</span>
</div>
<div class="field-right">
<a-dropdown :trigger="['click']"
:disabled="!!task.deleted || !!task.hasUnDone"
:class="{'disabled': task.hasUnDone}">
<a-tooltip placement="top">
<template slot="title">
<span v-if="task.hasUnDone" style="font-size: 12px;">子任务尚未全部完成,无法完成父任务</span>
</template>
<span>
<!--<a-icon type="check-square"/>-->
<a-tag v-if="task.done" color="green">已完成</a-tag>
<span v-show="!task.done">未完成</span>
</span>
</a-tooltip>
<a-menu class="field-right-menu" slot="overlay"
:selectable="false"
@click="taskDone(task.code,!task.done)">
<a-menu-item key="done">
<div class="menu-item-content">
<a-tag color="green">已完成</a-tag>
<a-icon type="check" class="check muted"
v-show="task.done"></a-icon>
</div>
</a-menu-item>
<a-menu-item key="undone">
<div class="menu-item-content">
<a-tag color="grey">未完成</a-tag>
<a-icon type="check" class="check muted"
v-show="!task.done"></a-icon>
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="deployment-unit"/>
<span class="field-name">执行状态</span>
</div>
<div class="field-right">
<a-dropdown :trigger="['click']"
:disabled="!!task.deleted">
<span>{{task.statusText}}</span>
<a-menu class="field-right-menu" slot="overlay"
:selectable="false"
@click="taskStatusChange">
<a-menu-item :key="status.id" v-for="(status, index) in taskStatusList">
<div class="menu-item-content">
<span color="green">{{status.name}}</span>
<a-icon type="check" class="check muted"
v-show="task.status == status.id"></a-icon>
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="user"/>
<span class="field-name">执行者</span>
</div>
<div class="field-right">
<a-dropdown
:trigger="['click']"
v-model="visibleTaskMemberMenu"
:disabled="!!task.deleted"
placement="bottomCenter"
>
<a-tooltip :mouseEnterDelay="0.5" v-if="!task.deleted">
<template slot="title">
<span>点击设置执行者</span>
</template>
<div class="field-flex">
<template v-if="task.executor">
<a-avatar :src="task.executor.avatar" icon="user"
size="small"/>
<a class="muted name">{{task.executor.name}}</a>
</template>
<template v-if="!task.executor">
<a-avatar icon="user" size="small"/>
<a class="muted name">待认领</a>
</template>
</div>
</a-tooltip>
<div class="field-flex" v-else>
<template v-if="task.executor">
<a-avatar :src="task.executor.avatar" icon="user"
size="small"/>
<a class="muted name">{{task.executor.name}}</a>
</template>
<template v-if="!task.executor">
<a-avatar icon="user" size="small"/>
<a class="muted name">待认领</a>
</template>
</div>
<div slot="overlay">
<task-member-menu
v-if="visibleTaskMemberMenu"
:projectCode="projectCodeCurrent"
:taskCode="code"
@close="init(false)"
@inviteProjectMember="
showInviteMember = true,
visibleTaskMemberMenu = false"
></task-member-menu>
</div>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="calendar"/>
<span class="field-name">时间</span>
</div>
<div class="field-right field-date">
<template v-if="task.openBeginTime">
<a-dropdown :trigger="['click']" v-model="showBeginTime"
:disabled="!!task.deleted">
<a-tooltip :mouseEnterDelay="0.5" v-if="!task.deleted">
<template slot="title">
<span>点击设置开始时间</span>
</template>
<div class="field-flex">
<a class="muted name" style="margin: 0">
<template v-if="!task.setBeginTime">设置开始时间
</template>
<template v-else>{{task.begin_time_format}}
</template>
</a>
</div>
</a-tooltip>
<div class="field-flex" v-else>
<a class="muted name" style="margin: 0">
<template v-if="!task.setBeginTime">设置开始时间</template>
<template v-else>{{task.begin_time_format}}</template>
</a>
</div>
<div slot="overlay">
<a-date-picker
v-model="task.begin_time"
size="small"
format="MM月DD日 HH:mm"
showTime
allowClear
:showToday="false"
:open="showBeginTime"
@ok="doBeginTime(true)"
>
<template slot="renderExtraFooter">
<a style="position: absolute;" size="small"
@click="doBeginTime(false)">清除</a>
</template>
</a-date-picker>
</div>
</a-dropdown>
<span class="m-l-sm m-r-sm">-</span>
</template>
<a-dropdown :trigger="['click']" v-model="showEndTime"
:disabled="!!task.deleted">
<a-tooltip :mouseEnterDelay="0.5" v-if="!task.deleted">
<template slot="title">
<span>点击设置截止时间</span>
</template>
<div class="field-flex">
<a class="muted name" style="margin: 0">
<template v-if="!task.setEndTime">设置截止时间</template>
<template v-else>{{task.end_time_format}}</template>
</a>
</div>
</a-tooltip>
<div class="field-flex" v-else>
<a class="muted name" style="margin: 0">
<template v-if="!task.setEndTime">设置截止时间</template>
<template v-else>{{task.end_time_format}}</template>
</a>
</div>
<div slot="overlay">
<a-date-picker
v-model="task.end_time"
size="small"
format="MM月DD日 HH:mm"
showTime
allowClear
:showToday="false"
:open="showEndTime"
@ok="doEndTime(true)"
>
<template slot="renderExtraFooter">
<a style="position: absolute;" size="small"
@click="doEndTime(false)">清除</a>
</template>
</a-date-picker>
</div>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount pink-bg">
<div class="field">
<div class="field-left">
<a-icon type="file-text"/>
<span class="field-name">备注</span>
</div>
<div class="field-right width-block">
<div class="task-description" :class="{'disabled': task.deleted}"
v-show="!showTaskDescriptionEdit"
@click="showTaskDesc">
<div class="description-txt img-preview-content" v-show="task.description"
v-html="task.description"></div>
<span v-show="!task.description">添加备注</span>
</div>
<div class="m-t-sm" v-if="hasMoreDesc">
<a v-show="!showMoreDesc"
@click="checkShowMoreDesc(true)">显示更多</a>
<a v-show="showMoreDesc"
@click="checkShowMoreDesc(false)">收起备注</a>
</div>
<div v-show="showTaskDescriptionEdit">
<editor ref="vueWangeditor"
id="editor"
:uploadImgServer="editorConfig.uploadImgServer"
:uploadImgHeaders="editorConfig.uploadImgHeaders"
:menus="editorConfig.menus"
></editor>
<div class="action-btn pull-right">
<a type="text" class="cancel-text muted"
@click="showTaskDescriptionEdit = false,initContent(false)">
取消
</a>
<a-button type="primary" htmlType='submit'
class="middle-btn"
@click="doContent">保存
</a-button>
</div>
</div>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="bulb"/>
<span class="field-name">优先级</span>
</div>
<div class="field-right">
<a-dropdown :trigger="['click']" :disabled="!!task.deleted">
<span>
<a-tag :color="priColor(task.pri)">{{task.priText}}</a-tag>
</span>
<a-menu class="field-right-menu" slot="overlay" @click="doPri"
:selectable="false">
<a-menu-item :key="0">
<div class="menu-item-content">
<a-tag :color="priColor(0)">普通</a-tag>
<a-icon type="check" class="check muted"
v-show="task.pri == 0"></a-icon>
</div>
</a-menu-item>
<a-menu-item :key="1">
<div class="menu-item-content">
<a-tag :color="priColor(1)">紧急</a-tag>
<a-icon type="check" class="check muted"
v-show="task.pri == 1"></a-icon>
</div>
</a-menu-item>
<a-menu-item :key="2">
<div class="menu-item-content">
<a-tag :color="priColor(2)">非常紧急</a-tag>
<a-icon type="check" class="check muted"
v-show="task.pri == 2"></a-icon>
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount task-tag">
<div class="field">
<div class="field-left">
<a-icon type="tag"/>
<span class="field-name">标签</span>
</div>
<div class="field-right">
<div class="inline-block">
<a-tag :color="tag.tag.color" v-for="(tag,index) in task.tags"
:key="index">
{{tag.tag.name}}
<a-icon type="close" @click="removeTag(tag.tag,index)"/>
</a-tag>
</div>
<a-dropdown
:trigger="['click']"
v-model="visibleTaskTagMenu"
:disabled="!!task.deleted"
placement="bottomCenter"
>
<a-tooltip :mouseEnterDelay="0.5" v-if="!task.deleted">
<template slot="title">
<span>添加标签</span>
</template>
<div class="inline-block">
<a-icon class="member-item invite" type="plus-circle"
theme="twoTone"/>
</div>
</a-tooltip>
<div class="inline-block">
<a-icon class="member-item invite" type="plus-circle"
theme="twoTone"/>
</div>
<div slot="overlay">
<task-tag-menu
v-if="visibleTaskTagMenu"
:projectCode="projectCodeCurrent"
:taskCode="code"
@change="taskTagChange"
@update="taskTagUpdate"
@delete="taskTagDelete"
></task-tag-menu>
</div>
</a-dropdown>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="bars"/>
<span class="field-name">子任务 <span v-show="childTaskList.length"> · {{childTaskDoneNum}}/{{childTaskList.length}}</span></span>
</div>
<div class="field-right width-block">
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="block-field width-block">
<div class="task-child">
<div class="task-list" v-show="childTaskList.length">
<div v-for="done in [0,1]" :key="done">
<div
v-for="(childTask, index) in childTaskList"
:key="childTask.code">
<div class="list-item task"
v-if="childTask.done == done"
>
<a-tooltip placement="top">
<template slot="title">
<span v-if="childTask.parentDone"
style="font-size: 12px;">父任务已完成,无法重做子任务</span>
<span v-else-if="childTask.hasUnDone"
style="font-size: 12px;">子任务尚未全部完成,无法完成父任务</span>
</template>
<div class="check-box-wrapper task-item"
@click.stop="()=>{if(task.deleted || childTask.parentDone || childTask.hasUnDone) return false;taskDone(childTask.code,!childTask.done,index,'child')}">
<a-icon class="check-box"
:class="{'disabled': task.deleted || childTask.parentDone || childTask.hasUnDone}"
:type="childTask.done ? 'check-square' : 'border'"
:style="{fontSize:'16px'}"/>
</div>
<!--<a class="task-item check-box"
:class="{'disabled': task.deleted || childTask.parentDone || childTask.hasUnDone}"
@click="()=>{if(task.deleted || childTask.parentDone || childTask.hasUnDone) return false;taskDone(childTask.code,!childTask.done,index,'child')}">
<a-icon type="check"
v-show="childTask.done"/>
</a>-->
</a-tooltip>
<a-dropdown :trigger="['click']"
v-model="childTask.visibleChildTaskMemberMenu"
:disabled="!!task.deleted"
placement="bottomCenter"
>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span v-if="childTask.executor">{{childTask.executor.name}}</span>
<span v-else>待认领</span>
</template>
<a-avatar
v-if="childTask.executor"
class="task-item"
:class="{'disabled': task.deleted}"
size="small"
icon="user"
:src="childTask.executor.avatar"
></a-avatar>
<a-avatar
v-else
class="task-item"
:class="{'disabled': task.deleted}"
size="small"
icon="user"
></a-avatar>
</a-tooltip>
<div slot="overlay">
<task-member-menu
v-if="childTask.visibleChildTaskMemberMenu"
:projectCode="projectCodeCurrent"
:taskCode="childTask.code"
:isCommit="true"
@close="childTask.visibleChildTaskMemberMenu = false;getChildTasks();"
@inviteProjectMember="
showInviteMember = true,
childTask.visibleChildTaskMemberMenu = false"
></task-member-menu>
</div>
</a-dropdown>
<!-- <a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span v-if="childTask.executor">{{childTask.executor.name}}</span>
<span v-else>待认领</span>
</template>
<a-avatar
v-if="childTask.executor"
class="task-item"
:class="{'disabled': task.deleted}"
size="small"
icon="user"
:src="childTask.executor.avatar"
></a-avatar>
<a-avatar
v-else
class="task-item"
:class="{'disabled': task.deleted}"
size="small"
icon="user"
></a-avatar>
</a-tooltip>-->
<div class="task-item task-title"
@click.stop="init(childTask.code)">
<div class="title-text"
:class="{'done': childTask.done}"
>
{{childTask.name}}
</div>
</div>
<a class="muted" @click.stop="init(childTask.code)">
<a-icon class="task-item" type="right"/>
</a>
</div>
</div>
</div>
</div>
<div class="task-list" v-show="showChildTask">
<div class="add-task">
<div class="list-item task">
<span class="task-item check-box"></span>
<a-dropdown :trigger="['click']"
v-model="visibleChildTaskMemberMenu"
:disabled="!!task.deleted"
placement="bottomCenter"
>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span v-if="childExecutor">{{childExecutor.name}}</span>
<span v-else>待认领</span>
</template>
<div class="field-flex">
<template v-if="childExecutor">
<a-avatar class="task-item"
:src="childExecutor.avatar"
icon="user"
size="small"/>
</template>
<template v-if="!childExecutor">
<a-avatar class="task-item"
icon="user"
size="small"/>
</template>
</div>
</a-tooltip>
<div slot="overlay">
<task-member-menu
v-if="visibleChildTaskMemberMenu"
:projectCode="projectCodeCurrent"
:taskCode="childExecutor ? code : ''"
:isCommit="false"
@close="updateChildExecutor"
@inviteProjectMember="
showInviteChildTaskMember = true,
visibleChildTaskMemberMenu = false"
></task-member-menu>
</div>
</a-dropdown>
<div class="task-item task-input">
<a-input v-model="childTaskName"
@pressEnter="createTask"/>
</div>
</div>
<div class="action-btn text-right">
<a type="text" class="cancel-text muted"
@click="showChildTask = false">取消</a>
<a-button type="primary" htmlType='submit'
class="middle-btn"
@click="createTask">保存
</a-button>
</div>
</div>
</div>
<a-tooltip placement="top">
<template slot="title">
<span v-if="task.done" style="font-size: 12px;">父任务已完成,无法添加新的子任务</span>
</template>
<a class="add-handler"
:class="{'disabled': task.done}"
v-show="!showChildTask"
@click="()=>{if (task.deleted || task.done) return false; showChildTask = true}">
<a-icon type="plus" style="margin-right: 6px;"/>
添加子任务
</a>
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left" style="width: 100%">
<a-icon type="clock-circle"/>
<span class="field-name">工时
<span v-if="workTimeList.length"> · 实际工时 {{workTimeTotal}} 小时,工时记录 {{workTimeList.length}} 条,预估工时 {{task.work_time}} 小时 <a
class="muted m-l-sm" @click="doPlainWorkTime">
<a-icon class="task-item" type="edit"/>
</a>
</span>
<span v-else>
<span v-if="task.work_time"> · 预估工时 {{task.work_time}} 小时</span>
<a-tooltip>
<template slot="title">
<span>设置预估工时</span>
</template>
<a class="muted m-l-sm" @click="doPlainWorkTime">
<a-icon class="task-item" type="edit"/>
</a>
</a-tooltip>
</span>
</span>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="block-field width-block">
<div class="task-child">
<div class="task-list" v-show="workTimeList.length">
<div
v-for="(workTime, index) in workTimeList"
:key="workTime.code">
<div class="list-item task m-l-xs">
<div class="task-item task-title hover-none">
<div class="title-text"
>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span v-if="workTime.member">{{workTime.member.name}}</span>
<span v-else>待认领</span>
</template>
<a-avatar
class="task-item"
size="small"
icon="user"
:src="workTime.member.avatar"
></a-avatar>
</a-tooltip>
{{workTime.member.name}}
{{moment(workTime.begin_time).format('MM月DD日 HH:mm')}}开始 工时为
{{workTime.num}} 小时
<div class="muted"
v-show="workTime.content"
style="padding-left: 40px;margin-top: 6px;">
{{workTime.content}}
</div>
</div>
</div>
<a class="muted" @click="doWorkTime(workTime)">
<a-icon class="task-item" type="edit"/>
</a>
<a class="muted"
@click="deleteWorkTime(workTime, index)">
<a-icon class="task-item" type="delete"/>
</a>
</div>
</div>
</div>
<a-tooltip placement="top">
<a class="add-handler" @click="doWorkTime(false)">
<a-icon type="plus" style="margin-right: 6px;"/>
添加工时
</a>
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="field-left">
<a-icon type="paper-clip"/>
<span class="field-name">关联文件</span>
</div>
<div class="field-right width-block">
</div>
</div>
</div>
<div class="component-mount">
<div class="field">
<div class="block-field width-block">
<div class="task-child">
<a class="add-handler" id="upload-file">
<a-icon type="plus" style="margin-right: 6px;"/>
上传文件
</a>
</div>
</div>
</div>
</div>
<div class="component-mount" v-show="taskSourceList.length > 0">
<div class="field">
<div class="block-field width-block">
<div class="file-list">
<div class="m-xs m-t-none">关联的文件</div>
<a-list>
<a-list-item :key="index"
v-for="(item, index) in taskSourceList">
<a-list-item-meta>
<a-avatar size="small" slot="avatar" shape="square"
icon="link"
:src="item.sourceDetail.file_url"/>
<div slot="title">
<a class="muted" target="_blank"
:href="item.sourceDetail.file_url">{{ item.title
}}</a>
</div>
<div slot="description">
<!--{{item.create_time}}-->
</div>
</a-list-item-meta>
<a class="muted" slot="actions">
<span>{{item.sourceDetail.projectName}}</span>
</a>
<a class="muted" slot="actions">
<a-dropdown :trigger="['click']"
placement="bottomCenter">
<!-- <a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>更多操作</span>
</template>-->
<a class="action-item muted">
<a-icon type="down"/>
</a>
<!--</a-tooltip>-->
<a-menu v-clipboard="item.sourceDetail.file_url"
@click="doSource($event,item)"
class="field-right-menu"
slot="overlay">
<a-menu-item key="copy">
<a-icon type="link"/>
<span>复制链接</span>
</a-menu-item>
<a-menu-item key="unlink">
<a-icon type="disconnect"/>
<span>取消关联</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</a>
</a-list-item>
</a-list>
</div>
</div>
</div>
</div>
</div>
</div>
</vue-scroll>
</div>
<div class="content-right">
<div class="header">
<div class="title">
参与者 · {{taskMemberList.length}}
</div>
<div class="member-list">
<a-tooltip
:mouseEnterDelay="0.5"
v-for="member in taskMemberList"
:key="member.code"
>
<template slot="title">
<span>{{member.name}} <span v-if="member.is_owner"> · 创建者</span></span>
</template>
<a-avatar
class="member-item"
icon="user"
size="small"
:src="member.avatar"
@click="routerLink('/members/profile/' + member.membar_account_code + '?key=3')"
/>
</a-tooltip>
<a-dropdown :trigger="['click']" placement="bottomCenter"
v-model="visibleProjectMemberMenu">
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>点击添加参与者</span>
</template>
<a-icon class="member-item invite" type="plus-circle" theme="twoTone"
style="font-size: 24px;"/>
</a-tooltip>
<div slot="overlay">
<project-member-menu
v-if="visibleProjectMemberMenu"
:taskCode="code"
:projectCode="projectCodeCurrent"
@close="init(false)"
@inviteProjectMember="
showInviteMember = true,
visibleProjectMemberMenu = false"
></project-member-menu>
</div>
</a-dropdown>
</div>
</div>
<div class="log-wrap">
<div class="header">
<a-dropdown :trigger="['click']">
<a class="text-default">
所有动态
<a-icon type="down"/>
</a>
<a-menu v-model="taskLogType" class="field-right-menu" slot="overlay"
selectable>
<a-menu-item key="all">
<div class="menu-item-content">
<span>所有动态</span>
</div>
</a-menu-item>
<a-menu-item key="comment">
<div class="menu-item-content">
<span>仅评论</span>
</div>
</a-menu-item>
<a-menu-item key="log">
<div class="menu-item-content">
<span>仅动态</span>
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
<vue-scroll>
<div class="log-list muted">
<a class="show-more muted" v-show="checkShowMoreLog"
@click="getMoreTaskLog">
<a-icon type="ellipsis"/>
显示较早的 {{taskLogTotal - taskLogList.length}} 条动态
</a>
<div :class="{'log-comment': log.is_comment,'list-item': !log.is_comment}"
v-for="log in taskLogList" :key="log.code">
<template v-if="!log.is_comment">
<a-icon class="log-item" :type="log.icon"/>
<div class="log-item log-txt">
<div>{{log.member.name}} <span v-html="log.remark"></span></div>
<div v-if="log.content" class="log-content img-preview-content"
v-html="log.content"></div>
</div>
</template>
<template v-if="log.is_comment">
<div class="log-txt text-default" style="width:100%; display: flex;justify-content: space-between">
<div style="display: flex;align-items: center">
<a-avatar :size="24" :src="log.member.avatar" class="m-r-sm" style="margin-left: -5px"/>
<div>{{log.member.name}}</div>
</div>
<a-tooltip :mouseEnterDelay="0.5">
<template slot="title">
<span>{{log.create_time}}</span>
</template>
<span class="muted">{{log.create_time | formatLogTime}}</span>
</a-tooltip>
</div>
<div class="log-txt text-default" style="padding: 0 0 0 30px;">
<div class="m-t-xs" v-html="checkLink(log.content)" ></div>
</div>
</template>
<a-tooltip v-if="!log.is_comment" :mouseEnterDelay="0.5">
<template slot="title">
<span>{{log.create_time}}</span>
</template>
<span>{{log.create_time | formatLogTime}}</span>
</a-tooltip>
</div>
</div>
</vue-scroll>
</div>
<div class="footer" id="footer">
<a-popover trigger="click" placement="top" :visible="showMentions" arrowPointAtCenter :getPopupContainer="getPopup">
<template slot="content">
<div class="mentions-content" style="width: 200px;">
<div class="mentions-wrapper" v-for="member in taskMemberList" :key="member.id" @click="selectMentionMember(member)">
<a-avatar :src="member.avatar" icon="user" :size="28"/>
<span class="muted name m-l-xs">{{member.name}}</span>
</div>
</div>
</template>
<!-- <span slot="title">Title</span>-->
<a-textarea @focus="commenting = true" @blur="commenting = false" ref="commentText" v-model="comment" :rows="1" placeholder="@提及任务成员Ctrl+Enter发表评论"
style="margin-right: 24px;"/>
</a-popover>
<a-button class="middle-btn" type="primary" @click="createComment">评论</a-button>
</div>
</div>
</div>
</div>
</a-spin>
<invite-project-member v-model="showInviteMember" :project-code="projectCodeCurrent"
v-if="showInviteMember"></invite-project-member>
<!-- <a-modal
class="work-time-modal"
v-model="workTimeDo.modalStatus"
:title="workTimeDo.modalTitle"
:bodyStyle="{paddingBottom:'1px'}"
@ok="workTimeHandleSubmit"
:confirmLoading="workTimeDo.confirmLoading"
>
<a-form
layout="vertical"
@submit.prevent="workTimeHandleSubmit"
:form="workTimeDo.form">
<div>
<div style="font-size: 15px;">登记任务</div>
<div>{{task.name}}</div>
<a-divider class="m-t-xs m-b-md"></a-divider>
</div>
<div class="work-time-stats">
<div class="work-time-item">
<div class="muted title">预估工时</div>
<span class="work-time-num">{{task.work_time}}</span>
<span>小时</span>
</div>
<div class="work-time-item">
<div class="muted title">剩余工时</div>
<span class="work-time-num">
<template v-if="task.work_time - workTimeTotal < 0">0</template>
<template v-else>{{task.work_time - workTimeTotal}}</template>
</span>
<span>小时</span>
</div>
</div>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
label="开始时间"
>
<a-date-picker
showTime
format="YYYY年MM月DD日 HH:mm"
allowClear
placeholder="请选择日期"
v-decorator="[
'begin_time',
{rules: [{ required: true, message: '请选择日期' }], validateTrigger: 'change',initialValue: moment()}
]"
>
</a-date-picker>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="消耗时间"
>
<a-input
type="text"
placeholder="请输入数字"
addonAfter="小时"
v-decorator="[
'num',
{rules: [{ required: true, message: '请输入消耗时间' }], validateTrigger: 'change'}
]"
>
</a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
label="工作内容"
>
<a-textarea
:rows="4"
type="text"
placeholder="在这期间做了什么"
v-decorator="[
'content',
]"
>
</a-textarea>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal> -->
<!-- <a-modal
v-model="plainWorkTime.modalStatus"
:title="plainWorkTime.modalTitle"
:bodyStyle="{paddingBottom:'1px'}"
@ok="workTimePlainHandleSubmit"
:confirmLoading="plainWorkTime.confirmLoading"
>
<a-form
layout="vertical"
@submit.prevent="workTimePlainHandleSubmit"
:form="plainWorkTime.form">
<a-form-item
label=""
>
<a-input
type="text"
placeholder="请输入数字"
addonAfter="小时"
v-decorator="[
'work_time',
{rules: [{ required: true, message: '请输入预估工时' }], validateTrigger: 'change'}
]"
>
</a-input>
</a-form-item>
</a-form>
</a-modal> -->
</div>
</template>
<script>
import {mapState} from 'vuex'
import $ from 'jquery'
import moment from 'moment';
import {COMMON} from '../../const/common'
import editor from '@/components/editor'
import {createComment, del, edit, logs, read, recovery, recycle, star, taskDone} from "@/api/task";
import {del as delSourceLink} from "@/api/sourceLink";
import {list as getTaskMembers} from "@/api/taskMember";
import taskMemberMenu from "@/components/project/taskMemberMenu"
import taskTagMenu from "@/components/project/taskTagMenu"
import projectMemberMenu from "@/components/project/projectMemberMenu"
import inviteProjectMember from '@/components/project/inviteProjectMember'
import {getStore} from "@/assets/js/storage";
import {getApiUrl} from "@/assets/js/utils";
import {save as createTask, list as getTasks, like as doLike} from "@/api/task";
import {notice} from "@/assets/js/notice";
import {relativelyTaskTime, relativelyTime} from "@/assets/js/dateTime";
import {checkResponse} from "../../assets/js/utils";
import {
_taskWorkTimeList, delTaskWorkTime,
editTaskWorkTime,
saveTaskWorkTime,
setPrivate,
setTag,
taskSources
} from "../../api/task";
import ATextarea from "ant-design-vue/es/input/TextArea";
import {detail} from "../../api/departmentMember";
let tokenList = getStore('tokenList', true);
let authorization = '';
if (tokenList) {
let accessToken = tokenList.accessToken;
let tokenType = tokenList.tokenType;
authorization = `${tokenType} ${accessToken}`;
}
export default {
name: "task-detail",
components: {
ATextarea,
editor,
taskMemberMenu,
taskTagMenu,
projectMemberMenu,
inviteProjectMember
},
props: {
taskCode: {
type: [String],
default() {
return ''
}
},
width: {
type: [String, Number],
default() {
return '1360'
}
}
},
data() {
return {
moment,
loading: false,
code: this.taskCode,
projectCodeCurrent: '',
task: {},
taskStatusList: COMMON.TASK_STATUS,
taskLogList: [],
taskLogTotal: 0,
taskMemberList: [],
workTimeList: [],
workTimeTotal: [],
/*任务菜单*/
visibleTaskMenu: false,
/*成员菜单*/
visibleTaskMemberMenu: false,
/*任务标签*/
visibleTaskTagMenu: false,
visibleProjectMemberMenu: false,
showInviteMember: false,
/*任务标题*/
taskName: '',
/*日期*/
showEditName: false,
showBeginTime: false,
showEndTime: false,
/*备注*/
showTaskDescriptionEdit: false,
showMoreDesc: false,
hasMoreDesc: false,
editorConfig: {
uploadImgServer: getApiUrl('project/index/uploadImg'),
uploadImgHeaders: {
Authorization: authorization
},
menus: [
'head', // 标题
'bold', // 粗体
'italic', // 斜体
'justify', // 居中
'image', // 图片
'link', // 链接
'list', // 无序列表
'quote', // 引用
'table', // 表格
'|',
'fullscreen' // 全屏
],
},
departmentMemberInfo: null,
/*子任务*/
childTaskList: [],
showChildTask: false,
childTaskName: '',
childExecutor: null,
visibleChildTaskMemberMenu: false,
showInviteChildTaskMember: false,
/*资源*/
taskSourceList: [],
/*任务动态*/
taskLogType: ['all'],
showMoreTaskLog: 0,
hasMoreTaskLog: false,
hideShowMore: false,
/*评论*/
comment: '',
commenting: false,
/*工时*/
workTimeDo: {
form: this.$form.createForm(this),
info: null,
modalTitle: '登记工时记录',
modalStatus: false,
confirmLoading: false,
},
plainWorkTime: {
form: this.$form.createForm(this),
modalTitle: '设置预估工时',
modalStatus: false,
confirmLoading: false,
},
//显示评论提及
showMentions: false,
mentionsList: []
}
},
computed: {
...mapState({
userInfo: state => state.userInfo,
uploader: state => state.common.uploader,
socketAction: state => state.socketAction,
}),
childTaskDoneNum() {
const list = this.childTaskList.filter(item => item.done == 1);
return list.length;
},
checkShowMoreLog() {
if (!this.hideShowMore) {
if (this.taskLogTotal > 5) {
return true;
}
}
return false;
},
taskLink() {
return window.location.href;
},
scrollOps() {
return {
rail: {
background: '#e5e5e5',
opacity: 1
},
bar: {
keepShow: true
}
}
}
},
watch: {
$route(to, from) {
// if (from.name == 'taskdetail') {
// this.init();
// }
},
showInviteMember(val) {
if (!val) {
this.getTaskMembers();
}
},
taskLogType() {
this.getTaskLog();
},
socketAction(val) {
if (val.action === 'organization:task') {
const data = val.data.data;
if (data.taskCode == this.code) {
this.init(null, false);
}
}
},
uploader: {
handler(newVal, oldVal) {
//监听是否有上传文件行为
console.log(newVal);
console.log(newVal.fileList);
const files = newVal.fileList;
const index = files.findIndex(item => item.projectName == this.task.projectName);
if (index !== -1) {
this.taskSources();
this.getTaskLog();
}
},
deep: true
}
},
created() {
this.init();
},
mounted() {
this.$nextTick(()=>{
this.changeModalHeight();
})
window.onresize = () => {
return (() => {
this.changeModalHeight();
})()
};
document.onkeydown = (event) => {
console.log(event);
var e = event || window.event || arguments.callee.caller.arguments[0];
if (13 == e.keyCode && e.ctrlKey) {
//处理的部分
this.createComment();
}
if ('@' == e.key && this.commenting) {
this.showMentions = true;
}else{
this.showMentions = false;
}
};
setTimeout(() => {
this.uploader.assignBrowse(document.getElementById('upload-file'));
}, 500)
},
filters: {
formatLogTime(value) {
return relativelyTime(value)
}
},
methods: {
init(code = null, loading = true) {
if (code) {
this.code = code;//子任务
}
if (loading) {
this.loading = true;
}
this.clearMemberMenu();
this.getTask();
this.getChildTasks();
this.getTaskMembers();
this.getTaskWorkTimeList();
},
detailClose() {
this.$emit('close', this.task);
// this.$router.push(`/project/space/task/${this.task.project_code}`);
},
clearMemberMenu() {
this.visibleTaskTagMenu = false;
this.visibleTaskMemberMenu = false;
this.visibleProjectMemberMenu = false;
// this.showChildTask = false;
},
getTask() {
this.$store.commit('viewRefresh');
this.clearMemberMenu();
read(this.code).then((res) => {
this.getTaskLog();
this.taskSources();
this.taskName = res.data.name;
this.task = res.data;
this.projectCodeCurrent = res.data.project_code;
if (!this.task.end_time) {
this.task.setEndTime = false;
this.task.end_time = moment(moment().format('YYYY-MM-DD') + ' 18:00');
} else {
this.task.setEndTime = true;
this.task.end_time = moment(this.task.end_time);
}
this.task.end_time_format = relativelyTaskTime(this.task.end_time, true);
if (!this.task.begin_time) {
this.task.setBeginTime = false;
this.task.begin_time = moment(moment().format('YYYY-MM-DD') + ' 18:00');
} else {
this.task.setBeginTime = true;
this.task.begin_time = moment(this.task.begin_time);
}
this.task.begin_time_format = relativelyTaskTime(this.task.begin_time, true);
this.initContent(this.task.description);
if (this.task.executor && !this.childExecutor) {
this.childExecutor = this.task.executor;
}
this.loading = false;
this.$store.dispatch('setTempData', {
projectCode: this.projectCodeCurrent,
taskCode: this.code,
projectName: res.data.projectName
})
});
},
getTaskLog() {
logs({
taskCode: this.code,
all: this.showMoreTaskLog,
pageSize: 5,
comment: this.taskLogType[0] == 'comment' ? 1 : 0
}).then((res) => {
this.taskLogList = res.data.list;
this.taskLogTotal = res.data.total;
if (res.data.total > 5) {
this.hasMoreTaskLog = true;
// this.showMoreTaskLog = 1;
}
this.$nextTick(()=>{
$(".img-preview-content").find('img').click((e) =>{
window.open($(e.target).attr("src"));
})
})
})
},
taskSources() {
taskSources({taskCode: this.code,}).then((res) => {
this.taskSourceList = res.data;
})
},
getMoreTaskLog() {
this.showMoreTaskLog = 1;
this.hideShowMore = true;
this.getTaskLog();
},
getTaskWorkTimeList() {
_taskWorkTimeList({taskCode: this.code}).then(res => {
this.workTimeList = res.data;
let total = 0;
if (res.data) {
res.data.forEach(v => {
total += Number(v.num);
});
this.workTimeTotal = total;
}
})
},
getTaskMembers() {
this.clearMemberMenu();
getTaskMembers({taskCode: this.code, pageSize: 100}).then((res) => {
this.taskMemberList = res.data.list;
this.loading = false;
})
},
doTask(action) {
let app = this;
const actionKey = action.key;
switch (actionKey) {
case 'recycle':
this.$confirm({
title: '移到回收站',
content: `您确定要把该任务移到回收站吗?`,
okText: '确定',
okType: 'danger',
cancelText: `再想想`,
onOk() {
recycle(app.code).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
app.task.deleted = 1;
app.getTaskLog();
});
return Promise.resolve();
}
});
break;
case 'star':
this.task.stared ? this.task.stared = 0 : this.task.stared = 1;
star(app.code, this.task.stared);
return true;
case 'open':
var url = window.location.href;
if (url.indexOf('?') !== -1) {
url += '&full-screen';
}else{
url += '?full-screen';
}
window.open(url);
break;
case 'private':
setPrivate(app.code, Number(!this.task.private)).then(res => {
const result = checkResponse(res);
if (!result) {
return false;
}
this.task.private = Number(!this.task.private);
});
return true;
}
this.visibleTaskMenu = false;
},
editTitle() {
this.showEditName = true;
this.$nextTick(() => {
this.$refs.inputTitle.focus();
});
},
doSource(action, source) {
let app = this;
const actionKey = action.key;
switch (actionKey) {
case 'unlink':
this.$confirm({
title: '取消关联',
content: `您确定永久删除这个关联?`,
okText: '确定',
okType: 'danger',
cancelText: `再想想`,
onOk() {
delSourceLink(source.code).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
app.taskSources();
app.getTaskLog();
});
return Promise.resolve();
}
});
break;
case 'copy':
notice({
title: '复制链接成功',
msg: '在地址栏粘贴并打开可下载该资源'
}, 'notice', 'success', 5);
return true;
}
},
deleteTask() {
let app = this;
this.$confirm({
title: '彻底删除',
content: `彻底删除任务后,该任务及其子任务将会被永久被删除。`,
okText: '删除',
okType: 'danger',
cancelText: `再想想`,
onOk() {
del(app.code).then((res) => {
``
app.detailClose();
});
return Promise.resolve();
}
});
},
recoveryTask() {
let app = this;
this.$confirm({
title: '恢复内容',
content: `您确定要恢复该任务吗?`,
okText: '确定',
okType: 'primary',
cancelText: `再想想`,
onOk() {
recovery(app.code).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
app.getTaskLog();
});
app.task.deleted = 0;
return Promise.resolve();
}
});
},
copyLink() {
notice({
title: '复制链接成功',
msg: '你可以在其他标签页粘贴并快速打开任务页面'
}, 'notice', 'success', 5);
},
like(like) {
like = Number(like);
doLike(this.code, like);
if (like) {
this.task.like++;
} else {
this.task.like--;
}
this.task.liked = like;
},
taskDone(taskCode, done, index, type = 'self') {
done ? done = 1 : done = 0;
taskDone(taskCode, done).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
this.getTaskLog();
if (type == 'self') {
//自身完成
this.task.done = done;
// this.init();
} else {
//完成子任务
this.childTaskList[index].done = done;
// this.init(this.childTaskList[index].pcode);
}
this.getTask();
this.getChildTasks();
});
},
doBeginTime(setBeginTime = false, showBeginTime = false) {
this.task.setBeginTime = setBeginTime;
this.showBeginTime = showBeginTime;
let beginTime = '';
if (setBeginTime) {
beginTime = moment(this.task.begin_time).format('YYYY-MM-DD HH:mm');
this.task.begin_time_format = moment(this.task.begin_time).format('MM月DD日 HH:mm');
} else {
beginTime = '';
}
this.editTask({begin_time: beginTime});
},
doEndTime(setEndTime = false, showEndTime = false) {
this.task.setEndTime = setEndTime;
this.showEndTime = showEndTime;
let endTime = '';
if (setEndTime) {
endTime = moment(this.task.end_time).format('YYYY-MM-DD HH:mm');
this.task.end_time_format = moment(this.task.end_time).format('MM月DD日 HH:mm');
} else {
endTime = '';
}
this.editTask({end_time: endTime});
},
doPri(item) {
this.editTask({pri: item.key});
},
doName() {
this.showEditName = false;
if (!this.task.name.trim() || this.task.name == this.taskName) {
this.task.name = this.taskName;
return false;
}
this.editTask({name: this.task.name});
},
taskStatusChange(item) {
this.task.status = item.key;
this.editTask({status: item.key});
},
editTask(data) {
data.taskCode = this.code;
edit(data).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
this.getTask();
});
},
priColor(pri) {
switch (pri) {
case 1:
return '#ff9900';
case 2:
return '#ed3f14';
default:
return 'green';
}
},
showTaskDesc() {
if (this.task.deleted) {
return false;
}
this.showTaskDescriptionEdit = true;
this.initContent(this.task.description)
},
initContent(value) {
if (value) {
this.$refs.vueWangeditor.setHtml(value)
} else {
this.$refs.vueWangeditor.setHtml('')
}
this.$nextTick(() => {
this.checkShowMoreDesc(false, true);
});
},
doContent() {
let content = this.$refs.vueWangeditor.getHtml();
const obj = {
taskCode: this.code,
description: content
};
edit(obj).then(() => {
this.task.description = content;
this.showTaskDescriptionEdit = false;
this.$nextTick(() => {
this.checkShowMoreDesc(false, true);
});
this.getTaskLog();
});
},
createComment() {
let comment = this.comment.trim();
if (!comment) {
return false;
}
comment += ' ';
const regx = /(@[^@]+) /g;
const res = comment.match(regx);
if (res) {
res.forEach((v) => {
let str = v.substring(1, v.length - 1);
if (this.mentionsList.findIndex(item => item == str) === -1) {
this.mentionsList.push(str);
}
});
}
createComment(this.code, this.comment, JSON.stringify(this.mentionsList)).then(() => {
this.comment = '';
this.mentionsList = [];
this.getTaskLog();
});
},
checkShowMoreDesc(show = false, init = false) {
let dom = $(".description-txt");
if (!init) {
if (show) {
this.showMoreDesc = true;
dom.css("max-height", () => {
return '100%';
});
} else {
this.showMoreDesc = false;
dom.css("max-height", () => {
return '300px';
});
}
}
if (init) {
const height = dom.height();
if (height >= 300) {
this.hasMoreDesc = true;
return true;
} else {
this.hasMoreDesc = false;
return false;
}
}
return false;
},
createTask() {
let obj = {pcode: this.code, name: this.childTaskName};
if (this.childExecutor) {
obj.assign_to = this.childExecutor.code;
}
createTask(obj).then((res) => {
const result = checkResponse(res);
if (!result) {
return false;
}
this.childTaskName = '';
this.getChildTasks();
this.getTaskLog();
});
},
getChildTasks() {
getTasks({pcode: this.code, pageSize: 100, deleted: 0}).then((res) => {
let list = [];
res.data.list.forEach(v => {
v.visibleChildTaskMemberMenu = false;
list.push(v);
});
this.childTaskList = list;
})
},
taskTagChange(tag) {
const index = this.task.tags.findIndex(item => item.tag_code == tag.code);
if (index !== -1) {
this.task.tags.splice(index, 1);
} else {
this.task.tags.push({
tag_code: tag.code,
tag: tag
});
}
},
taskTagUpdate(tag) {
const index = this.task.tags.findIndex(item => item.tag_code == tag.code);
if (index !== -1) {
this.task.tags[index].tag = tag;
}
},
taskTagDelete(tag) {
const index = this.task.tags.findIndex(item => item.tag_code == tag.code);
if (index !== -1) {
this.task.tags.splice(index, 1);
}
},
removeTag(tag, index) {
setTag({taskCode: this.task.code, tagCode: tag.code}).then(() => {
this.task.tags.splice(index, 1);
});
},
doPlainWorkTime() {
let app = this;
this.plainWorkTime.modalStatus = true;
this.$nextTick(function () {
app.plainWorkTime.form.setFieldsValue({
work_time: app.task.work_time,
});
})
},
doWorkTime(workTime = false) {
let app = this;
this.workTimeDo.modalStatus = true;
if (workTime) {
this.workTimeDo.info = workTime;
this.$nextTick(function () {
app.workTimeDo.form.setFieldsValue({
num: workTime.num,
begin_time: moment(workTime.begin_time),
content: workTime.content,
});
})
} else {
this.workTimeDo.form.resetFields();
this.workTimeDo.info = null;
}
},
deleteWorkTime(workTime, index) {
let app = this;
this.$confirm({
title: '删除工时记录',
content: `确定要删除工时记录吗?`,
okText: '确定',
okType: 'danger',
cancelText: '再想想',
onOk() {
delTaskWorkTime({code: workTime.code}).then((res) => {
if (checkResponse(res)) {
app.workTimeList.splice(index, 1);
app.workTimeTotal -= workTime.num;
}
});
return Promise.resolve();
}
});
},
workTimePlainHandleSubmit() {
let app = this;
this.plainWorkTime.form.validateFields(
(err, values) => {
if (!err) {
app.editTask({work_time: values.work_time});
app.plainWorkTime.modalStatus = false;
}
}
);
},
workTimeHandleSubmit() {
let app = this;
this.workTimeDo.form.validateFields(
(err, values) => {
if (!err) {
app.workTimeDo.confirmLoading = true;
let data = {
beginTime: values.begin_time.format('YYYY-MM-DD HH:mm'),
num: values.num,
content: values.content,
taskCode: app.code,
};
if (app.workTimeDo.info) {
data.code = app.workTimeDo.info.code;
editTaskWorkTime(data).then(res => {
app.workTimeDo.confirmLoading = false;
if (checkResponse(res)) {
app.workTimeDo.modalStatus = false;
app.getTaskWorkTimeList();
}
})
} else {
saveTaskWorkTime(data).then(res => {
app.workTimeDo.confirmLoading = false;
if (checkResponse(res)) {
app.workTimeDo.modalStatus = false;
app.getTaskWorkTimeList();
}
});
}
}
}
);
},
checkLink(text) {
let reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g;
if(!reg.exec(text)){
return text
}else{
text = text.replace(reg, "<a target='_blank' href='$1$2'>$1$2</a>");
}
return text;
},
updateChildExecutor(member) {
this.visibleChildTaskMemberMenu = false;
this.childExecutor = member;
},
getPopup() {
return document.getElementById('footer');
},
getTaskMemberPopup() {
return document.getElementById('task-detail');
},
departmentMemberDetail(code) {
detail({code: code,organization: this.$store.state.currentOrganization.code}).then(res=>{
this.departmentMemberInfo = res.data;
})
},
selectMentionMember(member) {
this.showMentions = false;
this.comment += member.name + ' ';
this.$nextTick(() => {
this.$refs.commentText.focus();
});
},
changeModalHeight() {
const defaultWidth = this.width;
let width = $(window).width() - 100;
let height = $(window).height() - 150;
let logHeight = $(window).height() - 315;
if (defaultWidth === 'full-screen' || this.$route.query['full-screen'] !== undefined) {
//全屏显示
$(".task-detail-modal").css("width", $(window).width());
$(".task-detail").css("width", $(window).width());
$(".ant-modal").css("top", 0);
height += 85;
logHeight += 85;
$(".content-left").css("height", height + "px");
$(".log-wrap").css("height", logHeight + "px");
} else {
if (width > defaultWidth) {
width = defaultWidth;
}
$(".task-detail").css("width", width);
$(".content-left").css("height", height + "px");
$(".log-wrap").css("height", logHeight + "px");
}
}
}
}
</script>
<style lang="less">
@import "~ant-design-vue/lib/style/themes/default";
.field-flex {
display: flex;
justify-content: flex-start;
}
.task-tag {
.ant-tag {
margin-bottom: 6px;
}
}
.task-detail {
background: #FFF;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
min-height: 1px;
min-width: 1px;
margin: auto;
.task-detail-spin {
width: 100%;
}
.task-header {
padding: 20px 0;
border-bottom: 1px solid #e5e5e5;
display: flex;
vertical-align: middle;
.head-title {
padding: 0 20px 0 20px;
flex: 1 1;
.breadcrumb {
display: inline;
a {
color: inherit;
&:hover {
color: #40a9ff;
}
}
}
}
.header-action {
font-size: 16px;
padding: 0 20px;
display: flex;
max-height: 24px;
.action-item {
margin-left: 10px;
padding: 4px;
transition: 218ms;
transition-property: background, color;
border-radius: 4px;
align-items: center;
display: flex;
text-align: center;
justify-content: center;
min-width: 32px;
span {
margin-left: 6px;
font-size: 14px;
}
&.active {
color: #3da8f5;
}
&:hover {
background: #ecf6fe;
color: #3da8f5;
border-radius: 4px;
}
}
}
&.disabled {
background: #f5f5f5;
}
}
.task-wrap {
.task-content {
display: flex;
justify-content: flex-start;
.content-left {
border-right: 1px solid #e5e5e5;
min-width: 560px;
//width: 63%;
flex: 1;
/*width: 760px;*/
.task-title {
margin: 10px 40px 20px 20px;
cursor: text;
&.disabled {
cursor: not-allowed;
&:hover {
background: inherit;
}
.ant-input, .title-text {
&:hover, &:focus {
background: inherit;
}
}
}
&:hover {
background: #f5f5f5;
}
&.hover-none{
background: initial;
}
.title-text {
line-height: 24px;
}
.ant-input, .title-text {
font-size: 20px;
padding: 8px;
border: none !important;
border-radius: 4px;
&:hover, &:focus {
background: #f5f5f5;
box-shadow: none !important;
border-right-width: 0 !important;
border: none !important;
border-radius: 4px;
}
}
}
.task-basic-attrs-view {
.field-list {
padding: 0 40px 0 32px;
&.disabled {
.field-right, a {
cursor: not-allowed !important;
}
}
.component-mount {
.field {
display: flex;
justify-content: flex-start;
margin: 12px 0;
min-height: 36px;
.field-left {
align-self: flex-start;
width: 132px;
height: 36px;
padding-right: 12px;
}
.field-right {
cursor: pointer;
max-width: calc(100% - 132px);
.inline-block {
display: inline-block;
}
&.field-date {
display: flex;
}
&.width-block {
width: 100%;
.w-e-text {
cursor: text;
}
}
.name {
margin: 0 8px;
}
}
.block-field {
width: 100%;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 2px 0;
margin-bottom: 12px;
display: flex;
justify-content: flex-start;
flex-direction: row;
}
.task-child {
width: 100%;
.task-list {
padding: 8px 0;
.list-item {
padding: 4px 12px 4px 5px;
display: flex;
align-items: center;
/*justify-content: space-between;*/
.task-title, .task-input {
flex: 1 1;
margin: 0;
}
.title-text {
color: #333;
line-height: 14px;
&.done {
color: #a6a6a6;
}
}
.ant-input, .title-text {
font-size: 14px;
&:hover, &:focus {
}
}
.check-box-wrapper {
margin-top: 0;
}
.task-item {
cursor: pointer;
margin-right: 12px;
&.disabled {
cursor: not-allowed;
}
&.check-box {
.anticon-check {
visibility: visible;
top: 14px;
left: 18px;
}
}
}
}
.action-btn {
padding: 2px 12px 2px 16px;
}
}
.add-handler {
/*margin-bottom: 8px;*/
padding-left: 16px;
padding-right: 12px;
display: flex;
align-items: center;
height: 36px;
}
}
.file-list {
width: 100%;
padding: 8px;
.ant-list-item {
padding: 10px 12px;
border-bottom: none;
border-radius: 4px;
margin-bottom: 2px;
&:hover {
background-color: #f5f5f5;
}
.ant-list-item-meta-title {
margin-bottom: 0;
line-height: 24px;
}
}
}
}
.field-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 8px;
}
.task-description {
&:hover {
background: #f5f5f5;
border-radius: 4px;
}
&.disabled {
&:hover {
background: inherit;
}
}
padding: 8px;
margin: -8px;
.description-txt {
max-height: 300px;
overflow: hidden;
word-break: break-all;
}
p {
margin: 0;
}
}
}
}
}
#editor {
.w-e-text {
min-height: 300px;
overflow-y: auto;
}
.w-e-text-container{
height: inherit !important;
min-height: 300px;
}
}
}
.content-right {
//width: 37%;
width: 410px;
.header {
padding: 15px 20px 20px 20px;
}
.member-list {
padding-top: 12px;
display: flex;
justify-content: flex-start;
.member-item {
margin-right: 10px;
cursor: pointer;
&.invite {
cursor: pointer;
}
}
}
.log-wrap {
border-top: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
padding-bottom: 60px;
.header {
width: 100%;
}
.log-list {
/*font-size: 12px;*/
padding: 0 20px 0 20px;
.show-more {
display: block;
margin-bottom: 20px;
}
.log-comment {
max-width: 405px;
align-items: end;
margin-bottom: 15px;
}
.list-item {
display: flex;
justify-content: flex-start;
align-items: baseline;
vertical-align: middle;
width: 100%;
.log-item {
margin-right: 12px;
margin-bottom: 24px;
.anticon {
font-size: 14px;
}
.log-content {
overflow: hidden;
text-overflow: ellipsis;
border-left: 5px solid #ccc;
padding-left: 12px;
margin-top: 6px;
word-break: break-all;
img{
cursor: pointer;
}
}
}
.log-txt {
flex: 1 1;
}
}
}
}
.footer {
padding: 20px 20px 0 20px;
display: flex;
}
}
}
}
}
.work-time-modal {
.work-time-stats {
display: flex;
margin-bottom: 18px;
.work-time-item {
margin-right: 36px;
.title {
font-size: 12px;
}
.work-time-num {
font-size: 22px;
font-weight: 500;
margin-right: 5px;
font-family: dinmedium;
}
}
}
}
.footer{
.ant-popover-inner-content{
padding: 0;
.mentions-content {
width: 200px;
padding: 12px 0;
.mentions-wrapper {
width: 100%;
display: flex;
align-items: center;
padding: 6px 12px;
&:hover{
cursor: pointer;
background: rgba(51, 143, 229, 0.1);
}
}
}
}
}
</style>