Przeglądaj źródła

沿街店铺数据增加分类功能

mork 2 tygodni temu
rodzic
commit
c38a408dc1

+ 172 - 11
README.md

@@ -1,22 +1,183 @@
-# kdyjs_ui
+# 开店一件事(kdyjs_ui)
+
+青浦区"开店一件事"可视化平台前端。以 Cesium / SkyScenery 三维地图为核心,叠加 GeoServer 发布的专题图层与本地 GeoJSON 数据,提供开店限制查询、开店选址、沿街店铺分析等功能。
+
+## 功能概览
+
+- **三维地图**:天地图影像/矢量、ArcGIS 影像底图切换,支持相机定位、图层显隐、3D 模型加载
+- **专题图层**:居民小区、机关单位、医院、公园、学校、博物馆、图书馆、机场车站
+- **热力图**:餐饮、居民服务、零售、体育、卫生、娱乐、住宿行业,以及化妆品/奶粉/宠物用品订单分布
+- **店铺数据**:沿街店铺、空置店铺、小区及快递分布 GeoJSON 动态加载
+- **选址分析**:地址搜索(WFS)、点击地图取点、开店限制查询(餐饮油烟、网吧、娱乐场所、烟草零售、噪声污染)
+- **辅助功能**:用户反馈提交(DMS 内容接口)、天气信息、智小青 AI 助手(iframe)、测距、全屏、移动端适配
+
+## 技术栈
+
+| 分类 | 技术 |
+| --- | --- |
+| 框架 | Vue 3 + Vue Router 4 + Vuex 4(vuex-persistedstate) |
+| UI | Element Plus 2 + @element-plus/icons-vue |
+| 三维地图 | Cesium / SkyScenery(`public/static/plugins`) |
+| GIS 服务 | GeoServer WMS / WFS |
+| 其他 | Axios、ECharts、xlsx、Less、turf.js、heatmap.js |
+
+## 目录结构
 
-## Project setup
 ```
-npm install
+kdyjs_ui/
+├── public/
+│   ├── index.html                 # 入口模板,引入 config.js 与地图插件
+│   └── static/
+│       ├── config/config.js       # 运行时全局配置(webConfig)
+│       ├── data/                  # GeoJSON / Excel 业务数据
+│       ├── image/                 # 图标与图片资源
+│       └── plugins/               # Cesium、SkyScenery、jQuery、热力图等第三方库
+├── src/
+│   ├── api/                       # login.js 登录鉴权、interface.js 通用接口
+│   ├── components/
+│   │   ├── Header.vue             # 顶部标题、时间天气、测距、全屏
+│   │   └── Map.vue                # 地图初始化、底图切换、图层/模型加载
+│   ├── router/index.js            # 路由(/ 首页、/login 登录)
+│   ├── store/index.js             # Vuex 状态
+│   ├── utils/                     # request.js 请求封装、constant.js、util.js
+│   ├── views/
+│   │   ├── HomeView.vue           # 首页:地图容器、图层/热力图、店铺数据、用户反馈
+│   │   ├── KDYJS.vue              # 开店限制查询 / 开店选址业务面板
+│   │   └── Login.vue              # 登录页
+│   ├── App.vue
+│   └── main.js
+├── vue.config.js                  # 打包、代理、别名等配置
+└── package.json
 ```
 
-### Compiles and hot-reloads for development
-```
+## 快速开始
+
+环境要求:Node.js 14+(建议 16 / 18),npm 6+。
+
+```bash
+# 安装依赖
+npm install
+
+# 本地开发,默认端口 2118
 npm run serve
