work_admin/src/components/project/taskDetail.vue

2439 lines
130 KiB
Vue
Raw Normal View History

2024-01-03 11:23:06 +08:00
<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>
2024-01-05 19:01:49 +08:00
<!-- <a-modal
2024-01-03 11:23:06 +08:00
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>
2024-01-05 19:01:49 +08:00
</a-modal> -->
<!-- <a-modal
2024-01-03 11:23:06 +08:00
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>
2024-01-05 19:01:49 +08:00
</a-modal> -->
2024-01-03 11:23:06 +08:00
</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>