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.
ocr-web/src/views/task/content/Content.vue

1033 lines
26 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts" setup>
import { computed, onMounted, onUnmounted, reactive, ref, unref, watch } from 'vue'
import { chunk, clone } from 'lodash-es'
import { useDialog, useMessage } from 'naive-ui'
import { useRoute } from 'vue-router'
import BatchModal from '../modal/BatchModal.vue'
import CustomSettingModal from '../modal/CustomSettingModal.vue'
import PictureTable from './PictureTable.vue'
import TaskTable from './TaskTable.vue'
import History from './History.vue'
import NotPassed from '@/components/Approval/NotPassed.vue'
import { getAllfieldList, getfieldList } from '@/api/home/filter'
import { TASK_STATUS_OBJ } from '@/enums/index'
import {
audit,
dubiousfileyd,
getSimilarityList,
getTaskDetailInfo,
} from '@/api/task/task'
import { useTask } from '@/store/modules/task'
import { useUser } from '@/store/modules/user'
import { isEmpty } from '@/utils'
import { formatToDateHMS } from '@/utils/dateUtil'
import { hideDownload } from '@/utils/image'
const emit = defineEmits(['setAsideItemName'])
const batch = ref(false)
const selectItems = ref<any[]>([])
const message = useMessage()
const dialog = useDialog()
const notPassModalRef = ref(null)
const batchModalRef = ref(null)
const totalCount = ref(0)
const taskId: any = ref('') // 任务id
const packageId: any = ref('') // 包id
const CustomSettingModalRef = ref(null)
const taskTableData = ref<any[]>([])
const route = useRoute()
const sortBy: any = {
orderType: 'desc',
orderName: 'similarityScore',
}
function setBatch(value: boolean) {
if (totalCount.value === 0)
return
batch.value = value
if (value === false)
selectItems.value = []
}
function onCheckChange(checked: any, item: any) {
const index = selectItems.value.indexOf(item)
item.checked = checked
if (index === -1 && checked)
selectItems.value.push(item)
else selectItems.value.splice(index, 1)
}
const showActions = computed(() => {
return selectItems.value.length > 0 && batch
})
const taskpagination = reactive({
pageNo: 1,
pageSize: 10,
})
const taskStore = useTask()
const overTask = ref<any>(null)
const taskDetailInfo = ref<any>({})
const taskDetailPictureList = ref<any[]>([])
const userStore = useUser()
const imageRef = ref<ComponentElRef | null>()
let processItems: any[] = []
onMounted(() => {
window.addEventListener('keydown', handleKeydown)
if (route.query.id) {
taskId.value = route.query.id
packageId.value = route.query.packageid
getDetail()
}
})
// 键盘左右箭头快捷切换
function handleKeydown(event) {
if (event.key === 'ArrowLeft')
backHandler()
// 在这里执行左箭头的逻辑
else if (event.key === 'ArrowRight')
forwardHandler()
// 在这里执行右箭头的逻辑
}
// 从store里面获取任务id
function currentTaskId() {
const index = taskStore.getCurrentIndex
return taskStore.getApprovalList[index]?.id || ''
}
// states:1未提交2待审批3通过4不通过
function validate(items: any[]) {
if (items.length === 0)
return '至少选中一个任务'
// const useInfo = userStore.getUserInfo
// const username = useInfo.loginname
// for (const item of items) {
// const { iztrueorfalse, states, assignee } = item
// if (iztrueorfalse === null)
// return '未判别真假'
// else if (states !== 2)
// return '审批状态不合法'
// else if (assignee !== username)
// return '审批人不一致'
// }
return null
}
function approvalHandler(items?: any) {
let cloneItem: any
if (batch.value) {
processItems = selectItems.value
}
else if (overTask.value) {
cloneItem = clone(overTask.value)
processItems = [cloneItem]
}
if (items !== undefined && !(items instanceof PointerEvent))
processItems = items
const msg = validate(processItems)
if (msg !== null) {
message.error(msg)
return
}
const list: any = []
processItems.forEach((item) => {
list.push({
formId: item.id,
taskId: item.taskId,
taskName: item.fromTaskName,
})
})
const param = {
result: true,
comment: '',
disposeType: '',
disposeTypeId: '',
failCauseId: '',
failCauseName: '',
flowTaskInfoList: list,
}
dialog.info({
title: '确认提示',
content: '确认给该任务审批为【通过】吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
doAudit(param)
},
onNegativeClick: () => {},
})
}
function rejectHandler(items?: any) {
const modal = unref(notPassModalRef)! as any
modal.showModal(selectItems.value)
}
function singleRejectHandler() {
const modal = unref(notPassModalRef)! as any
modal.showModal([taskDetailInfo.value])
}
function doAudit(param: any) {
audit(param).then((res) => {
const { code } = res
if (code === 'OK') {
message.success('审核成功')
setBatch(false)
reloadList(param, '通过')
}
})
}
function showModal(modalRef: any) {
const modal = unref(modalRef)! as any
modal.showModal()
}
function forwardHandler() {
taskStore.forward()
}
function backHandler() {
taskStore.back()
}
async function handleDragEnd(event, item) {
// 可以在这里添加拖拽结束后的逻辑
const flag = taskStore.getInFile
if (flag) {
const res = await dubiousfileyd({ pictureid: item.pictureId })
if (res.code === 'OK') {
message.success('加入成功')
getTableData()
}
else {
message.error(res.message)
}
taskStore.setInFile(false)
}
}
async function getTableData() {
const useInfo = userStore.getUserInfo
const listData = []
const reviewType = 3 // 类型
let res = await getAllfieldList(reviewType)
const fieldList = (res as any)?.data
res = await getfieldList(reviewType, useInfo.id)
const userFieldList = (res as any)?.data.userFieldFixed
const blueList = [
'拜访终端名称',
'定位信息',
'拜访日期',
'定位距离',
'拜访小结',
'拜访项目类别',
]
fieldList.map((v) => {
if (userFieldList.includes(v.name)) {
const item = {
label: v.fieldDesc,
value: taskDetailInfo.value.ocrPicture[v.name],
key: v.name,
blue: blueList.includes(v.fieldDesc),
}
listData.push(item)
}
})
taskTableData.value = chunk(listData, 2)
}
async function getImgList() {
if (!isEmpty(taskDetailInfo.value.ocrPicture.id)) {
const { data, total } = await getSimilarityList({
...taskpagination,
...sortBy,
pictureId: taskDetailInfo.value.ocrPicture.id,
})
taskDetailPictureList.value = data
totalCount.value = total
}
else {
taskDetailPictureList.value.length = 0
totalCount.value = 0
}
}
function overTaskHandle() {
const item = taskDetailInfo.value
if (item?.userapprove?.statshis === 2 || item?.userapprove?.statshis == 3) {
overTask.value = null
return
}
if (validate([item]) == null && batch.value === false)
overTask.value = item
}
function leaveTaskHandler() {
overTask.value = null
}
function showActionsModal() {
const modal = unref(CustomSettingModalRef)! as any
modal.showModal()
}
onUnmounted(() => {
taskStore.reset()
window.removeEventListener('keydown', handleKeydown)
})
function immersionHandler() {
taskStore.updateImmersion()
}
function previewHandler(event: MouseEvent) {
event.stopImmediatePropagation()
event.stopPropagation()
if (imageRef.value && (imageRef.value as any).src)
(imageRef.value as any).mergedOnClick()
}
watch(
() => [taskStore.activeId],
() => {
if (!isEmpty(taskStore.getActiveId)) {
packageId.value = taskStore.getPackageid
taskId.value = taskStore.getActiveId
getDetail()
}
},
)
// 获取数据
async function getDetail() {
taskDetailInfo.value = await getTaskDetailInfo(taskId.value, packageId.value)
setBatch(false)
getTableData()
getImgList()
}
function reloadList(param, text) {
// 修改左侧状态
const id = currentTaskId()
const hasCurrentId = param.flowTaskInfoList.find(item => item.formId === id)
if (hasCurrentId)
emit('setAsideItemName', text)
getDetail()
}
</script>
<template>
<div class="wrapper">
<div class="wrapper-header">
<div class="left">
<span class="font">任务ID{{ taskDetailInfo.fromtaskname }}</span>
<SvgIcon size="22" class="forward" name="arrow-left" @click="backHandler" />
<SvgIcon size="22" class="back" name="arrow-right" @click="forwardHandler" />
</div>
<div class="right">
<div v-show="!showActions" style="display: flex; align-items: center">
<div class="btn" @click="setBatch(true)">
<SvgIcon style="margin-right: 6px" size="22" name="batch" />
批量审批
</div>
<!-- <n-popover
ref="popover"
:style="{ padding: '0px' }"
style="width: 148px"
:show-arrow="false"
placement="bottom-start"
trigger="click"
>
<template #trigger>
<div class="icon-wrap">
<SvgIcon size="20" name="more-blue" />
</div>
</template>
<ul class="wrapper-header-action">
<li @click="importHandler">
<SvgIcon size="20" name="download" /><span style="margin-left: 5px">批量导入数据</span>
</li>
<li @click="exportHandler">
<SvgIcon size="20" name="download" /><span style="margin-left: 5px">导出待审数据</span>
</li>
<li>
<SvgIcon size="20" name="download" /><span style="margin-left: 5px">导出全部数据</span>
</li>
</ul>
</n-popover> -->
<div class="icon-wrap">
<SvgIcon size="20" name="immersion-model" @click="immersionHandler" />
</div>
</div>
<div v-show="showActions" class="batch">
<n-button text @click="setBatch(false)">
<template #icon>
<SvgIcon name="revoke" />
</template>
返回
</n-button>
<img
class="btn-approval btn-left"
src="@/assets/images/task/btn-not-pass.png"
alt=""
@click.stop="rejectHandler"
>
<SvgIcon size="24" name="vs" />
<img
class="btn-approval"
src="@/assets/images/task/btn-pass.png"
alt=""
@click.stop="approvalHandler"
>
</div>
</div>
</div>
<div class="wrapper-detail">
<div
class="left"
:style="{
'background-image': `url(${taskDetailInfo?.ocrPicture?.imgurl})`,
}"
@mouseover="overTaskHandle"
@mouseleave="leaveTaskHandler"
@click="previewHandler"
>
<div v-show="overTask" class="action">
<SvgIcon
style="cursor: pointer"
width="168"
height="48"
name="r6"
@click.stop="approvalHandler"
/>
<SvgIcon
style="cursor: pointer; margin-left: 30px"
width="168"
height="48"
name="r7"
@click.stop="singleRejectHandler"
/>
</div>
<div class="check">
<n-checkbox
v-show="batch && taskDetailInfo?.userapprove?.statshis === 1"
v-model:checked="taskDetailInfo.checked"
@click.stop
@update:checked="onCheckChange($event, taskDetailInfo)"
/>
</div>
<div class="status">
<img
v-show="taskDetailInfo?.userapprove?.statshis === 2"
class="img-status"
src="@/assets/images/task/pass.png"
alt=""
>
<img
v-show="taskDetailInfo?.userapprove?.statshis === 3"
class="img-status"
src="@/assets/images/task/not_pass.png"
alt=""
>
</div>
<div class="mark">
<SvgIcon v-show="taskDetailInfo?.iztrueorfalse === 0" size="128" name="jia" />
</div>
<div class="mark">
<SvgIcon v-show="taskDetailInfo?.iztrueorfalse === 1" size="128" name="zhen" />
</div>
<div class="big-mark" />
<div class="preview" @click="previewHandler">
<SvgIcon size="16" name="zoom-out" />
</div>
<div class="info img-info">
<n-grid x-gap="12" y-gap="10" :cols="12">
<n-gi span="4" class="gi1">
<span>
<img class="icon-status" src="@/assets/images/task/status.png" alt="">
</span>
</n-gi>
<n-gi span="8" class="gi2">
<span class="value">{{
TASK_STATUS_OBJ[taskDetailInfo?.userapprove?.statshis]
}}</span>
<span class="label">审批状态</span>
</n-gi>
<n-gi span="4" class="gi1">
<span>
<img
class="icon-status"
src="@/assets/images/task/similarity.png"
alt=""
>
</span>
</n-gi>
<n-gi span="8" class="gi2">
<span class="value num">{{ totalCount }}<span class="unit"></span> </span>
<span class="label">相似匹配</span>
</n-gi>
</n-grid>
</div>
<div class="time">
<div class="time-item">
<SvgIcon class="svg-time" color="#FFF" size="16" name="camera-time" />
<span>{{ taskDetailInfo?.ocrPicture?.photoDateTimestamp ? formatToDateHMS(Number(taskDetailInfo.ocrPicture.photoDateTimestamp)) : '-' }}</span>
</div>
<div class="time-item time-item2">
<SvgIcon class="svg-time" color="#FFF" size="16" name="submit-time" />
<span>{{ taskDetailInfo?.ocrPicture?.submitDateTimestamp ? formatToDateHMS(Number(taskDetailInfo.ocrPicture.submitDateTimestamp)) : '-' }}</span>
</div>
</div>
<div style="display: none">
<n-image
ref="imageRef"
:img-props="{ onClick: hideDownload }"
:src="taskDetailInfo?.ocrPicture?.imgurl"
/>
</div>
</div>
<div class="right">
<n-scrollbar v-if="totalCount > 0" style="max-height: 100%">
<div class="right-card">
<div class="header">
<span>相似图片({{ totalCount }})</span>
<SvgIcon
style="margin-right: 20px; cursor: pointer"
name="max"
size="24"
@click="showModal(batchModalRef)"
/>
</div>
<div class="list">
<div v-for="item in taskDetailPictureList" :key="item.id" class="item">
<div
draggable="true"
class="img-wrapper"
:style="{ 'background-image': `url(${item.imgUrl})` }"
@dragend="
(event) => {
handleDragEnd(event, item);
}
"
/>
<div class="small-mark" />
<div class="check">
<n-checkbox
v-show="batch && item.historyStates === 1"
v-model:checked="item.checked"
@click.stop
@update:checked="onCheckChange($event, item)"
/>
</div>
<img
v-if="item.historyStates === 2"
class="tag-status"
src="@/assets/images/task/tag-pass.png"
alt=""
>
<img
v-if="item.historyStates === 3"
class="tag-status"
src="@/assets/images/task/tag-not-pass.png"
alt=""
>
<div class="time">
<div class="time-item">
<SvgIcon class="svg-time" color="#FFF" size="8" name="camera-time" />
<span>{{ item.photoDateTimestamp ? formatToDateHMS(Number(item.photoDateTimestamp)) : '-' }}</span>
</div>
<div class="time-item time-item2">
<SvgIcon class="svg-time" color="#FFF" size="8" name="submit-time" />
<span>{{ item.submitDateTimestamp ? formatToDateHMS(Number(item.submitDateTimestamp)) : '-' }}</span>
</div>
</div>
<div
:class="{ 'percent-red': item.similarityScore === 100 }"
class="percent"
>
{{ item.similarityScore }}<span class="percent-unit">%</span>
</div>
</div>
</div>
</div>
</n-scrollbar>
<n-empty v-else description="暂无数据" />
</div>
</div>
<n-tabs type="line" animated>
<n-tab-pane name="task-info" tab="任务信息">
<TaskTable
:data="taskDetailInfo"
:task-table-data="taskTableData"
@show-modal="showActionsModal"
/>
</n-tab-pane>
<n-tab-pane name="picture-info" tab="图片信息">
<PictureTable :data="taskDetailInfo" />
</n-tab-pane>
<n-tab-pane name="history" tab="审查日志">
<History :data="taskDetailInfo" />
</n-tab-pane>
</n-tabs>
<NotPassed ref="notPassModalRef" @success="(param) => reloadList(param, '不通过')" />
<BatchModal
ref="batchModalRef"
@reject="rejectHandler"
@approval="approvalHandler"
/>
</div>
</template>
<style lang="less" scoped>
::v-deep(.n-tabs-tab__label) {
font-size: 14px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
color: #666666;
}
::v-deep(.n-tabs-tab--active .n-tabs-tab__label) {
color: #507afd;
font-size: 16px;
font-weight: 500;
}
.wrapper-header-action {
padding: 8px;
li {
height: 32px;
line-height: 32px;
cursor: pointer;
&:hover {
background-color: #f3f8ff;
}
}
}
.icon-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
background: rgba(80, 122, 253, 0.1);
border-radius: 8px;
margin-left: 10px;
cursor: pointer;
}
.img-info {
.icon-status {
width: 32px;
height: 32px;
}
.label {
font-size: 11px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: 500;
color: #ffffff;
}
.value {
font-size: 15px;
font-family: PingFang SC, PingFang SC-Semibold;
font-weight: 600;
color: #ffffff;
}
.num {
font-size: 18px;
font-family: PingFang SC, PingFang SC-Semibold;
font-weight: 600;
color: #ffffff;
}
.unit {
font-size: 11px;
}
}
.wrapper {
display: flex;
flex: 1;
flex-direction: column;
box-sizing: border-box;
margin-left: 16px;
padding: 16px 16px 0px 16px;
background: #fff;
border-radius: 3px;
border: 1px solid rgb(239, 239, 245);
height: calc(100vh - 88px);
overflow-y: scroll;
&-header {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
box-sizing: border-box;
border-radius: 3px;
margin-bottom: 16px;
width: 100%;
height: 36px;
.left {
display: flex;
align-items: center;
.forward {
cursor: pointer;
margin-left: 16px;
}
.back {
cursor: pointer;
margin-left: 6px;
}
}
.right {
display: flex;
align-items: center;
.btn {
width: 118px;
height: 36px;
background: linear-gradient(135deg, #5b85f8, #3c6cf0);
border-radius: 17px;
box-shadow: 0px 2px 6px 0px rgba(116, 153, 253, 0.3);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
margin-right: 6px;
cursor: pointer;
}
.batch {
display: flex;
align-items: center;
.btn-approval {
width: 68px;
height: 28px;
cursor: pointer;
}
.btn-left {
margin-left: 16px;
}
}
}
.font {
font-size: 20px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: 500;
color: #0d0b22;
}
}
&-detail {
display: flex;
height: calc((100vh - 88px - 72px) / 2);
.left {
flex: 0.6;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 8px;
position: relative;
.preview {
position: absolute;
right: 10px;
top: 10px;
z-index: 3;
width: 30px;
height: 30px;
background: rgba(255, 255, 255, 0.20);
border-radius: 6px;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.big-mark{
width: 100%;
height: 151px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 29%,
rgba(3, 0, 0, 0.73)
);
position: absolute;
left: 0;
bottom: 0;
z-index: 0;
}
.mark {
position: absolute;
z-index: 1;
right: 15%;
top: 0px;
width: 128px;
height: 80px;
overflow: hidden;
overflow: hidden;
svg {
position: absolute;
top: -48px;
}
}
.action {
position: absolute;
z-index: 3;
width: 100%;
height: 100%;
display: flex;
border-radius: 8px;
align-items: center;
justify-content: center;
background-color: rgba(102, 102, 102, 0.5);
}
.info {
position: absolute;
z-index: 3;
right: 16px;
bottom: 16px;
width: 136px;
height: 119px;
background: rgba(216, 216, 216, 0.4);
border-radius: 7px;
padding: 16px 0 0 23px;
color: #fff;
}
.time {
position: absolute;
z-index: 3;
left: 16px;
bottom: 16px;
.time-item {
display: flex;
align-items: center;
font-size: 14px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: 500;
color: #ffffff;
margin-bottom: 4px;
}
.time-item2 {
margin-bottom: 0;
}
.svg-time {
margin-right: 5px;
}
}
.gi1 {
display: flex;
align-items: center;
}
.gi2 {
display: flex;
align-items: left;
flex-direction: column;
justify-content: center;
}
.check {
position: absolute;
z-index: 3;
left: 2%;
top: 2%;
}
.status {
position: absolute;
z-index: 3;
left: 0;
top: 0;
.img-status {
width: 133px;
height: 129px;
}
}
}
.right {
flex: 0.5;
background: #f6f9fd;
border-radius: 8px;
margin-left: 20px;
padding: 24px;
.right-card {
padding: 3px;
}
.header {
display: flex;
justify-content: space-between;
font-size: 17px;
font-weight: bold;
color: #333333;
margin-bottom: 16px;
}
.list {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: flex-start;
flex-shrink: 0;
}
.item {
// flex-basis: calc((100% - 48px) / 3);
box-sizing: border-box;
border-radius: 8px;
position: relative;
// overflow: hidden;
margin: 0px 16px 27px 0px;
.small-mark {
width: 100%;
height: 36px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.01),
rgba(0, 0, 0, 0.44) 88%
);
border-radius: 0px 8px 8px 8px;
position: absolute;
left: 0;
bottom: 0;
z-index: 0;
}
.tag-status {
width: 46px;
height: 22px;
position: absolute;
left: -4px;
top: 4px;
}
.time {
position: absolute;
z-index: 3;
left: 3px;
bottom: 3px;
.time-item {
display: flex;
align-items: center;
font-size: 10px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: 500;
color: #ffffff;
margin-bottom: 2px;
line-height: 12px;
}
.time-item2 {
margin-bottom: 0;
}
.svg-time {
margin-right: 5px;
}
}
.img-wrapper {
width: 122px;
height: 70px;
overflow: hidden;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 8px;
cursor: n-resize;
}
.check {
position: absolute;
z-index: 5;
left: 6px;
top: 4px;
}
}
.percent {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 35px;
height: 18px;
opacity: 0.9;
background: #6f92fd;
border-radius: 6px 0px 6px 0px;
z-index: 5;
right: 12px;
top: 2px;
color: #fff;
font-size: 14px;
}
.percent-unit {
font-size: 8px;
margin-top: 4px;
}
.percent-red {
background: #ff4e4f;
}
}
}
}
</style>