-```
 
-### Compiles and minifies for production
-```
+# 生产打包,输出至 dist/
 npm run build
 ```
 
-## 青浦区开店一件事
+开发访问地址:`http://localhost:2118/kdyjs/`。项目 `publicPath` 固定为 `/kdyjs/`,请勿直接访问根路径。
+
+## 配置说明
+
+### 运行时配置 public/static/config/config.js
+
+该文件通过 `index.html` 以全局变量 `webConfig` 引入,**部署后可直接在服务器上修改,无需重新打包**:
+
+- `OAUTH_URL` / `SCRIPT_OAUTH_URL`:统一认证服务地址
+- `KDYJS_URL`:开店限制查询等业务接口
+- `PROXY_URL`:地址检索代理接口
+- `DMS_URL`:内容管理接口(用户反馈提交)
+- `GEOSERVER_URL_WMS` / `GEOSERVER_URL_WFS`:GeoServer 服务地址
+- `layerList` / `heatmapList` / `kdwlList`:图层与热力图清单,增删图层只需改这里
+- `allowText` / `JZText` / `JZTextCYType`:开店限制提示文案
+- `userFeedback`:用户反馈表单对应的栏目与模型 ID
+- `zxqUrl`:智小青 AI 助手地址
+
+### 开发代理 vue.config.js
+
+`devServer.proxy` 将以下前缀转发到后端,本地调试时无需处理跨域:
+
+| 前缀 | 说明 |
+| --- | --- |
+| `/proxy_auth` | 统一认证 |
+| `/proxy_kdyjs` | 开店一件事业务服务 |
+| `/proxy_proxy` | 地址检索代理 |
+| `/proxy_dms` | 内容管理服务 |
+| `/proxy_zwdt` | 政务地图服务(HTTPS) |
+
+## 数据与图层
+
+- 矢量数据位于 `public/static/data/`,包括沿街店铺、空置店铺、小区清单及各行业/快递分布 GeoJSON
+- 热力图数据位于 `public/static/data/heatMapData/`
+- 动态图层通过 `webConfig.layerList` 与 `heatmapList` 配置,由 GeoServer 以 WMS 方式加载
+- 三维模型地址在 `webConfig.modelArr` 中配置(3D Tiles)
+
+## 部署
+
+1. 执行 `npm run build`,产物在 `dist/`
+2. 将 `dist/` 部署到站点 `/kdyjs/` 目录下
+3. 生产环境需在 Nginx(或其他网关)中配置上述 5 个 `/proxy_*` 前缀的反向代理,规则与 `vue.config.js` 中 `devServer.proxy` 保持一致
+4. `dist/static/config/config.js` 可按部署环境单独修改,不必重新构建
+
+## 路由
+
+| 路径 | 页面 | 说明 |
+| --- | --- | --- |
+| `/` | HomeView | 地图首页(默认进入) |
+| `/login` | Login | 账号密码登录 |
+
+## 浏览器支持
+
+面向现代浏览器(Chrome / Edge 等),不支持 IE 11;WebGL 为三维地图运行前提。
+
+## 版本记录
+
+### **2025-08-07**
+
+- 初始版本,包含基础功能
+- 登录流程,添加 token 过期检测
+- 数据上图功能,添加数据上图功能
+- 首页看板图表,添加项目统计
+- config.js配置
+
+### **2025-09-08**
+
+- 开店一件事AIURL地址修改
+- 增加与AI页面的推荐点数据传输,并在地图上加载数据展示
+- 内置登录用户修改
+- 适配移动端展示
+- 餐饮服务项目提示修改,智小青功能嵌入,允许添加麦克风标签
+
+### **2025-09-12**
+
+- 适配移动端界面,样式展示优化
+- 弹窗背景图路径修改
+- PC端界面整体风格重新调整
+- 按钮样式风格调整,增加地图的放大缩小功能
+- 智小青返回推荐图标及弹出修改
+
+### **2025-09-22**
+
+- 开店一件事移动端界面优化
+- 增加窗口关闭和登录调用量统计
+- 数据更新增加盈浦街道数据
+
+### **2026-03-26**
+
+- 增加其它行业的热力图显示
+- 修改提示语和增加赵巷数据
+- 不同类型展示不同的提示词
+- 文字描述内容修改
+
+### **2026-05-25**
+
+- 增加快递物流功能
+- 修改快递物流勾选点击事件
+- 修改小区名称内容显示
+- 修改浏览器无痕操作
+
+### **2026-05-27**
+
+- 增加宠物数据功能
+- 修改点击图层显示的效果
+
+### **2026-06-10**
+
+- 底图TDT显示异常,进行TDT替换,双击事件异常修改
 
-### 配置项
+### **2026-10-21**
 
