Files
lc_frontend/src/views/screen/components/HeaderSelector.vue
2026-01-06 12:08:08 +08:00

399 lines
9.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div :class="['header-left', `theme-${theme}`]">
<!-- 返回按钮 - PNG图片类型暗色系主题使用保持原样 -->
<img
v-if="backButtonType === 'image' && showBackButton && theme === 'dark'"
class="back-img"
@click="handleBack"
src="@/assets/images/screen/back_image.png"
/>
<!-- 返回按钮 - SVG图片类型亮色系主题使用支持主题颜色 -->
<svg
v-if="backButtonType === 'image' && showBackButton && theme === 'light'"
class="back-svg"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
@click="handleBack"
>
<path
d="M620.8 348.16H276.48l102.4-102.4c10.24-10.24 10.24-25.6 0-35.84-10.24-10.24-25.6-10.24-35.84 0L197.12 354.56c-5.12 5.12-7.68 11.52-7.68 17.92 0 6.4 2.56 12.8 7.68 17.92l144.64 144.64c10.24 10.24 25.6 10.24 35.84 0 10.24-10.24 10.24-25.6 0-35.84L277.76 399.36h343.04C716.8 399.36 793.6 476.16 793.6 572.16S716.8 744.96 620.8 744.96H358.4c-14.08 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6h262.4a223.4368 223.4368 0 0 0 224-224A223.4368 223.4368 0 0 0 620.8 348.16z"
:fill="svgFillColor"
/>
</svg>
<!-- 返回按钮 - 图标类型 -->
<el-icon
v-if="backButtonType === 'icon' && showBackButton"
class="back-arrow"
@click="handleBack"
>
<ArrowLeft />
</el-icon>
<!-- 文本按钮/显示 -->
<div
v-if="displayText"
:class="[
'back-button',
{
'clickable': clickable,
'non-clickable': !clickable
}
]"
@click="handleTextClick"
>
{{ displayText }}
<span v-if="showSelectorIndicator">···</span>
</div>
</div>
<!-- 区域/园区选择弹窗 -->
<RegionSelector
v-model="selectorVisible"
:modelSelected="selectedValue"
:regions="options"
@change="handleSelectorChange"
/>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ArrowLeft } from '@element-plus/icons-vue'
import RegionSelector from './RegionSelector.vue'
import { getTableList } from '@/api/design/report'
interface OptionItem {
name: string
code: string
}
interface Props {
// 返回按钮类型:'image' | 'icon' | 'none'
backButtonType?: 'image' | 'icon' | 'none'
// 是否显示返回按钮
showBackButton?: boolean
// 返回按钮点击事件
onBack?: () => void
// 显示的文本
displayText?: string
// 文本是否可点击
clickable?: boolean
// 是否显示选择器指示器(···)
showSelectorIndicator?: boolean
// 选择器类型:'region' | 'park' | 'none'
selectorType?: 'region' | 'park' | 'none'
// 选择器选项(如果不提供,会根据 selectorType 自动获取)
options?: OptionItem[]
// 当前选中的值
selectedValue?: string
// 选择器变化事件
onSelectorChange?: (item: OptionItem) => void
// 区域代码(用于获取园区列表)
regionCode?: string
// 主题:'light' | 'dark'
theme?: 'light' | 'dark'
}
const props = withDefaults(defineProps<Props>(), {
backButtonType: 'none',
showBackButton: false,
clickable: true,
showSelectorIndicator: false,
selectorType: 'none',
options: () => [],
selectedValue: '',
regionCode: '',
theme: 'dark'
})
const emit = defineEmits<{
selectorChange: [item: OptionItem]
}>()
const router = useRouter()
const selectorVisible = ref(false)
const options = ref<OptionItem[]>(props.options || [])
// 根据主题计算 SVG 填充颜色
const svgFillColor = computed(() => {
return props.theme === 'light' ? '#409eff' : '#ffffff'
})
// 监听 props.options 的变化
watch(() => props.options, (newOptions) => {
if (newOptions && newOptions.length > 0) {
options.value = newOptions
}
}, { immediate: true, deep: true })
// 缓存工具函数
const CACHE_KEY = 'shared_regionOption_cache'
interface CacheData {
records: any[]
timestamp: number
}
const getCachedRegionOption = (): any[] | null => {
try {
const cached = sessionStorage.getItem(CACHE_KEY)
if (cached) {
const cacheData: CacheData = JSON.parse(cached)
return cacheData.records
}
} catch (error) {
console.error('读取缓存失败:', error)
sessionStorage.removeItem(CACHE_KEY)
}
return null
}
const setCachedRegionOption = (records: any[]) => {
try {
const cacheData: CacheData = {
records,
timestamp: Date.now()
}
sessionStorage.setItem(CACHE_KEY, JSON.stringify(cacheData))
console.log('regionOption 数据已缓存')
} catch (error) {
console.error('保存缓存失败:', error)
}
}
// 初始化选项数据 - 统一从内部获取,不再依赖外部传入
const initOptions = async () => {
// 如果外部传入了 options优先使用保留兼容性
if (props.options && props.options.length > 0) {
options.value = props.options
return
}
if (props.selectorType === 'none') {
return
}
try {
// 先检查缓存
const cachedRecords = getCachedRegionOption()
let records = cachedRecords
if (!records || records.length === 0) {
// 缓存不存在或已过期,调用接口
const result = await getTableList('park_info_list')
records = result.records || []
if (records && records.length > 0) {
// 保存到缓存
setCachedRegionOption(records)
}
}
if (records && records.length > 0) {
if (props.selectorType === 'region') {
// 区域选择去重region字段
const regionMap = new Map()
records.forEach((el: any) => {
if (!regionMap.has(el.region)) {
regionMap.set(el.region, {
name: el.region,
code: el.region_id
})
}
})
options.value = Array.from(regionMap.values())
} else if (props.selectorType === 'park') {
// 园区选择根据regionCode过滤去重park_name字段
if (!props.regionCode) {
// 如果 regionCode 还没有值,等待它被设置
console.log('等待 regionCode 设置...')
return
}
const parkMap = new Map()
const filteredRecords = records.filter((el: any) => el.region_id == props.regionCode)
if (filteredRecords.length === 0) {
console.warn(`未找到 regionCode 为 ${props.regionCode} 的园区数据`)
}
filteredRecords.forEach((el: any) => {
if (!parkMap.has(el.park_name)) {
parkMap.set(el.park_name, {
name: el.park_name,
code: el.park_code
})
}
})
options.value = Array.from(parkMap.values())
console.log('园区选项已更新:', options.value)
}
}
} catch (error) {
console.error('初始化选项数据失败:', error)
}
}
// 处理返回按钮点击
const handleBack = () => {
if (props.onBack) {
props.onBack()
} else {
router.back()
}
}
// 处理文本点击
const handleTextClick = () => {
if (!props.clickable) {
return
}
if (props.selectorType !== 'none') {
selectorVisible.value = true
}
}
// 处理选择器变化
const handleSelectorChange = (item: OptionItem) => {
if (props.onSelectorChange) {
props.onSelectorChange(item)
}
// 同时发出事件,供父组件使用 @selector-change 监听
emit('selectorChange', item)
}
onMounted(() => {
initOptions()
})
// 监听 regionCode 的变化,当它变化时重新初始化选项(用于园区选择)
watch(() => props.regionCode, (newRegionCode) => {
if (props.selectorType === 'park' && newRegionCode) {
initOptions()
}
}, { immediate: false })
</script>
<style scoped lang="scss">
.header-left {
display: flex;
padding-left: 1vw;
line-height: 80px;
flex: 1;
align-items: center;
.back-img {
height: 3vh;
cursor: pointer;
transition: all 0.3s ease;
}
.back-svg {
height: 3vh;
width: auto;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
opacity: 0.8;
}
}
.back-arrow {
font-size: 20px;
color: #409eff;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
color: #79bbff;
transform: translateX(-2px);
}
}
.back-button {
display: inline-flex;
height: 2vh;
min-width: 6vw;
padding: 4px 16px;
margin-left: 0.5vw;
font-size: 0.9rem;
transition: all 0.3s ease;
align-items: center;
justify-content: space-between;
color: white;
&.clickable {
cursor: pointer;
}
&.non-clickable {
cursor: default;
}
span {
font-size: 18px;
line-height: 1;
}
}
// 暗色系主题(默认)
&.theme-dark {
.back-button {
background: rgb(13 24 84 / 80%);
border: 1px solid rgb(59 130 246 / 40%);
&:hover {
background: rgb(59 130 246 / 30%);
border-color: rgb(59 130 246 / 60%);
}
}
}
// 亮色系主题 - 只改颜色保持UI结构
&.theme-light {
.back-button {
background: #409eff;
border: 1px solid #e5e7eb;
&:hover {
background: #79bbff;
border-color: #cbd5e1;
}
}
}
}
/* 响应式设计 */
@media (width <= 1024px) {
.header-left .back-button {
min-width: 8vw;
font-size: 0.8rem;
}
}
@media (width <= 768px) {
.header-left {
line-height: 70px;
.back-button {
min-width: 12vw;
padding: 3px 12px;
font-size: 0.7rem;
}
}
}
@media (width <= 480px) {
.header-left {
line-height: 60px;
.back-button {
min-width: 15vw;
padding: 2px 10px;
font-size: 0.65rem;
}
}
}
</style>