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/aside/Aside.vue

259 lines
6.8 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, nextTick, onBeforeMount, onMounted, reactive, ref, shallowRef, unref, watch } from 'vue'
import { CustomFilterModalVue, FilterModalVue, NewFilterModalVue } from './comp/modals'
import Search from './comp/Search.vue'
import AdvanceFilter from './comp/AdvanceFilter.vue'
import { getViewportOffset } from '@/utils/domUtils'
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn'
import { useConfig } from '@/store/modules/asideConfig'
import type { Filter } from '/#/home'
import type { AsideEntity } from '@/config/aside'
import { asideMap } from '@/config/aside'
import type { AsideConfig } from '/#/api'
import emitter from '@/utils/mitt'
const configStore = useConfig()
// 所有左侧模块的值
const asideValue: Record<keyof typeof asideMap, any> = reactive({})
// 左侧某个模块是否显示: 显示需同时满足系统配置和个人配置
const asideVisible: Partial<Record<keyof AsideConfig, boolean>> = reactive({})
// 当前显示的模块,按照数组顺序显示
const showItems = shallowRef<{ key: string, config: AsideEntity }[]>([])
Object.keys(asideMap).forEach((key) => {
const entity = asideMap[key]
const { defaultValue } = entity
asideValue[key] = defaultValue
})
const filterModalRef = ref(null)
const newFilterModalRef = ref(null)
const customModalRef = ref(null)
function showModal(modalRef: any) {
const modal = unref(modalRef)! as any
modal.showModal()
}
onMounted(() => {
nextTick(() => {
computeSlideHeight()
})
})
const collapse = ref(false)
function collapseHandler() {
collapse.value = !collapse.value
}
const asideWidth = computed(() => {
return collapse.value ? 0 : 308
})
const asideHeight = ref(500)
const asideStyle = computed(() => {
return {
width: `${asideWidth.value}px`,
height: `${asideHeight.value}px`,
}
})
const collapseIcon = computed(() => {
return collapse.value ? 'expand-cir' : 'collapse-cir'
})
function computeSlideHeight() {
const headEl = document.querySelector('.aside-header')!
const { bottomIncludeBody } = getViewportOffset(headEl)
const height = bottomIncludeBody
asideHeight.value = height - 24
}
useWindowSizeFn(computeSlideHeight, 280)
onBeforeMount(async () => {
configStore.fetchConfig()
configStore.fetchCustomConfig()
})
configStore.$subscribe(() => {
const config = configStore.getConfig
const customConfig = configStore.getCustomConfig
if (config === null || customConfig === null)
return
const showKeys = [...customConfig].filter(key => !asideMap[key].isDefaultFilter)
const defaultKeys = Object.keys(asideMap).filter(key => asideMap[key].isDefaultFilter)
showKeys.unshift(...defaultKeys)
Object.keys(config).forEach((key) => {
if (key.startsWith('iz') && asideMap[key] !== undefined)
asideVisible[key] = (showKeys.includes(key) || asideMap[key].isDefaultFilter) && config[key] === 'Y'
})
const items = showKeys.reduce((acc, key) => {
const { render } = asideMap[key]
if (render !== false) {
const str = key.toLowerCase()
const o = {
key: str,
config: asideMap[str],
}
return [...acc, o]
}
else {
return acc
}
}, [])
showItems.value = items
})
const asideEnter = ref(false)
const showCollapse = computed(() => {
return collapse.value ? true : asideEnter.value
})
const showSearch = ref(false)
function setShowSearch(value: boolean) {
showSearch.value = value
}
// 滚动容器让key对应模块处于可视区域
function scrollHandler(key: string) {
const element = document.querySelector(`#${key}`)
element?.scrollIntoView(true)
}
// 选择某个过滤配置,刷新图片墙
function filterHandler(filterList: Filter[]) {
const filerMap: Record<string, any> = {}
for (const filter of filterList) {
const { key, value } = filter
filerMap[key] = value
}
emitter.emit('filter', filerMap)
}
function editFilter(filter: any) {
const modal = unref(newFilterModalRef)! as any
modal.showModal()
modal.edit(filter)
}
watch(asideValue, (newVal) => {
configStore.setAsideValue(newVal)
})
</script>
<template>
<div class="aside" :style="asideStyle" @mouseenter="asideEnter = true" @mouseleave="asideEnter = false">
<div v-show="showCollapse" class="aside-collapse">
<div class="aside-collapse-btn" @click="collapseHandler">
<SvgIcon :name="collapseIcon" size="40" />
</div>
</div>
<n-scrollbar trigger="none">
<div class="aside-header">
<!-- -->
<Search v-show="showSearch" @select="scrollHandler" @close="setShowSearch(false)" />
<!-- 高级筛选 -->
<AdvanceFilter
v-show="!showSearch" @select="filterHandler" @update:search="setShowSearch(true)"
@show-custom="showModal(customModalRef)" @show-filter="showModal(filterModalRef)"
/>
</div>
<component
:is="item.config.component" v-for="(item, index) in showItems" :id="item.key" :key="index" v-model:value="asideValue[item.key]"
:label="item.config.label"
/>
<!-- 过滤列表 -->
<FilterModalVue ref="filterModalRef" @edit-filter="editFilter" @show-new-filter="showModal(newFilterModalRef)" />
<!-- 新增过滤 -->
<NewFilterModalVue ref="newFilterModalRef" />
<!-- 筛选 -->
<CustomFilterModalVue ref="customModalRef" />
</n-scrollbar>
</div>
</template>
<style lang="less" scoped>
.aside {
display: flex;
position: relative;
flex-direction: column;
background: #FFF;
border: 1px solid #efeff5;
border-radius: 3px;
box-sizing: border-box;
&-header {
padding: 10px;
width: 100%;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 15px;
}
&-divider {
width: 100%;
height: 1px;
background-color: #e8e8e8;
}
&-collapse {
width: 2px;
height: 100%;
background: #507afd;
position: absolute;
right: 0;
top: 0;
z-index: 18;
}
&-collapse-btn {
position: absolute;
cursor: pointer;
width: 40px;
height: 40px;
top: calc(15%);
right: -20px;
}
::v-deep(.n-collapse .n-collapse-item.n-collapse-item--right-arrow-placement .n-collapse-item__header .n-collapse-item-arrow) {
margin-left: 8px;
}
::v-deep(.n-collapse .n-collapse-item .n-collapse-item__header .n-collapse-item__header-main) {
font-weight: bold;
justify-content: space-between;
}
::v-deep(.n-collapse .n-collapse-item .n-collapse-item__header) {
padding: 0px;
}
::v-deep(.n-collapse .n-collapse-item:not(:first-child)) {
border-top: 0px;
}
::v-deep(.n-collapse .n-collapse-item .n-collapse-item__content-wrapper .n-collapse-item__content-inner) {
padding-top: 10px;
}
::v-deep(.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar, .n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar) {
width: 0px;
}
}
</style>