This commit is contained in:
zlwu committed 2026-05-12 22:07:53 +08:00
1 parent c87732fcd5
commit 058811ae77
107 files changed
+16983 -12758

No files matched your search

@@ -0,0 +1,129 @@
<template>
<teleport to="body">
<div v-show="visibleProxy" class="order-operate-bridge-layer" @click.self="visibleProxy = false">
<div class="order-operate-bridge-dialog">
<iframe ref="frameRef" class="order-operate-bridge-frame" :src="frameSrc" frameborder="0" />
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
const props = defineProps<{
modelValue: boolean
orderId: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.modelValue,
set: (value: boolean) => emit('update:modelValue', value)
})
const frameSrc = computed(() => {
return `#/demo/order?embedded=1`
})
const frameRef = ref<HTMLIFrameElement | null>(null)
const handleMessage = (ev: MessageEvent) => {
const data = ev?.data as any
if (!data || typeof data !== 'object') return
if (data.type === 'sqxp-order-operate-close') {
visibleProxy.value = false
}
}
const postOpen = (id: string) => {
const win = frameRef.value?.contentWindow
if (!win) return
win.postMessage({ type: 'sqxp-order-operate-open', orderId: id }, '*')
}
onMounted(() => {
window.addEventListener('message', handleMessage)
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleMessage)
})
watch(
() => [visibleProxy.value, props.orderId] as const,
([vis, id]) => {
if (!vis) return
const orderId = (id || '').trim()
if (!orderId) return
// 给 iframe 一帧时间完成渲染/初始化后再发消息,避免首次打开时丢消息
requestAnimationFrame(() => postOpen(orderId))
},
{ immediate: true }
)
</script>
<style lang="scss" scoped>
.order-operate-bridge-layer {
position: fixed;
inset: 0;
z-index: 4000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.28);
padding: 48px 24px;
box-sizing: border-box;
}
.order-operate-bridge-dialog {
width: min(1400px, calc(100vw - 48px));
max-height: calc(100vh - 96px);
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow:
0 20px 64px rgba(15, 23, 42, 0.25),
0 2px 12px rgba(15, 23, 42, 0.12);
display: flex;
flex-direction: column;
}
.order-operate-bridge-frame {
width: 100%;
height: 100%;
min-height: 680px;
max-height: calc(100vh - 96px);
display: block;
background: transparent;
border: 0;
flex: 1 1 auto;
}
.order-operate-bridge-empty {
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: center;
}
@media (max-width: 900px) {
.order-operate-bridge-layer {
padding: 20px 10px;
}
.order-operate-bridge-dialog {
width: calc(100vw - 20px);
max-height: calc(100vh - 40px);
border-radius: 10px;
}
.order-operate-bridge-frame {
min-height: 520px;
max-height: calc(100vh - 40px);
}
}
</style>
@@ -0,0 +1,67 @@
<template>
<teleport to="body">
<div v-show="visibleProxy" class="order-operate-local-layer" @click.self="visibleProxy = false">
<iframe class="order-operate-local-frame" :src="frameSrc" frameborder="0" allowtransparency="true" />
</div>
</teleport>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted } from 'vue'
const props = defineProps<{
modelValue: boolean
orderId: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.modelValue,
set: (value: boolean) => emit('update:modelValue', value)
})
const frameSrc = computed(() => {
const id = encodeURIComponent((props.orderId || '').trim())
return id ? `#/demo/order?bridge=1&viewOrderId=${id}` : '#/demo/order?bridge=1'
})
const handleMessage = (ev: MessageEvent) => {
const data = ev?.data as any
if (!data || typeof data !== 'object') return
if (data.type === 'sqxp-order-operate-close') {
visibleProxy.value = false
}
}
onMounted(() => {
window.addEventListener('message', handleMessage)
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleMessage)
})
</script>
<style lang="scss" scoped>
.order-operate-local-layer {
position: fixed;
inset: 0;
z-index: 4000;
display: flex;
flex-direction: column;
background: transparent;
overflow: hidden;
}
.order-operate-local-frame {
width: 100%;
height: 100%;
min-height: 0;
display: block;
background: transparent;
border: 0;
}
</style>
File diff suppressed because it is too large. Load diff
@@ -4,7 +4,7 @@
<img
:style="logoStyle"
src="/logo1.jpg"
alt="中科兴皖一体化门户"
alt="陇耘鲜达"
class="block object-contain"
/>
</div>
@@ -1,330 +0,0 @@
<!-- 地图图表 -->
<template>
<div class="relative w-full" :style="{ height: 'calc(100vh - 120px)' }">
<div v-if="isEmpty" class="h-full flex-cc">
<ElEmpty description="暂无地图数据" />
</div>
<div v-else id="china-map" ref="chinaMapRef" class="h-full w-full overflow-hidden rounded-lg" />
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { echarts } from '@/plugins/echarts'
import { useSettingStore } from '@/store/modules/setting'
import { fetchGetChinaMapData } from '@/api/system'
import type { MapChartProps } from '@/types/component/chart'
defineOptions({ name: 'ArtMapChart' })
const chinaMapRef = ref<HTMLElement | null>(null)
const chartInstance = shallowRef<echarts.ECharts | null>(null)
const settingStore = useSettingStore()
const { isDark } = storeToRefs(settingStore)
// 默认地图数据
const defaultMapJson = {
type: 'FeatureCollection' as const,
features: []
}
const props = withDefaults(defineProps<MapChartProps>(), {
mapData: () => [],
selectedRegion: '',
showLabels: true,
showScatter: true,
isEmpty: false
})
// 地图GeoJSON数据
const mapJsonData = ref<{ type: 'FeatureCollection'; features: unknown[] }>(defaultMapJson)
// 定义 emit
const emit = defineEmits<{
renderComplete: []
regionClick: [region: { name: string; adcode: string; level: string }]
}>()
// 检查是否为空数据
const isEmpty = computed(() => {
return props.isEmpty || (!props.mapData?.length && !mapJsonData.value.features.length)
})
// 获取地图数据
const fetchMapData = async () => {
try {
const { data } = await fetchGetChinaMapData()
if (data && data.features && data.features.length > 0) {
mapJsonData.value = data as { type: 'FeatureCollection'; features: unknown[] }
}
} catch (error) {
console.error('获取地图数据失败:', error)
}
}
// 根据 geoJson 数据准备地图数据
const prepareMapData = (geoJson: {
features: Array<{ properties: Record<string, unknown> }>
}) => {
return geoJson.features.map((feature) => ({
name: feature.properties.name as string,
value: Math.round(Math.random() * 1000),
adcode: feature.properties.adcode as string,
level: feature.properties.level as string,
selected: false
}))
}
// 获取主题相关的样式配置
const getThemeStyles = () => ({
borderColor: isDark.value ? 'rgba(255,255,255,0.6)' : 'rgba(147,235,248,1)',
shadowColor: isDark.value ? 'rgba(0,0,0,0.8)' : 'rgba(128,217,248,1)',
labelColor: isDark.value ? '#fff' : '#333',
backgroundColor: isDark.value ? 'rgba(0,0,0,0.8)' : 'rgba(255,255,255,0.9)'
})
// 构造 ECharts 配置项
const createChartOption = (mapData: Array<Record<string, unknown>>) => {
const themeStyles = getThemeStyles()
return {
animation: false, // 关闭动画效果,减少鼠标移动高亮时的掉帧感
tooltip: {
show: true,
backgroundColor: themeStyles.backgroundColor,
borderColor: isDark.value ? '#333' : '#ddd',
borderWidth: 1,
textStyle: {
color: themeStyles.labelColor
},
formatter: ({ data }: { data?: Record<string, unknown> }) => {
const { name, adcode, level } = data || {}
return `
<div style="padding: 8px;">
<div><strong>名称:</strong> ${name || '未知区域'}</div>
<div><strong>代码:</strong> ${adcode || '暂无'}</div>
<div><strong>级别:</strong> ${level || '暂无'}</div>
</div>
`
}
},
geo: {
map: 'china',
zoom: 1,
show: true,
roam: false,
scaleLimit: {
min: 0.8,
max: 3
},
layoutSize: '100%',
emphasis: {
label: { show: props.showLabels },
itemStyle: {
areaColor: 'rgba(82,180,255,0.9)',
borderColor: '#fff',
borderWidth: 3
}
},
itemStyle: {
borderColor: themeStyles.borderColor,
borderWidth: 2,
shadowColor: themeStyles.shadowColor,
shadowOffsetX: 2,
shadowOffsetY: 15,
shadowBlur: 15
}
},
series: [
{
type: 'map',
map: 'china',
aspectScale: 0.75,
zoom: 1,
label: {
show: props.showLabels,
color: '#fff',
fontSize: 10
},
itemStyle: {
borderColor: 'rgba(147,235,248,0.8)',
borderWidth: 2,
areaColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(147,235,248,0.3)' },
{ offset: 1, color: 'rgba(32,120,207,0.9)' }
]
},
shadowColor: 'rgba(32,120,207,1)',
shadowOffsetY: 15,
shadowBlur: 20
},
emphasis: {
label: {
show: true,
color: '#fff',
fontSize: 12
},
itemStyle: {
areaColor: 'rgba(82,180,255,0.9)',
borderColor: '#fff',
borderWidth: 3
}
},
select: {
label: {
show: true,
color: '#fff',
fontWeight: 'bold'
},
itemStyle: {
areaColor: '#4FAEFB',
borderColor: '#fff',
borderWidth: 2
}
},
data: mapData
},
// 散点标记配置(例如:城市标记)
...(props.showScatter
? [
{
name: '城市',
type: 'scatter',
coordinateSystem: 'geo',
symbol: 'pin',
symbolSize: 15,
label: { show: false },
itemStyle: {
color: '#F99020',
shadowBlur: 10,
shadowColor: '#333'
},
data: [
{ name: '北京', value: [116.405285, 39.904989, 100] },
{ name: '上海', value: [121.472644, 31.231706, 100] },
{ name: '深圳', value: [114.085947, 22.547, 100] }
]
}
]
: [])
]
}
}
// 初始化并渲染地图
const initMap = async (): Promise<void> => {
if (!chinaMapRef.value) return
chartInstance.value = echarts.init(chinaMapRef.value)
// 注册地图
const mapData = mapJsonData.value.features.length > 0 ? mapJsonData.value : defaultMapJson
echarts.registerMap('china', mapData as any)
const preparedData = props.mapData.length > 0 ? props.mapData : prepareMapData(mapData as any)
const option = createChartOption(preparedData)
chartInstance.value.setOption(option)
// 绑定事件
chartInstance.value.on('click', handleMapClick)
emit('renderComplete')
}
// 处理地图点击事件
const handleMapClick = (params: Record<string, unknown>) => {
if (params.componentType === 'series') {
const data = params.data as Record<string, unknown> | undefined
const regionData = {
name: params.name as string,
adcode: (data?.adcode as string) || '',
level: (data?.level as string) || ''
}
console.log(`选中区域: ${params.name}`, params)
// 高亮选中区域
chartInstance.value?.dispatchAction({
type: 'select',
seriesIndex: 0,
dataIndex: params.dataIndex as number
})
emit('regionClick', regionData)
}
}
// 窗口 resize 时调整图表大小
const resizeChart = () => {
chartInstance.value?.resize()
}
// 处理组件销毁
const cleanupChart = () => {
if (chartInstance.value) {
chartInstance.value.off('click', handleMapClick)
chartInstance.value.dispose()
chartInstance.value = null
}
window.removeEventListener('resize', resizeChart)
}
// 生命周期钩子
onMounted(async () => {
await fetchMapData()
if (!isEmpty.value) {
initMap().then(() => {
setTimeout(resizeChart, 100)
})
}
window.addEventListener('resize', resizeChart)
})
onUnmounted(cleanupChart)
// 监听主题变化,重新初始化地图
watch(isDark, (newVal, oldVal) => {
if (newVal !== oldVal && chartInstance.value) {
cleanupChart()
nextTick(() => {
if (!isEmpty.value) {
initMap()
}
})
}
})
// 监听数据变化
watch(
() => props.mapData,
() => {
if (chartInstance.value && !isEmpty.value) {
const mapData = props.mapData.length > 0 ? props.mapData : prepareMapData(mapJsonData.value as any)
const option = createChartOption(mapData)
chartInstance.value.setOption(option)
}
},
{ deep: true }
)
// 监听地图数据变化
watch(
mapJsonData,
() => {
if (chartInstance.value && !isEmpty.value) {
const mapData = props.mapData.length > 0 ? props.mapData : prepareMapData(mapJsonData.value as any)
const option = createChartOption(mapData)
chartInstance.value.setOption(option)
}
},
{ deep: true }
)
</script>
@@ -83,9 +83,9 @@
opacity: 1
}"
>
<template v-if="AppConfig.systemInfo.name === '中科兴皖一体化门户'">
<span class="system-name-main">中科兴皖</span>
<span class="system-name-accent">一体化门户</span>
<template v-if="AppConfig.systemInfo.name === '陇耘鲜达'">
<span class="system-name-main">陇耘</span>
<span class="system-name-accent">鲜达</span>
</template>
<template v-else>
{{ AppConfig.systemInfo.name }}