Sfoglia il codice sorgente

Merge branch 'master' of http://39.106.8.246:3003/web/wanda-adm

YaolongHan 4 anni fa
parent
commit
2558d4d8b3

+ 5 - 4
package.json

@@ -15,9 +15,9 @@
   "dependencies": {
     "@babel/runtime": "^7.12.5",
     "@persagy-web/base": "2.2.1",
-    "@persagy-web/big": "2.2.48",
-    "@persagy-web/draw": "2.2.12",
-    "@persagy-web/graph": "2.2.44",
+    "@persagy-web/big": "2.2.49",
+    "@persagy-web/draw": "2.2.13",
+    "@persagy-web/graph": "2.2.45",
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
     "element-ui": "^2.15.0",
@@ -33,7 +33,8 @@
     "vue-router": "^3.4.8",
     "vue-svgicon": "^3.2.9",
     "vuex": "^3.5.1",
-    "vuex-module-decorators": "^1.0.1"
+    "vuex-module-decorators": "^1.0.1",
+    "polybooljs": "1.2.0"
   },
   "devDependencies": {
     "@babel/core": "^7.12.10",

+ 17 - 4
src/layout/components/Sidebar/SidebarItem.vue

@@ -1,11 +1,14 @@
 <template>
-    <div v-if="!item.meta || !item.meta.hidden" :class="['menu-wrapper', isCollapse ? 'simple-mode' : 'full-mode', { 'first-level': isFirstLevel }]">
-        <template v-if="theOnlyOneChild && !theOnlyOneChild.children">
+    <div v-if="!item.meta || !item.meta.hidden"
+         :class="['menu-wrapper', isCollapse ? 'simple-mode' : 'full-mode', { 'first-level': isFirstLevel }]">
+        <template v-if="theOnlyOneChild && !theOnlyOneChild.children && !alwaysShowRootMenu">
             <sidebar-item-link v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
-                <el-menu-item :index="resolvePath(theOnlyOneChild.path)" :class="{ 'submenu-title-noDropdown': isFirstLevel }">
+                <el-menu-item :index="resolvePath(theOnlyOneChild.path)"
+                              :class="{ 'submenu-title-noDropdown': isFirstLevel }">
                     <!-- <svg-icon v-if="theOnlyOneChild.meta.icon" :name="theOnlyOneChild.meta.icon" /> -->
                     <i v-if="theOnlyOneChild.meta.icon" class="iconfont" :class="theOnlyOneChild.meta.icon"></i>
-                    <span v-if="theOnlyOneChild.meta.title" slot="title" class="title-name">{{ theOnlyOneChild.meta.title }}</span>
+                    <span v-if="theOnlyOneChild.meta.title" slot="title"
+                          class="title-name">{{ theOnlyOneChild.meta.title }}</span>
                 </el-menu-item>
             </sidebar-item-link>
         </template>
@@ -50,6 +53,13 @@ export default class extends Vue {
     @Prop({ default: true }) private isFirstLevel!: boolean;
     @Prop({ default: "" }) private basePath!: string;
 
+    get alwaysShowRootMenu() {
+        if (this.item.meta && this.item.meta.alwaysShow) {
+            return true
+        }
+        return false
+    }
+
     get showingChildNumber() {
         if (this.item.children) {
             const showingChildren = this.item.children.filter((item) => {
@@ -106,6 +116,7 @@ export default class extends Vue {
         &:hover {
             background-color: $subMenuHover !important;
         }
+
         &:active {
             background-color: $subMenuHover !important;
         }
@@ -147,9 +158,11 @@ export default class extends Vue {
     color: #ffffff;
     padding: 0 5px;
 }
+
 .title-name {
     color: #ffffff;
 }
+
 .svg-icon {
     margin-right: 16px;
 }

+ 2 - 1
src/layout/components/Sidebar/index.vue

@@ -10,7 +10,8 @@
             :collapse-transition="false"
             mode="vertical"
         >
-            <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" :is-collapse="isCollapse" />
+            <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path"
+                          :is-collapse="isCollapse"/>
         </el-menu>
     </el-scrollbar>
 </template>

+ 35 - 15
src/router/index.ts

@@ -30,7 +30,7 @@ export default new Router({
             component: () =>
                 import(
                     /* webpackChunkName: "login" */ "@/views/login/index.vue"
-                ),
+                    ),
             meta: { hidden: true }
         },
         {
@@ -43,7 +43,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "project" */ "@/views/project/index.vue"
-                        ),
+                            ),
                     meta: {
                         title: "项目管理",
                         icon: "icon-xiangmuguanli"
@@ -63,7 +63,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "allDetails" */ "@/layout/components/Navbar/MessageSever/MsgAllDetails.vue"
-                        ),
+                            ),
                     meta: {
                         title: "消息中心"
                     }
@@ -83,7 +83,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "build" */ "@/views/manage/build/index.vue"
-                        ),
+                            ),
                     meta: {
                         title: "建筑楼层管理"
                         // icon: "tree"
@@ -94,7 +94,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "model" */ "@/views/manage/model/index.vue"
-                        ),
+                            ),
                     meta: {
                         title: "模型修改任务"
                         // icon: "table"
@@ -105,7 +105,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "path" */ "@/views/manage/path/index.vue"
-                        ),
+                            ),
                     meta: {
                         hidden: true,
                         title: "模型后台处理监控"
@@ -127,7 +127,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "device" */ "@/views/maintain/device/index.vue"
-                        ),
+                            ),
                     meta: { title: "设备" }
                 },
                 {
@@ -135,7 +135,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "space" */ "@/views/maintain/space/index.vue"
-                        ),
+                            ),
                     meta: { title: "空间" }
                 },
                 {
@@ -179,7 +179,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "tiepoint" */ "@/views/scene/tiepoint/index.vue"
-                        ),
+                            ),
                     meta: { title: "绑点" }
                 },
                 {
@@ -187,7 +187,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "system" */ "@/views/scene/system/index.vue"
-                        ),
+                            ),
                     meta: { title: "系统图" }
                 },
                 {
@@ -195,8 +195,16 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "plane" */ "@/views/scene/plane/index.vue"
-                        ),
+                            ),
                     meta: { title: "平面图" }