-- config.js中的配置项为适配域名服务器,在服务器上有修改
+- 沿街店铺数据增加分类功能
+- 地图上按沿街店铺分类展示不同类型的图标
+- 弹框增加类型描述信息,区分是哪种类型
+- 分类多选功能,选择不同类型展示不同相关的数据

Plik diff jest za duży
+ 0 - 0
public/static/data/沿街店铺-20261020.geojson


BIN
public/static/image/point-cy.png


BIN
public/static/image/point-gg.png


BIN
public/static/image/point-jmfw.png


BIN
public/static/image/point-jy.png


BIN
public/static/image/point-ls.png


BIN
public/static/image/point-null.png


BIN
public/static/image/point-syfw.png


BIN
public/static/image/point-ty.png


BIN
public/static/image/point-ws.png


BIN
public/static/image/point-yl.png


BIN
public/static/image/point-ys.png


BIN
public/static/image/point-zs.png


+ 197 - 52
src/views/HomeView.vue

@@ -38,7 +38,31 @@
       <div class="close" @click="closeZXQ">×</div>
       <iframe allow="geolocation; microphone; camera; midi; encrypted-media;" :src="zxqUrl" frameborder="0" ></iframe>
     </div>
-
+    <div :class="isMobile == true ? 'filterCascader-mb':'filterCascader'" v-if=isShowFilter> 
+      <el-cascader
+        v-model="filterArr"
+        :options="filterOptions"
+        :props="filterProps"
+        placeholder="请选择类型"
+        collapse-tags
+        :max-collapse-tags="1"
+        collapse-tags-tooltip
+        @change="handleTypeChange"
+      >
+        <template #default="{ node, data }">
+          <el-tooltip
+            :content="data.label"
+            placement="top"
+            effect="dark"
+          >
+            <div class="cascader-item" style="overflow: hidden;text-align: left;text-overflow: ellipsis;white-space: nowrap;">
+              <el-image style="width: 16px; height: 16px;top: 2px;padding-right: 5px;" :src="`./static/image/${data.icon}`" />
+              <span class="cascader-label">{{ data.label }}</span>
+            </div>
+          </el-tooltip>
+        </template>
+      </el-cascader>
+    </div>
     <!-- <div class="control3D">
       <div class="label">三维模型</div>
       <el-switch v-model="isShow3D" size="large" active-text @change="changeModel" />
@@ -130,25 +154,29 @@
 
     <div class="tips">试运行阶段服务范围:夏阳街道、盈浦街道、赵巷镇</div>
     <div class="yjdpInfoDialog" v-show="yjdpInfoDialogShow">
-      <div class="close" @click="closeYJDP">×</div>
+      <div class="title">
+        <div v-if="nowPoint != null"><el-image style="width: 18px; height: 18px;top: 2px;padding-right: 5px;" :src="getICONPATH(nowPoint.properties['类型'])" />{{ nowPoint.properties["类型"] }}</div>
+        <div class="close" @click="closeYJDP">×</div>
+      </div>
       <div class="content" v-if="nowPoint != null">
         <el-scrollbar>
           <div class="item">店铺名称: {{ nowPoint.properties["店铺名称"] }}</div>
           <div class="item">店铺类型: {{ nowPoint.properties["店铺类型"] }}</div>
           <div class="item">街镇名称: {{ nowPoint.properties["街镇"] }}</div>
-          <div class="item">道路名称: {{ nowPoint.properties["道路名称"] }}</div>
+          <!-- <div class="item">道路名称: {{ nowPoint.properties["道路名称"] }}</div> -->
           <div class="item">店铺地址: {{ nowPoint.properties["地址"] }}</div>
         </el-scrollbar>
       </div>
     </div>
     <div class="kzdpInfoDialog" v-show="kzdpInfoDialogShow">
-      <div class="close" @click="closeKZDP">×</div>
+      <!-- <div class="close" @click="closeKZDP">×</div> -->
+      <div class="title"><div v-if="nowPoint != null">空置店铺</div><div class="close" @click="closeKZDP">×</div></div>
       <div class="content" v-if="nowPoint != null">
         <el-scrollbar>
           <div class="item">店铺名称: {{ nowPoint.properties["店铺名称"] }}</div>
           <div class="item">店铺类型: {{ nowPoint.properties["店铺类型"] }}</div>
           <div class="item">街镇名称: {{ nowPoint.properties["街镇"] }}</div>
