提交
This commit is contained in:
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 }}
|
||||
|
||||
Reference in new issue
Block a user