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

460 lines
11 KiB

<script lang="ts" setup>
import { useInfiniteScroll } from '@vueuse/core'
import imagesloaded from 'imagesloaded'
import { debounce } from 'lodash-es'
import Masonry from 'masonry-layout'
import { useMessage } from 'naive-ui'
import { computed, nextTick, onMounted, onUnmounted, onUpdated, reactive, ref, unref, watch } from 'vue'
import GeneratePackageModal from './modal/GeneratePackageModal.vue'
import PackageSettingsModal from './modal/PackageSettingsModal.vue'
import LoginSuccessModal from './modal/LoginSuccessModal.vue'
import { getPictureList, oneClickCheck } from '@/api/home/main'
import avatar from '@/assets/images/avatar.jpg'
import { timeOptions, viewOptions } from '@/config/home'
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn'
import { useConfig } from '@/store/modules/asideConfig'
import { getViewportOffset } from '@/utils/domUtils'
import { hideDownload } from '@/utils/image'
import emitter from '@/utils/mitt'
import { getImgUrl } from '@/utils/urlUtils'
const deviceHeight = ref(600)
let _masonry: null | Masonry = null
let _imagesload: any
const masonryRef = ref<ComponentRef>(null)
const el = ref<HTMLDivElement | null>(null)
const viewMode = ref('masonry')
const pagination = reactive({
pageNo: 0,
pageSize: 30,
})
const configStore = useConfig()
const packageModalRef = ref(null)
const generateModalRef = ref(null)
const LoginSuccessModalRef = ref(null)
const loading = ref(false)
const message = useMessage()
const totalCount = ref(0)
const sortBy = ref<'asc' | 'desc'>('desc')
let canloadMore = true
let filterId = null
async function computeListHeight() {
const headEl = document.querySelector('.wrapper-content')!
const { bottomIncludeBody } = getViewportOffset(headEl)
const height = bottomIncludeBody
deviceHeight.value = height - 40 - 16 - 24
}
useWindowSizeFn(computeListHeight)
const listStyle = computed(() => {
return {
height: `${deviceHeight.value}px`,
}
})
const layout = debounce(() => {
if (masonryRef.value == null || el.value == null)
return
if (_masonry !== null)
(_masonry as any).destroy()
_masonry = new Masonry(masonryRef.value as any, {
itemSelector: '.grid-item',
gutter: 18,
columnWidth: 182,
percentPosition: true,
stagger: 10,
})
_imagesload = imagesloaded('.grid-item')
_imagesload.on('done', (instance) => {
(_masonry as any).layout()
if (!el.value)
return
loading.value = false
})
_imagesload.on('fail', (instance) => {
message.error('图片错误')
loading.value = false
})
}, 300)
useInfiniteScroll(
el as any,
() => {
loadMore()
},
{ distance: 10, canLoadMore: () => canloadMore },
)
onUpdated(() => {
layout()
})
const timeRange = ref('99')
const timeLabel = computed(() => {
const item = timeOptions.find((option) => {
return option.value === timeRange.value
})
return item?.label
})
const viewLabel = computed(() => {
const item = viewOptions.find((option) => {
return option.value === viewMode.value
})
return item?.label
})
const listData = ref<any[]>([])
async function featchList() {
loading.value = true
try {
const contentParams = {
search_month: timeRange.value,
search_history: 0,
}
pagination.pageNo += 1
const asideParams = unref(configStore.getAsideValue)
const params = filterId ? { userSearchId: filterId } : asideParams
const result = await getPictureList({ ...pagination, ...contentParams, ...params, ordertype: sortBy.value })
const { data, pageCount, total } = result
totalCount.value = total
canloadMore = pageCount >= pagination.pageNo && pageCount > 0
const list = data.map((item) => {
return {
imgUrl: item.imgurl,
thumburl: item.serverThumbnailUrl || item.imgurl,
upname: item.upname,
ocrPictureclass: item.ocrPictureclass,
uphead: item.uphead,
similar: item.similarityscore || 0,
}
})
return list
}
catch (error) {
canloadMore = false
return []
}
}
async function loadMore() {
if (loading.value || el.value == null)
return
const more = await featchList()
listData.value.push(...more)
}
const gridHeight = computed(() => {
return viewMode.value !== 'masonry' ? '145px' : ''
})
async function oneCheck() {
const modal = packageModalRef.value as any
modal.showModal()
}
async function showLoginSuccessModal() {
const modal = LoginSuccessModalRef.value as any
// modal.showModal()
}
async function commitHandler(settingParam) {
const contentParams = {
search_month: timeRange.value,
}
const asideVal = configStore.getAsideValue
const finalParam = { ...contentParams, ...asideVal }
finalParam.buessinessno = settingParam.packagename
finalParam.search_history = settingParam.comparehistory ? 1 : 0
const modal = generateModalRef.value as any
modal.showModal()
oneClickCheck(finalParam).then(() => {
modal.closeModal()
}, () => {
modal.closeModal()
})
}
onMounted(() => {
emitter.on('filter', refreshHandler)
nextTick(() => {
computeListHeight()
// 登录后展示成功
showLoginSuccessModal()
})
})
onUnmounted(() => {
emitter.off('filter', refreshHandler)
})
watch(timeRange, () => {
refreshHandler()
})
watch(() => configStore.asideValue, (newVal, oldVal) => {
refreshHandler()
}, { deep: true })
function reset() {
pagination.pageNo = 0
pagination.pageSize = 30
listData.value.length = 0
loading.value = false
canloadMore = true
filterId = null
layout()
}
async function refreshHandler(filtersearchId?: any) {
reset()
if (filtersearchId)
filterId = filtersearchId
nextTick(() => {
setTimeout(() => {
useInfiniteScroll(
el as any,
() => {
loadMore()
},
{ distance: 10, canLoadMore: () => canloadMore },
)
}, 500)
})
}
function getAvatar(url: string): string {
return url ? getImgUrl(url) : avatar
}
function sortHandler() {
sortBy.value = sortBy.value === 'asc' ? 'desc' : 'asc'
refreshHandler()
}
</script>
<template>
<div class="wrapper">
<div class="wrapper-header">
<div class="left">
<SvgIcon size="32" name="magnifying" />
<span class="font">AI一键查重</span>
</div>
<SvgIcon style="cursor: pointer;" size="105" name="yijianchachong" @click="oneCheck" />
</div>
<div class="wrapper-content">
<div style="display: flex;justify-content: space-between;">
<div class="form">
<n-popselect v-model:value="timeRange" :options="timeOptions" trigger="click">
<div class="dropdown">
<span>{{ timeLabel || '请选择' }}</span>
<SvgIcon class="gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
<n-popselect v-model:value="viewMode" :options="viewOptions" trigger="click">
<div class="dropdown">
<span>{{ viewLabel || '请选择' }}</span>
<SvgIcon class="gap" name="arrow-botton" size="14" />
</div>
</n-popselect>
<div style="margin-left: 15px;cursor: pointer" @click="sortHandler()">
<span>相似度排序</span>
<SvgIcon style="margin-left: 8px;" name="sort" size="12" />
</div>
</div>
<span>{{ totalCount }}</span>
</div>
<n-spin :show="loading">
<div ref="el" class="scroll" :style="listStyle">
<!-- <n-scrollbar :on-scroll="scrollHandler"> -->
<div ref="masonryRef" class="grid">
<div v-for="(item, index) in listData" :key="index" :style="{ height: gridHeight }" class="grid-item">
<!-- <div :style="{ 'background-color': randomColor(0.2) }" class="wrapper-content-item-img" /> -->
<!-- <img
class="wrapper-content-item-img" :class="{ 'wrapper-content-item-img-fit': viewMode !== 'masonry' }"
:src="item.imgUrl"
> -->
<n-image
class="img" :img-props="{ onClick: hideDownload }" :class="{ 'img-fit': viewMode !== 'masonry' }"
:src="item.thumburl"
/>
<div class="percent">
<SvgIcon size="42" name="tag" />
<div class="val">
{{ `${item.similar}%` }}
</div>
</div>
<div class="info">
<div class="left">
<n-avatar :src="getAvatar(item.uphead)" class="avatar" round />
<span>{{ item.upname }}</span>
</div>
<div class="right">
<span :style="{ marginRight: '5px' }"></span>
<span>{{ item.ocrPictureclass?.classname }}</span>
</div>
</div>
</div>
</div>
<!-- </n-scrollbar> -->
</div>
</n-spin>
</div>
<PackageSettingsModal ref="packageModalRef" @commit="commitHandler" />
<GeneratePackageModal ref="generateModalRef" />
<LoginSuccessModal ref="LoginSuccessModalRef" />
</div>
</template>
<style lang="less" scoped>
.wrapper {
display: flex;
flex: 1;
flex-direction: column;
box-sizing: border-box;
margin-left: 16px;
width: 100%;
&-header {
display: flex;
justify-content: space-between;
align-items: center;
background: #FFF;
padding: 0px 15px 0px 15px;
box-sizing: border-box;
height: 64px;
border: 1px solid rgb(239, 239, 245);
border-radius: 3px;
width: 100%;
.left {
display: flex;
align-items: center;
}
.font {
font-size: 18px;
font-weight: bold;
color: #333333;
line-height: 25px;
margin-left: 12px;
}
}
&-content {
flex: 1;
padding: 16px 16px 0px 16px;
margin-top: 16px;
background: #FFF;
border-radius: 3px;
border: 1px solid rgb(239, 239, 245);
.form {
display: flex;
align-items: center;
font-size: 14px;
padding-bottom: 16px;
}
.img {
border-radius: 7px;
display: block;
height: calc(100% - 25px);
}
.img-fit {
width: 100%;
overflow: hidden;
}
.info {
display: flex;
justify-content: space-between;
margin-top: 4px;
.left {
display: flex;
align-items: center;
}
.right {
display: flex;
align-items: center;
}
.avatar {
width: 15px;
height: 15px;
margin-right: 5px;
}
}
.dropdown {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 24px;
.gap {
margin-left: 5px;
}
}
.grid-item {
width: 182px;
border-radius: 7px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
}
.percent {
position: absolute;
text-align: center;
z-index: 3;
right: 0px;
top: -6px;
color: #FFF;
.val {
position: absolute;
left: 0;
top: 0;
display: block;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.scroll {
overflow-y: scroll;
}
}
}
</style>