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

550 lines
13 KiB

This file contains ambiguous Unicode characters!

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

<script lang="ts" setup>
import { computed, onBeforeMount, onMounted, onUpdated, reactive, ref, watch } from 'vue'
import Masonry from 'masonry-layout'
import { useDialog, useMessage } from 'naive-ui'
import { useInfiniteScroll } from '@vueuse/core'
import { debounce, throttle } from 'lodash-es'
import imagesloaded from 'imagesloaded'
import { randomInt } from '@/utils/index'
import { viewOptions } from '@/config/home'
import { dubiousfilelist, removeFiles } from '@/api/task/task'
import { off, on } from '@/utils/domUtils'
import img1 from '@/assets/images/1.jpg'
import img2 from '@/assets/images/2.jpg'
import img3 from '@/assets/images/3.jpg'
import img4 from '@/assets/images/4.jpg'
import img5 from '@/assets/images/5.jpg'
const cardStyle = {
'--n-padding-bottom': '40px',
'--n-padding-left': '120px',
}
const message = useMessage()
const timeRange = ref('')
const check = ref(false)
const timeOptions = [{
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('masonry')
const viewLabel = computed(() => {
const item = viewOptions.find((option) => {
return option.value === viewMode.value
})
return item?.label
})
const masonryRef = ref<ComponentRef>(null)
const el = 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 urls = [img1, img2, img3, img4, img5]
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
})
}, 300)
watch(viewMode, () => {
layout()
})
let canloadMore = true
useInfiniteScroll(
el as any,
() => {
loadMore()
},
{ distance: 10, canLoadMore: () => canloadMore },
)
function randomUrl() {
const index = randomInt(0, urls.length)
return urls[index]
}
async function featchList() {
loading = true
try {
const result = await dubiousfilelist({ ...pagination, orderbyname: timeRange.value })
// TODO测试数据
// result.data = Array.from({ length: 30 })
result.pageCount = 1
const { data, pageCount } = result
pagination.pageNo += 1
canloadMore = pageCount >= pagination.pageNo
return result.data.records
// const list = data.map((item) => {
// return {
// imgUrl: randomUrl(),
// }
// })
// console.log(list)
// return list
}
catch (error) {
return []
}
}
async function loadMore() {
if (loading || el.value == null)
return
loading = true
const more = await featchList()
listData.value.push(...more)
}
onUpdated(() => {
layout()
})
onBeforeMount(async () => {
console.log(888)
// const list = await featchList()
// listData.value = list
})
let start: { x: number, y: number } | null = null
let selectionBox: HTMLDivElement | null
const selectIds = ref<string[]>([])
function downHandler(event: MouseEvent) {
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)
})
}
function isSelected(id: number) {
return selectIds.value.includes(String(id))
}
function moveHandler(e: MouseEvent) {
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 upHandler(event: MouseEvent) {
if (!selectionBox)
return
selectionBox.style.display = 'none'
start = null
}
const gridHeight = computed(() => {
return viewMode.value !== 'masonry' ? '157px' : ''
})
function addListeners() {
selectionBox = document.querySelector('.selection-box') as HTMLDivElement
on(el.value!, 'mousedown', downHandler)
on(el.value!, 'mousemove', moveHandler)
on(document, 'mouseup', upHandler)
}
function removeListeners() {
off(el.value!, 'mousedown', downHandler)
on(el.value!, 'mousemove', moveHandler)
on(document, 'mouseup', upHandler)
}
function afterEnter() {
addListeners()
}
function afterLeave() {
removeListeners()
}
onMounted(() => {
show.value && addListeners()
})
async function showModal() {
show.value = true
pagination.pageNo = 1
const list = await featchList()
listData.value = list
}
async function onChange() {
pagination.pageNo = 1
const list = await featchList()
listData.value = list
}
function closeModal(event: MouseEvent) {
show.value = false
}
async function remove() {
if ( !selectIds.value|| selectIds.value.length === 0) {
message.error('至少选中一个')
return
}
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 getPercent(pictureid: string, item) {
console.log(item)
const { ocpictureid, pictureresult } = item
const splitId = ocpictureid || ''
const index = splitId.split(',').indexOf(String(pictureid))
const results = (pictureresult || '').split(',')
const percent = results[index] || '0'
const val = Number.parseFloat(percent)
return `${val}%`
}
function showCheck() {
check.value = true
}
defineExpose({
showModal,
})
const checked = ref(false)
function onCheckChange(val: any, item: any) {
checked.value = val
}
</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
: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="timeRange" :options="timeOptions" trigger="click" @change="onChange">
<div class="wrapper-content-form-dropdown">
<span>{{ timeLabel || '时间排序' }}</span>
<SvgIcon class="wrapper-content-form-dropdown-gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
<n-popselect v-model:value="viewMode" :options="viewOptions" trigger="click">
<div class="wrapper-form-dropdown">
<span>{{ viewLabel || '视图模式' }}</span>
<SvgIcon class="wrapper-content-form-gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
</div>
<div>
<div class="remove" @click="remove">
</div>
<div class="wrapper-content-form-button" @click="showCheck">
<SvgIcon style="margin-right: 6px;" size="14" name="tf" />
批量审批
</div>
</div>
</div>
<div ref="el" class="scroll">
<!-- <n-scrollbar :on-scroll="scrollHandler"> -->
<div ref="masonryRef" class="grid">
<div
v-for="(item, index) in listData" :key="item.id" :data-id="item.id"
:class="{ 'grid-item-selected': isSelected(item.id) }" :style="{ height: gridHeight }" class="grid-item"
>
<!-- <img
class="wrapper-content-item-img"
:class="{ 'wrapper-content-item-img-fit': viewMode !== 'masonry' }" :src="item.imgUrl"
> -->
<n-image
:src="item.imgurl"
class="img "
:class="{ 'img-fit': viewMode === 'horizontalVersion', 'img-full': viewMode === '3:4' || viewMode === 'verticalVersion' }"
/>
<n-checkbox
v-if="check"
v-model:checked="item.checked"
style="position:absolute;left:20px;top:20px" @click.prevent
@update:checked="onCheckChange($event, item)"
/>
<!-- <div class="percent">
{{ getPercent(item.pictureid, item) }}
</div> -->
</div>
</div>
<!-- </n-scrollbar> -->
</div>
</div>
<div class="close" @pointerdown="closeModal">
<div class="icon" />
</div>
</div>
</n-card>
</n-modal>
</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;
/* Standard syntax */
}
.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: 8px;
}
.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;
}
.percent {
position: absolute;
text-align: center;
width: 35px;
height: 18px;
opacity: 0.9;
background: #6f92fd;
border-radius: 6px 0px 6px 0px;
z-index: 5;
right: 22px;
top: 22px;
color: #fff;
}
.grid-item-selected {
background-color: #dae3ff;
}
.scroll {
overflow-y: auto;
height: calc(100vh - 282px);
}
}
}
.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>