+                },
+                {
+                    path: "mapping",
+                    component: () =>
+                        import(
+                            /* webpackChunkName: "mapping" */ "@/views/management/mapping/index.vue"
+                            ),
+                    meta: { title: "xxxxxxx" }
                 }
             ]
         },
@@ -219,10 +227,22 @@ export default new Router({
         {
             path: "/management",
             component: Layout,
+            redirect: "/management/mapping",
             meta: {
                 title: "综合管理与其他",
-                icon: "icon-zongheguanliyuqita"
+                icon: "icon-zongheguanliyuqita",
+                alwaysShow: true
             },
+            children: [
+                {
+                    path: "mapping",
+                    component: () =>
+                        import(
+                            /* webpackChunkName: "mapping" */ "@/views/management/mapping/index.vue"
+                            ),
+                    meta: { title: "映射关系维护" }
+                }
+            ]
         },
         {
             path: "/error",
@@ -238,7 +258,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "error-page-401" */ "@/views/error-page/401.vue"
-                        ),
+                            ),
                     name: "Page401",
                     meta: {
                         title: "401",
@@ -250,7 +270,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "error-page-404" */ "@/views/error-page/404.vue"
-                        ),
+                            ),
                     name: "404",
                     meta: {
                         title: "404",
@@ -262,7 +282,7 @@ export default new Router({
                     component: () =>
                         import(
                             /* webpackChunkName: "error-page-demo" */ "@/views/error-page/demo.vue"
-                        ),
+                            ),
                     name: "demo",
                     meta: {
                         title: "demo",

+ 5 - 5
src/store/modules/app.ts

@@ -14,12 +14,12 @@ export enum DeviceType {
 }
 
 export interface IAppState {
-    device: DeviceType;
-    uploaderList: any[];
+    device: DeviceType
+    uploaderList: any[]
     sidebar: {
-        opened: boolean;
-        withoutAnimation: boolean;
-    };
+        opened: boolean
+        withoutAnimation: boolean
+    }
 }
 
 @Module({ dynamic: true, store, name: "app" })

+ 2 - 2
src/styles/_variables.scss

@@ -17,7 +17,7 @@ $subMenuHover:#001528;
 $subMenuActiveText:#f4f4f5;
 $menuBg:#304156;
 $menuText:#bfcbd9;
-$menuActiveText:#409EFF; // Also see settings.sidebarTextTheme
+$menuActiveText:#409EFF; // 参考 settings.sidebarTextTheme
 
 // Login page
 $lightGray: #eee;
@@ -25,7 +25,7 @@ $darkGray:#889aa4;
 $loginBg: #2d3a4b;
 $loginCursorColor: #fff;
 
-// The :export directive is the magic sauce for webpack
+// The :export 指定输出
 // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript
 :export {
   menuBg: $menuBg;

+ 375 - 0
src/utils/graph/CustomWall.ts

@@ -0,0 +1,375 @@
+import { SMouseEvent, SUndoStack } from "@persagy-web/base/lib";
+import { SItemStatus } from "@persagy-web/big/lib";
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil"
+import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw/lib";
+import { SGraphItem, SGraphPointListInsert, SGraphStyleItem } from "@persagy-web/graph/lib";
+
+/**
+ * 用户自定义墙
+*/
+export class CustomWall extends SGraphStyleItem {
+    /** X 坐标最小值 */
+    private minX = Number.MAX_SAFE_INTEGER;
+    /** X 坐标最大值 */
+    private maxX = Number.MIN_SAFE_INTEGER;
+    /** Y 坐标最小值 */
+    private minY = Number.MAX_SAFE_INTEGER;
+    /** Y 坐标最大值 */
+    private maxY = Number.MIN_SAFE_INTEGER;
+    /** 折点信息 */
+    pointList: SPoint[] = [];
+    /** 是否绘制完成 */
+    _status: SItemStatus = SItemStatus.Normal;
+    get status(): SItemStatus {
+        return this._status;
+    }
+    set status(v: SItemStatus) {
+        this._status = v;
+        this.undoStack.clear();
+        this.update();
+    }
+
+    /** 鼠标移动时的点 */
+    protected lastPoint: SPoint | null = null;
+
+    /** 全局灵敏度 */
+    dis: number = 10;
+    /** 灵敏度转换为场景长度 */
+    protected sceneDis: number = 10;
+    /** 当前点索引 */
+    protected curIndex: number = -1;
+    /** 当前点 */
+    private curPoint: SPoint | null = null;
+    /** undo/redo 堆栈 */
+    private undoStack: SUndoStack = new SUndoStack();
+
+    /**
+     * 构造函数
+     *
+     * @param parent    父级
+     * @param list      坐标集合
+     */
+    constructor(parent: null | SGraphItem, list: SPoint[]);
+
+    /**
+     * 构造函数
+     *
+     * @param parent    父级
+     * @param list      第一个坐标
+     */
+    constructor(parent: null | SGraphItem, list: SPoint);
+
+    /**
+     * 构造函数
+     *
+     * @param parent    父级
+     * @param list      第一个坐标|坐标集合
+     */
+    constructor(parent: null | SGraphItem, list: SPoint | SPoint[]) {
+        super(parent);
+        // 数据类型为 SPoint
+        if (list instanceof SPoint) {
+            this.pointList.push(list);
+        } else {
+            this.pointList = list;
+        }
+    }
+
+    /**
+     * 添加点至数组中
+     *
+     * @param p         添加的点
+     * @param index     添加到的索引
+     */
+    private addPoint(p: SPoint, index?: number): void {
+        // 添加的索引存在且索引值有效
+        if (index && this.canHandle(index)) {
+            this.pointList.splice(index, 0, p);
+            this.recordAction(SGraphPointListInsert, [
+                this.pointList,
+                p,
+                index
+            ]);
+        } else {
+            // 否则
+            this.pointList.push(p);
+            this.recordAction(SGraphPointListInsert, [this.pointList, p]);
+        }
+
+        this.update();
+    }
+
+    /**
+     * 是否可以添加点到数组中
+     *
+     * @param index     要添加到的索引
+     * @return 是否可添加
+     */
+    private canHandle(index: number): boolean {
+        return index >= 0 && index <= this.pointList.length;
+    }
+
+    /**
+     * 根据索引删除点
+     *
+     * @param index     删除点
+     */
+    deletePoint(index: number): void {
+        // 索引值有效且折线列表有2个以上点
+        if (this.canHandle(index) && this.pointList.length > 2) {
+            const p = new SPoint(
+                this.pointList[this.curIndex].x,
+                this.pointList[this.curIndex].y
+            );
+            this.pointList.splice(index, 1);
+            this.recordAction(SGraphPointListInsert, [
+                this.pointList,
+                p,
+                index
+            ]);
+            this.curIndex = -1;
+            this.curPoint = null;
+            this.update();
+        }
+    }
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     *
+     * @param x     x 坐标
+     * @param y     y 坐标
+     */
+    moveToOrigin(x: number, y: number): void {
+        super.moveToOrigin(x, y);
+        // 遍历点集列表
+        this.pointList = this.pointList.map(
+            (t): SPoint => {
+                t.x = t.x + x;
+                t.y = t.y + y;
+                return t;
+            }
+        );
+        this.x = 0;
+        this.y = 0;
+    }
+
+    /**
+     * 获取点击点与点集中距离最近点
+     *
+     * @param p     鼠标点击点
+     */
+    findNearestPoint(p: SPoint): void {
+        let len = this.sceneDis;
+        // 遍历点集列表
+        for (let i = 0; i < this.pointList.length; i++) {
+            let dis = SMathUtil.pointDistance(
+                p.x,
+                p.y,
+                this.pointList[i].x,
+                this.pointList[i].y
+            );
+            // 当前距离小于最短距离
+            if (dis < len) {
+                len = dis;
+                this.curIndex = i;
+                this.curPoint = new SPoint(
+                    this.pointList[this.curIndex].x,
+                    this.pointList[this.curIndex].y
+                );
+            }
+        }
+    }
+
+    /**
+     * 计算增加点的位置
+     *
+     * @param p     鼠标点击点
+     */
+    findAddPos(p: SPoint): void {
+        let len = SMathUtil.pointToLine(
+            p,
+            new SLine(this.pointList[0], this.pointList[1])
+        ),
+            index = 0;
+        // 折线点集多余2
+        if (this.pointList.length > 2) {
+            for (let i = 1; i < this.pointList.length - 1; i++) {
+                let dis = SMathUtil.pointToLine(
+                    p,
+                    new SLine(this.pointList[i], this.pointList[i + 1])
+                );
+                if (dis.MinDis < len.MinDis) {
+                    len = dis;
+                    index = i;
+                }
+            }
+        }
+        // 最短距离在可吸附范围内且吸附点存在
+        if (len.MinDis < this.sceneDis && len.Point) {
+            this.addPoint(len.Point, index + 1);
+        }
+    }
+
+    /**
+     * shift 垂直水平创建或编辑
+     *
+     * @param event     事件
+     * @return 事件对象
+     */
+    compare(event: SMouseEvent): SMouseEvent {
+        // 点集列表存在
+        if (this.pointList.length) {
+            let last = new SPoint(event.x, event.y);
+            if (this.status == SItemStatus.Create) {
+                last = this.pointList[this.pointList.length - 1];
+            } else if (this.status == SItemStatus.Edit) {
+                if (this.curIndex > 1) {
+                    last = this.pointList[this.curIndex - 1];
+                }
+            }
+
+            const dx = Math.abs(event.x - last.x);
+            const dy = Math.abs(event.y - last.y);
+            // 纵向变量更大
+            if (dy > dx) {
+                event.x = last.x;
+            } else {
+                // 否则
+                event.y = last.y;
+            }
+        }
+
+        return event;
+    }
+
+    /**
+     * 记录相关动作并推入栈中
+     *
+     * @param SGraphCommand     相关命令类
+     * @param any               对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const command = new SGraphCommand(this.scene, this, ...any);
+        this.undoStack.push(command);
+    }
+
+    /**
+     * Item 对象边界区域
+     *
+     * @return 外接矩阵
+     */
+    boundingRect(): SRect {
+        // 点集列表有值
+        if (this.pointList.length) {
+            this.minX = this.pointList[0].x;
+            this.maxX = this.pointList[0].x;
+            this.minY = this.pointList[0].y;
+            this.maxY = this.pointList[0].y;
+            this.pointList.forEach((it): void => {
+                let x = it.x,
+                    y = it.y;
+
+                // 如果数据 x 坐标小于 x 坐标最小值
+                if (x < this.minX) {
+                    this.minX = x;
+                }
+
+                // 如果数据 y 坐标小于 y 坐标最小值
+                if (y < this.minY) {
+                    this.minY = y;
+                }
+
+                // 如果数据 x 坐标大于 x 坐标最小值
+                if (x > this.maxX) {
+                    this.maxX = x;
+                }
+
+                // 如果数据 y 坐标大于 y 坐标最小值
+                if (y > this.maxY) {
+                    this.maxY = y;
+                }
+            });
+        }
+
+        return new SRect(
+            this.minX,
+            this.minY,
+            this.maxX - this.minX,
+            this.maxY - this.minY
+        );
+    }
+
+    /**
+     * 判断点是否在区域内
+     *
+     * @param x     x 坐标
+     * @param y     y 坐标
+     * @return 是否在区域内
+     */
+    contains(x: number, y: number): boolean {
+        let p = new SPoint(x, y);
+        // 遍历点集列表
+        for (let i = 1; i < this.pointList.length; i++) {
+            let PTL = SMathUtil.pointToLine(
+                p,
+                new SLine(
+                    this.pointList[i - 1].x,
+                    this.pointList[i - 1].y,
+                    this.pointList[i].x,
+                    this.pointList[i].y
+                )
+            );
+            // 点在吸附范围内
+            if (PTL.MinDis < this.sceneDis) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 撤销操作
+     */
+    undo(): void {
+        // 非正常态
+        if (this._status != SItemStatus.Normal) {
+            this.undoStack.undo();
+        }
+    }
+
+    /**
+     * 重做操作
+     */
+    redo(): void {
+        // 非正常态
+        if (this._status != SItemStatus.Normal) {
+            this.undoStack.redo();
+        }
+    }
+
+    /**
+     * 取消操作执行
+     */
+    cancelOperate(): void {
+        // 创建态
+        if (this.status == SItemStatus.Create) {
+            this.parent = null;
+            this.releaseItem();
+        } else if (this.status == SItemStatus.Edit) {
+            // 编辑态
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+        }
+    }
+
+    /**
+     * Item 绘制操作
+     *
+     * @param painter   绘制对象
+     */
+    onDraw(painter: SPainter): void {
+        // 缓存场景长度
+        this.sceneDis = painter.toPx(this.dis);
+    }
+}

+ 235 - 0
src/utils/graph/DivideFloorScene.ts

@@ -0,0 +1,235 @@
+import { SMouseEvent } from "@persagy-web/base/lib";
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
+import { SPoint, SRect } from "@persagy-web/draw/lib";
+import { FloorScene } from "./FloorScene";
+import { HighlightItem } from "./HighlightItem";
+import { ShadeItem } from "./ShadeItem";
+
+export class DivideFloorScene extends FloorScene {
+    /** 划分item  */
+    cutItem: ShadeItem | null = null;
+    /** 是否开启切分  */
+    isCutting: boolean = false;
+
+    /** 是否开启吸附  */
+    private _isAbsorbing: boolean = true;
+    get isAbsorbing(): boolean {
+        return this._isAbsorbing;
+    } // Get isAbsorbing
+    set isAbsorbing(v: boolean) {
+        this._isAbsorbing = v;
+    } // Set isAbsorbing
+
+    /** 高亮item  */
+    highLight: HighlightItem | null = null;
+
+    /**
+     * 清除划分区域
+     */
+    clearCut(): void {
+        if (this.cutItem) {
+            this.grabItem = null;
+            this.removeItem(this.cutItem);
+            this.cutItem = null;
+            this.isCutting = false;
+            this.view && this.view.update();
+        }
+    } // Function clearCut()
+
+    /**
+    * 鼠标按下事件
+    *
+    * @param   event   保存事件参数
+    * @return  boolean
+    */
+    onMouseDown(event: SMouseEvent): boolean {
+        if (event.buttons == 1) {
+            if (this.isCutting) {
+                // 判断是否开启吸附,并且有吸附的点
+                if (
+                    this.isAbsorbing &&
+                    this.highLight &&
+                    this.highLight.visible
+                ) {
+                    event.x = this.highLight.point.x;
+                    event.y = this.highLight.point.y;
+                }
+                if (this.cutItem) {
+                    return this.cutItem.onMouseDown(event);
+                } else {
+                    let point = new SPoint(event.x, event.y);
+                    let cut = new ShadeItem(null, point);
+                    this.addItem(cut);
+                    this.cutItem = cut;
+                    this.grabItem = cut;
+                    return true;
+                }
+            }
+        }
+        return super.onMouseDown(event)
+    }
+
+    /**
+     *  吸附空间
+     *
+     *  @param  event   鼠标事件对象
+     */
+    onMouseMove(event: SMouseEvent): boolean {
+        super.onMouseMove(event);
+        if (this.isAbsorbing) {
+            if (!this.highLight) {
+                this.highLight = new HighlightItem(null);
+                this.addItem(this.highLight);
+            }
+            this.highLight.visible = false;
+            // if (!this.absorbShade(event)) {
+            this.absorbSpace(event);
+            // }
+        }
+        return false;
+    } // Function onMouseMove()
+
+    /**
+     *  吸附空间
+     *
+     *  @param  event   鼠标事件对象
+     *  @return boolean 是否找到吸附的对象
+     */
+    absorbSpace(event: SMouseEvent): boolean {
+        if (!this.highLight) {
+            return false;
+        }
+        let absorbLen = 1000;
+        if (this.view) {
+            absorbLen = 10 / this.view.scale;
+        }
+        let P = this.absorbSpacePoint(event, absorbLen);
+        if (P.Point) {
+            this.highLight.distance = P.MinDis;
+            this.highLight.point = new SPoint(P.Point.X, -P.Point.Y);
+            this.highLight.visible = true;
+            return true;
+        } else {
+            let L = this.absorbSpaceLine(event, absorbLen);
+            if (L.Line && L.Point) {
+                this.highLight.distance = L.MinDis;
+                this.highLight.point = L.Point;
+                this.highLight.line = L.Line;
+                this.highLight.visible = true;
+                return true;
+            }
+            return false;
+        }
+    } // Function absorbSpace()
+
+    /**
+     *  吸附空间点
+     *
+     *  @param  event       鼠标事件对象
+     *  @param  absorbLen   吸附距离
+     *  @return MinDis      吸附的点
+     */
+    absorbSpacePoint(event: SMouseEvent, absorbLen: number): any {
+        let minPointDis = Number.MAX_SAFE_INTEGER;
+        let Point;
+        this.spaceList.map((space): void => {
+            if (
+                DivideFloorScene.isPointInAbsorbArea(
+                    new SPoint(event.x, event.y),
+                    space.minX,
+                    space.maxX,
+                    space.minY,
+                    space.maxY
+                )
+            ) {
+                space.data.OutLine.forEach((item): void => {
+                    let minDis = SMathUtil.getMinDisPoint(
+                        new SPoint(event.x, event.y),
+                        item
+                    );
+                    if (
+                        minDis &&
+                        minDis.MinDis < absorbLen &&
+                        minDis.MinDis < minPointDis
+                    ) {
+                        minPointDis = minDis.MinDis;
+                        Point = minDis.Point;
+                    }
+                });
+            }
+        });
+        return {
+            MinDis: minPointDis,
+            Point: Point
+        };
+    } // Function absorbSpacePoint()
+
+    /**
+     *  点是否在吸附区域内
+     *
+     *  @param  p       要判断的点
+     *  @param  minX    空间区域
+     *  @param  minY    空间区域
+     *  @param  maxX    空间区域
+     *  @param  maxY    空间区域
+     */
+    static isPointInAbsorbArea(
+        p: SPoint,
+        minX: number,
+        maxX: number,
+        minY: number,
+        maxY: number
+    ): boolean {
+        let rect = new SRect(
+            minX - 1000,
+            minY - 1000,
+            maxX - minX + 2000,
+            maxY - minY + 2000
+        );
+        return rect.contains(p.x, p.y);
+    } // Function isPointInAbsorbArea()
+
+    /**
+         *  吸附空间线
+         *
+         *  @param  event       鼠标事件对象
+         *  @param  absorbLen   吸附距离
+         *  @return PointToLine 吸附的线
+         */
+    absorbSpaceLine(event: SMouseEvent, absorbLen: number): any {
+        let minPointDis = Number.MAX_SAFE_INTEGER;
+        let Point, Line;
+        this.spaceList.forEach((space): void => {
+            if (
+                DivideFloorScene.isPointInAbsorbArea(
+                    new SPoint(event.x, event.y),
+                    space.minX,
+                    space.maxX,
+                    space.minY,
+                    space.maxY
+                )
+            ) {
+                space.data.OutLine.forEach((item): void => {
+                    let minDisLine = SMathUtil.getMinDisLine(
+                        new SPoint(event.x, event.y),
+                        item
+                    );
+                    if (
+                        minDisLine &&
+                        minDisLine.MinDis < absorbLen &&
+                        minDisLine.MinDis < minPointDis
+                    ) {
+                        minPointDis = minDisLine.MinDis;
+                        Point = minDisLine.Point;
+                        Line = minDisLine.Line;
+                    }
+                });
+            }
+        });
+        return {
+            MinDis: minPointDis,
+            Point: Point,
+            Line: Line
+        };
+    } // Function absorbSpaceLine()
+}

+ 1 - 1
src/utils/graph/FloorScene.ts

@@ -268,7 +268,7 @@ export class FloorScene extends SGraphScene {
     /**
      * 清空业务空间
      */
-    removeAllZone(){
+    removeAllZone() {
         this.zoneList.forEach(t => {
             this.removeItem(t);
         })

+ 75 - 0
src/utils/graph/HighlightItem.ts

@@ -0,0 +1,75 @@
+import { ItemOrder } from "@persagy-web/big/lib";
+import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw/lib";
+import { SGraphItem, SGraphStyleItem } from "@persagy-web/graph/lib";
+
+/**
+ * 吸附时高亮对象
+ *
+ * @author  郝建龙
+ */
+export class HighlightItem extends SGraphStyleItem {
+    /** 对象与鼠标位置距离   */
+    distance: number = 0;
+    /** 对象类型   */
+    private type: number = 1;
+
+    /** 点对象数据-当吸附的为线时,此点为垂线与线段的交点   */
+    _point: SPoint = new SPoint();
+    get point(): SPoint {
+        return this._point;
+    } // Get point
+    set point(v: SPoint) {
+        this._point = v;
+        this.type = 1;
+        this.update();
+    } // Set point
+
+    /** 点对象数据   */
+    _line: SLine = new SLine();
+    get line(): SLine {
+        return this._line;
+    } // Get line
+    set line(v: SLine) {
+        this._line = v;
+        this.type = 2;
+        this.update();
+    } // Set line
+
+    /**
+     * 构造函数
+     *
+     * @param   parent  指向父对象
+     */
+    constructor(parent: SGraphItem | null) {
+        super(parent);
+        this.visible = false;
+        this.zOrder = ItemOrder.highLightOrder;
+        this.strokeColor = new SColor('#409EFF')
+        this.fillColor = new SColor('#ff8d00')
+    } // Constructor
+
+    /**
+     * Item对象边界区域
+     *
+     * @return	SRect
+     */
+    boundingRect(): SRect {
+        return new SRect(this.point.x, this.point.y, 10, 10);
+    } // Function boundingRect()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter painter对象
+     */
+    onDraw(painter: SPainter): void {
+        if (this.type == 2) {
+            painter.pen.color = this.strokeColor;
+            painter.pen.lineWidth = painter.toPx(6);
+            painter.drawLine(this.line);
+        }
+        painter.pen.color = SColor.Transparent;
+        painter.brush.color = this.fillColor;
+        painter.drawCircle(this.point.x, this.point.y, painter.toPx(5));
+    } // Function onDraw()
+} // Class HighlightItem

+ 172 - 0
src/utils/graph/ShadeItem.ts

@@ -0,0 +1,172 @@
+import { SMouseEvent } from "@persagy-web/base/lib";
+import { ItemOrder } from "@persagy-web/big/lib";
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
+import { SColor, SLineCapStyle, SPainter, SPoint, SPolygonUtil } from "@persagy-web/draw/lib";
+import { SGraphItem, SGraphStyleItem } from "@persagy-web/graph/lib";
+
+/**
+ * 遮罩item
+ *
+ * @author  郝建龙
+ */
+export class ShadeItem extends SGraphStyleItem {
+    /** 轮廓线坐标  */
+    outLine: SPoint[] = [];
+    /** 是否闭合    */
+    closeFlag = false;
+    /** 鼠标移动点  */
+    private lastPoint = new SPoint();
+
+    /**
+     * 构造函数
+     *
+     * @param parent    指向父对象
+     * @param data      遮罩起点数据
+     */
+    constructor(parent: SGraphItem | null, data: SPoint); // Constructor
+
+    /**
+     * 构造函数
+     *
+     * @param parent    指向父对象
+     * @param data      遮罩轮廓线数据
+     */
+    constructor(parent: SGraphItem | null, data: SPoint[]); // Constructor
+
+    /**
+     * 构造函数
+     *
+     * @param parent    指向父对象
+     * @param data      遮罩起点数据 | 遮罩轮廓线数据
+     */
+    constructor(parent: SGraphItem | null, data: SPoint[] | SPoint) {
+        super(parent);
+        if (data instanceof Array) {
+            this.outLine = data;
+            this.createMask();
+        } else {
+            this.outLine.push(data);
+            this.lastPoint = data;
+        }
+        this.zOrder = ItemOrder.shadeOrder;
+        this.fillColor = new SColor('#00000080')
+    } // Constructor
+
+    /**
+     * 鼠标按下事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseDown(event: SMouseEvent): boolean {
+        if (!this.closeFlag && event.buttons == 1) {
+            if (
+                this.lastPoint.x == this.outLine[0].x &&
+                this.lastPoint.y == this.outLine[0].y &&
+                this.outLine.length >= 3
+            ) {
+                this.createMask();
+                return true;
+            }
+            let p = new SPoint(event.x, event.y);
+            this.lastPoint.x = p.x;
+            this.lastPoint.y = p.y;
+            this.outLine.push(p);
+        }
+        this.update();
+        return true;
+    } // Function onMouseDown()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseMove(event: SMouseEvent): boolean {
+        if (!this.closeFlag) {
+            this.lastPoint = new SPoint(event.x, event.y);
+            if (this.outLine.length >= 3) {
+                let le = SMathUtil.pointDistance(
+                    this.lastPoint.x,
+                    this.lastPoint.y,
+                    this.outLine[0].x,
+                    this.outLine[0].y
+                );
+                // @ts-ignore
+                let scale = this.parent.scene.view.scale;
+                if (le * scale < 30) {
+                    this.lastPoint.x = this.outLine[0].x;
+                    this.lastPoint.y = this.outLine[0].y;
+                }
+            }
+        }
+        this.update();
+        return true;
+    } // Function onMouseMove()
+
+    /**
+     * 鼠标抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseUp(event: SMouseEvent): boolean {
+        return false;
+    } // Function onMouseUp()
+
+    /***
+     * 键盘按键弹起事件
+     *
+     * @param	event         事件参数
+     */
+    onKeyUp(event: KeyboardEvent): void {
+        if (event.keyCode == 13 && this.outLine.length >= 3) {
+            this.createMask();
+        }
+    } // Function onKeyUp()
+
+    /**
+     * 创建蒙版 
+     *
+     */
+    createMask(): void {
+        this.closeFlag = true;
+        this.releaseItem();
+        this.$emit('createSuc')
+        this.update();
+    } // Function createMask()
+
+    /**
+     * 判断点是否在区域内
+     *
+     * @param x
+     * @param y
+     */
+    contains(x: number, y: number): boolean {
+        let arr = this.outLine;
+        return SPolygonUtil.pointIn(x, y, arr);
+    } // Function contains()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter       painter对象
+     */
+    onDraw(painter: SPainter): void {
+        painter.pen.lineCapStyle = SLineCapStyle.Square;
+        if (this.closeFlag) {
+            painter.pen.color = SColor.Transparent;
+            painter.brush.color = this.fillColor;
+            painter.drawPolygon(this.outLine);
+        } else {
+            painter.pen.color = new SColor("#ff0000");
+            painter.pen.lineWidth = painter.toPx(3);
+            painter.drawPolyline(this.outLine);
+            painter.drawLine(
+                this.outLine[this.outLine.length - 1],
+                this.lastPoint
+            );
+        }
+    } // Function onDraw()
+} // Class SceneMarkItem

+ 143 - 0
src/utils/graph/generate.ts

@@ -0,0 +1,143 @@
+import { SPoint } from "@persagy-web/draw/lib";
+// @ts-ignore
+import { intersect, polygon, segments, combine, selectUnion, selectDifference, selectDifferenceRev} from "polybooljs";
+
+export class generate {
+    /**
+     * 计算角平分线上的距离这两条直线l的点  
+     * 
+     * @param p1 第一个点
+     * @param p2 第二个点 也是2条线的交点
+     * @param p3 第三个点
+     * @param l  距离2条线的距离
+    */
+    static getBisector(p1: SPoint, p2: SPoint, p3: SPoint, l: number) {
+        const dy1 = p1.y - p2.y;
+        const dx1 = p1.x - p2.x;
+
+        const dy2 = p3.y - p2.y;
+        const dx2 = p3.x - p2.x;
+
+        // 线1的斜率
+        const k1 = dy1 / dx1;
+        // 线2的斜率
+        const k2 = dy2 / dx2;
+        // 线1与x轴的夹角
+        const temp1 = Math.atan(k1)
+        const a1 = k1 >= 0 ? temp1 : temp1 + Math.PI;
+        // 线2与x轴的夹角
+        const temp2 = Math.atan(k2)
+        const a2 = k2 >= 0 ? temp2 : temp2 + Math.PI;
+        // 角平分线斜率
+        const k = Math.tan((a1 + a2) / 2);
+        // 角平分线b
+        const b = p2.y - k * p2.x;
+        // 距离两条线l的点 到交点p2的距离
+        const Lb2 = l / Math.sin(Math.abs(a1 - a2) / 2);
+        // 将距离公式与直线方程连立,并且将直线方程代入,得到一元二次方程 Ax^2 + Bx + C = 0;然后根据得根公式得到2个解
+        const A = k * k + 1;
+        const B = 2 * k * b - 2 * p2.x - 2 * k * p2.y;
+        const C = b * b - Lb2 * Lb2 + p2.x * p2.x + p2.y * p2.y - 2 * b * p2.y;
+        // 求解
+        const X1 = (-B + Math.sqrt(B * B - 4 * A * C)) / (2 * A);
+        const X2 = (-B - Math.sqrt(B * B - 4 * A * C)) / (2 * A);
+        const Y1 = k * X1 + b;
+        const Y2 = k * X2 + b;
+
+        return [Number(X1.toFixed(2)), Number(Y1.toFixed(2)), Number(X2.toFixed(2)), Number(Y2.toFixed(2))]
+    }
+
+    /**
+     * 计算一条线的垂线上距离线l的2个点
+     * 
+     * @param p1 点1
+     * @param p2 点2
+     * @param l  距离这条线的距离
+    */
+    static getVertical(p1: SPoint, p2: SPoint, l: number) {
+        const dy1 = p1.y - p2.y;
+        const dx1 = p1.x - p2.x;
+
+        // 线1的斜率
+        const k1 = dy1 / dx1;
+        // 垂线的斜率
+        const k = -1 / k1;
+        // 垂线的b
+        const b = p1.y - k * p1.x;
+        // 将距离公式与直线方程连立,并且将直线方程代入,得到一元二次方程 Ax^2 + Bx + C = 0;然后根据得根公式得到2个解
+        const A = k * k + 1;
+        const B = 2 * k * b - 2 * p1.x - 2 * k * p1.y;
+        const C = b * b - l * l + p1.x * p1.x + p1.y * p1.y - 2 * b * p1.y;
+        // 求解
+        const X1 = (-B + Math.sqrt(B * B - 4 * A * C)) / (2 * A);
+        const X2 = (-B - Math.sqrt(B * B - 4 * A * C)) / (2 * A);
+        const Y1 = k * X1 + b;
+        const Y2 = k * X2 + b;
+
+        return [Number(X1.toFixed(2)), Number(Y1.toFixed(2)), Number(X2.toFixed(2)), Number(Y2.toFixed(2))]
+    }
+
+    /**
+     * 计算线段交点
+     *
+     * @param   line1   线段1
+     * @param   line2   线段2
+     * @return  SPoint  交点 null 平行但不重合 'repeat' 重合
+     */
+    static lineIntersection(
+        p1: SPoint, p2: SPoint, p3: SPoint, p4: SPoint
+    ): SPoint | null | string {
+        let k1 = (p2.y - p1.y) / (p2.x - p1.x);
+        let b1 = p2.y - k1 * p2.x;
+        let k2 = (p4.y - p3.y) / (p4.x - p3.x);
+        let b2 = p3.y - k2 * p3.x;
+        if (k1 == k2) {
+            if (b1 == b2) {
+                return "repeat";
+            }
+            return null;
+        }
+        let intersectionX = (b2 - b1) / (k1 - k2);
+        let intersectionY = k1 * intersectionX + b1;
+        // 取线段上的最大最小值可以上下换
+        let minX = Math.min(p1.x, p2.x);
+        let maxX = Math.max(p3.x, p4.x);
+        if (intersectionX >= minX && intersectionX <= maxX) {
+            return new SPoint(intersectionX, intersectionY);
+        }
+        return null;
+    }
+
+
+    static llll() {
+        const a = {
+            regions: [
+                [[0, 0], [0, 200], [200, 200], [200, 0]],
+                // [[150, 150], [250, 150], [250, 250], [150, 250]],
+            ],
+            inverted: false
+        }
+        const b = {
+            regions: [
+                [[150, 150], [250, 150], [250, 250], [150, 250]],
+            ],
+            inverted: false
+        }
+        const start = +new Date()
+        const sa = segments(a)
+        const sb = segments(b)
+        const comb = combine(sa, sb)
+        // const start = +new Date()
+        // const poly = segments(a)
+        // console.log(segments(a))
+        // const end = +new Date();
+        // console.log(end - start);
+        // console.log(polygon(poly));
+        console.log(comb);
+        const selecomb = selectDifference(comb)
+        console.log(selecomb)
+        console.log(polygon(selecomb))
+        const end = +new Date();
+        console.log(end - start);
+    }
+}

+ 15 - 13
src/views/maintain/device/index.vue

@@ -32,7 +32,6 @@
         </div>
         <!--        添加/编辑 设备-->
         <el-scrollbar style="height:400px;">
-
             <el-dialog
                 :title="deviceMsg"
                 :visible.sync="dialogVisible"
@@ -40,11 +39,14 @@
             >
                 <template slot="title">
                     <div class="alertTitle">
-                        <span>添加设备</span>
-                        <el-button type="text"><span class="el-icon-question"></span>维护帮助</el-button>
+                        <span>{{ deviceMsg }}</span>
+                        <el-button type="text" >
+                            <span class="el-icon-question" />
+                            <a href="/datacenter/object/equip/downloads" download="维护帮助"> 维护帮助</a>
+                           </el-button>
                     </div>
                 </template>
-                <template v-if="status == '添加'">
+                <template v-if="deviceMsg == '添加设备'">
                     <template v-if="next">
                         <div class="align" style="height: 400px">
                             <span class="text ">设备类别</span>
@@ -62,9 +64,7 @@
                         </span>
                     </template>
                     <template v-else>
-                        <div>
-                            <dataForm :deviceHeaders="deviceHeaders" ref="dataForm"/>
-                        </div>
+                        <dataForm :deviceHeaders="deviceHeaders" ref="dataForm" :currRowContent="currRowContent"/>
                         <span slot="footer" class="dialog-footer">
                          <el-button type="primary" @click="handleDataForm">确 定</el-button>
                          <el-button @click="handlePosition">维护位置</el-button>
@@ -80,7 +80,7 @@
                         </span>
                     </template>
                     <template v-else>
-                        <dataForm :deviceHeaders="deviceHeaders"  ref="dataForm" :currRowContent="currRowContent"/>
+                        <dataForm :deviceHeaders="deviceHeaders" ref="dataForm" :currRowContent="currRowContent"/>
                         <span slot="footer" class="dialog-footer">
                          <el-button type="primary" @click="handleDataForm">确 定</el-button>
                          <el-button @click="handlePosition">维护位置</el-button>
@@ -155,7 +155,7 @@ export default class extends Vue {
     // 下一步
     next = true
     // 弹窗 title
-    deviceMsg = '添加设备'
+    deviceMsg = ''
     // 默认当前阶段
     currentHeader = ''
     // 主体数据
@@ -164,8 +164,7 @@ export default class extends Vue {
     deviceHeaders = {}
     // 当前行数据
     currRowContent = {}
-    // 弹窗显示
-    status = '添加'
+
     // 维护位置开关
     maintain = ''
     // 传到维护位置的设备信息
@@ -380,7 +379,7 @@ export default class extends Vue {
 
     // 添加设备
     addDevice() {
-        this.status = '添加'
+        this.deviceMsg = '添加设备'
         this.dialogVisible = true
         this.currRowContent = {}
     }
@@ -388,6 +387,7 @@ export default class extends Vue {
 
     // 维护位置
     handlePosition() {
+        this.currentRow= this.$refs.dataForm.form
         this.$refs.dataForm.submitForm(this.handlePositionSave)
     }
 
@@ -415,7 +415,7 @@ export default class extends Vue {
 
     // 编辑当前行
     handleCurrentEdit(val: object) {
-        this.status = '编辑'
+        this.deviceMsg = '编辑设备'
         this.currRowContent = val
         this.handleNext()
         this.dialogVisible = true
@@ -497,9 +497,11 @@ export default class extends Vue {
         this.$refs.deviceGraph.resetLocation()
     }
 
+
     // 返回
     goBack() {
         this.displayLocation = false
+        this.currRowContent = this.currentRow
     }
 
     @Watch("deviceType", { immediate: true, deep: true })

+ 16 - 8
src/views/maintain/space/components/spaceGraph.vue

@@ -68,7 +68,7 @@
 <script lang="ts">
 import { Vue, Component, Watch } from "vue-property-decorator";
 import { FloorView } from "@/utils/graph/FloorView";
-import { FloorScene } from "@/utils/graph/FloorScene";
+import { DivideFloorScene } from "@/utils/graph/DivideFloorScene";
 import canvasFun from "./canvasFun.vue";
 import {
     queryZone,
@@ -91,7 +91,7 @@ export default class spaceGraph extends Vue {
     canvasWidth = 800;
     canvasHeight = 800;
     view: FloorView | null = null;
-    scene: FloorScene | null = null;
+    scene: DivideFloorScene | null = null;
     canvasLoading = false;
     type = 1;
     config = {
@@ -130,9 +130,9 @@ export default class spaceGraph extends Vue {
         this.canvasLoading = true;
         if (floor.infos && floor.infos.floorMap) {
             this.floor = floor;
-            const url = this.mapBaseUrl + floor.infos.floorMap
+            const url = this.mapBaseUrl + floor.infos.floorMap;
             if (url == this.floorKey) {
-                this.init(2)
+                this.init(2);
             } else {
                 this.floorKey = this.mapBaseUrl + floor.infos.floorMap;
                 this.init(1);
@@ -152,6 +152,7 @@ export default class spaceGraph extends Vue {
         if (this.scene) {
             this.scene.selectContainer.clear();
             this.scene.initSpaceColor();
+            this.scene.clearCut();
             this.zoneDisable = true;
             this.scene.isZoneSelectable = true;
             this.scene.isSpaceSelectable = false;
@@ -175,12 +176,16 @@ export default class spaceGraph extends Vue {
             divide: true,
             groupSelect: true,
         };
+        if (this.$refs.canvasFun) {
+            // @ts-ignore
+            this.$refs.canvasFun.active = "";
+        }
         // 获取当前楼层的元空间
         this.getFloorISpace();
     }
     // 获取底图
     getGraph(): void {
-        const scene = new FloorScene();
+        const scene = new DivideFloorScene();
         this.canvasLoading = true;
         this.clearGraphy();
         this.scene = null;
@@ -256,7 +261,6 @@ export default class spaceGraph extends Vue {
         this.scene!.isSpaceSelectable = true;
         this.scene!.isZoneSelectable = false;
         this.view?.update();
-        console.log(12312312);
     }
     // 创建新的业务空间
     createNewZone() {
@@ -382,8 +386,12 @@ export default class spaceGraph extends Vue {
                 }
             } else {
                 this.scene?.initSpaceColor();
-                this.scene?.initZoneColor();
-                this.zoneDisable = true;
+                if (this.type == 3) {
+                    console.log(3);
+                } else {
+                    this.scene?.initZoneColor();
+                    this.zoneDisable = true;
+                }
             }
             data[0].forEach((t) => {
                 t.fillColor = ItemColor.selectColor;

+ 10 - 6
src/views/maintain/space/index.vue

@@ -296,28 +296,32 @@ export default class spaceIndex extends Vue {
                 pageSize: this.paginationList.size,
                 orders: "createTime asc, localName asc, localId desc, id asc",
             };
-
+            let search = this.inputSearch ? `;localName contain '${ this.inputSearch }' or localId contain '${ this.inputSearch }'` : undefined
             if (this.building.length === 1) {
                 param2.filters = "";
                 switch (this.building[0]) {
                     case "noKnow":
-                        param2.filters = `buildingId='isnull'`;
+                        param2.filters = `buildingId='isnull'${ search }`;
                         break;
                     case "all":
-                        delete param2.filters;
+                        if (this.inputSearch) {
+                            param2.filters = `localName contain '${ this.inputSearch }' or localId contain '${ this.inputSearch }'`
+                        } else {
+                            delete param2.filters;
+                        }
                         break;
                 }
             } else if (this.building.length === 2) {
                 switch (this.building[1]) {
                     case "noKnow":
-                        param2.filters = `buildingId='${ this.building[0] }';floorId='isnull'`;
+                        param2.filters = `buildingId='${ this.building[0] }';floorId='isnull'${ search }`;
 
                         break;
                     case "all":
-                        param2.filters = `buildingId='${ this.building[0] }'`;
+                        param2.filters = `buildingId='${ this.building[0] }'${ search }`;
                         break;
                     default:
-                        param2.filters = `buildingId='${ this.building[0] }';floorId='${ this.building[1] }'`;
+                        param2.filters = `buildingId='${ this.building[0] }';floorId='${ this.building[1] }'${ search }`;
                 }
             }
 

+ 1 - 1
src/views/management/mappingRelation/index.vue

@@ -4,7 +4,7 @@
 
 <script>
 export default {
-    name: 'index'
+    name: 'mappingRelation'
 }
 </script>