399 lines
9.6 KiB
Vue
399 lines
9.6 KiB
Vue
<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>
|
||
|