Newer
Older
DH_Apicture / src / components / Map / Map.vue
@wudi wudi 19 days ago 26 KB 1
<template>
  <div class="mapPage">
    <div id="cesiumContainer"></div>
  </div>
</template>
<script>
import {
  reactive,
  toRefs,
  onBeforeMount,
  onMounted,
  nextTick,
  onBeforeUnmount,
} from "vue";
import bus from "@/bus";
import axios from "axios";
const baseUrl = import.meta.env.VITE_APP_MAP_BASE_API || "";
export default {
  components: {},
  props: {
    initJson: {
      type: String,
      default: () => "/static/libs/mapbox/style/floodOneMap.json",
    },
    loadCallback: {
      type: Function,
      default: () => function () {},
    },
  },
  setup(props, event) {
    const allData = reactive({
      layerIds: [],
    });
    let geojson = {
      point: { type: "FeatureCollection", features: [] },
      polygon: { type: "FeatureCollection", features: [] },
      linestring: { type: "FeatureCollection", features: [] },
    };
    let map = null;
    let config = null;
    let highlightLayers = {
      highlight_point: 1,
      highlight_polygon: 1,
      highlight_linestring: 1,
    };
    let isMvtCache = null;
    let isGeojsonCache = null;
    const initeMap = async () => {
      console.log("baseUrl", baseUrl);
      config = (await axios.get(baseUrl + props.initJson)).data;
      isMvtCache = config.params.mvt.includes("/v1/mvt");
      isGeojsonCache = config.params.geojson.includes("/v1/geojson");
      const { basemap } = config.params;
      const { style, localStyle } = config.params.init;
      let styleJson = (
        await axios.get(
          localStyle
            ? baseUrl + localStyle
            : (style.includes("http") ? "" : location.origin + basemap) +
                config.params.init.style
        )
      ).data;
      styleJson.glyphs = styleJson.glyphs.includes("http")
        ? styleJson.glyphs
        : location.origin + baseUrl + styleJson.glyphs;
      styleJson.sprite = styleJson.sprite.includes("http")
        ? styleJson.sprite
        : location.origin + baseUrl + styleJson.sprite;

      window.newfiberMap = new mapboxL7.Scene({
        id: "cesiumContainer",
        map: new mapboxL7.Mapbox({
          // style:  localStyle?localStyle:(style.includes('http')?'':location.origin + config.params.basemap) + config.params.init.style,
          ...config.params.init,
          style: styleJson,
        }),
      });
      newfiberMap.config_ = config;
      map = newfiberMap.map;
      map.ogcLayers = [];
      newfiberMap.unLoadLayers = [];

      map.on("load", async () => {
        /*          const mouseLocation = new mapboxL7.MouseLocation({
            transform: position => {
              return position;
            },
          });
          newfiberMap.addControl(mouseLocation);*/
        let { sprites, l7, mapbox } = config.params;
        (sprites || []).forEach((url) =>
          map.style._loadSprite(
            url.includes("http")
              ? baseUrl + url
              : window.location.origin.split("#")[0] + url
          )
        );
        ((l7 || []).images || []).forEach((item) =>
          newfiberMap.addImage(
            item.name,
            item.url.includes("http")
              ? baseUrl + item.url
              : window.location.origin.split("#")[0] + item.url
          )
        );
        ((mapbox || []).images || []).forEach((item) =>
          item.url.includes("gif")
            ? setGifImage(item)
            : newfiberMap.map.loadImage(
                item.url.includes("http")
                  ?  item.url
                  : baseUrl + item.url,
                (error, image) => newfiberMap.map.addImage(item.name, image)
              )
        );
        await getGeoJSON();
        await loadData();
        newfiberMap
          .getLayers()
          .filter(
            (i) => highlightLayers[i.newfiberId] && (highlightLayers[i.newfiberId] = i)
          );
        props.loadCallback && props.loadCallback();
        getLayerIdByClick();

        let timeout = setTimeout(() => {
          let { pitch, center } = config.params.init;
          pitch && newfiberMap.map.setPitch(pitch);
          center && newfiberMap.map.setCenter(center);
          (config.orders || []).forEach((item) => setLayerOrder(...item));
          clearTimeout(timeout);
        }, 2000);
        // three();

        // getLayerIdByClick();

        let layerIds = (config.mapbox || []).map((i) => i.id);
        map.on("click", (e) => {
          Object.values(highlightLayers).forEach((layer) => {
            if (layer.setData) layer.setData({ type: "FeatureCollection", features: [] });
          });
          const feature = (
            map.queryRenderedFeatures([
              [e.point.x - 10 / 2, e.point.y - 10 / 2],
              [e.point.x + 10 / 2, e.point.y + 10 / 2],
            ]) || []
          ).filter((i) => layerIds.includes(i.layer.id))[0];
          if (feature && feature.properties.geometry)
            setHighlight(
              turf.feature(
                Terraformer.WKT.parse(feature.properties.geometry),
                feature.properties
              )
            );
          event.emit(
            "map-click",
            Object.values(e.lngLat),
            (feature || {}).properties,
            ((feature || {}).layer || {}).id
          );
        });
      });
      async function getGeoJSON() {
        config.geojsonMvts = {};
        let url = config.mapbox
          .filter((i) => i.mType == "geojsonMvt")
          .map((item) => {
            let { mType, columns, geom_column, id, key } = item;
            let sourceID = geom_column ? key + "_" + (geom_column || "") : key;
            let params = [];
            if (columns) params.push(`columns=${columns}`);
            if (geom_column) params.push(`geom_column=${geom_column}`);
            return (
              config.params.geojson +
              `/${key}${
                !isGeojsonCache ? "_" + (geom_column || "geometrys") + ".geojson" : ""
              }?` +
              params.join("&")
            );
          });
        url = Array.from(new Set(url));
        let fetchs = url.map((url) => axios.get(url));
        let results = await Promise.all(fetchs);
        url.forEach(
          (url, index) =>
            (config.geojsonMvts[url] = {
              ...results[index].data,
              features:
                results[index].data.features &&
                results[index].data.features.map((i) => ({
                  ...i,
                  properties: {
                    ...i.properties,
                    name: i.properties.name
                      ? i.properties.name.replaceAll("\\n", "\n")
                      : undefined,
                  },
                })),
            })
        );
      }

      async function setGifImage(item) {
        const arrayBuffer = await fetchImageAsArrayBuffer(item.url);
        const canvasIcon = new mapboxgl1.CanvasIcon(
          item.width || 512,
          item.height || 512,
          {
            autoPixelRatio: true,
            onAdd(ctx) {
              ctx.gif = mapboxgl1.AnimatedGIF.fromBuffer(arrayBuffer, ctx.context, {});
            },
            renderCallback(ctx) {
              ctx.gif.update(performance.now() / 1000);
              ctx.gif.updateFrame();
            },
            postRender(ctx) {
              map.triggerRepaint();
            },
          }
        );
        if (!map.hasImage(item.name)) map.addImage(item.name, canvasIcon);
        async function fetchImageAsArrayBuffer(url) {
          try {
            const response = await fetch(url);
            if (!response.ok) {
              throw new Error("Network response was not ok");
            }
            const arrayBuffer = await response.arrayBuffer();
            return arrayBuffer;
          } catch (error) {
            console.error("There has been a problem with your fetch operation:", error);
          }
        }
      }
    };

    const loadData = async () => {
      let { mapbox, l7, ogc } = config;

      if (ogc)
        ogc.forEach((item) => {
          let { id, type, params, methods } = item;
          Object.keys(params).forEach(
                  (i) =>
                          (params[i] =
                                  typeof params[i] == "string"
                                          ? params[i].includes("||")
                                          ? eval(params[i])
                                          : params[i]
                                          : params[i])
          );
          let layer = new mapboxgl1[type](params);
          layer.newfiberId = id;
          methods.forEach((method) => {
            let ms = method.params.map((i) =>
                    typeof i == "string" ? (i.includes("||") ? eval(i) : i) : i
            );
            layer[method.name](...ms);
          });
          map.ogcLayers.push(layer);
        });

      if (l7)
        l7.forEach((item) => {
          let { id, columns, filter } = item;
          let params = [];
          if (columns) params.push(`columns=${columns}`);
          if (filter) params.push(`filter=${filter}`);
          let urlKey =
            config.params.geojson +
            `/${id}${!isGeojsonCache ? "_" + "geometrys" + ".geojson" : ""}?` +
            params.join("&");
          let _geojson = config.geojsonMvts[urlKey];
          id
            ? _geojson
              ? initMapBoxL7Class(item, _geojson)
              : axios
                  .get(urlKey)
                  .then(({ data: geojson }) => initMapBoxL7Class(item, geojson))
            : initMapBoxL7Class(item, geojson[item.key] || turf.featureCollection([]));
        });



      if (mapbox) initMapBoxLayer();

      function initMapBoxL7Class({ type, params, methods, id, show, key }, geojson) {
        let layer = new mapboxL7[type](params).source(geojson);
        methods.forEach((method) => {
          let ms = method.params.map((i) =>
            typeof i == "string" ? (i.includes("||") ? eval(i) : i) : i
          );
          layer[method.name](...ms);
        });
        layer.newfiberId = key;
        newfiberMap.addLayer(layer);
        if (!show) layer.hide();
        allData.layerIds.push(key);
      }

      function initMapBoxLayer() {
        mapbox.forEach((item) => {
          let { mType, columns, geom_column, id, key } = item;
          let sourceID = geom_column ? key + "_" + (geom_column || "") : key;
          let params = [];
          if (columns) params.push(`columns=${columns}`);
          if (geom_column) params.push(`geom_column=${geom_column}`);
          if (!!!map.getSource(sourceID)) {
            let flag = mType == "geojson";
            flag
              ? map.addSource(sourceID, { type: mType, data: geojson[key] })
              : map.addSource(sourceID, {
                  ...(mType == "mvt"
                    ? {
                        type: "vector",
                        tiles: [
                          config.params.mvt +
                            `/${key}/{z}/{x}/{y}${!isMvtCache ? ".pbf" : ""}?` +
                            params.join("&"),
                        ],
                        tileSize: 512,
                        scheme: "xyz",
                        maxzoom: 14,
                        minzoom: 1,
                      }
                    : {
                        type: "geojson",
                        data:
                          config.geojsonMvts[
                            config.params.geojson +
                              `/${key}${
                                !isGeojsonCache
                                  ? "_" + (geom_column || "geometrys") + ".geojson"
                                  : ""
                              }?` +
                              params.join("&")
                          ],
                      }),
                });
          }
          map.addLayer({ ...item, source: sourceID });
          map.moveLayer(id);
          allData.layerIds.push(id);
        });
      }
    };

    const setLayerVisible = ({ layername, isCheck, values = [] }) => {
      if (!!!layername) return;
      if (((config || {}).filter || [])[layername]) {
        config.filter[layername].layerName.forEach((name, index) => {
          let layer = newfiberMap.getLayers().filter((i) => i.newfiberId == name)[0];
          let fValues = config.filter[layername].filter[index] || [];
          if (layer) {
            let geojson = layer.getSource().originData;
            geojson.features.forEach(
              (i) =>
                fValues.includes(i.properties.type) && (i.properties.visible = isCheck)
            );
            layer.setData(geojson);
          }
          if (map.getLayer(name)) {
            fValues = fValues.filter(Boolean);
            let filter = map.getFilter(name);
            if (filter) {
              if (values.length) fValues = values;
              if (filter[0] == "in") {
                isCheck
                  ? filter.push(...fValues)
                  : (filter = filter.filter((i) => !fValues.includes(i)));
              } else {
                let _filter =
                  filter[filter.length - 1][filter[filter.length - 1].length - 1][
                    filter[filter.length - 1][filter[filter.length - 1].length - 1]
                      .length - 1
                  ];
                isCheck
                  ? filter[filter.length - 1][filter[filter.length - 1].length - 1][
                      filter[filter.length - 1][filter[filter.length - 1].length - 1]
                        .length - 1
                    ].push(...fValues)
                  : (filter[filter.length - 1][filter[filter.length - 1].length - 1][
                      filter[filter.length - 1][filter[filter.length - 1].length - 1]
                        .length - 1
                    ] = _filter.filter((i) => !fValues.includes(i)));
              }
              map.setFilter(name, filter);
            }
          }
        });
        config.filter[layername].easeTo &&
          isCheck &&
          map.easeTo({ ...config.filter[layername].easeTo, pitch: 51 });
      }

      let features = Object.values(geojson)
        .map((i) => i.features)
        .flat(Infinity)
        .filter((i) => i.properties.type == layername);
      if (features.length) {
        let types = Array.from(
          new Set(features.map((i) => i.geometry.type.toLocaleLowerCase()))
        );
        types.forEach((type1) => {
          let filter = map.getFilter(type1);
          if (filter[0] == "in") {
            isCheck
              ? filter.push(layername)
              : (filter = filter.filter((i) => i != layername));
          } else {
            let _filter =
              filter[filter.length - 1][filter[filter.length - 1].length - 1][
                filter[filter.length - 1][filter[filter.length - 1].length - 1].length - 1
              ];
            isCheck
              ? filter[filter.length - 1][filter[filter.length - 1].length - 1][
                  filter[filter.length - 1][filter[filter.length - 1].length - 1].length -
                    1
                ].push(layername)
              : (filter[filter.length - 1][filter[filter.length - 1].length - 1][
                  filter[filter.length - 1][filter[filter.length - 1].length - 1].length -
                    1
                ] = _filter.filter((i) => i != layername));
          }
          map.setFilter(type1, filter);
        });
      }

      let layer = newfiberMap.getLayers().filter((i) => i.newfiberId == layername)[0];
      if (!!!layer) {
        layer = newfiberMap.unLoadLayers.filter((i) => i.newfiberId == layername)[0];
        isCheck && layer && newfiberMap.addLayer(layer);
      }
      layer && layer[["hide", "show"][Number(isCheck)]]();

      layer = map.getLayer(layername);
      layer &&
        map.setLayoutProperty(layername, "visibility", isCheck ? "visible" : "none");

      layer = map.getLayer(layername + "_text");
      layer &&
        map.setLayoutProperty(
          layername + "_text",
          "visibility",
          isCheck ? "visible" : "none"
        );

      layer = map.getLayer(layername + "_top");
      layer &&
        map.setLayoutProperty(
          layername + "_top",
          "visibility",
          isCheck ? "visible" : "none"
        );

      layer = map.ogcLayers.filter((i) => i.id == layername)[0];
      layer && (layer.map ? layer[isCheck ? "show" : "hide"]() : layer.addTo(map));
    };

    const getGeojsonByType = ({ type, callback }) => {
      let geojs = turf.featureCollection(
        Object.values(geojson)
          .map((i) => i.features)
          .flat(Infinity)
          .filter((i) => i.properties.type == type)
      );
      callback && callback(geojs);
    };

    const removeLayers = (ids) => {
      let { getLayers, removeAllLayer, removeLayer: removeLayerL7 } = newfiberMap;
      let { getLayer, removeLayer } = map;

      if (!!!ids) {
        allData.layerIds.forEach((id) => getLayer(id) && removeLayer(id));
        return removeAllLayer();
      }

      ids.forEach((id) => {
        getLayer(id) && removeLayer(id);
        getLayers().forEach((layer) => layer.newfiberId == id && removeLayerL7(layer));
      });
    };

    const closeAllLayer = () => {
      if (newfiberMap.getLayerByName("dynamicLine")) {
        newfiberMap.removeLayer(newfiberMap.getLayerByName("dynamicLine"));
      }
      if (newfiberMap && newfiberMap.RainsLayer) {
        newfiberMap.RainsLayer.destroy();
      }
      if (newfiberMap.popup) newfiberMap.popup.remove();
      let point = (newfiberMap.map.getFilter("point") || []).flat(Infinity);
      let line = (newfiberMap.map.getFilter("linestring") || []).flat(Infinity);
      let polygon = (newfiberMap.map.getFilter("polygon") || []).flat(Infinity);
      if (point.concat(line).concat(polygon).length == 0) return;
      config.ogc
        .map((i) => i.id)
        .concat(config.l7.filter((i) => i.id).map((i) => i.key))
        .concat(Object.keys(config.filter))
        .concat(
          Array.from(
            new Set(
              [point, line, polygon]
                .map((i) => _.slice(i, i.indexOf("")))
                .flat()
                .filter(Boolean)
            )
          )
        )
        .forEach((i) => setLayerVisible({ layername: i, isCheck: false }));
      bus.emit("openMapPopup", {});
      bus.emit("setHighlight", []);
      bus.emit("clearTuLiCheck", false);
    };

    const removeMapDatas = (types) => {
      types = types.map(i => i+'_text').concat(types);
      !!!types
        ? Object.keys(geojson).forEach((key) => (geojson[key].features.length = 0))
        : Object.keys(geojson).forEach(
            (key) =>
              (geojson[key].features = geojson[key].features.filter(
                (feature) => !types.includes(feature.properties.type)
              ))
          );
      return refreshGeoJSON();
    };

    const setLegendData = (list) => {
      list.forEach((i) => {
        let points = [];
        if (!i.data) return;
        i.data.features.forEach((a) => {
          if (!!!a || !!!a.properties) return;
          (a.properties.minzoom = a.properties.minzoom || 0),
            (a.properties.color = a.properties.color || "rgba(0,0,0,1)"),
            (a.properties.type = a.properties.type || i.layername);
          a.properties.name =
            a.properties.name ||
            a.properties.Name ||
            a.properties.stName ||
            a.properties.pointNumber ||
            a.properties.sewageName ||
            a.properties.sectionName ||
            a.properties.pumpName;
          if (
            a.properties.name &&
            !turf.getType(a).toLocaleLowerCase().includes("point")
          ) {
            let center = turf.center(a);
            center.properties = { ...a.properties, minzoom: 10,type:a.properties.type +"_text" };
            center.properties.geometry = Terraformer.WKT.convert(center.geometry);
            points.push(center);
          }
        });
        i.data.features = i.data.features.concat(points);
      });

      let types = {};
      list
        .map((i) => i.data && i.data.features)
        .filter(Boolean)
        .flat(Infinity)
        .forEach((feature) => {
          if (!!!feature || !!!feature.properties) return;
          feature.properties.geometry = Terraformer.WKT.convert(feature.geometry);
          let type = feature.geometry.type.toLocaleLowerCase();
          let flag = type.includes("multi");
          type = type.replaceAll("multi", "");
          let features = null;
          if (flag) features = turf.flatten(feature).features;
          !!!types[type] && (types[type] = []);
          types[type].push(...(features ? features : [feature]));
        });

      Object.keys(types).forEach((key) => geojson[key].features.push(...types[key]));
      refreshGeoJSON();
    };

    const refreshGeoJSON = () => {
      Object.keys(geojson).forEach(
        (key) => map.getSource(key) && map.getSource(key).setData(geojson[key])
      );
      map.triggerRepaint();
    };

    const setGeoJSON = ({ json, key }) => {
      let layer = newfiberMap.getLayers().filter((i) => i.newfiberId == key)[0];
      if (!!!layer) {
        layer = newfiberMap.unLoadLayers.filter((i) => i.newfiberId == key)[0];
        layer && layer.setData(json);
        layer && newfiberMap.addLayer(layer);
      }
      if (layer) return layer.setData(json);
      setLegendData([{ data: json, layername: key, type: "point" }]);
    };

    const beansToMap = ({
      beans,
      fields = { geometry: "geometry", lng: "lng", lat: "lat", name: "name" },
      type,
      isConvert,
    }) => {
      let geojson = turf.featureCollection(
        beans.filter((i) => i[fields.geometry] || (Boolean(Number(i[fields.lng])) && Boolean(Number(i[fields.lat])))).map((i) => {
            let feature = null;
            if (i[fields.geometry]) feature = turf.feature(Terraformer.WKT.parse(i[fields.geometry]), {...i, name: i[fields.name],});
            if (i[fields.lng]) feature = turf.point([i[fields.lng], i[fields.lat]].map(Number), {...i, name: i[fields.name],});
            feature.properties.visible = false;
            return feature;
          }).filter(Boolean));
      isConvert && (geojson = gcoord.transform(geojson, gcoord.AMap, gcoord.WGS84));
      setGeoJSON({ json: geojson, key: type });
    };

    const setHighlight = (features = {}) => {
      let array = {};
      features = features.forEach ? features : [features];
      Object.values(highlightLayers).forEach(
        (layer) =>
          layer.setData && layer.setData({ type: "FeatureCollection", features: [] })
      );
      features.forEach((feature) => {
        if (!!!feature || !Boolean(Object.keys(feature).length)) return;
        let geometry = feature.geometry;
        let type = geometry.type.toLocaleLowerCase().replaceAll("multi", "");
        let key = Object.keys(highlightLayers).filter(
          (key) => key.lastIndexOf(type) > -1
        )[0];
        if (!array[key]) array[key] = [];
        array[key].push(feature);
      });
      Object.keys(array).forEach(
        (key) =>
          highlightLayers[key] &&
          highlightLayers[key].setData({
            type: "FeatureCollection",
            features: array[key],
          })
      );
    };

    const setLayerOrder = (sourceLayer, targetLayer) => {
      map.moveLayer(sourceLayer, targetLayer);
    };

    const getLayerIdByClick = () => {
      map.getStyle().layers.forEach((i) =>
        map.on("click", i.id, (a, b, c, d) => {
          console.log("layer-id", a.features[0].layer.id);
        })
      );
    };

    const updateStyle = (styleJson) => {
      styleJson.glyphs = styleJson.glyphs.includes("http")
        ? styleJson.glyphs
        : location.origin + styleJson.glyphs;
      styleJson.sprite = styleJson.sprite.includes("http")
        ? styleJson.sprite
        : location.origin + styleJson.sprite;

      const currentStyle = map.getStyle();
      styleJson.sources = Object.assign({}, currentStyle.sources, styleJson.sources);
      let labelIndex = styleJson.layers.findIndex((el) => {
        return el.id == "waterway-label";
      });
      if (labelIndex === -1) {
        labelIndex = styleJson.layers.length;
      }
      const appLayers = currentStyle.layers.filter((el) => {
        return (
          el.source &&
          el.source != "mapbox://mapbox.satellite" &&
          el.source != "mapbox" &&
          el.source != "composite"
        );
      });
      styleJson.layers = [
        ...styleJson.layers.slice(0, labelIndex),
        ...appLayers,
        ...styleJson.layers.slice(labelIndex, -1),
      ];
      map.setStyle(styleJson);
    };

    const addDynamicLine = ({ c_layer, c_layer1 }) => {
      if (newfiberMap.getLayerByName("dynamicLine")) {
        newfiberMap.removeLayer(newfiberMap.getLayerByName("dynamicLine"));
      }
      let dynamicLineJson = turf.featureCollection(
        newfiberMap.map
          .getSource("sx_wn_hm_merge")
          ._data.features.filter(
            (feature) =>
              feature.properties.c_layer.includes(c_layer) &&
              feature.properties.c_layer.includes(c_layer1)
          )
      );
      console.log(c_layer, dynamicLineJson);
      let layer = new mapboxL7.LineLayer({
        name: "dynamicLine",
      })
        .source(dynamicLineJson)
        .size(3)
        .shape("line")
        .color("color")
        .animate({
          interval: 1,
          duration: 2,
          trailLength: 0.8,
        });
      newfiberMap.addLayer(layer);
    };
    onMounted(() => {
      initeMap();
      bus.on("beansToMap", beansToMap);
      bus.on("setLayerVisible", setLayerVisible);
      bus.on("setLegendData", setLegendData);
      bus.on("setGeoJSON", setGeoJSON);
      bus.on("getGeojsonByType", getGeojsonByType);
      bus.on("removeLayers", removeLayers);
      bus.on("removeMapDatas", removeMapDatas);
      bus.on("setHighlight", setHighlight);
      bus.on("updateStyle", updateStyle);
      bus.on("closeAllLayer", closeAllLayer);
      bus.on("addDynamicLine", addDynamicLine);
    });

    onBeforeUnmount(() => {
      bus.off("beansToMap");
      bus.off("setLayerVisible");
      bus.off("setLegendData");
      bus.off("setGeoJSON");
      bus.off("getGeojsonByType");
      bus.off("removeMapDatas");
      bus.off("setHighlight");
      bus.off("updateStyle");
      bus.off("closeAllLayer");
      bus.off("addDynamicLine");
      if (newfiberMap) {
        if (newfiberMap.RainsLayer) newfiberMap.RainsLayer.destroy();
        newfiberMap.destroy();
        newfiberMap = null;
      }
    });

    return {
      ...toRefs(allData),
    };
  },
};
</script>
<style>
.mapPage {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0px;
  top: 0px;
  z-index: 0;
  background: lavender;
}

#cesiumContainer {
  width: 100%;
  height: 100%;
  position: absolute;
}

.l7-control-mouse-location {
  background: none;
}

.l7-control-mouse-location {
  color: #ffffff;
}
</style>