莳松-行政管理系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ss-oa-manage-web/src/views/OKR/Management/Components/DialogOkr.vue

638 lines
21 KiB

2 months ago
<template>
<el-dialog
v-if="show"
v-model="show"
width="94vw"
class="dialog-okr"
:show-close="false"
:close-on-click-modal="false"
>
<template #header>
<div class="dialog-okr-header">
<div class="flex-1 flex items-center" style="line-height: 50px">
<div class="dialog-okr-title">
<Icon icon="ep:circle-check-filled" color="#30d1fc" :size="14" />
<span>目标</span>
</div>
<el-divider direction="vertical" />
<span class="text-14px ml-0.25">ork落地</span>
<div class="ml-20px text-14px">
<span>节点</span>
3 weeks ago
<span>{{ nodeInfo.allNodeName }}</span>
2 months ago
</div>
</div>
<div class="flex items-center">
<el-button link @click="emit('edit')">
<el-tooltip content="编辑" placement="top" effect="dark">
<Icon icon="ep:edit" :size="16" />
</el-tooltip>
</el-button>
<el-button link @click="show = false">
<el-tooltip content="关闭" placement="top" effect="dark">
<Icon icon="ep:close" :size="16" />
</el-tooltip>
</el-button>
</div>
</div>
</template>
<template #default>
<div class="dialog-okr-body">
<div class="dialog-okr-content pr-10px">
<div class="dialog-okr-content-detail">
<div class="detail-basic-title">
<div class="basic-title-item">
<div class="basic-title-label">创建人</div>
4 weeks ago
<div class="basic-title-value">{{ nodeInfo.creator }}</div>
2 months ago
</div>
<div class="basic-title-item">
<div class="basic-title-label">执行人</div>
4 weeks ago
<div class="basic-title-value">{{ nodeInfo.executorName }}</div>
2 months ago
</div>
<div class="basic-title-item">
<div class="basic-title-label">目标数</div>
4 weeks ago
<div class="basic-title-value">{{ okrList.length }}</div>
2 months ago
</div>
<div class="basic-title-item" style="min-width: 200px">
<div class="basic-title-label">总体进度</div>
4 weeks ago
<el-progress :percentage="nodeInfo.progress || 0" :stroke-width="8" />
2 months ago
</div>
</div>
<div class="flex detail-basic-info">
4 weeks ago
<span>开始日期{{ nodeInfo.startTime }}</span>
<span>截止日期{{ nodeInfo.endTime }}</span>
<span>最新更新时间{{ nodeInfo.updateTime }}</span>
2 months ago
</div>
</div>
<div class="flex" style="position: relative; flex: 1; height: 100px">
<el-tabs v-model="workIndex" style="flex: 1">
<el-tab-pane label="目标/关键成果" name="okr">
<div class="content-wrap">
3 weeks ago
<OkrTable ref="okrTableRef" :canEdit="canEdit" />
2 months ago
</div>
</el-tab-pane>
</el-tabs>
<el-button class="sav-btn" type="primary" @click="handleSaveProcess">
保存并更新
</el-button>
</div>
</div>
<div class="dialog-okr-side pl-10px">
<el-tabs v-model="sideIndex" style="flex: 1; height: 100%">
<el-tab-pane label="子节点" name="subNode">
<div class="overflow-y-auto" style="height: calc(100% - 50px)">
<div
4 weeks ago
v-for="item in childNodeList"
:key="item.nodeId"
2 months ago
class="border-b-1 child-item"
style="padding: 10px 5px; cursor: pointer"
@click="handleChildItem"
>
<div
class="flex justify-between items-center overflow-hidden text-16px"
style="line-height: 30px"
>
4 weeks ago
<div class="child-label">节点{{ item.nodeName }}</div>
2 months ago
<el-progress
type="line"
4 weeks ago
:percentage="item.progress || 0"
2 months ago
:stroke-width="6"
style="width: 120px"
/>
</div>
<div class="ml-10px flex items-center text-13px" style="line-height: 30px">
<span>目标数</span>
4 weeks ago
<span style="color: #aaa">{{ item.objectiveCount }}</span>
2 months ago
<el-divider direction="vertical" style="margin: 0 20px" />
<div
class="flex-1 overflow-hidden h-30px"
style="text-overflow: ellipsis; white-space: nowrap"
>
<span>执行人</span>
4 weeks ago
<span style="color: #aaa">{{ item.executorName }}</span>
2 months ago
</div>
</div>
<div
class="ml-10px flex items-center text-12px"
style="line-height: 20px; color: #aaa"
>
4 weeks ago
<span>开始日期{{ item.startTime }}</span>
2 months ago
<el-divider direction="vertical" style="margin: 0 20px" />
4 weeks ago
<span>截止日期{{ item.endTime }}</span>
2 months ago
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="评论" name="conclusion">
<div class="relative overflow-y-auto" style="height: calc(100% - 50px)">
<div class="flex justify-between items-center">
<el-select
v-if="addNewComment"
v-model="form.commentType"
filterable
size="small"
style="width: 120px"
@change="getCommentTemplate"
>
3 weeks ago
<el-option
v-for="it in commentTypeOptions"
:label="it.label"
:value="it.id"
:key="it.id"
/>
2 months ago
</el-select>
<div v-if="addNewComment">
<el-button size="small" @click="addNewComment = false"> 取消 </el-button>
<el-button type="primary" size="small" @click="handleSaveComment">
发布
</el-button>
</div>
<el-button v-else type="primary" size="small" @click="handleInsertComment">
新增评论
</el-button>
</div>
<div class="mt-10px" v-if="addNewComment">
<Editor
v-model:modelValue="form.commentValue"
height="300px"
:toolbarConfig="toolbarConfig"
/>
</div>
3 weeks ago
<div
2 weeks ago
v-for="(it, index) in commentList"
3 weeks ago
:key="it.commentId"
class="border-b-1"
style="padding: 10px 5px"
>
2 months ago
<div
class="flex items-center justify-between overflow-hidden text-16px"
style="line-height: 30px"
>
<div class="flex items-center">
<el-avatar
shape="circle"
style="
background-color: var(--el-color-primary-light-3);
width: 30px;
height: 30px;
"
fit="fill"
>
2 weeks ago
<span class="text-12px">{{ it.creatorName.slice(-2) }}</span>
2 months ago
</el-avatar>
3 weeks ago
<div class="ml-10px text-16px">{{ it.creatorName }}</div>
2 months ago
</div>
</div>
3 weeks ago
<div class="ml-10px" v-dompurify-html="it.content"></div>
2 months ago
<div
2 weeks ago
class="ml-10px mt-10px flex items-center justify-between text-12px"
2 months ago
style="line-height: 20px; color: #aaa"
>
2 weeks ago
<div class="flex items-center">
<div class="flex items-center mr-50px">
<el-button link @click="good(it)">
<Icon
icon="fa:thumbs-o-up"
:size="16"
:color="it.currentUserIsLike ? 'var(--el-color-primary)' : '#333'"
/>
</el-button>
<span
class="ml-5px"
:style="{
color: it.currentUserIsLike ? 'var(--el-color-primary)' : '#333'
}"
>{{ it.likeCount }}</span
>
</div>
<div class="flex items-center mr-50px">
<el-button link @click="showComment(index)">
<Icon icon="ep:chat-dot-square" :size="16" color="#333" />
</el-button>
<span class="ml-5px" style="color: #333">{{ it.commentCount }}</span>
</div>
2 months ago
</div>
2 weeks ago
<div class="ml-10px text-13px text-gray-400">
{{ formatDate(it.createTime, 'YYYY-MM-DD HH:mm') }}
2 months ago
</div>
</div>
<!-- 评论 -->
<div
v-if="showCommentIndex == index"
class="bg-gray-100 pl-10px pr-10px pt-5px pb-5px"
style="margin: 10px 10px 0 10px; border-radius: 4px"
label="评论"
>
2 weeks ago
<div
v-for="subComment in it.children.sort((a, b) => a.createTime - b.createTime)"
:key="subComment.commentId"
class="text-14px"
style="line-height: 24px"
>
<span class="font-bold">{{ subComment.creatorName }}</span>
2 months ago
<span>
2 weeks ago
{{ subComment.content }}
2 months ago
</span>
</div>
<div class="mt-10px relative">
2 weeks ago
<!-- <el-input
2 months ago
v-model="form.commentValue"
placeholder="请输入评论"
type="textarea"
:autosize="{ minRows: 4 }"
clearable
size="small"
style="width: 100%"
2 weeks ago
/> -->
<el-mention
v-model="form.commentValue"
type="textarea"
:autosize="{ minRows: 4 }"
:options="employeeOptions"
style="width: 100%"
size="small"
whole
placeholder="请输入评论"
@select="handleMention"
>
<template #label="{ item }">
<div class="flex items-center justify-between h-full">
<span class="text-14px text-dark-700">{{ item.name }}</span>
<span class="text-12px text-gray-400">{{ item.dept }}</span>
</div>
</template>
</el-mention>
2 months ago
<el-button
type="primary"
size="small"
style="position: absolute; right: 2px; bottom: 2px"
2 weeks ago
@click="handleSendCommnet(index)"
2 months ago
>
发布
</el-button>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="进度历史" name="history">
<div class="overflow-y-auto pl-15px" style="height: calc(100% - 50px)">
<el-timeline class="ml-10px">
4 weeks ago
<el-timeline-item
v-for="item in nodeRecords"
:key="item.recordId"
placement="bottom"
3 weeks ago
:timestamp="formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')"
4 weeks ago
color="#30d1fc"
>
<div>{{ item.creator }}</div>
2 months ago
<div class="mt-10px text-14px" style="line-height: 24px; color: #666">
4 weeks ago
{{ item.content }}
2 months ago
</div>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup name="DialogOkr">
4 weeks ago
import { formatDate } from '@/utils/formatTime'
1 month ago
import OkrTable from './OkrTable.vue'
4 weeks ago
import { getOkrNodeDetail, getOkrNodeHistory } from '@/api/okr/okr'
2 weeks ago
import {
getCommentTypeOptions,
createComment,
getCommentPage,
likeComment
} from '@/api/okr/comment'
import { listToTree } from '@/utils/tree'
import { getEmployeeSimpleList } from '@/api/pers/employee'
4 weeks ago
2 weeks ago
const message = useMessage()
2 months ago
const emit = defineEmits(['edit'])
const show = ref(false)
3 weeks ago
const canEdit = ref(false)
2 months ago
const toolbarConfig = {
toolbarKeys: [
'bold', // 加粗
'underline', // 下划线
'italic', // 斜体
'color', // 文字颜色
'bgColor', // 背景色
'fontSize', // 字号
'bulletedList', // 无序列表
'numberedList', // 有序列表
'insertTable', // 插入表格
'insertLink', // 插入链接
'undo' // 撤销
]
}
const workIndex = ref('okr')
const okrList = ref([])
4 weeks ago
const childNodeList = ref([])
2 months ago
const sideIndex = ref('subNode')
1 month ago
const okrTableRef = ref(null)
4 weeks ago
const nodeInfo = ref({})
1 month ago
4 weeks ago
const nodeRecords = ref([])
3 weeks ago
const commentTypeOptions = ref([])
4 weeks ago
3 weeks ago
async function open(curNode) {
canEdit.value = curNode.canEdit
3 weeks ago
nodeInfo.value.nodeId = curNode.nodeId
// 获取数据详情
3 weeks ago
searchInfo(curNode)
show.value = true
}
2 weeks ago
const employeeOptions = ref([])
3 weeks ago
function searchInfo(curNode) {
4 weeks ago
try {
3 weeks ago
getOkrNodeDetail(curNode.nodeId).then((resp) => {
4 weeks ago
nodeInfo.value = resp
if (resp.objectives) {
okrList.value = resp.objectives.map((item) => ({
...item,
keyResults: item.keyResults || []
}))
} else {
okrList.value = []
}
childNodeList.value = [...resp.children]
nextTick(() => {
okrTableRef.value.prepareData(okrList.value)
})
})
3 weeks ago
getOkrNodeHistory(curNode.nodeId).then((resp) => {
4 weeks ago
nodeRecords.value = resp
})
2 weeks ago
getEmployeeSimpleList({ status: 0 }).then((resp) => {
employeeOptions.value = resp.map((item) => ({
...item,
label: item.name,
value: item.name
}))
})
3 weeks ago
getCommentTypeOptions().then((resp) => {
commentTypeOptions.value = resp
})
searchCommentList()
4 weeks ago
} finally {
}
2 months ago
}
function close() {
show.value = false
}
defineExpose({ open, close })
2 weeks ago
function handleMention(item) {
form.value.mentionedUserIdList.push(item.id)
}
2 months ago
function handleSaveProcess() {
3 weeks ago
okrTableRef.value.updateProcess(nodeInfo.value.nodeId).then(() => {
message.success('更新成功')
searchInfo()
})
2 months ago
}
function handleChildItem() {
console.log('handleChildItem')
}
const addNewComment = ref(false)
const form = ref({
commentValue: '',
2 weeks ago
commentType: undefined,
mentionedUserIdList: []
2 months ago
})
function handleInsertComment() {
addNewComment.value = true
3 weeks ago
const defaultComment = commentTypeOptions.value[0]
if (defaultComment) {
form.value = {
commentType: defaultComment.id,
mentionedUserIdList: []
}
2 months ago
}
getCommentTemplate()
}
function getCommentTemplate() {
3 weeks ago
if (form.value.commentType) {
form.value.commentValue = commentTypeOptions.value.find(
(item) => item.id == form.value.commentType
).remark
} else {
form.value.commentValue = `<h2 style=\"text-align: start;\">一、工作概况</h2><p> &nbsp; &nbsp;</p><h2 style=\"text-align: start;\">二、数据统计</h2><ol><li>呼出电话数:</li><li>有效沟通数:</li><li>销售成果</li></ol><h2 style=\"text-align: start;\">三、问题与改进方案</h2><p><br></p>`
}
2 months ago
}
function handleSaveComment() {
addNewComment.value = false
3 weeks ago
try {
const data = {
businessType: 1,
businessId: nodeInfo.value.nodeId,
contentType: 1,
content: form.value.commentValue,
mentionedUserIdList: form.value.mentionedUserIdList
}
2 weeks ago
createComment(data)
.then(() => {
message.success('评论成功')
searchCommentList()
})
.finally(() => {
form.value.commentValue = ''
})
3 weeks ago
} catch (error) {
message.error('评论失败')
}
2 months ago
}
3 weeks ago
const commentList = ref([])
2 months ago
3 weeks ago
function searchCommentList() {
getCommentPage({
businessType: 1,
businessId: nodeInfo.value.nodeId,
pageNum: 1,
2 weeks ago
pageSize: -1
3 weeks ago
}).then((resp) => {
2 weeks ago
commentList.value = listToTree(resp.list, {
id: 'commentId',
pid: 'parentId',
children: 'children'
})
3 weeks ago
})
}
2 months ago
2 weeks ago
function good(item) {
likeComment(item.commentId).then(() => {
message.success('点赞成功')
searchCommentList()
})
2 months ago
}
const showCommentIndex = ref(-1)
function showComment(index) {
showCommentIndex.value = showCommentIndex.value == index ? -1 : index
}
2 weeks ago
function handleSendCommnet(idx) {
try {
// 过滤掉删除的用户,方式为遍历mentionedUserIdList,查找评论中是否有对应的用户名
const userList = [...form.value.mentionedUserIdList]
const arr = []
userList.map((item) => {
if (form.value.commentValue.indexOf(`@${item.name}`) != -1) {
arr.push(item.id)
// 然后移除对应的用户名,防止有多个
form.value.commentValue = form.value.commentValue.replace(`@${item.name}`, '')
}
})
const data = {
businessType: 1,
businessId: nodeInfo.value.nodeId,
contentType: 1,
content: form.value.commentValue,
mentionedUserIdList: arr,
parentId: commentList.value[idx].commentId
}
createComment(data)
.then(() => {
message.success('评论成功')
searchCommentList()
})
.finally(() => {
form.value.commentValue = ''
})
} catch (error) {
message.error('评论失败')
}
2 months ago
}
</script>
<style lang="scss" scoped>
.dialog-okr {
.dialog-okr-header {
display: flex;
height: 51px;
min-height: 51px;
vertical-align: middle;
justify-content: space-between;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 1.25rem;
.dialog-okr-title {
display: flex;
margin-right: 5px;
align-items: center;
background: rgba(48, 209, 252, 0.1);
border-radius: 3px;
padding: 0 10px;
line-height: 24px;
span {
color: #2a344b;
font-size: 12px;
margin-left: 6px;
}
}
}
.dialog-okr-body {
display: flex;
background: #f0f3fa;
overflow: hidden;
min-width: 1064px;
height: calc(94vh - 85px);
.dialog-okr-content {
display: flex;
flex-direction: column;
height: 100%;
width: 70%;
flex: auto;
min-width: 700px;
background: #fff;
margin: 0 1px 0 0;
justify-content: space-between;
overflow-y: hidden !important;
.dialog-okr-content-detail {
position: relative;
margin-bottom: 30px;
overflow-y: auto;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
.detail-basic-title {
display: flex;
font-size: 14px;
margin-bottom: 16px;
.basic-title-item {
margin-right: 50px;
.basic-title-label {
color: #aaa;
margin-bottom: 6px;
}
.basic-title-value {
color: #2a344b;
font-size: 14px;
}
}
}
.detail-basic-info {
font-size: 14px;
color: #aaa;
span {
margin-right: 40px;
}
}
}
.content-wrap {
overflow-y: auto;
max-height: calc(100% - 70px);
}
}
.dialog-okr-side {
padding-left: 10px;
display: flex;
flex-direction: column;
width: 30%;
overflow: hidden;
min-width: 364px;
max-width: 500px;
background: #fff;
margin: 0;
.child-item:hover {
background: #f0f3fa;
}
}
}
}
.sav-btn {
position: absolute;
right: 10px;
top: 0;
width: auto;
}
</style>