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

2206 lines
55 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden 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,
nextTick,
} from "vue";
import { chunk, clone } from "lodash-es";
import { useDialog, useMessage } from "naive-ui";
import { useRoute, useRouter } 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 { useFinal } from "@/store/modules/final";
import { useInfiniteScroll } from "@vueuse/core";
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 router = useRouter();
const loading = ref(false);
const batch = ref(false);
const batchtwo = ref(false);
const selectItems = ref<any[]>([]);
const message = useMessage();
const dialog = useDialog();
const notPassModalRef = ref(null);
const batchModalRef: any = 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 isDetail = ref(false); // 是否是详情
const finalStore = useFinal();
const imgbigshow = ref(true);
const sortBy: any = {
orderType: "desc",
orderName: "similarityScore",
};
const el = ref<HTMLDivElement | null>(null);
const pagination = reactive({
pageNo: 0,
pageSize: 30,
});
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: 30,
});
const taskStore = useTask();
const overTask = ref<any>(null);
const overTasktwo = ref<any>(null);
const taskDetailInfo = ref<any>({});
const taskDetailPictureList = ref<any[]>([]);
const userStore = useUser();
const imageRef = ref<ComponentElRef | null>();
let processItems: any[] = [];
const isFullScreen = ref(false);
const fullscreenStyles = computed<any>(() => ({
width: isFullScreen.value ? "100vw" : "",
height: isFullScreen.value ? "100vh" : "",
position: isFullScreen.value ? "fixed" : "",
top: isFullScreen.value ? "0" : "",
left: isFullScreen.value ? "0" : "",
zIndex: isFullScreen.value ? "100" : "",
}));
onMounted(() => {
window.addEventListener("keydown", handleKeydown);
if (route.query.id) {
taskId.value = route.query.id;
packageId.value = route.query.packageid;
isDetail.value = true;
getDetail();
}
});
let lastKeyPressTime = 0;
let keyPressTimer = null;
function changeimgbigshow() {
imgbigshow.value = !imgbigshow.value;
}
const doubleClickInterval = 300; // 可以自定义间隔时间,单位是毫秒
// 键盘左右箭头快捷切换
function handleKeydown(event) {
if (event.key === "ArrowLeft") backHandler();
// 在这里执行左箭头的逻辑
else if (event.key === "ArrowRight") forwardHandler();
// 在这里执行右箭头的逻辑
else if (event.keyCode === 67) {
isFullScreen.value = false;
// batchModalRef.value.closeModal()
} else if (event.keyCode === 27) {
overTask.value = null;
overTasktwo.value = null;
} else if (event.key === "p" || event.key === "P") {
// 获取当前时间
const now = Date.now();
// 如果两次按键时间间隔小于我们设定的双击间隔,则认为是双击
if (now - lastKeyPressTime < doubleClickInterval) {
// 清除已经设置的定时器(如果有的话)
clearTimeout(keyPressTimer);
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;
// 执行想要的操作
approvalHandler();
// 重置上次按键时间
lastKeyPressTime = 0;
} else {
// 如果不是双击,则更新上次按键时间,并开始一个新的计时器
lastKeyPressTime = now;
// 通过定时器重置上次按键时间
// 这可以防止如果用户只按了一次键,也会触发双击的情况
clearTimeout(keyPressTimer);
keyPressTimer = setTimeout(function () {
lastKeyPressTime = 0;
}, doubleClickInterval);
}
} else if (event.key === "x" || event.key === "X") {
// 获取当前时间
const now = Date.now();
// 如果两次按键时间间隔小于我们设定的双击间隔,则认为是双击
if (now - lastKeyPressTime < doubleClickInterval) {
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;
// 执行想要的操作
const modal = unref(notPassModalRef)! as any;
modal.showModal([taskDetailInfo.value]);
// 重置上次按键时间
lastKeyPressTime = 0;
} else {
// 如果不是双击,则更新上次按键时间,并开始一个新的计时器
lastKeyPressTime = now;
// 通过定时器重置上次按键时间
// 这可以防止如果用户只按了一次键,也会触发双击的情况
clearTimeout(keyPressTimer);
keyPressTimer = setTimeout(function () {
lastKeyPressTime = 0;
}, doubleClickInterval);
}
}
}
function setBatch(value: boolean) {
// if (totalCount.value === 0)
// return
batch.value = value;
if (value === false) {
taskDetailInfo.value.checked = false;
selectItems.value = [];
taskDetailPictureList.value.forEach((item) => {
item.checked = false;
});
}
}
// 从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;
}
console.log(processItems);
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);
overTask.value = null;
},
onNegativeClick: () => {
overTask.value = null;
},
});
}
function approvalHandlerx(item?: any) {
console.log(item);
alert(1);
}
function singleRejectHandlex(item?: any) {
console.log(item);
const modal = unref(notPassModalRef)! as any;
modal.showModal([item]);
}
function rejectHandler(items?: any) {
console.log(items);
const modal = unref(notPassModalRef)! as any;
modal.showModal(items);
}
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(res.message);
setBatch(false);
reloadList(param, "通过");
batchModalRef.value.reload();
}
});
}
function showModal(modalRef: any) {
const modal = unref(modalRef)! as any;
modal.showModal(taskId.value);
}
function forwardHandler() {
taskStore.forward();
}
function backHandler() {
taskStore.back();
}
async function handleDragEnd(event, item) {
console.log(event, item);
// 可以在这里添加拖拽结束后的逻辑
const flag = taskStore.getInFile;
if (flag) {
const res = await dubiousfileyd({ pictureid: item.pictureId });
if (res.code === "OK") {
message.success("加入成功");
setBatch(false);
getTableData();
getImgList();
} else {
message.error(res.message);
}
taskStore.setInFile(false);
taskStore.setInFile(item.pictureId);
}
}
async function addSuspicious() {
taskStore.setInFileId(taskDetailInfo.value.ocrPicture);
console.log(taskDetailInfo.value.ocrPicture);
if (taskDetailInfo.value.ocrPicture.pictureid) {
const res = await dubiousfileyd({
pictureid: taskDetailInfo.value.ocrPicture.id,
});
if (res.code === "OK") {
message.success("加入成功");
setBatch(false);
getTableData();
getImgList();
} 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)) {
let locationobj = { address: "" };
if (v.name == "location") {
locationobj = JSON.parse(taskDetailInfo.value.ocrPicture[v.name]);
}
const item = {
label: v.fieldDesc,
value:
v.name == "location"
? locationobj.address
: 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;
overTasktwo.value = null;
}
function showActionsModal() {
const modal = unref(CustomSettingModalRef)! as any;
modal.showModal();
}
onUnmounted(() => {
taskStore.reset();
window.removeEventListener("keydown", handleKeydown);
});
function immersionHandler() {
// taskStore.updateImmersion()
toggleFullScreen();
}
// 切换全屏状态
function toggleFullScreen() {
isFullScreen.value = !isFullScreen.value;
if (isFullScreen.value) {
fetchData();
window.addEventListener("scroll", checkBottom);
} else {
window.removeEventListener("scroll", checkBottom);
}
}
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 notPassSuccess(param) {
batchModalRef.value.reload();
reloadList(param, "不通过");
overTask.value = null;
}
function reloadList(param, text) {
// 修改左侧状态
const id = currentTaskId();
const hasCurrentId = param.flowTaskInfoList.find(
(item) => item.formId === id
);
finalStore.setListKey();
if (hasCurrentId) emit("setAsideItemName", text);
getDetail();
}
function goBack() {
router.back();
}
function switchBatch() {
setBatch(!batch.value);
}
function getrowValue(e) {
if (e.key == "location") {
let locationobj = JSON.parse(taskDetailInfo.value.ocrPicture.location);
let lat = locationobj.lat.$numberDecimal;
let lng = locationobj.lng.$numberDecimal;
let name = e.value;
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const hostWithPort = protocol + "//" + hostname + ":" + port + "/";
window.open(
hostWithPort + "map" + "?name=" + name + "&lat=" + lat + "&lng=" + lng
);
}
}
function overTaskHandelr(item: any) {
if (item?.historyStates === 2 || item?.historyStates == 3) {
overTasktwo.value = null;
return;
}
if (validate([item]) == null && batchtwo.value === false)
overTasktwo.value = item;
}
function closePassno() {
console.log(notPassModalRef.value)
overTask.value = null;
}
//const loadingx = ref(false);
const items = ref([]);
const scrollContainer = ref(null);
let debounceTimer;
async function fetchData() {
if (loading.value) {
return; // 如果已经在加载中,则不重复加载
}
loading.value = true;
try {
loadMore();
} finally {
loading.value = false;
}
}
//const throttledCheckScroll = throttle(checkBottom, 200);
let num = 1;
// 检查是否滚动到底部
function checkBottom() {
const container = scrollContainer.value;
// console.log(1)
if (!container) {
return;
}
//const { scrollTop, clientHeight, scrollHeight } = container;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// 获取视口的内部高度
const clientHeight =
window.innerHeight || document.documentElement.clientHeight;
// 获取整个文档的高度
const scrollHeight = document.documentElement.scrollHeight;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (scrollTop + clientHeight >= scrollHeight - 10) {
num = num + 1;
console.log(num);
fetchData(); // 接近底部时加载更多数据
}
}, 500);
}
async function loadMore() {
console.log(loading.value);
if (loading.value) {
const more = await fetchList();
taskDetailPictureList.value.push(...more);
console.log(taskDetailPictureList);
}
}
let canloadMore = true;
async function fetchList() {
try {
pagination.pageNo += 1;
const { data, pageCount, total } = await getSimilarityList({
...pagination,
...sortBy,
pictureId: taskDetailInfo.value.ocrPicture.id,
});
canloadMore = pageCount >= pagination.pageNo && pageCount > 0;
totalCount.value = total;
return data;
} catch (error) {
canloadMore = false;
return [];
}
}
async function reset() {
pagination.pageNo = 0;
pagination.pageSize = 30;
taskDetailPictureList.value.length = 0;
loading.value = false;
canloadMore = true;
// layout()
}
async function refreshHandler() {
getImgList();
}
function sortHandler(orderby: "similarityScore" | "createdate") {
sortBy.orderName = orderby;
sortBy.orderType = sortBy.orderType === "asc" ? "desc" : "asc";
refreshHandler();
}
</script>
<template>
<div
ref="scrollContainer"
@scroll="checkBottom"
class="wrapper fullscreen-container"
:style="fullscreenStyles"
>
<div class="wrapper-header">
<div class="left">
<span class="font">任务ID{{ taskDetailInfo.fromtaskname }}</span>
<template v-if="!isDetail">
<SvgIcon
size="22"
class="forward"
name="arrow-left"
@click="backHandler"
/>
<SvgIcon
size="22"
class="back"
name="arrow-right"
@click="forwardHandler"
/>
</template>
</div>
<div v-if="!isDetail" class="right">
<div v-show="!showActions" style="display: flex; align-items: center">
<div class="btn" @click="switchBatch()">
<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
v-if="isFullScreen"
size="20"
name="power-off"
style="cursor: pointer"
@click="immersionHandler"
/>
<SvgIcon
v-else
size="20"
name="immersion-model"
style="cursor: pointer"
@click="immersionHandler"
/>
</div>
</div>
<div v-show="showActions" class="batch">
<n-button text @click="switchBatch()">
<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(selectItems)"
/>
<SvgIcon size="24" name="vs" />
<img
class="btn-approval"
src="@/assets/images/task/btn-pass.png"
alt=""
@click.stop="approvalHandler"
/>
</div>
</div>
<div v-else class="right">
<n-button text @click="goBack">
<template #icon>
<SvgIcon name="revoke" />
</template>
返回
</n-button>
</div>
</div>
<div
class="wrapper-detail"
:style="
isFullScreen
? {
height: '596px',
}
: {}
"
>
<div
v-show="!imgbigshow"
class="aside-collapse-btn"
@click="collapseHandler"
:style="{
position: 'fixed',
right: '0px',
top: '155px',
cursor: 'pointer',
zIndex: 100,
}"
>
<SvgIcon
:name="false ? 'expand-cir' : 'collapse-cir'"
size="40"
@click="changeimgbigshow"
/>
</div>
<div
class="left"
:style="
isFullScreen
? imgbigshow
? {
position: 'relative',
flex: 2,
'background-image': `url(${taskDetailInfo?.ocrPicture?.imgurl})`,
}
: {
height: '92vh',
flex: 2,
'background-image': `url(${taskDetailInfo?.ocrPicture?.imgurl})`,
}
: {
'background-image': `url(${taskDetailInfo?.ocrPicture?.imgurl})`,
}
"
@mouseover="overTaskHandle"
@mouseleave="leaveTaskHandler"
@click="previewHandler"
>
<div v-show="overTask" class="action">
<SvgIcon
style="cursor: pointer"
name="t1"
size="74.95"
@click.stop="approvalHandler"
/>
<SvgIcon
style="cursor: pointer; margin-left: 32px"
name="t2"
size="74.95"
@click.stop="singleRejectHandler"
/>
<SvgIcon
style="cursor: pointer; margin-left: 32px"
name="t9"
size="74.95"
@click.stop="addSuspicious"
/>
</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 ?? "-"
}}</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 class="leftback"></div>
</div>
<div
class="right"
v-show="imgbigshow"
:style="
isFullScreen
? {
width: '320px',
borderLeft: '2px solid #507AFD',
position: 'relative',
}
: {}
"
>
<div
v-show="isFullScreen"
class="aside-collapse-btn"
@click="collapseHandler"
:style="{
position: 'absolute',
left: '-20px',
top: '85px',
cursor: 'pointer',
}"
>
<SvgIcon
:name="true ? 'expand-cir' : 'collapse-cir'"
size="40"
@click="changeimgbigshow"
/>
</div>
<n-scrollbar v-if="totalCount > 0" style="max-height: 100%">
<div class="right-card">
<div class="header" v-show="!isFullScreen">
<span>相似图片({{ totalCount }})</span>
<SvgIcon
style="margin-right: 20px; cursor: pointer"
name="max"
size="24"
@click="showModal(batchModalRef)"
/>
</div>
<div class="list" v-if="!isFullScreen">
<div
v-for="item in taskDetailPictureList"
:key="item.id"
class="item"
draggable="true"
@dragend="
(event) => {
handleDragEnd(event, item);
}
"
>
<div
class="img-wrapper"
:style="{
'background-image': `url(${item.serverThumbnailUrl})`,
}"
/>
<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>
<n-scrollbar v-else style="max-height: 100%">
<n-ellipsis style="">
{{ taskDetailInfo.fromtaskname }}
</n-ellipsis>
<div class="tags">
<div
class="tag tag-submiting"
v-if="taskDetailInfo?.userapprove?.statshis == 1"
>
待审批
</div>
<div
class="tag tag-submited"
v-else-if="
taskDetailInfo?.userapprove?.statshis == 2 ||
taskDetailInfo?.userapprove?.statshis == 3
"
>
已审批
</div>
<div
class="tag tag-passed"
v-if="taskDetailInfo?.userapprove?.statshis == 2"
>
{{ TASK_STATUS_OBJ[taskDetailInfo?.userapprove?.statshis] }}
</div>
<div
class="tag tag-not-passed"
v-else-if="taskDetailInfo?.userapprove?.statshis == 3"
>
{{ TASK_STATUS_OBJ[taskDetailInfo?.userapprove?.statshis] }}
</div>
</div>
<n-divider class="divider-line" />
<div>
<div v-for="(item, index) in taskTableData" :key="index">
<div
v-if="item[0].label == '拜访终端名称'"
:style="{ display: 'flex', height: '26px' }"
>
<div
:style="{
width: '100px',
borderRight: '2px solid',
marginTop: '4px',
}"
>
{{ item[0].label }}
</div>
<div :style="{ marginLeft: '15px', fontSize: '16px' }">
{{ item[0].value }}
</div>
</div>
<div
class="viewlabel"
v-if="!(item[0].label == '拜访终端名称')"
>
{{ item[0].label }}
</div>
<div
class="viewvalue"
v-if="!(item[0].label == '拜访终端名称')"
>
{{ item[0].value }}
</div>
<div
class="viewlabel"
v-if="item[1] && !(item[1].label == '拜访终端名称')"
>
{{ item[1].label }}
</div>
<div
class="viewvalue"
v-if="item[1] && !(item[1].label == '拜访终端名称')"
>
{{ item[1].value }}
</div>
</div>
</div>
</n-scrollbar>
</div>
</n-scrollbar>
<n-empty v-else description="暂无数据" />
</div>
</div>
<n-tabs type="line" animated v-if="!isFullScreen">
<n-tab-pane name="task-info" tab="任务信息">
<TaskTable
:data="taskDetailInfo"
:task-table-data="taskTableData"
@show-modal="showActionsModal"
@getrow-value="getrowValue"
/>
</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>
<div v-else class="imgbottom">
<div
v-show="imgbigshow"
style="
display: flex;
justify-content: space-between;
padding: 12px 0px 3px 0;
"
>
<div>
<span
style="
font-size: 18px;
font-weight: Medium;
color: #333333;
font-family: PingFang SC, PingFang SC-Medium;
"
>相似图片({{ totalCount }})</span
>
</div>
<div
style="
display: flex;
align-items: center;
font-size: 14px;
margin-right: 25px;
color: #323233;
"
>
<div style="cursor: pointer" @click="sortHandler('createdate')">
<span>时间排序</span>
<SvgIcon
v-if="true"
style="margin-left: 5px"
name="sort"
size="12"
/>
<SvgIcon
v-else
style="margin-left: 5px"
name="active-sort"
size="12"
/>
</div>
<div
style="margin-left: 15px; cursor: pointer"
@click="sortHandler('similarityScore')"
>
<span>相似度排序</span>
<SvgIcon
v-if="true"
style="margin-left: 5px"
name="sort"
size="12"
/>
<SvgIcon
v-else
style="margin-left: 5px"
name="active-sort"
size="12"
/>
</div>
</div>
</div>
<div class="allview" v-show="imgbigshow">
<div class="list">
<div
v-for="item in taskDetailPictureList"
:key="item.id"
class="item"
draggable="true"
@dragend="
(event) => {
handleDragEnd(event, item);
}
"
>
<div
class="img-wrapper"
:style="{
'background-image': `url(${item.serverThumbnailUrl})`,
}"
@mouseover="overTaskHandelr(item)"
@mouseleave="leaveTaskHandler"
>
<div
v-show="overTasktwo && overTasktwo.id === item.id && !batch"
class="action"
>
<SvgIcon
style="cursor: pointer"
name="t1"
@click.stop="approvalHandler([item])"
/>
<SvgIcon
style="cursor: pointer; margin-left: 40px"
name="t2"
@click.stop="singleRejectHandlex(item)"
/>
</div>
</div>
<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="16"
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="16"
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>
<!-- <div class="wrapper-list">
<div
v-for="(item, index) in taskDetailPictureList"
:key="index"
class="grid-item"
@click="handleSelect(item)"
@mouseover="overTaskHandelr(item)"
@mouseleave="leaveTaskHandler"
>
<div
class="img-wrapper"
:style="{ 'background-image': `url(${item.serverThumbnailUrl})` }"
/>
<div class="time-wrapper">
<div class="time">
<SvgIcon color="#FFF" size="16" name="camera" />
<span class="current-time">{{
item.photoDateTimestamp
? formatToDateHMS(Number(item.photoDateTimestamp) || 0)
: "-"
}}</span>
</div>
<div class="time">
<SvgIcon color="#FFF" size="16" name="save" />
<span class="current-time">{{
item.submitDateTimestamp
? formatToDateHMS(Number(item.submitDateTimestamp) || 0)
: "-"
}}</span>
</div>
</div>
<div class="check">
<n-checkbox
v-show="
batchtwo && item.historyStates !== 2 && item.historyStates !== 3
"
v-model:checked="item.checked"
@click.stop
@update:checked="onCheckChange($event, item)"
/>
</div>
<div
class="percent"
:class="{ 'percent-red': item?.maxSimilarity >= 100 }"
>
<div class="val">
{{
item?.similarityScore &&
Number(item?.similarityScore).toFixed(0)
}}<span class="percent-unit">%</span>
</div>
</div>
<div class="pass-status" v-if="item.historyStates === 2">
<SvgIcon name="pass-icon" style="width: 52; height: 24px" />
</div>
<div class="pass-status" v-else-if="item.historyStates === 3">
<SvgIcon name="no-pass-icon" style="width: 52; height: 24px" />
</div>
<div
v-show="overTasktwo && overTasktwo.id === item.id"
class="action"
@mouseover="overTaskHandelr"
@mouseleave="leaveTaskHandler"
>
<SvgIcon
style="cursor: pointer"
name="t1"
@click.stop="approvalHandlerx(item)"
/>
<SvgIcon
style="cursor: pointer; margin-left: 40px"
name="t2"
@click.stop="singleRejectHandlex(item)"
/>
</div>
</div>
</div>-->
</div>
<NotPassed
ref="notPassModalRef"
@success="notPassSuccess"
@close="closePassno"
/>
<BatchModal
ref="batchModalRef"
@reject="rejectHandler"
@approval="approvalHandler"
/>
<CustomSettingModal ref="CustomSettingModalRef" @on-ok="getDetail" />
</div>
</template>
<style lang="less" scoped>
.fullscreen-container {
overflow-y: auto;
/* 可添加其他样式 */
width: 100vw;
height: 100vh;
margin: 0px !important;
max-height: 100%;
}
::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: 892px;
overflow-y: scroll;
// 62+82+16
// height: 100%;
// overflow-y: scroll;
&::-webkit-scrollbar-track {
background-color: #fff; /* 轨道背景颜色 */
display: none;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.25); /* 滑块颜色 */
:hover {
background-color: rgba(0, 0, 0, 0.4);
}
border-radius: 5px; /* 滑块边角半径 */
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: #555; /* 悬停时的滑块颜色 */
}
&-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.2);
border-radius: 6px;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.big-mark {
width: 100%;
height: 210px;
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(0, 0, 0, 0.5);
}
.info {
position: absolute;
z-index: 3;
right: 16px;
bottom: 16px;
width: 136px;
height: 119px;
background: rgba(216, 216, 216, 0.1);
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;
}
}
/* .leftback {
position: absolute;
width: 100%;
height: 151px;
bottom: 0;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 29%,
rgba(3, 0, 0, 0.73)
);
}*/
}
.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;
}
}
}
.imgbottom {
.header {
padding: 12px 0px 3px;
font-size: 18px;
color: #333333;
margin-bottom: 16px;
}
}
.wrapper-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 5px;
position: relative;
.img-wrapper {
width: 230px;
height: 130px;
overflow: hidden;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 8px;
}
.time-wrapper {
position: absolute;
bottom: 9px;
width: calc(100% - 11px);
height: 58px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.01),
rgba(0, 0, 0, 0.71) 100%
);
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
font-weight: Medium;
padding-left: 12px;
color: white;
.time .current-time {
margin-left: 9px;
font-weight: Medium;
}
}
.check {
position: absolute;
z-index: 3;
left: 12px;
top: 12px;
}
.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: 15px;
color: #fff;
font-size: 14px;
}
.percent-unit {
font-size: 8px;
margin-top: 4px;
}
.percent-red {
background: #ff4e4f;
}
.pass-status {
position: absolute;
left: 0px;
top: 15px;
}
.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: 4px;
top: 9px;
width: 230px;
height: 130px;
display: flex;
border-radius: 8px;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.48);
}
}
}
.tags {
display: flex;
margin-top: 8px;
}
.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-submited {
color: #507afd;
border: 1px solid #507afd;
}
.tag-submiting {
color: #ffb800;
border: 1px solid #ffb800;
}
.tag-passed {
color: #02c984;
border: 1px solid #02c984;
}
.tag-not-passed {
color: #ff7575;
border: 1px solid #ff7575;
}
.tag-approved {
color: #507afd;
border: 1px solid #507afd;
}
}
.viewlabel {
margin-top: 10px;
font-size: 14px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: Regular;
text-align: left;
color: #999999;
line-height: 20px;
height: 20px;
}
.viewvalue {
font-size: 14px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: Regular;
text-align: left;
color: #333333;
line-height: 20px;
height: 100%;
margin: 3px 0 10px 0;
max-width: 650px;
}
.allview {
// overflow-y: auto;
//height: 400px;
flex: 0.5;
background: #f6f9fd;
border-radius: 8px;
// margin-left: 20px;
padding-top: 24px;
.action {
position: absolute;
z-index: 10;
width: 100%;
height: 100%;
display: flex;
border-radius: 8px;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
}
.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: 53px;
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: 16px;
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 {
position: relative;
width: 230px;
height: 130px;
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>