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