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.
692 lines
17 KiB
692 lines
17 KiB
<script lang="ts" setup>
|
|
import { computed, onUnmounted, reactive, ref, unref, watch } from 'vue'
|
|
import { useDialog, useMessage } from 'naive-ui'
|
|
import { clone, pickBy } from 'lodash-es'
|
|
import ConfrimModal from '../modal/ConfrimModal.vue'
|
|
import type { PictureSortParam, SetTFParam } from '/#/api'
|
|
import { useWorkOrder } from '@/store/modules/workOrder'
|
|
import { clearTF, getPackageTaskList, getTaskDetailInfo, getTaskDetailPictureList, setTF } from '@/api/work/work'
|
|
import { fieldMap } from '@/config/workorder'
|
|
|
|
const batch = ref(false)
|
|
const selectItems = ref<any[]>([])
|
|
const message = useMessage()
|
|
const dialog = useDialog()
|
|
const totalCount = ref(0)
|
|
|
|
function setBatch(value: boolean) {
|
|
batch.value = value
|
|
if (value === false) {
|
|
selectItems.value.forEach(item => item.checked = false)
|
|
selectItems.value.length = 0
|
|
}
|
|
}
|
|
|
|
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 packagepagination = reactive({
|
|
pageNo: 1,
|
|
pageSize: 10,
|
|
})
|
|
const taskpagination = reactive({
|
|
pageNo: 1,
|
|
pageSize: 10,
|
|
})
|
|
const sortBy: PictureSortParam = {
|
|
orderbyname: 'desc',
|
|
orderbyvalue: 'fromuptime',
|
|
}
|
|
const workStore = useWorkOrder()
|
|
const selectTask = ref<any>(null)
|
|
const overTask = ref<any>(null)
|
|
const taskList = ref<any[]>([])
|
|
const taskDetailInfo = ref<any>({})
|
|
const taskDetailPictureList = ref<any[]>([])
|
|
|
|
const confrimModalRef = ref(null)
|
|
let processItems: any[] = []
|
|
|
|
function validate(items: any[]) {
|
|
if (items.length === 0)
|
|
return '至少选中一个任务'
|
|
|
|
for (const item of items) {
|
|
const { iztrueorfalse, history, states } = item
|
|
if (iztrueorfalse !== null)
|
|
return '存在已经辨识过的任务'
|
|
|
|
else if (history)
|
|
return '包含历史数据'
|
|
|
|
else if (states !== 1 && states !== 2)
|
|
return '审批状态不合法'
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function falseHandler() {
|
|
let cloneItem: any
|
|
if (batch.value) { processItems = selectItems.value }
|
|
else if (overTask.value) {
|
|
cloneItem = clone(overTask.value)
|
|
processItems = [cloneItem]
|
|
}
|
|
const msg = validate(processItems)
|
|
|
|
if (msg !== null) {
|
|
message.error(msg)
|
|
return
|
|
}
|
|
|
|
const modal = unref(confrimModalRef)! as any
|
|
modal.showModal()
|
|
}
|
|
|
|
function trueHandler() {
|
|
let cloneItem: any
|
|
if (batch.value) { processItems = selectItems.value }
|
|
else if (overTask.value) {
|
|
cloneItem = clone(overTask.value)
|
|
processItems = [cloneItem]
|
|
}
|
|
|
|
const msg = validate(processItems)
|
|
|
|
if (msg !== null) {
|
|
message.error(msg)
|
|
return
|
|
}
|
|
|
|
dialog.info({
|
|
title: '确认提示',
|
|
content: '确认给该任务图判真吗?',
|
|
positiveText: '确定',
|
|
negativeText: '取消',
|
|
onPositiveClick: () => {
|
|
setTrue()
|
|
},
|
|
onNegativeClick: () => { },
|
|
})
|
|
}
|
|
|
|
function setTrue() {
|
|
const ids: any[] = processItems.map(item => item.id)
|
|
|
|
const param: SetTFParam = {
|
|
taskchildpictureids: ids.join(','),
|
|
iztrueorfalse: 1,
|
|
packageid: workStore.getActiveId,
|
|
judgeid: '0',
|
|
}
|
|
|
|
doSetTF(param)
|
|
}
|
|
|
|
function setFalse(id: string, desc: null | string) {
|
|
const ids: any[] = processItems.map(item => item.id)
|
|
|
|
const param: SetTFParam = {
|
|
taskchildpictureids: ids.join(','),
|
|
iztrueorfalse: 0,
|
|
packageid: workStore.getActiveId,
|
|
judgeid: id,
|
|
}
|
|
|
|
if (desc)
|
|
param.judgedesc = desc
|
|
|
|
doSetTF(param)
|
|
}
|
|
|
|
function doSetTF(param: SetTFParam) {
|
|
setTF(param).then((res) => {
|
|
const { code } = res
|
|
processItems.length = 0
|
|
if (code === 'OK')
|
|
updateList(param)
|
|
else
|
|
message.error(res.message)
|
|
})
|
|
}
|
|
|
|
function updateList(param: SetTFParam) {
|
|
const list = taskDetailPictureList.value
|
|
const ids = param.taskchildpictureids.split(',')
|
|
|
|
for (const item of list) {
|
|
if (ids.includes(item.id))
|
|
item.iztrueorfalse = param.iztrueorfalse
|
|
}
|
|
}
|
|
|
|
function forwardHandler() {
|
|
workStore.forward()
|
|
}
|
|
|
|
function backHandler() {
|
|
workStore.back()
|
|
}
|
|
|
|
watch(() => workStore.activeId, async (newValue, oldValue) => {
|
|
const res = await getPackageTaskList(newValue, packagepagination)
|
|
const { data } = res
|
|
taskList.value = data
|
|
|
|
if (taskList.value.length > 0)
|
|
handleSelect(taskList.value[0])
|
|
})
|
|
|
|
const packageName = computed(() => {
|
|
const index = workStore.getCurrentIndex
|
|
return workStore.getOrderList[index]?.packagename || ''
|
|
})
|
|
|
|
async function handleSelect(item: any) {
|
|
selectTask.value = item
|
|
const taskId = item.id
|
|
taskDetailInfo.value = await getTaskDetailInfo(taskId, workStore.activeId)
|
|
|
|
const { data, total } = await getTaskDetailPictureList(workStore.activeId, taskId, { ...taskpagination, ...sortBy })
|
|
taskDetailPictureList.value = data
|
|
totalCount.value = total
|
|
}
|
|
|
|
async function sortHandler(orderby: 'pictureResult' | 'fromuptime') {
|
|
if (!selectTask.value)
|
|
return
|
|
|
|
taskpagination.pageNo = 1
|
|
taskpagination.pageSize = 10
|
|
sortBy.orderbyvalue = orderby
|
|
|
|
const res = await getTaskDetailPictureList(workStore.activeId, selectTask.value.id, { ...taskpagination, ...sortBy })
|
|
taskDetailPictureList.value = res.data
|
|
}
|
|
|
|
const propertys = computed(() => {
|
|
const { ocrPicture } = taskDetailInfo.value
|
|
const v = pickBy(ocrPicture, (value, key: string) => {
|
|
return key.startsWith('field') && value !== null
|
|
})
|
|
return v
|
|
})
|
|
|
|
async function clearHandler() {
|
|
dialog.info({
|
|
title: '确认提示',
|
|
content: '确认给该任务图片的标记印记吗?',
|
|
positiveText: '确定',
|
|
negativeText: '取消',
|
|
onPositiveClick: () => {
|
|
clearMark()
|
|
},
|
|
onNegativeClick: () => { },
|
|
})
|
|
}
|
|
|
|
async function clearMark() {
|
|
const res = await clearTF(workStore.activeId, selectTask.value.id)
|
|
if (res.code === 'OK') {
|
|
taskDetailInfo.value.iztrueorfalse = null
|
|
message.info('清除标记成功')
|
|
}
|
|
else { message.error(res.message) }
|
|
}
|
|
|
|
function overTaskHandelr(item: any) {
|
|
if (validate([item]) == null && batch.value === false)
|
|
overTask.value = item
|
|
}
|
|
|
|
function leaveTaskHandler() {
|
|
overTask.value = null
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
workStore.reset()
|
|
})
|
|
|
|
function getPercent(pictureid: string) {
|
|
const { ocpictureid, pictureresult } = taskDetailInfo.value
|
|
const index = ocpictureid.split(',').indexOf(String(pictureid))
|
|
const results = pictureresult.split(',')
|
|
const percent = results[index] || '0'
|
|
const val = Number.parseFloat(percent)
|
|
return `${val}%`
|
|
}
|
|
|
|
const mark = computed(() => {
|
|
return taskDetailInfo.value.iztrueorfalse === null ? '未标记' : '已标记'
|
|
})
|
|
|
|
function immersionHandler() {
|
|
workStore.updateImmersion()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="wrapper">
|
|
<div class="wrapper-header">
|
|
<div class="left">
|
|
<span class="font">{{ packageName }}</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;">
|
|
<n-button text @click="clearHandler">
|
|
<SvgIcon size="12" name="delete" />
|
|
清除标记
|
|
</n-button>
|
|
<div class="btn" style="margin: 0px 10px;" @click="setBatch(true)">
|
|
<SvgIcon style="margin-right: 6px;" size="14" name="tf" />
|
|
辨别真假
|
|
</div>
|
|
<SvgIcon style="cursor: pointer;" size="20" name="immersion-model" @click="immersionHandler" />
|
|
</div>
|
|
|
|
<div v-show="showActions" class="batch">
|
|
<n-button text @click="setBatch(false)">
|
|
<template #icon>
|
|
<SvgIcon name="revoke" />
|
|
</template>
|
|
返回
|
|
</n-button>
|
|
<div style="cursor: pointer;margin-left: 16px;" @click="falseHandler">
|
|
<SvgIcon width="64" height="28" name="t4" />
|
|
</div>
|
|
<SvgIcon size="24" name="vs" />
|
|
<div style="cursor: pointer;" @click="trueHandler">
|
|
<SvgIcon width="64" height="28" name="t3" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wrapper-detail">
|
|
<div class="left" :style="{ 'background-image': `url(${taskDetailInfo?.ocrPicture?.imgurl})` }">
|
|
<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="info">
|
|
<n-grid x-gap="16" y-gap="0" :cols="12">
|
|
<n-gi span="4">
|
|
<span style="color:#8b8d8f;"><SvgIcon name="m1" /></span>
|
|
</n-gi>
|
|
<n-gi span="8" style="display: flex;align-items: left;flex-direction: column;justify-content: center;">
|
|
<span>{{ mark }}</span>
|
|
<span>图片标记</span>
|
|
</n-gi>
|
|
<n-gi span="4">
|
|
<span style="color:#8b8d8f;"><SvgIcon name="m2" /></span>
|
|
</n-gi>
|
|
<n-gi span="8" style="display: flex;align-items: left;flex-direction: column;justify-content: center;">
|
|
<span>{{ totalCount }}</span>
|
|
<span>相似匹配</span>
|
|
</n-gi>
|
|
</n-grid>
|
|
</div>
|
|
</div>
|
|
<div class="right">
|
|
<n-scrollbar style="max-height: 100%;">
|
|
<span class="name">图片名称</span>
|
|
<div class="tags">
|
|
<div class="tag tag-actived">
|
|
重复图片
|
|
</div>
|
|
<div class="tag">
|
|
基线任务
|
|
</div>
|
|
</div>
|
|
<n-divider />
|
|
<div class="property">
|
|
<span class="property-name top" style="font-weight: bold;color: #333333;">拜访终端
|
|
</span>
|
|
<span style="font-weight: bold;color: #333333;font-size: 16px;">拜访终端</span>
|
|
</div>
|
|
<div v-for="(key) in Object.keys(propertys)" :key="key" class="property">
|
|
<span class="property-name">{{ fieldMap[key] }}</span>
|
|
<span class="property-content ">{{ propertys[key] }}</span>
|
|
</div>
|
|
</n-scrollbar>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex;justify-content: space-between;padding: 12px 0px;">
|
|
<div><span style="font-size: 21px;font-weight: bold;">相似图片</span><span>(16)</span></div>
|
|
<div style="display: flex;align-items: center;" @click="sortHandler('fromuptime')">
|
|
<div style="cursor: pointer;">
|
|
<span>按时间排序</span>
|
|
<SvgIcon style="margin-left: 8px;" name="sort" size="12" />
|
|
</div>
|
|
<div style="margin-left: 15px;cursor: pointer" @click="sortHandler('pictureResult')">
|
|
<span>相似度排序</span>
|
|
<SvgIcon style="margin-left: 8px;" name="sort" size="12" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wrapper-list">
|
|
<div
|
|
v-for="(item, index) in taskDetailPictureList" :key="index" :class="{ 'item-selected': item === selectTask }"
|
|
class="item" @click="handleSelect(item)" @mouseover="overTaskHandelr(item)" @mouseleave="leaveTaskHandler"
|
|
>
|
|
<div class="img-wrapper" :style="{ 'background-image': `url(${item.imgurl})` }" />
|
|
<div class="check">
|
|
<n-checkbox
|
|
v-show="batch" v-model:checked="item.checked" @click.stop
|
|
@update:checked="onCheckChange($event, item)"
|
|
/>
|
|
</div>
|
|
<div class="percent">
|
|
<SvgIcon size="42" name="tag" />
|
|
<div class="val">
|
|
{{ getPercent(item.pictureid) }}
|
|
</div>
|
|
</div>
|
|
<div class="mark">
|
|
<SvgIcon v-show="item.iztrueorfalse === 0" name="jia" />
|
|
</div>
|
|
<div class="mark">
|
|
<SvgIcon v-show="item.iztrueorfalse === 1" name="zhen" />
|
|
</div>
|
|
<div v-show="overTask && overTask.id === item.id" class="action">
|
|
<SvgIcon style="cursor: pointer;" name="t1" @click.stop @click="trueHandler" />
|
|
<SvgIcon style="cursor: pointer;" name="t2" @click.stop @click="falseHandler" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ConfrimModal ref="confrimModalRef" @commit="setFalse" />
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.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.30);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #FFF;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.batch {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.font {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
color: #333333;
|
|
line-height: 25px;
|
|
margin-left: 12px;
|
|
}
|
|
}
|
|
|
|
&-detail {
|
|
display: flex;
|
|
height: calc((100vh - 88px - 72px)/2);
|
|
|
|
.left {
|
|
flex: 1;
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
border-radius: 8px;
|
|
position: relative;
|
|
|
|
.mark {
|
|
position: absolute;
|
|
z-index: 3;
|
|
right: 15%;
|
|
top: 0px;
|
|
width: 128px;
|
|
height: 80px;
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
svg {
|
|
position: absolute;
|
|
top: -48px;
|
|
}
|
|
}
|
|
|
|
.info {
|
|
position: absolute;
|
|
z-index: 3;
|
|
right: 2%;
|
|
bottom: 2%;
|
|
width: 136px;
|
|
height: 119px;
|
|
opacity: 0.44;
|
|
background: rgba(216, 216, 216, 0.4);
|
|
border-radius: 7px;
|
|
}
|
|
}
|
|
|
|
.right {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
background: #fafafa;
|
|
border-radius: 8px;
|
|
margin-left: 20px;
|
|
padding: 24px;
|
|
|
|
.name {
|
|
font-size: 22px;
|
|
font-weight: bold;
|
|
color: #0d0b22;
|
|
}
|
|
|
|
.tags {
|
|
display: flex;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.tag {
|
|
border: 1px solid #d8d8d8;
|
|
box-sizing: border-box;
|
|
border-radius: 8px;
|
|
color: #666666;
|
|
margin-right: 12px;
|
|
padding: 4px 8px;
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
}
|
|
|
|
.tag-actived {
|
|
color: #FE9800;
|
|
border: 1px solid #FE9800;
|
|
}
|
|
|
|
.property {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.property-name {
|
|
width: 100px;
|
|
color: #999999;
|
|
position: relative;
|
|
}
|
|
|
|
.top {
|
|
&::after {
|
|
position: absolute;
|
|
left: 72px;
|
|
top: 4px;
|
|
content: '';
|
|
width: 1px;
|
|
height: 12px;
|
|
background: #979797;
|
|
}
|
|
}
|
|
|
|
.property-content {
|
|
flex: 1;
|
|
color: #333333;
|
|
}
|
|
}
|
|
}
|
|
|
|
&-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
flex-shrink: 0;
|
|
|
|
.item-selected {
|
|
box-shadow: 0px 2px 8px 0px rgba(0, 65, 207, 0.28);
|
|
background-color: #FFF;
|
|
}
|
|
|
|
.item {
|
|
box-sizing: border-box;
|
|
border-radius: 8px;
|
|
padding: 9px 10px;
|
|
position: relative;
|
|
|
|
.img-wrapper {
|
|
width: 230px;
|
|
height: 130px;
|
|
overflow: hidden;
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.check {
|
|
position: absolute;
|
|
z-index: 3;
|
|
left: 12px;
|
|
top: 12px;
|
|
}
|
|
|
|
.percent {
|
|
position: absolute;
|
|
text-align: center;
|
|
z-index: 3;
|
|
right: 12px;
|
|
top: 2px;
|
|
color: #FFF;
|
|
|
|
.val {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
.mark {
|
|
position: absolute;
|
|
z-index: 3;
|
|
right: 62px;
|
|
top: 9px;
|
|
width: 64px;
|
|
height: 40px;
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
svg {
|
|
position: absolute;
|
|
top: -20px;
|
|
}
|
|
}
|
|
|
|
.action {
|
|
position: absolute;
|
|
z-index: 5;
|
|
left: 10px;
|
|
top: 9px;
|
|
width: 230px;
|
|
height: 130px;
|
|
display: flex;
|
|
border-radius: 8px;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
background-color: #666666;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
::v-deep(.n-divider:not(.n-divider--vertical)) {
|
|
margin-top: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
</style>
|