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/worksheet/content/Content.vue

990 lines
25 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 {
clearTF, getTaskDetailInfo,
getTaskDetailPictureList,
setTF
} from "@/api/work/work";
import { fieldMap } from "@/config/workorder";
import { useWorkOrder } from "@/store/modules/workOrder";
import { isEmpty } from "@/utils";
import { formatToDateHMS } from "@/utils/dateUtil";
import { hideDownload } from "@/utils/image";
import { useInfiniteScroll } from "@vueuse/core";
import { format } from 'date-fns';
import imagesloaded from "imagesloaded";
import { clone, debounce, pickBy } from "lodash-es";
import { useDialog, useMessage } from "naive-ui";
import { computed, onUnmounted, onUpdated, reactive, ref, unref, watch } from "vue";
import ConfrimModal from "../modal/ConfrimModal.vue";
import type { SetTFParam, SimilarityPictureSortParam } from "/#/api";
const batch = ref(false);
const selectItems = ref<any[]>([]);
const message = useMessage();
const dialog = useDialog();
const totalCount = ref(0);
let _imagesload: any;
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: SimilarityPictureSortParam = {
orderType: "asc",
orderName: "similarityScore",
};
const workStore = useWorkOrder();
const selectTask = ref<any>(null);
const overTask = ref<any>(null);
const taskList = ref<any[]>([]);
const taskDetailInfo = ref<any>({});
const confrimModalRef = ref(null);
const imageRef = ref<ComponentElRef | null>();
const listData = ref<any[]>([]);
const loading = ref(false);
const el = ref<HTMLDivElement | null>(null);
let canloadMore = true;
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 = listData.value;
const ids = param.taskchildpictureids.split(",");
for (const item of list) {
if (ids.includes(item.id)) item.iztrueorfalse = param.iztrueorfalse;
}
if (ids.includes(taskDetailInfo.value.id))
taskDetailInfo.value.iztrueorfalse = param.iztrueorfalse;
}
function forwardHandler() {
workStore.forward();
}
function backHandler() {
workStore.back();
}
function reset() {
taskpagination.pageNo = 0;
taskpagination.pageSize = 20;
listData.value.length = 0;
loading.value = false;
canloadMore = true;
}
async function refreshHandler() {
reset();
useInfiniteScroll(
el as any,
() => {
loadMore();
},
{ distance: 10, canLoadMore: () => canloadMore }
);
}
async function loadMore() {
if (loading.value || el.value == null) return;
const more = await featchList();
listData.value.push(...more);
}
async function featchList() {
loading.value = true;
try {
taskpagination.pageNo += 1;
const { data, total, pageCount } = await getTaskDetailPictureList(
{ ...taskpagination, ...sortBy ,checkDuplicateId: workStore.activeId}
);
totalCount.value = total;
canloadMore = pageCount >= taskpagination.pageNo && pageCount > 0;
return data;
} catch (error) {
debugger
canloadMore = false;
return [];
}
}
const layout = debounce(() => {
if (el.value == null) return;
_imagesload = imagesloaded(".grid-item");
_imagesload.on("done", (instance) => {
if (!el.value) return;
loading.value = false;
});
_imagesload.on("fail", (instance) => {
message.error("图片错误");
loading.value = false;
});
}, 300);
onUpdated(() => {
layout();
});
watch(
() => workStore.activeId,
async (newValue, oldValue) => {
const packageid = workStore.getActiveId;
if (isEmpty(packageid)) {
listData.value.length = 0;
totalCount.value = 0;
return;
}
queryDetail(packageid)
// 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]?.name || "";
});
async function queryDetail(checkDuplicateId: any) {
taskDetailInfo.value = await getTaskDetailInfo(checkDuplicateId);
const packageid = workStore.getActiveId;
if (isEmpty(packageid)) {
listData.value.length = 0;
totalCount.value = 0;
return;
}
refreshHandler();
}
async function handleSelect(item: any) {
taskDetailInfo.value = await getTaskDetailInfo( workStore.activeId);
const packageid = workStore.getActiveId;
if (isEmpty(packageid)) {
listData.value.length = 0;
totalCount.value = 0;
return;
}
refreshHandler();
}
async function sortHandler(orderby: "similarityScore" | "createdate") {
sortBy.orderName = orderby;
sortBy.orderType = sortBy.orderType === "asc" ? "desc" : "asc";
refreshHandler();
}
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 (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;
}
onUnmounted(() => {
workStore.reset();
});
function getPercent(pictureid: string) {
const { ocpictureid, pictureresult } = taskDetailInfo.value;
const index = ocpictureid ? ocpictureid.split(",").indexOf(String(pictureid)):'';
const results = pictureresult ? pictureresult.split(",") : '';
const percent = results[index] || "0";
const val = Math.floor(Number.parseFloat(percent));
return `${val}%`;
}
const mark = computed(() => {
return taskDetailInfo.value.iztrueorfalse === null ? "未标记" : "已标记";
});
function immersionHandler() {
workStore.updateImmersion();
}
function showAction() {
const item = taskDetailInfo.value;
if (batch.value === false) overTask.value = item;
}
function hideAction() {
overTask.value = null;
}
function previewHandler(event: MouseEvent) {
event.stopImmediatePropagation();
event.stopPropagation();
if (imageRef.value && (imageRef.value as any).src)
(imageRef.value as any).mergedOnClick();
}
</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>
<n-spin :show="loading">
<div ref="el" class="scroll">
<div class="wrapper-detail">
<div
class="left"
:style="{ 'background-image': `url(${taskDetailInfo?.imgurl})` }"
@click="showAction"
@mouseleave="leaveTaskHandler"
>
<!-- 真假标记 -->
<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="time">
<SvgIcon color="#FFF" size="16" name="time" />
<span>{{ taskDetailInfo.createTime }} </span>
</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 class="preview" @click="previewHandler">
<SvgIcon size="30" name="zoom-out" />
</div>
<!-- 预览大图组件 -->
<div style="display: none">
<n-image
ref="imageRef"
:img-props="{ onClick: hideDownload }"
:src="taskDetailInfo?.imgurl"
/>
</div>
<!-- 操作 -->
<div
v-show="overTask && overTask.id === taskDetailInfo.id"
class="action"
@click.stop="hideAction"
>
<SvgIcon style="cursor: pointer" name="t1" @click.stop="trueHandler" />
<SvgIcon
style="cursor: pointer; margin-left: 30px"
name="t2"
@click.stop="falseHandler"
/>
</div>
</div>
<div class="right">
<n-scrollbar style="max-height: 100%">
<span class="name">任务ID{{taskDetailInfo.taskname}}</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=" color: #666666"
>图片大小
</span>
<span style=" color: #333333; font-size: 16px"
>{{ (taskDetailInfo?.pictureInfo?.imgSize /1000).toFixed(2) }}KB</span
>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>图片格式
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.pictureInfo?.imgSpace }}</span
>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>图片尺寸
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.pictureInfo?.imgMeasure }}</span
>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>色彩空间
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.pictureInfo?.imgMeasure }}</span
>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>提报人
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.pictureInfo?.source || "-" }}</span
>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>创建时间
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.pictureInfo && format(taskDetailInfo?.pictureInfo?.createTime, 'yyyy-MM-dd HH:mm:ss')}}</span>
</div>
<div class="property">
<span class="property-name top" style=" color: #666666"
>提报时间
</span>
<span style=" color: #333333; font-size: 16px"
>{{ taskDetailInfo?.uploadTime && format(taskDetailInfo?.pictureInfo?.uploadTime, 'yyyy-MM-dd HH:mm:ss') || '-'}}</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>({{ totalCount }})</span>
</div>
<div style="display: flex; align-items: center">
<div style="cursor: pointer" @click="sortHandler('createdate')">
<span>按时间排序</span>
<SvgIcon style="margin-left: 8px" name="sort" size="12" />
</div>
<div
style="margin-left: 15px; cursor: pointer"
@click="sortHandler('similarityScore')"
>
<span>相似度排序</span>
<SvgIcon style="margin-left: 8px" name="sort" size="12" />
</div>
</div>
</div>
<div class="wrapper-list">
<div
v-for="(item, index) in listData"
:key="index"
:class="{ 'item-selected': item === selectTask }"
class="grid-item"
@click="handleSelect(item)"
@mouseover="overTaskHandelr(item)"
@mouseleave="leaveTaskHandler"
>
<div
class="img-wrapper"
:style="{ 'background-image': `url(${item.imgurl})` }"
/>
<div class="time">
<SvgIcon color="#FFF" size="16" name="time" />
<span>{{ formatToDateHMS(item.createdate || 0) }}</span>
</div>
<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="trueHandler" />
<SvgIcon style="cursor: pointer" name="t2" @click.stop="falseHandler" />
</div>
</div>
</div>
</div>
</n-spin>
<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);
.scroll {
height: calc(100vh - 88px - 72px);
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;
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;
.preview {
position: absolute;
z-index: 3;
right: 10px;
top: 10px;
width: 50px;
height: 50px;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.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: 60px;
opacity: 0.44;
background: rgba(216, 216, 216, 0.4);
border-radius: 7px;
}
.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);
}
}
.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 {
}
.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;
}
.grid-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;
font-size: 12px;
font-family: PingFang SC, PingFang SC-Semibold;
font-weight: Semibold;
text-align: left;
color: #ffffff;
line-height: 24px;
}
}
.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;
}
.time {
position: absolute;
z-index: 3;
left: 5%;
bottom: 5%;
color: #fff;
display: flex;
align-items: center;
}
</style>