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/layout/components/Header/RecycleModal.vue

984 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 Masonry from 'masonry-layout'
import { useDialog, useMessage } from 'naive-ui'
import { computed, nextTick, onBeforeMount, onMounted, onUpdated, reactive, ref, unref, watch } from 'vue'
import { dubiousfilelist, removeFiles } from '@/api/task/task'
import { viewOptions } from '@/config/home'
import { useInfiniteScroll } from '@vueuse/core'
import imagesloaded from 'imagesloaded'
import { debounce, cloneDeep } from 'lodash-es'
import { audit } from '@/api/task/task'
import NotPassed from '@/components/Approval/NotPassed.vue'
import { formatToDateHMS } from '@/utils/dateUtil'
import { off, on } from '@/utils/domUtils'
import ConfrimModal from "./ConfrimModal.vue"
const cardStyle = {
'--n-padding-bottom': '40px',
'--n-padding-left': '120px',
}
let startTime = 0;
let endTime = 0;
let startCalTime = false;
const message = useMessage()
const timeRange = ref('desc')
const similarRange = ref('desc')
const batch = ref(false)
const imageRef = ref<ComponentElRef | null>();
let filterId = null;
let sortObj: any = {
orderByUptime: 'desc'
};
const timeOptions = [{
label: '升序',
value: 'asc',
}, {
label: '降序',
value: 'desc',
}]
// const similarOptions = [{
// label: '升序',
// value: 'asc',
// }, {
// label: '降序',
// value: 'desc',
// }]
const timeLabel = computed(() => {
const item = timeOptions.find((option) => {
return option.value === timeRange.value
})
return item?.label
})
const viewMode = ref('horizontalVersion')
const viewLabel = computed(() => {
const item = viewOptions.find((option) => {
return option.value === viewMode.value
})
return item?.label
})
const maxHeight = computed(() => {
let height = "800";
const screenWidth = window.screen.width;
if(screenWidth <= 1920) {
height = "600";
}
return height + 'px';
})
const masonryRef = ref<ComponentRef>(null)
const el = ref<HTMLDivElement | null>(null)
const elwc = ref<HTMLDivElement | null>(null)
const listData = ref<any[]>([])
const pagination = reactive({
pageNo: 1,
pageSize: 30,
})
let loading = false
let _masonry: null | Masonry = null
const show = ref(false)
const selectedApproveItems = ref<any[]>([]); // 审核批量选中
const dialog = useDialog();
let processItems: any[] = [];
const confrimModalRef = ref<any>();
const layout = debounce(() => {
if (!show.value)
return
if (_masonry !== null)
(_masonry as any).destroy()
_masonry = new Masonry(masonryRef.value as any, {
itemSelector: '.grid-item',
columnWidth: 214,
percentPosition: true,
stagger: 10,
})
imagesloaded('.grid-item', () => {
(_masonry as any).layout()
const scrollHeight = el.value!.scrollHeight
const clientHeight = el.value!.clientHeight
const top = scrollHeight - clientHeight - 20
el.value!.scrollTo({ top, behavior: 'instant' })
loading = false
console.log("loading---------------", loading);
})
}, 300)
watch(viewMode, () => {
layout()
})
onBeforeMount(async () => {
})
let canloadMore = true
useInfiniteScroll(
el as any,
() => {
loading = false
console.log("加载了000000000000000---------------------------");
loadMore()
},
{ distance: 10, canLoadMore: () => canloadMore },
)
async function featchList() {
loading = true
try {
// const result = await dubiousfilelist({ ...pagination, orderbyname: timeRange.value })
console.log("pagination.pageNo------------", pagination.pageNo);
const result = await dubiousfilelist({ ...pagination, ...sortObj })
// TODO测试数据
// result.data = Array.from({ length: 30 })
const { data, pageCount } = result
// canloadMore = pageCount >= pagination.pageNo && pageCount > 0;
canloadMore = data.pages >= pagination.pageNo && data.pages > 0;
console.log("canloadMore------------", canloadMore);
return result.data.records
// const list = data.map((item) => {
// return {
// imgUrl: randomUrl(),
// }
// })
// return list
}
catch (error) {
canloadMore = false;
return []
}
}
async function loadMore() {
console.log("执行l------------------------", loading, el.value, pagination.pageNo);
if (loading || el.value == null)
return
// loading = true
pagination.pageNo = pagination.pageNo + 1;
const more = await featchList()
console.log("more------------------------", more);
listData.value.push(...more)
layout()
}
onUpdated(() => {
if(startCalTime) {
endTime = new Date().getTime();
if(endTime - startTime > 500) {
createDom()
}
}else {
removeDom()
}
// if(elwc.value) {
// on(elwc.value!, 'click', showjjj)
// }
layout()
})
let start: { x: number, y: number } | null = null
let selectionBox: HTMLDivElement | null
const selectIds = ref<string[]>([])
function downHandler(event: MouseEvent) {
startTime = new Date().getTime();
startCalTime = true;
if (!selectionBox)
return
const classname = (event.target as any).className
if (!classname.includes('checkbox')) {
selectIds.value.length = 0
start = { x: event.clientX, y: event.clientY }
selectionBox.style.width = '0'
selectionBox.style.height = '0'
selectionBox.style.left = `${start.x}px`
selectionBox.style.top = `${start.y}px`
selectionBox.style.display = 'block'
selectionBox.style.zIndex = '9999'
}
}
function imUpdateSelectIds(x: number, y: number, w: number, h: number) {
const items = document.querySelectorAll('.grid-item')
items.forEach((item: HTMLDivElement) => {
const rect = item.getBoundingClientRect()
const index = selectIds.value.indexOf(item.dataset.id!)
if (rect.right > x && rect.bottom > y && rect.left < x + w && rect.top < y + h)
index === -1 && selectIds.value.push(item.dataset.id!)
else index !== -1 && selectIds.value.splice(index, 1)
// let tempApproveIndex = -1;
// selectedApproveItems.value.map((approveItem, approveIndex) => {
// console.log("approveItem and item", approveItem, item );
// if(approveItem.selfId == item.dataset.id) {
// tempApproveIndex = approveIndex;
// }
// })
// if (tempApproveIndex === -1 && item.className.indexOf('grid-item-selected') != -1) {
// let tempItem: any = cloneDeep(item);
// tempItem.selfId = item.dataset.id;
// selectedApproveItems.value.push(tempItem)
// } else {
// selectedApproveItems.value.splice(tempApproveIndex, 1);
// }
})
selectedApproveItems.value = [];
// 选中复选框
listData.value.map(item => {
if(selectIds.value.includes(String(item.pictureId))) {
item.checked = true;
selectedApproveItems.value.push(item);
}
});
}
function isSelected(pictureId: number) {
return selectIds.value.includes(String(pictureId))
}
function moveHandler(e: MouseEvent) {
if(startCalTime) {
endTime = new Date().getTime();
if(endTime - startTime > 500) {
createDom()
}
}else {
removeDom()
}
if (!selectionBox || !start)
return
const x = Math.min(e.clientX, start.x)
const y = Math.min(e.clientY, start.y)
const w = Math.abs(e.clientX - start.x)
const h = Math.abs(e.clientY - start.y)
selectionBox.style.width = `${w}px`
selectionBox.style.height = `${h}px`
selectionBox.style.left = `${x}px`
selectionBox.style.top = `${y}px`
imUpdateSelectIds(x, y, w, h)
}
function createDom() {
let dom = document.createElement('div');
dom.className = "selection-box";
dom.style.width = "1px";
dom.style.border = "1px dotted black";
dom.style.position = "absolute";
dom.style.display = "block";
dom.style.zIndex = '9999';
document.body.appendChild(dom);
selectionBox = document.querySelector('.selection-box') as HTMLDivElement
}
function removeDom() {
let dom = document.getElementsByClassName("selection-box")[0];
if(dom) {
document.body.removeChild(dom);
}
}
function upHandler(event: MouseEvent) {
endTime = new Date().getTime();
startCalTime = false;
if(endTime - startTime > 500) {
createDom()
}else {
removeDom()
}
if (!selectionBox)
return
selectionBox.style.display = 'none'
start = null
if(selectionBox.style.width == "0px") {
removeDom()
if(event.target?.className.indexOf("n-checkbox-box__border") != -1 ) {
return
}
selectIds.value = []
selectedApproveItems.value.forEach((item) => (item.checked = false));
selectedApproveItems.value.length = 0;
listData.value.map(item => {
item.checked = false;
});
// batch.value = false;
}
}
const gridHeight = computed(() => {
// return viewMode.value !== 'masonry' ? '157px' : ''
let height = "";
if (viewMode.value === "masonry") {
height = "";
} else if (viewMode.value === "horizontalVersion") {
height = "145px";
} else if (viewMode.value === "verticalVersion") {
height = "300px";
} else if (viewMode.value === "3:4") {
height = "240px";
}
return height;
})
function addListeners() {
// selectionBox.style['z-index'] = '-1'
on(el.value!, 'mousedown', downHandler)
on(el.value!, 'mousemove', moveHandler)
on(document, 'mouseup', upHandler)
// on(elwc.value!, 'mouseup', upHandler)
}
function removeListeners() {
off(el.value!, 'mousedown', downHandler)
on(el.value!, 'mousemove', moveHandler)
on(document, 'mouseup', upHandler)
// on(elwc.value!, 'mouseup', upHandler)
}
function afterEnter() {
addListeners()
}
function afterLeave() {
removeListeners()
}
onMounted(async() => {
show.value && addListeners()
removeDom()
})
async function showModal() {
show.value = true
reset()
pagination.pageNo = 1
const list = await featchList()
listData.value = list
layout()
}
async function onChange() {
if(timeRange.value == "desc") {
timeRange.value = "asc";
}else {
timeRange.value = "desc";
}
sortObj = {
// orderbyname: val,
orderByUptime: timeRange.value
}
pagination.pageNo = 1;
canloadMore = true;
const list = await featchList()
listData.value = list
layout()
}
async function onChangeView() {
if(similarRange.value == "desc") {
similarRange.value = "asc";
}else {
similarRange.value = "desc";
}
sortObj = {
// orderbyname: val,
orderBySimilarity: similarRange.value
}
pagination.pageNo = 1;
canloadMore = true;
const list = await featchList()
listData.value = list
layout()
}
function closeModal(event: MouseEvent) {
selectedApproveItems.value = []
show.value = false
}
async function commit() {
const ids = selectIds.value.join(',')
const res = await removeFiles({ pictureid: ids })
if (res.code === 'OK') {
message.success('移除成功')
pagination.pageNo = 1
const list = await featchList()
listData.value = list
layout()
}
}
function remove() {
// console.log("finally-selectedApproveItems------------", selectedApproveItems.value);
if (!selectIds.value || selectIds.value.length === 0) {
message.error('至少选中一个')
return
}
confrimModalRef?.value?.showModal()
}
function setBatch(value) {
if (value && batch.value) {
batch.value = !value;
} else {
batch.value = value;
}
if (value === false) {
selectIds.value = []
selectedApproveItems.value.forEach((item) => (item.checked = false));
selectedApproveItems.value.length = 0;
}
}
defineExpose({
showModal,
})
const notPassModalRef = ref(null)
const showActions = computed(() => {
return selectedApproveItems.value.length > 0 && batch;
});
function onCheckChange(checked: any, item: any) {
item.checked = checked
const index = selectedApproveItems.value.indexOf(item);
const picIndex = selectIds.value.indexOf(item.pictureId);
if (index === -1 && checked) {
selectedApproveItems.value.push(item)
} else {
selectedApproveItems.value.splice(index, 1);
}
if (picIndex === -1 && checked) {
selectIds.value.push(item.pictureId);
} else {
selectIds.value.splice(picIndex, 1);
}
setTimeout(() => {
nextTick(() => {
batch.value = true
})
}, 100)
}
function rejectHandler() {
const modal = unref(notPassModalRef)! as any
modal.showModal(selectedApproveItems.value)
}
function reset() {
batch.value = false;
// pagination.pageNo = 1;
pagination.pageNo = 1;
pagination.pageSize = 20;
selectIds.value = [];
selectedApproveItems.value.length = 0;
loading = false;
canloadMore = true;
layout();
}
function validate(items: any[]) {
if (items.length === 0) return "至少选中一个任务";
return null;
}
function approvalHandler(items?: any) {
// if (batch.value) {
processItems = selectedApproveItems.value
// }
// 任务包图片 => 点击 => 通过/不通过按钮
if (items !== undefined && !(items instanceof PointerEvent))
processItems = [items]
console.log("batch-------------",batch.value,selectedApproveItems.value,processItems);
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 doAudit(param: any) {
audit(param).then((res) => {
const { code } = res
setBatch(false)
if (code === 'OK') {
message.info(res.message)
// emitter.emit('refresh')
refreshHandler()
}
else message.error(res.message)
})
}
function reloadList() {
refreshHandler()
}
async function refreshHandler(filtersearchId?: any) {
// 注释 rao start
reset()
pagination.pageNo = 1;
const list = await featchList()
listData.value = list
layout()
// 注释 rao end
// reset();
// if (filtersearchId) {
// filterId = filtersearchId;
// }
nextTick(() => {
setTimeout(() => {
useInfiniteScroll(
el as any,
() => {
console.log("加载了333333333333333333333333---------------------------");
loadMore();
},
{ distance: 10, canLoadMore: () => canloadMore }
);
}, 300);
});
}
watch(() => show.value,
async(newVal) => {
if(show.value) {
pagination.pageNo = 1;
const list = await featchList()
listData.value = list
console.log("加载了1111111111111---------------------------", listData.value);
layout()
}
},
)
watch(() => pagination.pageNo,
(newVal, oldVal) => {
console.log("加载了22222222222222222---------------------------", newVal, oldVal, canloadMore);
if(newVal == oldVal) {
return
}
if((newVal == 1 || newVal == 2) && canloadMore) {
setTimeout(() => {
nextTick(() => {
loading = false
loadMore();
})
}, 500)
}
},
);
</script>
<template>
<div>
<n-modal v-model:show="show" :mask-closable="false" style="position: relative;" transform-origin="center"
@after-enter="afterEnter" @after-leave="afterLeave">
<n-card ref="elwc" :style="cardStyle" class="card card-1" style="position: fixed;top:64px" :bordered="false" size="huge"
role="dialog" aria-modal="true">
<div class="wrapper">
<div class="wrapper-m32">
<SvgIcon name="recycle" size="16" />
<span style="margin-left: 8px;">可疑图片文件夹</span>
</div>
<div class="wrapper-title wrapper-m32">
可疑图片文件夹
</div>
<div class="wrapper-content">
<div class="wrapper-content-form wrapper-m32">
<div>
<n-popselect v-model:value="viewMode" :options="viewOptions" trigger="click">
<div class="wrapper-form-dropdown">
<span>视图模式</span>
<SvgIcon class="wrapper-content-form-gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
<div
style="margin-left: 15px; cursor: pointer; color: #323233"
@click="onChange()"
>
<span>时间排序</span>
<SvgIcon style="margin-left: 8px" name="sort" size="12" />
</div>
<div
style="margin-left: 15px; cursor: pointer; color: #323233"
@click="onChangeView()"
>
<span>相似度排序</span>
<SvgIcon style="margin-left: 8px" name="sort" size="12" />
</div>
<!-- <n-popselect v-model:value="timeRange" :options="timeOptions" trigger="click" @change="onChange">
<div class="wrapper-content-form-dropdown">
<span>时间排序</span>
<SvgIcon class="wrapper-content-form-dropdown-gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
<n-popselect v-model:value="similarRange" :options="timeOptions" trigger="click" @change="onChangeView">
<div class="wrapper-content-form-dropdown">
<span>相似度排序</span>
<SvgIcon class="wrapper-content-form-dropdown-gap" name="arrow-botton" size="14" />
</div>
</n-popselect> -->
</div>
<div>
<div class="remove" @click="remove">
</div>
<div class="wrapper-content-form-button" @click="setBatch(true)" v-show="!showActions">
<SvgIcon style="margin-right: 6px;" size="14" name="tf" />
批量审批
</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.stop="rejectHandler">
<SvgIcon width="64" height="28" name="a1" />
</div>
<SvgIcon size="24" name="vs" />
<div style="cursor: pointer" @click.stop="approvalHandler">
<SvgIcon width="64" height="28" name="a2" />
</div>
</div>
</div>
</div>
<div ref="el" class="scroll" :style="{height: maxHeight}">
<!-- <n-scrollbar :on-scroll="scrollHandler"> -->
<div ref="masonryRef" class="grid">
<div v-for="(item, index) in listData" :key="item.pictureId" :data-id="item.pictureId"
:class="{ 'grid-item-selected': isSelected(item.pictureId) }" :style="{ height: gridHeight }"
class="grid-item">
<n-image
ref="imageRef"
:src="item.imgUrl"
:preview-src="item.imgUrl"
class="img "
:class="{
'img-fit': viewMode === 'horizontalVersion',
'img-full': viewMode === '3:4' || viewMode === 'verticalVersion' }"
/>
<n-checkbox v-if="batch && item.historyStates === 1" v-model:checked="item.checked"
style="position:absolute;left:20px;top:20px" @click.prevent.stop
@update:checked="onCheckChange($event, item)" />
<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="{ 'percent-red': item.similarityScore === 100 }" class="percent">
{{ item.similarityScore }}<span class="percent-unit">%</span>
</div>
<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 class="time-item" style="margin-bottom: 4px;">
<SvgIcon color="#FFF" size="16" name="camera" style="margin-right: 4px;" />
<span class="time-value">{{ item?.photoDateTimestamp ?
formatToDateHMS(Number(item.photoDateTimestamp)) : '-' }} </span>
</div>
<div class="time-item">
<SvgIcon class="svg-time" color="#FFF" size="16" name="time" />
<span>{{ item.submitDateTimestamp ?
formatToDateHMS(Number(item.submitDateTimestamp)) : '-' }}</span>
</div>
</div>
</div>
</div>
<!-- </n-scrollbar> -->
</div>
</div>
<div class="close" @pointerdown="closeModal">
<div class="icon" />
</div>
</div>
</n-card>
</n-modal>
<NotPassed ref="notPassModalRef" @success="reloadList" />
<ConfrimModal ref="confrimModalRef" @commit="commit" :selectedTotal="selectIds.length" />
</div>
</template>
<style lang="less" scoped>
.card {
width: 100vw;
height: calc(100vh - 64px);
user-select: none;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.remove {
cursor: pointer;
}
.close {
position: absolute;
right: -90px;
top: -70px;
width: 18px;
height: 18px;
cursor: pointer;
}
.icon {
background: #FFF;
display: inline-block;
width: 18px;
height: 1px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
&:after {
content: '';
display: block;
width: 18px;
height: 1px;
background: #FFF;
transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
}
}
.img {
border-radius: 7px;
display: block;
height: 100%;
}
.wrapper {
display: flex;
flex-direction: column;
position: relative;
&-title {
font-weight: bold;
font-size: 21px;
padding: 24px 0px 12px 0px;
}
&-m32 {
margin-left: 32px;
}
&-content {
&-form {
display: flex;
justify-content: space-between;
&-dropdown {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 24px;
&-gap {
margin-left: 5px;
}
}
&-button {
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;
margin-left: 18px;
}
div {
display: flex;
align-items: center;
}
}
&-item {
&-img {
border-radius: 7px;
display: block;
height: 100%;
}
&-img-fit {
width: 100%;
object-fit: cover;
}
}
.grid-item {
width: 214px;
padding: 16px;
position: relative;
.tag-status {
width: 46px;
height: 22px;
position: absolute;
left: 16px;
top: 20px;
}
}
.grid-item-selected {
background-color: #dae3ff;
}
.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: 28px;
top: 20px;
color: #fff;
font-size: 14px;
.percent-unit {
font-size: 8px;
margin-top: 4px;
}
}
.percent-red {
background: #ff4e4f;
}
.time {
position: absolute;
width: 182px;
padding-left: 10px;
z-index: 3;
left: 16px;
bottom: 16px;
background: linear-gradient(180deg,rgba(6,0,0,0.01),rgba(0, 0, 0, 0.44) 100%);
.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;
}
}
.scroll {
overflow-y: auto;
// height: calc(100vh - 282px);
height: 800px;
margin-left: 20px;
}
}
}
.wrapper-content-form-button {
cursor: pointer;
}
.img-fit {
width: 100%;
overflow: hidden;
}
.img-full {
width: 100%;
overflow: hidden;
::v-deep(img) {
width: 100%;
height: 100%;
object-fit: cover;
}
}</style>