-          <div class="item">道路名称: {{ nowPoint.properties["道路名称"] }}</div>
+          <!-- <div class="item">道路名称: {{ nowPoint.properties["道路名称"] }}</div> -->
           <div class="item">店铺地址: {{ nowPoint.properties["地址"] }}</div>
         </el-scrollbar>
       </div>
@@ -467,7 +495,7 @@ export default {
       layerList: arr,
       wmslayer: {},
 
-      curFileName:'沿街店铺-20260317.geojson',
+      curFileName:'沿街店铺-20261020.geojson',
 
       heatmapPanelShow: false,
       heatmapList: arrHeatmap,
@@ -485,7 +513,20 @@ export default {
       kdwlPointTCHandle:1,
       layerRequesting: {},
       layerAbortControllers: {},
-
+      filterArr:['餐饮业','零售业','居民服务业','广播、电视、电影和录音制作业','体育业','娱乐业','商业服务业','住宿业','卫生'],//过滤显示的字段
+      filterOptions:[
+        {value:'餐饮业',label:'餐饮业',icon:'point-cy.png'},
+        {value:'零售业',label:'零售业',icon:'point-ls.png'},
+        {value:'居民服务业',label:'居民服务业',icon:'point-jmfw.png'},
+        {value:'广播、电视、电影和录音制作业',label:'广播、电视、电影和录音制作业',icon:'point-ys.png'},
+        {value:'体育业',label:'体育业',icon:'point-ty.png'},
+        {value:'娱乐业',label:'娱乐业',icon:'point-yl.png'},
+        {value:'商业服务业',label:'商业服务业',icon:'point-syfw.png'},
+        {value:'住宿业',label:'住宿业',icon:'point-zs.png'},
+        {value:'卫生',label:'卫生',icon:'point-ws.png'},
+      ],
+      filterProps:{multiple:true},
+      isShowFilter:false,
       yhfkForm: {
         name: "",
         tele: "",
@@ -886,35 +927,45 @@ export default {
         fetch("./static/data/"+this.curFileName)
           .then(response => response.json())
           .then(function (result) {
+            that.isShowFilter= true;
             // 过滤空店铺数据
-            result=result.features.filter(person => 
-              person.properties['店铺名称'] != '空'
+            const res =result.features.filter(person => 
+              person.properties['店铺名称'] != '空' && person.properties['店铺类型'] != '空'
               // (person.properties['店铺名称'] != '空' && person.properties['街镇'] != '夏阳街道' && person.properties['街镇'] != '盈浦街道')
             );
             // let arr = []
-            // result.forEach(element => {
+            // res.forEach(element => {
             //   arr.push(element.properties['街镇'])
             // });
             //  const uniqueArray = [...new Set(arr)]
             // console.log(uniqueArray);
-
-            that.pointYJDPArr = result.map(function (pointInfo) {
-              return viewer.entities.add(new SkyScenery.Entity({
-                position: SkyScenery.Cartesian3.fromDegrees(pointInfo.geometry.coordinates[0], pointInfo.geometry.coordinates[1]),
-                type: "yjdp",
-                info: {
-                  coor: [pointInfo.geometry.coordinates[0], pointInfo.geometry.coordinates[1]],
-                  properties: pointInfo.properties
-                },
-                billboard: {
-                  image: "./static/image/point-dp.png",
-                  disableDepthTestDistance: Number.POSITIVE_INFINITY,
-                  scale: 0.3,
-                  horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
-                  verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM,
-                }
-              }));
+            let arrs = []
+            that.pointYJDPArr = res.map(function (pointInfo) {
+              // let str = {value: pointInfo.properties['类型'],label: pointInfo.properties['类型']}
+              // arrs.push(str)
+              let arr = [];
+              that.filterOptions.forEach(element => {
+                arr.push(element.label)
+              });
+              if(arr.includes(pointInfo.properties['类型'])){
+                return viewer.entities.add(new SkyScenery.Entity({
+                  position: SkyScenery.Cartesian3.fromDegrees(pointInfo.geometry.coordinates[0], pointInfo.geometry.coordinates[1]),
+                  type: "yjdp",
+                  info: {
+                    coor: [pointInfo.geometry.coordinates[0], pointInfo.geometry.coordinates[1]],
+                    properties: pointInfo.properties
+                  },
+                  billboard: {
+                    image: that.getICONPATH(pointInfo.properties['类型']),
+                    disableDepthTestDistance: Number.POSITIVE_INFINITY,
+                    scale: 0.2,
+                    horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
+                    verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM,
+                  }
+                }));
+              }
             })
+            // that.filterOptions = that.uniqueByKey(arrs, 'value');
           })
       } else {
         // 删除
@@ -924,13 +975,52 @@ export default {
         this.pointYJDPArr = []
         this.yjdpInfoDialogShow = false;
         this.nowPoint = null
+        this.isShowFilter= false;
       }
       this.pointTCHandle();
     },
+    getICONPATH(param){
+      let imgName = "";
+      this.filterOptions.forEach(element => {
+        if(element.label == param){
+          imgName = element.icon
+        }
+      });
+      return  `./static/image/${imgName}`;
+    },
+    /**
+     * 数组对象去重
+     * @param {Array} arr 原始数组
+     * @param {string} key 去重依据字段,如'id'
+     * @returns {Array} 去重后新数组
+     */
+    uniqueByKey(arr, key) {
+      return Object.values(arr.reduce((acc, item) => {
+        if (!acc[item[key]]) {
+          acc[item[key]] = item;
+        }
+        return acc;
+      }, {}));
+    },
     closeYJDP() {
       this.yjdpInfoDialogShow = false;
       this.nowPoint = null
     },
+    handleTypeChange(){
+      // 切换过滤条件时执行
+      const rawArr = JSON.parse(JSON.stringify(this.filterArr))
+      // 当前结构
+      const selectTypes = rawArr.map(item => item[0])
+      viewer.entities.values.forEach(entity => {
+        if(entity.type === 'yjdp') {
+          if(entity.info.properties['类型'] == undefined){
+            entity.show = true
+          }else{
+            entity.show = selectTypes.includes(entity.info.properties['类型']);
+          }
+        }
+      })
+    },
     // 空置店铺
     addkzdp() {
       let that = this;
@@ -941,11 +1031,11 @@ export default {
           .then(response => response.json())
           .then(function (result) {
             // 过滤空店铺数据
-            result=result.features.filter(person => 
-              person.properties['店铺名称'] == '空'
+            const res = result.features.filter(person => 
+              person.properties['店铺名称'] == '空' || person.properties['店铺类型'] == '空'
             );
             // console.log('[ ddd ] >', result)
-            that.pointKZDPArr = result.map(function (pointInfo) {
+            that.pointKZDPArr = res.map(function (pointInfo) {
               return viewer.entities.add(new SkyScenery.Entity({
                 position: SkyScenery.Cartesian3.fromDegrees(pointInfo.geometry.coordinates[0], pointInfo.geometry.coordinates[1], 0.5),
                 type: "kzdp",
@@ -954,9 +1044,9 @@ export default {
                   properties: pointInfo.properties
                 },
                 billboard: {
-                  image: "./static/image/kzdp.png",
+                  image: "./static/image/point-null.png",
                   disableDepthTestDistance: Number.POSITIVE_INFINITY,
-                  scale: 0.3,
+                  scale: 0.2,
                   horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
                   verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM,
                 }
@@ -1012,7 +1102,7 @@ export default {
               that.nowPoint = entity.info;
               that.nowPoint["type"] = "yjdp";
               let xy = that.lonlatConvertToScreenXY(entity.info.coor)
-              document.querySelector(".yjdpInfoDialog").style.top = (xy.y - 250) + "px";
+              document.querySelector(".yjdpInfoDialog").style.top = (xy.y - 220) + "px";
               document.querySelector(".yjdpInfoDialog").style.left = (xy.x - 150) + "px";
               // document.querySelector("#yjdpInfoDialog")
               // that.removePanel();
@@ -1022,7 +1112,7 @@ export default {
               that.nowPoint = entity.info;
               that.nowPoint["type"] = "kzdp";
               let xy = that.lonlatConvertToScreenXY(entity.info.coor)
-              document.querySelector(".kzdpInfoDialog").style.top = (xy.y - 250) + "px";
+              document.querySelector(".kzdpInfoDialog").style.top = (xy.y - 220) + "px";
               document.querySelector(".kzdpInfoDialog").style.left = (xy.x - 150) + "px";
             } else if (entity.type == "tjdw") {
               that.tjdwInfoDialogShow = true
@@ -1104,10 +1194,10 @@ export default {
 
           } else {
             if (obj.type == "yjdp") {
-              document.querySelector(".yjdpInfoDialog").style.top = (xy.y - 250) + "px";
+              document.querySelector(".yjdpInfoDialog").style.top = (xy.y - 220) + "px";
               document.querySelector(".yjdpInfoDialog").style.left = (xy.x - 150) + "px";
             } else if (obj.type == "kzdp") {
-              document.querySelector(".kzdpInfoDialog").style.top = (xy.y - 250) + "px";
+              document.querySelector(".kzdpInfoDialog").style.top = (xy.y - 220) + "px";
               document.querySelector(".kzdpInfoDialog").style.left = (xy.x - 150) + "px";
             } else if (obj.type == "tjdw") {
               document.querySelector(".tjdwInfoDialog").style.top = (xy.y - 300) + "px";
@@ -2556,26 +2646,37 @@ export default {
     top: 0px;
     left: 0px;
     max-width: 400px;
-    height: 200px;
+    // height: 200px;
+    height: auto;
     // background: #01346f99;
     background: #ffffff;
     border-radius: 10px;
-
-    .close {
-      font-size: 24px;
+    .title{
       position: absolute;
       top: 6px;
-      right: 8px;
+      display: flex;
       line-height: 24px;
-      width: 24px;
-      height: 24px;
+      left: 20px;
+      right: 10px;
+      font-weight: bold;
+      justify-content: space-between;
+    }
+    .close {
+      font-size: 24px;
+      // position: absolute;
+      // top: 6px;
+      // right: 8px;
+      // line-height: 24px;
+      // width: 24px;
+      // height: 24px;
       cursor: pointer;
       // color: #ffffff;
       color: #000000;
     }
 
     .content {
-      height: 160px;
+      min-width: 280px;
+      height: 130px;
       // color: #ffffff;
       color: #000000;
       margin: 30px 0px 10px 20px;
@@ -2592,26 +2693,36 @@ export default {
     top: 0px;
     left: 0px;
     width: 300px;
-    height: 200px;
+    height:auto;
+    // height: 200px;
     // background: #01346f99;
     background: #ffffff;
     border-radius: 10px;
-
-    .close {
-      font-size: 24px;
+    .title {
       position: absolute;
       top: 6px;
-      right: 8px;
+      display: flex;
       line-height: 24px;
-      width: 24px;
-      height: 24px;
+      left: 20px;
+      right: 10px;
+      font-weight: bold;
+      justify-content: space-between;
+    }
+    .close {
+      font-size: 24px;
+      // position: absolute;
+      // top: 6px;
+      // right: 8px;
+      // line-height: 24px;
+      // width: 24px;
+      // height: 24px;
       cursor: pointer;
       // color: #ffffff;
       color: #000000;
     }
 
     .content {
-      height: 160px;
+      height: 130px;
       // color: #ffffff;
       color: #000000;
       margin: 30px 0px 10px 20px;
@@ -2763,6 +2874,40 @@ export default {
     }
   }
 
+  .filterCascader{
+    position: absolute;
+    top: 80px;
+    right: 140px;
+  }
+  .filterCascader-mb{
+    position: absolute;
+    top: 65px;
+    right: 58px;
+  }
+  /* 重点:穿透级联面板内部样式,限制节点宽度 */
+  :deep(.el-cascader-panel .el-cascader-node__label) {
+    width: 100% !important;
+    padding: 0 !important;
+  }
+  .cascader-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    width: 100%;
+    max-width: 200px; /* 控制这一行最大宽度,根据面板宽度调整! */
+  }
+  .cascader-label {
+    flex: 1;
+    min-width: 0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .cascader-icon {
+    flex-shrink: 0;
+    color: #909399;
+  }
+
   .zxqIframeContainer-mb-mx {
     position: absolute;
     top: 0px;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików