This commit is contained in:
磷叶 2025-02-10 15:42:03 +08:00
parent 12fcfc0fa7
commit cfa0aa6e9d
12 changed files with 368 additions and 146 deletions

View File

@ -61,15 +61,6 @@ export function submitTask(data) {
})
}
// 审核任务
export function verifyTask(data) {
return request({
url: '/bst/task/verify',
method: 'put',
data: data
})
}
// 取消任务
export function cancelTask(data) {
return request({

View File

@ -42,3 +42,13 @@ export function delTaskSubmit(id) {
method: 'delete'
})
}
// 审核任务提交记录
export function verifyTaskSubmit(data) {
return request({
url: '/bst/taskSubmit/verify',
method: 'put',
data
})
}

View File

@ -12,6 +12,17 @@
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
<!-- 悬浮信息层 -->
<div class="hover-info">
<el-tooltip :content="getFileName(url)" placement="bottom">
<div class="attach-name">{{ getFileName(url) }}</div>
</el-tooltip>
<div class="attach-action">
<el-link type="primary" @click.stop="downloadFile(url)">
<i class="el-icon-download"></i>
</el-link>
</div>
</div>
</div>
<!-- 非图片文件 -->
<div v-else class="file-item">
@ -23,15 +34,16 @@
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</div>
<div class="attach-info">
<el-tooltip :content="getFileName(url)" placement="top">
<div class="attach-name">{{ getFileName(url) }}</div>
</el-tooltip>
<div class="attach-action">
<el-link type="primary" @click="downloadFile(url)">
<i class="el-icon-download"></i>
</el-link>
<!-- 悬浮信息层 -->
<div class="hover-info">
<el-tooltip :content="getFileName(url)" placement="bottom">
<div class="attach-name">{{ getFileName(url) }}</div>
</el-tooltip>
<div class="attach-action">
<el-link type="primary" @click.stop="downloadFile(url)">
<i class="el-icon-download"></i>
</el-link>
</div>
</div>
</div>
</el-card>
@ -88,20 +100,25 @@ export default {
margin-bottom: 10px;
overflow: hidden;
width: 100%;
position: relative;
.attach-image-box {
width: 100%;
height: 120px;
overflow: hidden;
position: relative;
.el-image {
width: 100%;
height: 100%;
display: block;
transition: all 0.25s ease-in-out;
}
.el-image:hover {
cursor: pointer;
transform: scale(1.15);
&:hover {
.el-image {
transform: scale(1.15);
}
}
}
@ -113,22 +130,26 @@ export default {
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 40px;
color: #909399;
}
position: relative;
}
.attach-info {
padding: 8px;
background: #fff;
border-top: 1px solid #ebeef5;
.hover-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
padding: 4px;
display: flex;
justify-content: space-between;
align-items: center;
transition: opacity 0.3s ease;
.attach-name {
font-size: 13px;
color: #606266;
margin-bottom: 5px;
flex: 1;
font-size: 12px;
color: #fff;
margin-right: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -136,12 +157,20 @@ export default {
}
.attach-action {
text-align: right;
flex-shrink: 0;
.el-link {
color: #fff;
font-size: 14px;
&:hover {
color: #409EFF;
}
}
}
}
}
//
.image-slot {
width: 100%;

View File

@ -8,15 +8,14 @@
<!-- 查看全部 -->
<el-button
v-if="formatFileList.length > 0"
type="primary"
v-if="formatFileList.length > maxShow"
plain
@click="dialogVisible = true"
style="width: 100%;"
size="small"
icon="el-icon-view"
>查看全部 {{ formatFileList.length }} </el-button>
<el-empty v-else description="暂无附件" :image-size="64"/>
<el-empty v-if="formatFileList.length == 0" description="暂无附件" :image-size="64"/>
<el-dialog :visible.sync="dialogVisible" title="查看全部附件" width="1000px" append-to-body>
<el-row :gutter="10">

View File

@ -82,3 +82,14 @@ export const ProjectStatus = {
return [this.ACCEPTED, this.MAINTENANCE_OVERDUE]
}
}
// 任务提交状态
export const TaskSubmitStatus = {
WAIT_CONFIRM: "1", // 待审核
PASS: "2", // 通过
REJECT: "3", // 驳回
// 获取可以审核的任务状态
canVerify() {
return [this.WAIT_CONFIRM]
}
}

View File

@ -526,6 +526,9 @@ export function formatFraction(numerator, denominator) {
// 获取真实url
export function getRealUrl(url) {
if (url == null ) {
return url;
}
if (url.startsWith('http')) {
return url;
}

View File

@ -13,22 +13,19 @@
<form-col :span="24" label="图片" prop="picture">
<image-upload v-model="form.picture" />
</form-col>
<form-col :span="span" label="任务名称" prop="name">
<el-input v-model="form.name" placeholder="请输入任务名称" />
</form-col>
<form-col :span="span" label="项目" prop="projectId">
<form-col :span="24" label="项目" prop="projectId">
<project-select v-model="form.projectId" :disabled="initData.projectId != null"/>
</form-col>
<form-col :span="24" label="类型" prop="type">
<el-radio-group v-model="form.type">
<el-radio-button v-for="dict in dict.type.task_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</form-col>
<form-col :span="24" label="优先级" prop="level">
<el-radio-group v-model="form.level">
<el-radio-button v-for="dict in dict.type.task_level" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</form-col>
<form-col :span="24" label="类型" prop="type">
<el-radio-group v-model="form.type">
<el-radio-button v-for="dict in dict.type.task_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</form-col>
<form-col :span="24" label="任务内容" prop="description">
<el-input v-model="form.description" placeholder="请输入任务内容" type="textarea" maxlength="1000" show-word-limit/>
</form-col>

View File

@ -1,41 +1,107 @@
<template>
<el-card class="card-box" header="任务审核">
<el-form ref="form" :model="form" :rules="rules" label-position="top" v-loading="loading">
<el-form-item prop="remark">
<el-input v-model="form.remark" placeholder="请输入审核意见" type="textarea" maxlength="200" show-word-limit/>
</el-form-item>
</el-form>
<el-row type="flex" >
<el-button
style="flex: 1"
plain
type="success"
icon="el-icon-check"
@click="handleVerify(true)"
>通过</el-button>
<el-button
style="flex: 1"
type="danger"
icon="el-icon-close"
plain
@click="handleVerify(false)"
>驳回</el-button>
</el-row>
</el-card>
<el-card class="card-box" header="提交记录" v-if="submitList.length > 0">
<el-timeline reverse>
<el-timeline-item
v-for="(item, index) in submitList"
:key="index"
:type="timelineType(item)"
:icon="timelineIcon(item)"
>
<div class="verify-item">
<!-- 提交内容区域 -->
<div class="submit-section">
<div class="submit-header">
<div class="user-info">
<el-avatar
:size="20"
:src="getRealUrl(item.userAvatar)"
>{{ item.userName ? item.userName.substring(0,1) : ''}}</el-avatar>
<span class="user-name">{{ item.userName }}</span>
</div>
<div class="submit-time">{{ item.createTime }}</div>
</div>
<div class="submit-content" v-if="item.remark">
<div class="content-text">{{ item.remark }}</div>
</div>
<!-- 提交附件 -->
<div class="submit-attachs" v-if="item.attaches">
<attach-list :file-list="item.attaches" :column="3" :max-show="3" />
</div>
</div>
<!-- 审核内容区域 -->
<template v-if="item.verify != null">
<div class="verify-section">
<div class="verify-header">
<div class="user-info">
<el-avatar
:size="20"
:src="getRealUrl(item.verify.userAvatar)"
>{{ item.verify.userName ? item.verify.userName.substring(0,1) : ''}}</el-avatar>
<span class="user-name">{{ item.verify.userName }}</span>
<span class="verify-status" :class="{'success': item.verify.status === '1'}">
{{ item.verify.status === '1' ? '通过审核' : '驳回' }}
</span>
</div>
<div class="verify-time">{{ item.verify.createTime | dv }}</div>
</div>
<div class="verify-content" v-if="item.verify.remark">
<div class="content-text">{{ item.verify.remark }}</div>
</div>
</div>
</template>
<!-- 审核表单 -->
<div v-if="canVerify(item)" class="verify-form">
<el-form :model="form" :rules="rules" label-position="top" v-loading="loading">
<el-form-item prop="remark">
<el-input v-model="form.remark" placeholder="请输入审核意见" type="textarea" maxlength="200" show-word-limit/>
</el-form-item>
</el-form>
<el-row type="flex" >
<el-button
style="flex: 1"
plain
type="success"
icon="el-icon-check"
@click="handleVerify(index, true)"
>通过</el-button>
<el-button
style="flex: 1"
type="danger"
icon="el-icon-close"
plain
@click="handleVerify(index, false)"
>驳回</el-button>
</el-row>
</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
</template>
<script>
import FormCol from "@/components/FormCol/index.vue";
import { verifyTask } from "@/api/bst/task";
import { DatePickerOptions } from '@/utils/constants';
import { TaskSubmitStatus } from '@/utils/enums';
import { getRealUrl } from '@/utils';
import AttachList from '@/components/AttachList/index.vue'
import { $taskSubmit } from '@/views/bst/taskSubmit/mixins'
import { verifyTaskSubmit } from '@/api/bst/taskSubmit'
export default {
name: "TaskVerifyPanel",
components: { FormCol},
mixins: [$taskSubmit],
components: { FormCol, AttachList},
props: {
id: {
type: String,
default: null
},
submitList: {
type: Array,
default: () => []
}
},
data() {
return {
@ -50,39 +116,180 @@ export default {
}
};
},
computed: {
// timeline
timelineType() {
return (item) => {
if (item.status === TaskSubmitStatus.WAIT_CONFIRM) {
return 'warning';
}
else if (item.status === TaskSubmitStatus.PASS) {
return 'success';
}
else if (item.status === TaskSubmitStatus.REJECT) {
return 'danger';
}
}
},
// timeline
timelineIcon() {
return (item) => {
if (item.status === TaskSubmitStatus.WAIT_CONFIRM) {
return 'el-icon-more';
}
else if (item.status === TaskSubmitStatus.PASS) {
return 'el-icon-check';
}
else if (item.status === TaskSubmitStatus.REJECT) {
return 'el-icon-close';
}
}
}
},
created() {
this.reset();
},
methods: {
getRealUrl,
//
reset() {
this.form = {
id: this.id,
pass: false,
remark: null,
};
this.resetForm("form");
},
//
handleVerify(pass) {
handleVerify(index, pass) {
let row = this.submitList[index];
this.$confirm(`确定要${pass ? '通过' : '驳回'}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$refs["form"].validate(valid => {
if (valid) {
this.submitLoading = true;
this.form.pass = pass;
verifyTask(this.form).then(response => {
this.$modal.msgSuccess(`${pass ? '通过' : '驳回'}成功`);
this.$emit('success');
}).finally(() => {
this.submitLoading = false;
});
}
});
this.submitLoading = true;
this.form.pass = pass;
this.form.id = row.id;
console.log(this.form);
verifyTaskSubmit(this.form).then(response => {
this.$modal.msgSuccess(`${pass ? '通过' : '驳回'}成功`);
this.$emit('success');
}).finally(() => {
this.submitLoading = false;
});
});
}
}
}
</script>
</script>
<style lang="scss" scoped>
.verify-item {
padding: 12px;
background: #f8f9fa;
border-radius: 4px;
margin-bottom: 16px;
.submit-section {
margin-bottom: 12px;
.submit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.user-info {
display: flex;
align-items: center;
.user-name {
margin-left: 8px;
font-weight: 500;
color: #303133;
font-size: 14px;
}
}
.submit-time {
color: #909399;
font-size: 12px;
}
}
.submit-content {
margin-bottom: 8px;
.content-text {
color: #606266;
line-height: 1.6;
font-size: 14px;
}
}
.submit-attachs {
margin-top: 8px;
}
}
.verify-section {
position: relative;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
.verify-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.user-info {
display: flex;
align-items: center;
.user-name {
margin-left: 8px;
margin-right: 8px;
font-weight: 500;
color: #303133;
font-size: 14px;
}
.verify-status {
padding: 2px 6px;
border-radius: 2px;
font-size: 12px;
background: #fef0f0;
color: #f56c6c;
&.success {
background: #f0f9eb;
color: #67c23a;
}
}
}
.verify-time {
color: #909399;
font-size: 12px;
}
}
.verify-content {
.content-text {
color: #606266;
line-height: 1.4;
font-size: 13px;
padding-left: 28px;
}
}
}
.verify-form {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
}
</style>

View File

@ -12,9 +12,8 @@
<el-card class="card-box">
<el-descriptions :column="2" title="任务详情">
<template slot="extra">
<el-button size="small" @click="handleCancel" icon="el-icon-close" v-if="canCancel(detail)">取消任务</el-button>
<el-button size="small" type="danger" plain @click="handleCancel" icon="el-icon-close" v-if="canCancel(detail)">取消任务</el-button>
</template>
<el-descriptions-item label="任务名称">{{ detail.name | dv}}</el-descriptions-item>
<el-descriptions-item label="所属项目">{{ detail.projectName | dv}}</el-descriptions-item>
<el-descriptions-item label="任务类型">
<dict-tag :options="dict.type.task_type" :value="detail.type" size="small"/>
@ -30,10 +29,12 @@
<el-descriptions-item label="创建时间">{{ detail.createTime | dv}}</el-descriptions-item>
<el-descriptions-item label="截止时间">{{ detail.expireTime | dv}}</el-descriptions-item>
<el-descriptions-item label="预计完成时间">{{ detail.expectFinishTime | dv}}</el-descriptions-item>
<el-descriptions-item label="通过时间">{{ detail.passTime | dv}}</el-descriptions-item>
<el-descriptions-item label="任务描述" :span="2">{{ detail.description | dv}}</el-descriptions-item>
</el-descriptions>
</el-card>
<!--提交内容-->
<el-card class="card-box" v-if="detail.submitTime">
<el-descriptions :column="1" title="提交内容">
@ -44,22 +45,15 @@
</el-card>
<!--取消内容-->
<el-card class="card-box" v-if="detail.cancelTime">
<el-descriptions :column="1" title="取消内容">
<el-card class="card-box" v-if="TaskStatus.CANCEL === detail.status">
<el-descriptions :column="1">
<el-descriptions-item label="取消时间">{{ detail.cancelTime | dv}}</el-descriptions-item>
<el-descriptions-item label="取消备注">{{ detail.cancelRemark | dv}}</el-descriptions-item>
<el-descriptions-item label="操作人">{{ detail.cancelUserName | dv}}</el-descriptions-item>
</el-descriptions>
</el-card>
<!--开始任务-->
<task-start-panel v-if="canStart(detail)" :id="detail.id" @success="getInfo(detail.id)"/>
<!--提交任务-->
<task-submit-panel v-if="canSubmit(detail)" :id="detail.id" @success="getInfo(detail.id)"/>
<!-- 审核 -->
<task-verify-panel v-if="canVerify(detail)" :id="detail.id" @success="getInfo(detail.id)"/>
<!-- 提交记录审核 -->
<task-verify-panel :submit-list="detail.submitList" @success="getInfo(detail.id)"/>
</el-col>
@ -71,40 +65,11 @@
<attach-list :file-list="detail.picture" :column="2" />
</el-card>
<!-- 附件展示 -->
<el-card class="card-box" header="提交附件" v-if="detail.submitAttaches">
<attach-list :file-list="detail.submitAttaches" :column="2" />
</el-card>
<!-- 审核内容 -->
<el-card class="card-box" v-if="detail.verifyId" header="审核记录">
<el-timeline>
<el-timeline-item
v-for="(item, index) in detail.verifyList"
:key="index"
:type="item.status === '1' ? 'success' : 'danger'"
:icon="item.status === '1' ? 'el-icon-check' : 'el-icon-close'"
>
<div class="verify-item">
<div class="verify-time">{{ item.createTime }}</div>
<div class="verify-user">
<el-avatar
:size="20"
:src="getRealUrl(item.userAvatar)"
>{{ item.userName ? item.userName.substring(0,1) : ''}}</el-avatar>
<span class="verify-user-name">{{ item.userName }}</span>
<span class="verify-status" :class="{'success': item.status === '1'}">
{{ item.status === '1' ? '通过审核' : '驳回' }}
</span>
</div>
<div class="verify-content" v-if="item.remark">
<div class="verify-remark">{{ item.remark }}</div>
</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
<!--开始任务-->
<task-start-panel v-if="canStart(detail)" :id="detail.id" @success="getInfo(detail.id)"/>
<!--提交任务-->
<task-submit-panel v-if="canSubmit(detail)" :id="detail.id" @success="getInfo(detail.id)"/>
</el-col>
</el-row>
@ -126,6 +91,7 @@ import TaskSubmitPanel from '@/views/bst/task/components/TaskSubmitPanel.vue';
import TaskVerifyPanel from '@/views/bst/task/components/TaskVerifyPanel.vue';
import TaskCancelDialog from '@/views/bst/task/components/TaskCancelDialog.vue';
import { getRealUrl } from '@/utils';
import { TaskStatus } from '@/utils/enums';
export default {
name: "TaskViewDialog",
@ -144,6 +110,7 @@ export default {
},
data() {
return {
TaskStatus,
loading: false,
submitLoading: false,
showCancelDialog: false,

View File

@ -224,11 +224,10 @@ export default {
columns: [
{key: 'id', visible: true, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'projectName', visible: true, label: '项目', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'name', visible: true, label: '名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'description', visible: true, label: '任务内容', minWidth: null, sortable: false, overflow: true, align: 'center', width: null},
{key: 'type', visible: true, label: '类型', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'level', visible: true, label: '优先级', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'status', visible: true, label: '状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'description', visible: true, label: '描述', minWidth: null, sortable: false, overflow: true, align: 'center', width: null},
{key: 'picture', visible: true, label: '图片', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'ownerName', visible: true, label: '负责人', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'expireTime', visible: true, label: '截止时间', minWidth: null, sortable: false, overflow: false, align: 'center', width: "100"},

View File

@ -20,13 +20,6 @@ export const $task = {
&& row.ownerId === this.userId;
}
},
// 是否可以审核
canVerify() {
return (row) => {
return checkPermi(['bst:task:verify'])
&& TaskStatus.canVerify().includes(row.status);
}
},
// 是否可以取消
canCancel() {
return (row) => {

View File

@ -0,0 +1,16 @@
import { TaskStatus, TaskSubmitStatus } from '@/utils/enums'
import { checkPermi } from '@/utils/permission'
export const $taskSubmit = {
computed: {
// 是否允许审核
canVerify() {
return (row) => {
return checkPermi(['bst:taskSubmit:verify'])
&& TaskSubmitStatus.canVerify().includes(row.status)
&& TaskStatus.canVerify().includes(row.taskStatus)
}
},
}
}