6 changed files with 493 additions and 1 deletions
@ -0,0 +1,117 @@ |
|||||
|
<template> |
||||
|
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9" @click="collapsed = !collapsed"> |
||||
|
<div class="relative flex justify-between"> |
||||
|
<div class="absolute text-sm bg-white reviewer-title">审核人</div> |
||||
|
<div class="flex flex-wrap"> |
||||
|
<div v-for="(item, index) in checkers" :key="index"> |
||||
|
<a-button |
||||
|
v-if="index < 4" |
||||
|
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'" |
||||
|
size="small" |
||||
|
class="m-1" |
||||
|
@click.stop="handleSelectChecker(item)" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</a-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<DownOutlined style="line-height: 32px" v-if="!collapsed" /> |
||||
|
<UpOutlined style="line-height: 32px" v-else /> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 隐藏的审核人选项 --> |
||||
|
<div v-show="!collapsed" class="mt-2 flex flex-wrap"> |
||||
|
<div v-for="(item, index) in checkers" :key="index"> |
||||
|
<a-button |
||||
|
v-if="index >= 4" |
||||
|
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'" |
||||
|
size="small" |
||||
|
class="m-1" |
||||
|
@click.stop="handleSelectChecker(item)" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</a-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, computed, defineExpose, defineProps } from 'vue'; |
||||
|
import { useStore } from 'vuex'; |
||||
|
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
dataCheckers: { |
||||
|
type: Array, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const store = useStore(); |
||||
|
// 是否显示全部审核人 |
||||
|
const collapsed = ref(true); |
||||
|
|
||||
|
// 审核人员列表 从store获取真实的项目成员列表 |
||||
|
// 项目加载之初 就已经获取了 |
||||
|
const checkers = computed(() => store.state.role.members); |
||||
|
// 所有选中的审核人 |
||||
|
const checkedCheckers = ref([]); |
||||
|
|
||||
|
if (props.dataCheckers && checkers.value.length > 0) { |
||||
|
props.dataCheckers.forEach(item => { |
||||
|
item.name = item.checkerName; |
||||
|
item.memberId = item.checkerId; |
||||
|
}); |
||||
|
|
||||
|
checkedCheckers.value = props.dataCheckers; |
||||
|
|
||||
|
const arr = []; |
||||
|
checkers.value.forEach(item => { |
||||
|
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId); |
||||
|
if (data) { |
||||
|
arr.push(item); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
checkers.value.forEach(item => { |
||||
|
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId); |
||||
|
if (!data) { |
||||
|
arr.push(item); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
store.commit('role/setMembers', arr); |
||||
|
} |
||||
|
|
||||
|
// 折叠状态 显示的选中的检查人(超过3个用...显示) |
||||
|
// const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value)); |
||||
|
|
||||
|
defineExpose({ checkedCheckers, collapsed }); |
||||
|
|
||||
|
/** |
||||
|
* 点击成员 切换检查人的选中状态 |
||||
|
* @param {object} member 成员对象 |
||||
|
*/ |
||||
|
function handleSelectChecker(member) { |
||||
|
const target = checkedCheckers.value.find(item => item.memberId === member.memberId); |
||||
|
if (target) { |
||||
|
// 已经选中 将此项移除选中的数组中 |
||||
|
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId); |
||||
|
} else { |
||||
|
checkedCheckers.value.push(member); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.reviewer-title { |
||||
|
width: 60px; |
||||
|
height: 20px; |
||||
|
line-height: 20px; |
||||
|
text-align: center; |
||||
|
top: -25px; |
||||
|
left: 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,128 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<!-- 审核标题 --> |
||||
|
<div class="flex justify-between items-center" @click="collapsed = !collapsed"> |
||||
|
<div>{{ deliverData ? deliverData.deliverName : '' }}审核状态</div> |
||||
|
<DownOutlined v-if="!collapsed" /> |
||||
|
<UpOutlined v-else /> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 审核结果 --> |
||||
|
<div v-show="collapsed"> |
||||
|
<!-- 提交人和时间 --> |
||||
|
<div class="my-2 flex justify-between items-center"> |
||||
|
<div class="text-gray-400 text-xs"> |
||||
|
<span class="mr-4" v-if="deliverData.submitMemberName">{{ deliverData.submitMemberName }}</span> |
||||
|
<span v-if="deliverData.submitTime"> {{ dayjs(+deliverData.submitTime).format('MM-DD HH:mm') }}</span> |
||||
|
</div> |
||||
|
|
||||
|
<span class="text-blue-400 text-xs text-right" @click="openDeliverHistory">历史交付物</span> |
||||
|
</div> |
||||
|
|
||||
|
<div @click="openLink" class="break-all text-blue-400 text-xs my-1" v-if="deliverData.details && deliverData.details[0]"> |
||||
|
{{ deliverData.details[0] }} |
||||
|
</div> |
||||
|
|
||||
|
<!-- 审核人 标题 --> |
||||
|
<div class="text-gray-400 flex justify-between mt-3"> |
||||
|
<span>审核</span> |
||||
|
<span class="text-blue-400 text-xs" @click="openMoreRecords">更多审核记录</span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 审核人 列表 --> |
||||
|
<div v-if="deliverData.checkerList"> |
||||
|
<div class="mt-2 text-sm flex justify-between" v-for="(item, index) in deliverData.checkerList" :key="index"> |
||||
|
<div> |
||||
|
<div class="font-semibold">{{ item.checkerName }}</div> |
||||
|
<div class="text-xs text-gray-400">{{ item.remark }}</div> |
||||
|
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 不是自己 显示审核状态 --> |
||||
|
<div v-show="item.isMine !== 1" class="text-xs"> |
||||
|
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span> |
||||
|
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span> |
||||
|
<span v-else class="text-gray-400"> 待审核 </span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 自己是当前审核人 且未审核状态 --> |
||||
|
<div v-show="item.isMine === 1 && (item.status === null || item.status === 0)"> |
||||
|
<a-button size="small" shape="round" class="mr-4 h-1-4 leading-1-4" type="primary" @click="checkModal.mode = 'RESOLVE'"> |
||||
|
通过 |
||||
|
</a-button> |
||||
|
<a-button size="small" shape="round" class="h-1-4 leading-1-4" type="primary" danger @click="checkModal.mode = 'REJECT'"> |
||||
|
驳回 |
||||
|
</a-button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 自己是审核人 且审核过 当前审核人的审核状态并展示得分情况 --> |
||||
|
<div v-show="item.isMine === 1 && item.status > 0" class="text-xs"> |
||||
|
<div class="mb-1"> |
||||
|
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span> |
||||
|
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span> |
||||
|
</div> |
||||
|
|
||||
|
<a-progress v-if="item.score" type="circle" :percent="item.score" strokeColor="#FA8C16" :width="40" :strokeWidth="10"> |
||||
|
<template #format="percent"> |
||||
|
<span |
||||
|
class="inline-block text-center text-white text-sm rounded-full" |
||||
|
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px" |
||||
|
> |
||||
|
{{ percent }} |
||||
|
</span> |
||||
|
</template> |
||||
|
</a-progress> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- <checkFormModal :data="checkModal" @hide="checkModal.mode = 'HIDE'" @submit-end="$emit('check-success')" /> --> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, reactive, inject, defineEmits } from 'vue'; |
||||
|
import { useStore } from 'vuex'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
||||
|
// import checkFormModal from './check-form-modal.vue'; |
||||
|
|
||||
|
const store = useStore(); |
||||
|
const deliverData = inject('deliver'); |
||||
|
const collapsed = ref(false); // 展开/关闭 |
||||
|
defineEmits(['check-success']); |
||||
|
|
||||
|
// 交付物链接 |
||||
|
function openLink() { |
||||
|
window.open(deliverData.details[0], '_blank'); |
||||
|
} |
||||
|
|
||||
|
const checkModal = reactive({ |
||||
|
mode: 'HIDE', // HIDE->隐藏 RESOLVE->通过 REJECT->驳回 |
||||
|
deliverRecordId: () => (deliverData.value ? deliverData.value.deliverRecordId : ''), // 交付物记录id |
||||
|
}); |
||||
|
|
||||
|
// 历史交付物 |
||||
|
function openDeliverHistory() { |
||||
|
const { deliverId } = deliverData.value; |
||||
|
store.commit('task/setDeliverId', deliverId); // 设置交付物id |
||||
|
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
||||
|
store.commit('task/setTaskDetailShow', 'deliverHistory'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
||||
|
} |
||||
|
|
||||
|
// 审核记录 |
||||
|
function openMoreRecords() { |
||||
|
const { deliverRecordId } = deliverData.value; |
||||
|
store.commit('task/setDeliverRecordId', deliverRecordId); // 设置交付物记录id |
||||
|
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
||||
|
store.commit('task/setTaskDetailShow', 'auditRecords'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.ant-progress .ant-progress-inner { |
||||
|
width: 40px !important; |
||||
|
height: 40px !important; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,43 @@ |
|||||
|
<template> |
||||
|
<!-- 任务名插件 --> |
||||
|
<div> |
||||
|
<p-deliver-upload-second v-if="deliver" @upload-success="getDeliverData"></p-deliver-upload-second> |
||||
|
|
||||
|
<p-deliver-check-second |
||||
|
class="mt-3" |
||||
|
v-if="deliver && deliver.details && deliver.details.length" |
||||
|
@check-success="getDeliverData" |
||||
|
></p-deliver-check-second> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { useStore } from 'vuex'; |
||||
|
import { ref, inject, provide } from 'vue'; |
||||
|
import { getDeliverByTaskId } from 'apis'; |
||||
|
import { message } from 'ant-design-vue'; |
||||
|
import pDeliverUploadSecond from '@/plugins/p-deliver-second/p-deliver-upload-second.vue'; |
||||
|
import pDeliverCheckSecond from '@/plugins/p-deliver-second/p-deliver-check-second.vue'; |
||||
|
|
||||
|
const store = useStore(); |
||||
|
const task = inject('task'); |
||||
|
const pluginInfo = inject('pluginInfo'); |
||||
|
const deliver = ref(null); // 服务端返回的交付物信息 |
||||
|
|
||||
|
deliver.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null; |
||||
|
provide('deliver', deliver); |
||||
|
|
||||
|
// 根据任务id获取交付物信息 |
||||
|
async function getDeliverData() { |
||||
|
try { |
||||
|
const { url } = store.state.projects.project; |
||||
|
const { id: taskId } = task; |
||||
|
if (!taskId) return; |
||||
|
const param = { param: { taskId } }; |
||||
|
const data = await getDeliverByTaskId(param, url); |
||||
|
deliver.value = data; |
||||
|
} catch (error) { |
||||
|
message.info(error); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,202 @@ |
|||||
|
<template> |
||||
|
<div @longpress.prevent="showMask = true"> |
||||
|
<!-- 插件名称和提交按钮 --> |
||||
|
<div class="flex item-center justify-between"> |
||||
|
<div class="flex-1"> |
||||
|
<div v-if="deliver.deliverName" class="relative inline-block"> |
||||
|
{{ deliver.deliverName }} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 提交 --> |
||||
|
<a-button type="primary" size="small" @click="submit" :disabled="submitState" :loading="submitBtnLoading"> 提交 </a-button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 插件上传方式 --> |
||||
|
<div class="mt-3"> |
||||
|
<div class="link-box"> |
||||
|
<a-input v-model:value="linkValue" placeholder="请输入交付物地址/链接" /> |
||||
|
</div> |
||||
|
<div class="mt-3"> |
||||
|
<!-- <a-button type="primary" size="small" class="mr-3" @click="paste">粘贴</a-button> --> |
||||
|
|
||||
|
<div class="inline-block"> |
||||
|
<a-upload name="param" :action="action" :headers="headers" :showUploadList="false" :max-count="1" @change="handleChange"> |
||||
|
<a-button type="primary" size="small" class="mr-3">文件</a-button> |
||||
|
</a-upload> |
||||
|
</div> |
||||
|
|
||||
|
<!-- <a-button type="primary" size="small" class="mr-3" @click="uploadPhoto">拍照</a-button> --> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9"> |
||||
|
<div class="relative flex justify-between"> |
||||
|
<div class="absolute bg-white text-sm duration-title">工作量时长</div> |
||||
|
|
||||
|
<div class="mr-3 flex flex-wrap items-center"> |
||||
|
<a-button |
||||
|
:type="checkedIndex === 0 ? 'primary' : 'default'" |
||||
|
size="small" |
||||
|
class="m-1" |
||||
|
style="padding: 0 5px" |
||||
|
@click="handleSelectTime(0)" |
||||
|
> |
||||
|
半小时 |
||||
|
</a-button> |
||||
|
<a-button |
||||
|
:type="checkedIndex === 1 ? 'primary' : 'default'" |
||||
|
size="small" |
||||
|
class="m-1" |
||||
|
style="padding: 0 5px" |
||||
|
@click="handleSelectTime(1)" |
||||
|
> |
||||
|
1小时 |
||||
|
</a-button> |
||||
|
<a-button |
||||
|
:type="checkedIndex === 2 ? 'primary' : 'default'" |
||||
|
size="small" |
||||
|
class="m-1" |
||||
|
style="padding: 0 5px" |
||||
|
@click="handleSelectTime(2)" |
||||
|
> |
||||
|
2小时 |
||||
|
</a-button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 时长 --> |
||||
|
<div class="flex items-center justify-end flex-1 text-sm"> |
||||
|
<a-input-number id="inputNumber" v-model:value="duration" :min="0" placeholder="工作量时长" /> |
||||
|
<span class="flex-shrink-0">小时</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 插件审核人员选择 --> |
||||
|
<ReviewerSecond class="mt-3" ref="reviewerRef" :dataCheckers="deliver.checkerList ? deliver.checkerList : []" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { useStore } from 'vuex'; |
||||
|
import { ref, inject, computed, defineEmits } from 'vue'; |
||||
|
import { message } from 'ant-design-vue'; |
||||
|
import { submitDeliverInfo, uploadImg } from 'apis'; |
||||
|
import ReviewerSecond from '@/components/tall/Reviewer/ReviewerSecond.vue'; |
||||
|
|
||||
|
const store = useStore(); |
||||
|
const projectId = computed(() => store.getters['project/projectId']); |
||||
|
const sessionProjectId = sessionStorage.getItem('projectId'); |
||||
|
const roleId = computed(() => store.state.role.roleId); |
||||
|
|
||||
|
const deliver = inject('deliver'); // 交付物初始值 |
||||
|
const task = inject('task'); // 任务信息 |
||||
|
|
||||
|
const linkValue = ref(''); // 链接的值 |
||||
|
const duration = ref(2); // 工作量时长 |
||||
|
const checkedIndex = ref(2); // 工作量时长默认选中 |
||||
|
|
||||
|
// 上传文件 |
||||
|
const action = uploadImg; |
||||
|
const token = computed(() => store.state.user.token); |
||||
|
const headers = { Authorization: `Bearer ${token.value}` }; |
||||
|
|
||||
|
// 判断提交按钮的状态 |
||||
|
const submitState = computed(() => !linkValue.value); // 按钮状态 |
||||
|
const submitBtnLoading = ref(false); // 是否加载中 |
||||
|
|
||||
|
const reviewerRef = ref(null); // 审核人组件 |
||||
|
|
||||
|
const showMask = ref(false); // 编辑和删除页面是否显示 |
||||
|
|
||||
|
const emits = defineEmits(['upload-success']); |
||||
|
|
||||
|
function handleChange(info) { |
||||
|
console.log('info', info); |
||||
|
} |
||||
|
|
||||
|
// 点击按钮改变工作量时长 |
||||
|
function handleSelectTime(data) { |
||||
|
checkedIndex.value = data; |
||||
|
duration.value = data === 0 ? 0.5 : data === 1 ? 1 : 2; |
||||
|
} |
||||
|
|
||||
|
// 提交交付物 |
||||
|
async function submit() { |
||||
|
console.log(reviewerRef.value); |
||||
|
const { checkedCheckers } = reviewerRef.value; // 拿到选择检查人组件中选中的检查人 |
||||
|
// 提交前的验证 |
||||
|
if (!validateDeliverForm(checkedCheckers)) return; |
||||
|
|
||||
|
submitBtnLoading.value = true; // 按钮loading |
||||
|
|
||||
|
try { |
||||
|
const checkerList = []; |
||||
|
checkedCheckers.forEach(item => { |
||||
|
checkerList.push(item.memberId); |
||||
|
}); |
||||
|
|
||||
|
const { url } = store.state.projects.project; |
||||
|
|
||||
|
const params = { |
||||
|
param: { |
||||
|
projectId: projectId.value || sessionProjectId, |
||||
|
roleId: roleId.value, |
||||
|
deliverId: deliver.value.deliverId, |
||||
|
fileList: [linkValue.value], |
||||
|
checkerList, |
||||
|
msgId: task.msgId, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const data = await submitDeliverInfo(params, url); |
||||
|
message.info('提交交付物信息成功'); |
||||
|
resetControlState(); // 重置控件的初始状态 |
||||
|
emits('upload-success'); |
||||
|
} catch (error) { |
||||
|
message.info('提交交付物信息失败'); |
||||
|
submitBtnLoading.value = false; // 按钮loading |
||||
|
throw new Error(error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 粘贴 |
||||
|
// function paste() {} |
||||
|
|
||||
|
// 拍照 |
||||
|
// function uploadPhoto() {} |
||||
|
|
||||
|
// 验证提交的交付物信息格式 |
||||
|
function validateDeliverForm(checkedCheckers) { |
||||
|
const reg = /[a-zA-z]+:\/\/[^\s]*/; |
||||
|
if (!reg.test(linkValue.value)) { |
||||
|
// 显示toast信息 |
||||
|
message.info('请输入正确的链接'); |
||||
|
return false; |
||||
|
} |
||||
|
// 没有检查人 提示选择检查人 |
||||
|
if (!checkedCheckers || !checkedCheckers.length) { |
||||
|
message.info('请选择检查人'); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 重置控件的初始状态 |
||||
|
function resetControlState() { |
||||
|
submitBtnLoading.value = false; // 隐藏loading |
||||
|
linkValue.value = ''; // 清空输入框内容 |
||||
|
reviewerRef.value.collapsed = true; // 折叠检查人面板 |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.duration-title { |
||||
|
width: 85px; |
||||
|
height: 20px; |
||||
|
line-height: 20px; |
||||
|
text-align: center; |
||||
|
top: -25px; |
||||
|
left: 10px; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue