Skip to content

大屏项目遇到的问题

地图选择

离线瓦片下载

https://gitcode.com/open-source-toolkit/5ac8a

框架选择

选择用 openlayers

初始化地图

ts
mapObjRef.value = new Map({
  target: mapRef.value, // 地图容器
  view: new View({
    center: [替换为经度, 替换为纬度],
    zoom: 13, // 缩放
    projection: "EPSG:4326", // 坐标系 这个坐标系代表经纬度   另外一个3857  用的不是经纬度
  }),
});

加载离线瓦片,转换为暗色的瓦片,瓦片默认是白色的 注意,瓦片数据一般式按照放大倍数,z/x/y 这个目录结构放置,其实都是一些图片,地图引擎根据放大倍数查询某个网格应该加载哪个目录的图

ts
const offlineMapLayer = new TileLayer({
  source: new XYZ({
    url: "/public/tiles" + "/{z}/{x}/{y}.png", // 设置本地离线瓦片所在路径,前面的地址是你输入http-server之后的服务地址
    tileLoadFunction: (imageTile, src) => {
      // 使用滤镜 将白色修改为深色
      let img = new Image();
      // img.crossOrigin = ''
      // 设置图片不从缓存取,从缓存取可能会出现跨域,导致加载失败
      img.setAttribute("crossOrigin", "anonymous");
      (img.onload = () => {
        let canvas = document.createElement("canvas");
        let w = img.width;
        let h = img.height;
        canvas.width = w;
        canvas.height = h;
        let context = canvas.getContext("2d");
        if (context) {
          context.filter =
            "grayscale(98%) invert(100%) sepia(20%) hue-rotate(180deg) saturate(1600%) brightness(80%) contrast(90%)";
          context.drawImage(img, 0, 0, w, h, 0, 0, w, h);
        }
        (imageTile as any).getImage().src = canvas.toDataURL("image/png");
      }),
        (img.onerror = () => {
          (imageTile as any).getImage().src = import("@/assets/logo.svg");
        });
      img.src = src;
    },
  }),
});

标记地点,用图片标记

ts
const createIconStyle = (name?: string, isImportant?: boolean) => {
  return new Style({
    text: name
      ? new Text({
          text: name,
          font: "14px",
          placement: "point",

          offsetY: -75, // 距离图标底部的偏移量

          // 文本背景样式
          backgroundFill: new Fill({
            color: isImportant ? "#DD9344" : "#1F58A5", // 标准站点#1F58A5     重点站点#DD9344
          }),
          backgroundStroke: new Stroke({
            color: isImportant ? "#E0553F" : "#295EA4", // 边框颜色
            width: 1,
          }),

          // 文本填充和边框
          fill: new Fill({
            color: "#FCFFFD", // 文本颜色
          }),
          // stroke: new Stroke({ color: 'white', width: 2 }),
          // 文本周围的内边距
          padding: [0, 4, 0, 4],
          textAlign: "center",
          textBaseline: "middle",
        })
      : undefined,
    image: new Icon({
      src: isImportant ? markActiveIcon : markIcon,
      // 确保图片URL有效,使用稳定的CDN资源
      scale: 0.5, // 适当缩放,避免图标过小
      anchor: [0.5, 1], // 锚点设置在底部中心,与Circle对齐
      crossOrigin: "anonymous", // 解决跨域问题
    }),
  });
};

创建一个标记点

ts
const createMarker = (lon: number, lat: number, other: any) => {
  const iconFeature = new Feature({
    geometry: new Point([lon, lat]),
    ...other,
  });
  const iconStyle = createIconStyle(other.name, false);
  iconFeature.setStyle(iconStyle);
  return iconFeature;
};

创建标记点图层

ts
const createMarkerLayer = () => {
  const markerSource = new olSource.Vector();
  // 添加标记点到数据源
  stationList.value.forEach((marker) => {
    const { coordinates, ...other } = marker;
    const iconFeature = createMarker(
      marker.coordinates[0],
      marker.coordinates[1],
      marker
    );
    markerSource.addFeature(iconFeature);
  });

  // 创建矢量图层并添加到地图
  const markerLayer = new olLayer.Vector({
    source: markerSource,
    zIndex: 88,
    // name: 'markerLayer', // 设置图层名称用于事件过滤
  });
  mapObjRef.value.addLayer(markerLayer);
  console.log("创建marker");
  return markerSource;
};

绑定点击事件

ts
// 添加点击事件处理
const addMarkerClickEvent = () => {
  const selectedStyle = createIconStyle(undefined, true);

  // 点击交互
  const selectClick = new Select({
    condition: click,
    style: selectedStyle,
    // zIndex: 100  // 确保交互层在最上层
  });
  mapObjRef.value.addInteraction(selectClick);

  // 弹出框处理
  // const popup = document.getElementById('popup');
  selectClick.on("select", (e) => {
    const selectedFeatures = e.target.getFeatures();
    const feature = selectedFeatures.item(0);
    console.log(feature);
    if (feature) {
      emit("selectMark", feature.values_);

      console.log("选中了", feature, feature.get("name"), feature.get("id"));
    } else {
      emit("selectMark", undefined);
      console.log("没有选中");
    }
  });
};

屏幕适配

选择用 scale 方案等比缩放, 设计稿比例为 长比高 为 4:1

ts
// 设计稿原始尺寸
const DESIGN_WIDTH = 4320;
const DESIGN_HEIGHT = 1080;
function calculateScale() {
  const screenWidth = window.innerWidth;
  const screenHeight = window.innerHeight;

  // 计算宽度和高度方向的缩放比例
  const scaleX = screenWidth / DESIGN_WIDTH;
  const scaleY = screenHeight / DESIGN_HEIGHT;

  // 使用较小的缩放比例,确保内容完全可见
  // return Math.min(scaleX, scaleY);
  return scaleY; //只返回一个比例就行,因为长宽都是等比缩放,都一样
}
function applyScale() {
  const designContainer = document.getElementById("designContainer");
  const scale = calculateScale();
  if (designContainer) {
    designContainer.style.transform = `scale(${scale})`;
  }
}

onMounted(() => {
  // // 组件卸载时移除监听
  applyScale();
  window.addEventListener("resize", applyScale);
});

onUnmounted(() => {
  window.removeEventListener("resize", applyScale);
});

动态卡片配置

使用 vue3 components 配合 import.meta.glob 批量注册组件 使用动态插槽 动态插入 v-slot=[slotName]

vue
<Bigscreen ref="bigscreenRef">
  <template v-for="(compName, slotName,) in cardConfig" :key="slotName" v-slot:[slotName]>
    <component :is="compName" />
  </template>
</Bigscreen>

使用暗色 element-plus 主题

只需要在 main 文件引入 import '@/styles/dark/css-vars.css'

批量注册组件

ts
const components = import.meta.glob("@/components/cards/**/*.vue", {
  eager: true,
});

Object.entries(components).forEach(([path, module]) => {
  // 获取组件名称(如:GlobalButton.vue → GlobalButton)
  const componentName = path
    .split("/")
    .pop()
    ?.replace(/\.\w+$/, "");

  // 注册组件
  if (componentName) {
    app.component(componentName, (module as any).default);
  }
});

使用 unocss

需要再首页 main 引入import 'virtual:uno.css'

vite.config.ts 中添加插件 import UnoCSS from 'unocss/vite'

ts
省略...
    plugins: [
      UnoCSS({
        configFile: 'uno.config.ts',
      })
    ]
    ....

使用 ProPlusComponents 高级组件

引入 resolver 避免手动引入 import { PlusProComponentsResolver } from "@plus-pro-components/resolver";

引入 vue-echarts

echarts 就不过多介绍了 vue-echarts 是基于 echars 封装的 vue3 组件 https://vue-echarts.dev/ 一般式按需引入的,对于不知需要用到哪些组件,官方提供了一个工具,将 echarts 的 options 配置拷贝进,就可以返回需要用到哪些组件 https://vue-echarts.dev/#codegen