瀏覽代碼

新增编辑区域案例

YaolongHan 4 年之前
父節點
當前提交
8d780e1a91

+ 124 - 0
PropertyController2.kt

@@ -0,0 +1,124 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *                     :*$@@%$*:                         ;:                ;;    ;;
+ *                   :@@%!  :!@@%:                       %!             ;%%@@%$ =@@@@@@@%;     @%@@@%%%%@@@@@
+ *                  :@%;       :$=                       %%$$$%$$         ;$$  ;$@=   !@$
+ *                  =@!                                  %!              @ $=;%   !@@@%:      !$$$$$$$$$$$$$$=
+ *                  =@*                                  %!              @ $= % %@=   =%@!      %=
+ *             *$%%! @@=        ;=$%%%$*:                %!              @ $= % =%%%%%%@$      *%:         =%
+ *           %@@!:    !@@@%=$@@@@%!  :*@@$:              %!              @ $= % $*     ;@      @*          :%*
+ *         ;@@!          ;!!!;:         ;@%:      =======@%========*     @ $$ % $%*****$@     :@$=*********=@$
+ *         $@*   ;@@@%=!:                *@*
+ *         =@$    ;;;!=%@@@@=!           =@!
+ *          %@$:      =@%: :*@@@*       %@=                    Copyright (c) 2016-2019.  北京上格云技术有限公司
+ *           ;%@@$=$@@%*       *@@@$=%@@%;
+ *              ::;::             ::;::                                              All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package cn.sagacloud.server.datacenter.controllers
+
+import cn.sagacloud.server.datacenter.models.entities.Property
+import cn.sagacloud.server.datacenter.services.PropertyService
+import com.sybotan.base.extensions.toJson
+import com.sybotan.database.SFilter
+import com.sybotan.service.SPageContext
+import com.sybotan.service.models.requests.SCountRequest
+import com.sybotan.service.models.requests.SCreateRequest
+import com.sybotan.service.models.requests.SQueryRequest
+import com.sybotan.service.models.requests.SUpdateRequest
+import com.sybotan.service.models.responses.SBaseResponse
+import com.sybotan.service.models.responses.SCountResponse
+import com.sybotan.service.models.responses.SCreateResponse
+import com.sybotan.service.models.responses.SQueryResponse
+import io.swagger.annotations.Api
+import io.swagger.annotations.ApiOperation
+import org.slf4j.LoggerFactory
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+/**
+ * 资产接口
+ *
+ * @author 张维新
+ */
+@Api(tags = ["资产接口"])
+@RestController
+@RequestMapping("/object/property")
+open class PropertyController2 {
+    companion object {
+        /** 日志 */
+        private val logger = LoggerFactory.getLogger(PropertyController2::class.java)
+    } // Companion object
+
+    /**
+     * 创建资产
+     *
+     * @param   request        资产对象列表
+     * @return                      创建结果信息
+     */
+    @ApiOperation(value = "创建资产信息", notes = "")
+    @PostMapping(value = ["/create"])
+    fun create(@RequestBody request: SCreateRequest<Property>): SCreateResponse<Property> {
+        logger.debug("property =${request.toJson()}")
+        return PropertyService.createList( request)
+    } // Function create()
+
+    /**
+     * 根据id删除资产
+     *
+     * @param   idList      id数组
+     * @return              删除的结果信息
+     */
+    @ApiOperation(value = "根据id删除资产信息", notes = "")
+    @PostMapping(value = ["/delete"])
+    fun delete(@RequestBody idList: ArrayList<Property>): SBaseResponse {
+        return PropertyService.deleteByKeysList(idList)
+    } // Function delete()
+
+    /**
+     * 更新资产信息
+     *
+     * @param   request     更新的内容对象
+     * @return              更新的结果
+     */
+    @ApiOperation(value = "更新资产信息", notes = "")
+    @PostMapping(value = ["/update"])
+    fun update(@RequestBody request: SUpdateRequest<Property>): SBaseResponse {
+        logger.debug("equipUpdate =${request.toJson()}")
+        return PropertyService.updateList(request)
+    } // Function update()
+
+    /**
+     * 查询资产信息
+     *
+     * @param   request     查询信息条件
+     * @return              查询结果
+     */
+    @ApiOperation(value = "查询资产信息", notes = "级联查询对象:项目(project)、建筑(building)、楼层(floor)、设备(equipment)、设备族(equipFamilyList)、设备族名称(familyName)")
+    @PostMapping(value = ["/query"])
+    fun query(@RequestBody request: SQueryRequest): SQueryResponse<Property> {
+        logger.debug("equipQuery =${request.toJson()}")
+        val withFilterList = ArrayList<SFilter>()
+        withFilterList.add(SFilter.eq("projectId", SPageContext.getHeader("ProjectId").toString()))
+        return PropertyService.pageQuery(request,withFilterList)
+    } // Function query()
+
+    /**
+     * 根据条件查询统计数量
+     */
+    @ApiOperation(value = "根据条件查询统计数量", notes = "例子: 已经关联岗位的资产数量(\"Filters\": \" not EquipId isNull\")," +
+            "未关联岗位的资产数量(\"Filters\": \" EquipId isNull\")")
+    @PostMapping(value = ["/count"])
+    fun count(@RequestBody request: SCountRequest): SCountResponse {
+        val withFilterList = ArrayList<SFilter>()
+        withFilterList.add(SFilter.eq("projectId", SPageContext.getHeader("ProjectId").toString()))
+        return PropertyService.count(request,withFilterList)
+    } // Function count()
+
+
+} // Class AssetsController

+ 102 - 0
docs/.vuepress/components/edit/items/editline/editline1 copy.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="edit-line">
+    <div class="btn-list">
+      <el-button :class="[cmdStatus=='choice' ? 'heightLight' : '']" size="small" @click="choice">选择</el-button>
+      <el-button :class="[cmdStatus=='create' ? 'heightLight' : '']" size="small" @click="create">画线</el-button>
+      <el-button :class="[cmdStatus=='edit' ? 'heightLight' : '']" size="small" @click="edit">编辑</el-button>
+      <el-button :class="[cmdStatus=='deleteItem' ? 'heightLight' : '']" size="small" @click="deleteItem">删除</el-button>
+      <el-button :class="[cmdStatus=='undo' ? 'heightLight' : '']" size="small" @click="undo">undo</el-button>
+      <el-button :class="[cmdStatus=='redo' ? 'heightLight' : '']" size="small" @click="redo">redo</el-button>
+      <el-button :class="[cmdStatus=='eqDrawLine' ? 'heightLight' : '']" size="small" @click="eqDrawLine">垂直水平划线</el-button>
+    </div>
+    <div class="content">
+      <div class="left">
+        <canvas id="edit_line" width="700" height="460" tabindex="0" />
+      </div>
+      <div class="line-property">
+        <el-card shadow="always">
+          <div class="always-item">
+            <span>线宽:</span>
+            <input type="text" />
+          </div>
+          <div class="always-item">
+            <span>线类型:</span>
+            <input type="text" />
+          </div>
+          <div class="always-item">
+            <span>线颜色:</span>
+            <input type="text" />
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { SGraphScene, SGraphView } from "@persagy-web/graph/";
+import { SItemStatus } from "@persagy-web/big/lib/enums/SItemStatus";
+import {  SPoint } from "@persagy-web/draw/";
+import { EditLineItem } from "./../../../../../guides/edit/items/src/EditLineItem";
+//注: 开发者引入 SWallItem 包为: import {SWallItem} from "@persagy-web/edit/";
+export default {
+  name: "line",
+  data() {
+    return {
+        scene: null,
+        view: null,
+        isCreated:false, //是否创建完成
+        cmdStatus: 'choice'      //选中状态
+
+    };
+  },
+  mounted() {
+    this.view = new SGraphView("edit_line");
+    this.scene = new SGraphScene();
+    this.view.scene = this.scene;
+
+  },
+  methods: {
+    create() {
+        this.cmdStatus = 'create';
+        // this.scene.root.children = [];
+        const lineItem = new EditLineItem(null, [] );
+        lineItem.status = SItemStatus.Create;
+           lineItem.connect("finishCreated", this, this.finishCreated);
+        this.scene.addItem(lineItem);
+        this.scene.grabItem = lineItem;
+        this.scene.updated()
+    },
+    // 完成创建后的回调
+    finishCreated(){
+        this.cmdStatus = 'choice'
+    }
+  }
+};
+</script>
+
+<style scoped lang="less">
+.edit-line {
+  width: 100%;
+  height: 500px;
+  .content {
+    display: flex;
+    .left {
+      flex: 1;
+    }
+    .line-property {
+      width: 300px;
+      .always-item {
+        display: flex;
+        margin-top: 10px;
+        justify-content: space-around;
+      }
+    }
+  }
+  .heightLight{
+    color: #409EFF;
+    border-color: #c6e2ff;
+    background-color: #ecf5ff;
+  }
+}
+</style>

+ 219 - 0
docs/.vuepress/components/edit/items/editline/editline1.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="edit-line">
+    <div class="btn-list">
+      <el-button :class="[cmdStatus=='create' ? 'heightLight' : '']" size="small" @click="create">画线</el-button>
+      <el-tooltip class="item" effect="dark" content="双击 item 也可进入编辑状态" placement="top-start">
+        <el-button :class="[cmdStatus=='edit' ? 'heightLight' : '']" size="small" @click="edit">编辑</el-button>
+      </el-tooltip>
+
+      <el-button
+        :class="[cmdStatus=='deleteItem' ? 'heightLight' : '']"
+        size="small"
+        @click="deleteItem"
+      >删除</el-button>
+      <el-tooltip class="item" effect="dark" content="长按 Shoft 配合左键也可触发该功能" placement="top-start">
+        <el-button
+          :class="[cmdStatus=='eqDrawLine' ? 'heightLight' : '']"
+          size="small"
+          @click="eqDrawLine"
+        >垂直水平划线</el-button>
+      </el-tooltip>
+    </div>
+    <div class="content">
+      <div class="left">
+        <canvas id="edit_line" width="700" height="460" tabindex="0" />
+      </div>
+      <div class="line-property">
+        <el-card shadow="always">
+          <div slot="header" class="clearfix">
+            <span>属性修改</span>
+          </div>
+          <div class="always-item">
+            <span>线宽:</span>
+            <el-input-number
+              size="small"
+              v-model="lineWidth"
+              @change="changeLineWidth"
+              :min="1"
+              :max="50"
+            ></el-input-number>
+          </div>
+          <div class="always-item">
+            <span>线类型:</span>
+            <el-select
+              style="width:130px"
+              size="small"
+              v-model="lineType"
+              @change="changeType"
+              placeholder="请选择"
+            >
+              <el-option
+                v-for="item in options"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              ></el-option>
+            </el-select>
+          </div>
+          <div class="always-item">
+            <span>线颜色:</span>
+            <el-color-picker v-model="lineColor" @change="changeColor" show-alpha></el-color-picker>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { SGraphScene, SGraphView, SLineStyle } from "@persagy-web/graph/";
+import { SItemStatus } from "@persagy-web/big/lib/enums/SItemStatus";
+import { SPoint } from "@persagy-web/draw/";
+import { EditLineItem } from "./../../../../../guides/edit/items/src/EditLineItem";
+import { hexify } from "./../../../../public/until/rgbaUtil";
+//注: 开发者引入 SWallItem 包为: import {SWallItem} from "@persagy-web/edit/";
+export default {
+  name: "editLine",
+  data() {
+    return {
+      scene: null,
+      view: null,
+      isCreated: false, //是否创建完成
+      cmdStatus: "", //选中状态
+      lineItem: null, //存放创建的Item
+      lineWidth: 1,
+      lineColor: "",
+      lineType: "",
+      options: [
+        {
+          value: "Solid",
+          label: "实线"
+        },
+        {
+          value: "Dashed",
+          label: "虚线"
+        },
+        {
+          value: "Dotted",
+          label: "点"
+        }
+      ]
+    };
+  },
+  mounted() {
+    this.view = new SGraphView("edit_line");
+    this.scene = new SGraphScene();
+    this.view.scene = this.scene;
+  },
+  methods: {
+    create() {
+      this.cmdStatus = "create";
+      this.scene.root.children = [];
+      this.lineItem = new EditLineItem(null, []);
+      this.lineItem.status = SItemStatus.Create;
+      this.lineItem.connect("finishCreated", this, this.finishCreated);
+      this.scene.addItem(this.lineItem);
+      this.scene.grabItem = this.lineItem;
+      this.view.update();
+    },
+    deleteItem() {
+      this.cmdStatus = "";
+      this.scene.removeItem(this.lineItem);
+      this.lineItem = null;
+      this.view.update();
+    },
+    edit() {
+      if (this.lineItem) {
+        if (this.lineItem.status == SItemStatus.Normal) {
+          this.scene.grabItem = this.lineItem;
+          this.lineItem.status = SItemStatus.Edit;
+          this.cmdStatus = "edit";
+        } else {
+          this.lineItem.status = SItemStatus.Normal;
+          this.scene.grabItem = null;
+          this.cmdStatus = "";
+        }
+      }
+    },
+    eqDrawLine() {
+      this.cmdStatus = "eqDrawLine";
+      this.scene.root.children = [];
+      this.lineItem = new EditLineItem(null, []);
+      this.lineItem.verAndLeve = true;
+      this.lineItem.status = SItemStatus.Create;
+      this.lineItem.connect("finishCreated", this, this.finishCreated);
+      this.scene.addItem(this.lineItem);
+      this.scene.grabItem = this.lineItem;
+      this.view.update();
+    },
+    // 改变线宽属性
+    changeLineWidth(val) {
+      if (this.lineItem) {
+        this.lineWidth = val;
+        this.lineItem.lineWidth = val;
+      }
+    },
+    // 改变颜色
+    changeColor(val) {
+      if (this.lineItem) {
+        this.lineColor = hexify(val);
+        this.lineItem.strokeColor = this.lineColor;
+      }
+    },
+    //改变线得类型
+    changeType(val) {
+      if (this.lineItem) {
+        this.lineItem.lineStyle = SLineStyle[val];
+      }
+    },
+    // 完成创建后的回调
+    finishCreated() {
+      this.cmdStatus = "";
+    }
+  },
+  watch: {
+    lineItem(val) {
+      if (val) {
+        this.lineWidth = val.lineWidth; // 线宽
+        this.lineStyle = val.lineStyle; // 线条样式
+        this.lineColor = val.strokeColor.value; // 线条填充色
+        this.lineType = this.options[val.lineStyle].value;
+      } else {
+        this.lineWidth = 0;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped lang="less">
+.edit-line {
+  width: 100%;
+  height: 500px;
+  .content {
+    display: flex;
+    justify-content: flex-start;
+    .left {
+      margin-right: 20px;
+    }
+    .line-property {
+      width: 300px;
+      margin-top: 20px;
+      .always{
+        width: 100%;
+        height: 100%;
+      }
+      .always-item {
+        display: flex;
+        margin-top: 10px;
+        justify-content: space-between;
+      }
+    }
+  }
+  .heightLight {
+    color: #409eff;
+    border-color: #c6e2ff;
+    background-color: #ecf5ff;
+  }
+}
+</style>

+ 0 - 13
docs/.vuepress/components/edit/items/line.vue

@@ -1,13 +0,0 @@
-<template>
-    <div>1</div>
-</template>
-
-<script>
-    export default {
-        name: "line"
-    }
-</script>
-
-<style scoped>
-
-</style>

+ 71 - 32
docs/.vuepress/config.js

@@ -11,8 +11,7 @@ module.exports = {
         [
             'vuepress-plugin-typescript',
             {
-                tsLoaderOptions: {
-                },
+                tsLoaderOptions: {},
             },
         ],
         [
@@ -30,57 +29,97 @@ module.exports = {
     },
     themeConfig: {
         logo: "/assets/img/logo.png",
-        nav: [
-            {text: "首页", link: "/"},
+        nav: [{
+                text: "首页",
+                link: "/"
+            },
             {
                 text: "安装配置",
-                items: [
-                    {
+                items: [{
                         text: "开发环境",
-                        items: [
-                            {text: "IDEA", link: "/setup/ide/idea"},
-                            {text: "VS Code", link: "/setup/ide/vscode"},
-                            {text: "Node.js", link: "/setup/ide/nodejs"},
+                        items: [{
+                                text: "IDEA",
+                                link: "/setup/ide/idea"
+                            },
+                            {
+                                text: "VS Code",
+                                link: "/setup/ide/vscode"
+                            },
+                            {
+                                text: "Node.js",
+                                link: "/setup/ide/nodejs"
+                            },
                         ]
                     },
                     {
                         text: "服务器",
-                        items: [
-                            {text: "Nginx", link: "/setup/server/nginx"},
-                            {text: "Tomcat", link: "/setup/server/tomcat"}
+                        items: [{
+                                text: "Nginx",
+                                link: "/setup/server/nginx"
+                            },
+                            {
+                                text: "Tomcat",
+                                link: "/setup/server/tomcat"
+                            }
                         ]
                     },
                 ]
             },
             {
                 text: "开发文档",
-                items: [
-                    {
-                        text: "引擎",
-                        items: [
-                            {text: "安装", link: "/guides/base/"},
-                            {text: "图形引擎", link: "/guides/engine/"},
-                            {text: "场景管理", link: "/guides/scene/"},
-                            {text: "楼层平面图", link: "/guides/big/"},
-                            {text: "系统图", link: "/guides/system-diagram/"},
-                            {text: "编辑器", link: "/guides/edit/"}
-                        ]
-                    },
-                ]
+                items: [{
+                    text: "引擎",
+                    items: [{
+                            text: "安装",
+                            link: "/guides/base/"
+                        },
+                        {
+                            text: "图形引擎",
+                            link: "/guides/engine/"
+                        },
+                        {
+                            text: "场景管理",
+                            link: "/guides/scene/"
+                        },
+                        {
+                            text: "楼层平面图",
+                            link: "/guides/big/"
+                        },
+                        {
+                            text: "系统图",
+                            link: "/guides/system-diagram/"
+                        },
+                        {
+                            text: "编辑器",
+                            link: "/guides/edit/"
+                        }
+                    ]
+                }, ]
             },
             {
                 text: "API参考",
-                items: [
-                    { text: "图形引擎", link: "http://adm.sagacloud.cn:8080/api/web/graph/" },
-                    { text: "建筑信息图", link: "http://adm.sagacloud.cn:8080/api/web/big/" }
+                items: [{
+                        text: "图形引擎",
+                        link: "http://adm.sagacloud.cn:8080/api/web/graph/"
+                    },
+                    {
+                        text: "建筑信息图",
+                        link: "http://adm.sagacloud.cn:8080/api/web/big/"
+                    }
                 ]
             }
         ],
         displayAllHeaders: true,
         sidebarDepth: 0,
         sidebar: {
-            "/setup/" : setupContent,
+            "/setup/": setupContent,
             "/guides/": graphContent
         }
-    }
-};
+    },
+    module: {
+        rules: [{
+            test: /\.less$/,
+            loader: 'less-loader', // compiles Less to CSS
+        }, ],
+    },
+};

+ 24 - 0
docs/.vuepress/public/until/rgbaUtil.ts

@@ -0,0 +1,24 @@
+import transparency from './transparency'
+//rgba转成16进制
+export function hexify(color: string) {
+  if (color) {
+      let opacity = '',
+          rgb = color.split(','),
+          r = parseInt(rgb[0].split('(')[1]),
+          g = parseInt(rgb[1]),
+          b = parseInt(rgb[2].split(')')[0]),
+          a = Number(rgb[3].split(')')[0] || 1) * 100
+
+      transparency.forEach((i, index) => {
+          if (index == a) {
+              opacity = i
+          }
+      })
+
+      var hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) + opacity
+      
+      return hex
+  } else {
+      return
+  }
+}

+ 104 - 0
docs/.vuepress/public/until/transparency.ts

@@ -0,0 +1,104 @@
+ const Transparency = [
+     "00",
+     "03",
+     "05",
+     "08",
+     "0A",
+     "0D",
+     "0F",
+     "12",
+     "14",
+     "17",
+     "1A",
+     "1C",
+     "1F",
+     "21",
+     "24",
+     "26",
+     "29",
+     "2B",
+     "2E",
+     "30",
+     "33",
+     "36",
+     "38",
+     "3B",
+     "3D",
+     "40",
+     "42",
+     "45",
+     "47",
+     "4A",
+     "4D",
+     "4F",
+     "52",
+     "54",
+     "57",
+     "59",
+     "5C",
+     "5E",
+     "61",
+     "63",
+     "66",
+     "69",
+     "6B",
+     "6E",
+     "70",
+     "73",
+     "75",
+     "78",
+     "7A",
+     "7D",
+     "80",
+     "82",
+     "85",
+     "87",
+     "8A",
+     "8C",
+     "8F",
+     "91",
+     "94",
+     "96",
+     "99",
+     "9C",
+     "9E",
+     "A1",
+     "A3",
+     "A6",
+     "A8",
+     "AB",
+     "AD",
+     "B0",
+     "B3",
+     "B5",
+     "B8",
+     "BA",
+     "BD",
+     "BF",
+     "C2",
+     "C4",
+     "C7",
+     "C9",
+     "CC",
+     "CF",
+     "D1",
+     "D4",
+     "D6",
+     "D9",
+     "DB",
+     "DE",
+     "E0",
+     "E3",
+     "E6",
+     "E8",
+     "EB",
+     "ED",
+     "F0",
+     "F2",
+     "F5",
+     "F7",
+     "FA",
+     "FC",
+     "FF"
+ ];
+ export default Transparency

+ 54 - 0
docs/guides/edit/items/editLine.md

@@ -0,0 +1,54 @@
+
+# 线编辑类 LineEditItem 示例
+::: details 目录
+[[toc]]
+:::
+
+## 绘制示例
+
+<edit-items-editline-editline1 />
+
+::: details 示例代码
+
+<<< @/docs/.vuepress/components/edit/items/editline/editline1.vue
+
+:::
+
+## 源代码
+
+::: details 查看代码
+
+<<< @/docs/guides/edit/items/src/EditLineItem.ts
+
+:::
+
+## 代码说明
+### 1 当 LineEditItem 为编辑状态时,需要将 LineEditItem 赋给 grabItem
+``` js {4}
+ // 编辑状态时的 LineItem
+ this.lineItem = new EditLineItem(null, []);
+ this.lineItem.status = SItemStatus.Create;
+ this.scene.grabItem = this.lineItem;
+```
+### 2 当 LineEditItem 为正常状态时,需要将 grabItem 置为 null
+``` js {4}
+ // 正常状态时的 LineItem
+ this.lineItem = new EditLineItem(null, []);
+ this.lineItem.status = SItemStatus.Normal;
+ this.scene.grabItem = null;
+```
+### 3 当 LineEditItem 为试图垂直水平绘制时需要修改 verAndLeve 属性
+``` js {3}
+ // 编辑状态时的 LineItem
+ this.lineItem = new EditLineItem(null, []);
+ this.lineItem.status = SItemStatus.Create;
+ this.lineItem.verAndLeve = true;
+ this.scene.grabItem = this.lineItem;
+```
+### 4 当 LineEditItem 修改属性但是画板尚未变化时需要刷新
+``` js {4}
+ // 编辑状态时的 LineItem
+  this.lineItem = new EditLineItem(null, []);
+ this.lineItem.status = SItemStatus.Create;
+ this.view.update();
+```

+ 0 - 0
docs/guides/edit/items/editPolygonItem.md


+ 0 - 9
docs/guides/edit/items/line.md

@@ -1,9 +0,0 @@
-
-# 线编辑类 LineEditItem 示例
-::: details 目录
-[[toc]]
-:::
-
-## 源代码
-
-## 代码说明

+ 512 - 0
docs/guides/edit/items/src/EditLineItem.ts

@@ -0,0 +1,512 @@
+import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw/";
+import { SMouseButton, SMouseEvent, SUndoStack } from "@persagy-web/base";
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
+import { SItemStatus } from "@persagy-web/big";
+import {
+    SGraphItem,
+    SGraphPointListInsert,
+    SGraphPointListUpdate,
+    SLineStyle
+} from "@persagy-web/graph/";
+
+/**
+ * 编辑类直线 item
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ * */
+
+export class EditLineItem extends SGraphItem {
+    /** 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;
+
+    /** 线段   */
+    private _line: SPoint[] = [];
+    get line(): SPoint[] {
+        return this._line;
+    }
+    set line(arr: SPoint[]) {
+        this._line = arr;
+        this.update();
+    }
+
+    /** 是否垂直水平绘制   */
+    private _verAndLeve: Boolean= false;
+    get verAndLeve(): Boolean {
+        return this._verAndLeve;
+    }
+    set verAndLeve(bool: Boolean) {
+        this._verAndLeve = bool;
+        this.update();
+    }
+
+    /** 是否完成绘制  */
+    protected _status: SItemStatus = SItemStatus.Normal;
+    get status(): SItemStatus {
+        return this._status;
+    }
+    set status(v: SItemStatus) {
+        this._status = v;
+        this.undoStack.clear();
+        this.update();
+    }
+
+    /** 线条颜色    */
+    private _strokeColor: SColor = SColor.Black;
+    get strokeColor(): SColor {
+        return this._strokeColor;
+    }
+    set strokeColor(v: SColor) {
+        this._strokeColor = v;
+        this.update();
+    }
+
+    /** 线条样式    */
+    private _lineStyle: SLineStyle = SLineStyle.Solid;
+    get lineStyle(): SLineStyle {
+        return this._lineStyle;
+    }
+    set lineStyle(v: SLineStyle) {
+        this._lineStyle = v;
+        this.update();
+    }
+
+    /** 端点填充色 */
+    private _fillColor: SColor = SColor.White;
+    get fillColor(): SColor {
+        return this._fillColor;
+    }
+    set fillColor(v: SColor) {
+        this._fillColor = v;
+        this.update();
+    }
+
+    /** 选中端点填充色 */
+    private _activeFillColor: SColor = new SColor("#2196f3");
+    get activeFillColor(): SColor {
+        return this._activeFillColor;
+    }
+    set activeFillColor(v: SColor) {
+        this._activeFillColor = v;
+        this.update();
+    }
+
+    /** 线条宽度    */
+    private _lineWidth: number = 1;
+    get lineWidth(): number {
+        return this._lineWidth;
+    }
+    set lineWidth(v: number) {
+        this._lineWidth = v;
+        this.update();
+    }
+
+    /** 拖动灵敏度   */
+    dis: number = 5;
+
+    /** 拖动灵敏度   */
+    private sceneDis: number = 5;
+
+    /** 当前点索引   */
+    curIndex: number = -1;
+
+    /** 当前点坐标   */
+    private curPoint: SPoint | null = null;
+
+    /** undo/redo堆栈 */
+    private undoStack: SUndoStack = new SUndoStack();
+
+    /**
+     * 构造函数
+     *
+     * @param   parent  父级
+     * */
+    constructor(parent: SGraphItem | null);
+
+    /**
+     * 构造函数
+     *
+     * @param   parent  父级
+     * @param   line    坐标集合
+     * */
+    constructor(parent: SGraphItem | null, line: SPoint[]);
+
+    /**
+     * 构造函数
+     *
+     * @param   parent  父级
+     * @param   point   第一个点坐标
+     * */
+    constructor(parent: SGraphItem | null, point: SPoint);
+
+    /**
+     * 构造函数
+     *
+     * @param   parent  父级
+     * @param   l       坐标集合|第一个点坐标
+     * */
+    constructor(parent: SGraphItem | null, l?: SPoint | SPoint[]) {
+        super(parent);
+        if (l) {
+            if (l instanceof SPoint) {
+                this.line.push(l);
+            } else {
+                this.line = l;
+            }
+        } else {
+            this.line = [];
+        }
+    }
+
+    /**
+     * 添加点至数组中
+     *
+     * @param   p       添加的点
+     * */
+    private addPoint(p: SPoint): void {
+        if (this.line.length < 2) {
+            this.line.push(p);
+            this.recordAction(SGraphPointListInsert, [this.line, p]);
+        } else {
+            this.line[1] = p;
+            this.recordAction(SGraphPointListInsert, [this.line, p, 1]);
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+            this.$emit("finishCreated");
+        }
+        this.update();
+    } // Function addPoint()
+
+    /**
+     * 鼠标双击事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onDoubleClick(event: SMouseEvent): boolean {
+        if (this.status == SItemStatus.Normal) {
+            this.status = SItemStatus.Edit;
+            this.grabItem(this);
+        } else if (this.status == SItemStatus.Edit) {
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+        }
+        this.update();
+        return true;
+    } // Function onDoubleClick()
+
+    /**
+     * 鼠标按下事件
+     *
+     * @param   event   鼠标事件
+     * @return  是否处理事件
+     * */
+    onMouseDown(event: SMouseEvent): boolean {
+        this.curIndex = -1;
+        this.curPoint = null;
+        if (event.shiftKey || this._verAndLeve) {
+            event = this.compare(event);
+        }
+        if (event.buttons == SMouseButton.LeftButton) {
+            if (this.status == SItemStatus.Normal) {
+                return super.onMouseDown(event);
+            } else if (this.status == SItemStatus.Edit) {
+                // 判断是否点击到端点上(获取端点索引值)
+                this.findNearestPoint(new SPoint(event.x, event.y));
+            } else if (this.status == SItemStatus.Create) {
+                this.addPoint(new SPoint(event.x, event.y));
+                return true;
+            }
+        }
+        return true;
+    } // Function onMouseDown()
+
+    /**
+     * 鼠标抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseUp(event: SMouseEvent): boolean {
+        if (this.status == SItemStatus.Edit) {
+            if (this.curIndex > -1) {
+                const p = new SPoint(
+                    this.line[this.curIndex].x,
+                    this.line[this.curIndex].y
+                );
+                this.recordAction(SGraphPointListUpdate, [
+                    this.line,
+                    this.curPoint,
+                    p,
+                    this.curIndex
+                ]);
+            }
+        } else if (this.status == SItemStatus.Normal) {
+            this.moveToOrigin(this.x, this.y);
+            return super.onMouseUp(event);
+        }
+        this.curIndex = -1;
+        this.curPoint = null;
+        return true;
+    } // Function onMouseUp()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param   event   鼠标事件
+     * @return  是否处理事件
+     * */
+    onMouseMove(event: SMouseEvent): boolean {
+        if (event.shiftKey || this._verAndLeve) {
+            event = this.compare(event);
+        }
+        if (this.status == SItemStatus.Create) {
+            if (this.line[0] instanceof SPoint) {
+                this.line[1] = new SPoint(event.x, event.y);
+            }
+        } else if (this.status == SItemStatus.Edit) {
+            if (-1 != this.curIndex) {
+                this.line[this.curIndex].x = event.x;
+                this.line[this.curIndex].y = event.y;
+            }
+        } else {
+            return super.onMouseMove(event);
+        }
+        this.update();
+        return true;
+    } // Function onMouseMove()
+
+    /**
+     * 获取点击点与Point[]中的点距离最近点
+     *
+     * @param   p   鼠标点击点
+     * */
+    findNearestPoint(p: SPoint): void {
+        let len = this.sceneDis;
+        for (let i = 0; i < this.line.length; i++) {
+            let dis = SMathUtil.pointDistance(
+                p.x,
+                p.y,
+                this.line[i].x,
+                this.line[i].y
+            );
+            if (dis < len) {
+                len = dis;
+                this.curIndex = i;
+                this.curPoint = new SPoint(this.line[this.curIndex]);
+            }
+        }
+    } // Function findNearestPoint()
+
+    /**
+     * 记录相关动作并推入栈中
+     *
+     * @param	SGraphCommand   相关命令类
+     * @param	any             对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const command = new SGraphCommand(this.scene, this, ...any);
+        this.undoStack.push(command);
+    } // Function recordAction()
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     *
+     * @param   x   x坐标
+     * @param   y   y坐标
+     * */
+    moveToOrigin(x: number, y: number): void {
+        super.moveToOrigin(x, y);
+        this.line = this.line.map(t => {
+            t.x = t.x + x;
+            t.y = t.y + y;
+            return t;
+        });
+        this.x = 0;
+        this.y = 0;
+    } // Function moveToOrigin()
+
+    /**
+     * shift垂直水平创建或编辑
+     *
+     * @param   event   事件
+     * */
+    compare(event: SMouseEvent): SMouseEvent {
+        if (this.line.length) {
+            let last = new SPoint(event.x, event.y);
+            if (this.status == SItemStatus.Create) {
+                last = this.line[0];
+            } else if (this.status == SItemStatus.Edit) {
+                if (this.curIndex == 1) {
+                    last = this.line[0];
+                } else if (this.curIndex == 0) {
+                    last = this.line[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;
+    } // Function compare()
+
+    /**
+     * 判断点是否在区域内
+     *
+     * @param   x
+     * @param   y
+     * @return  true-是
+     */
+    contains(x: number, y: number): boolean {
+        if (this.line.length == 2) {
+            let p = new SPoint(x, y);
+            if (
+                SMathUtil.pointToLine(p, new SLine(this.line[0], this.line[1]))
+                    .MinDis < this.dis
+            ) {
+                return true;
+            }
+        }
+        return false;
+    } // Function contains()
+
+    /**
+     * 撤销操作
+     *
+     */
+    undo(): void {
+        if (this.status != SItemStatus.Normal) {
+            this.undoStack.undo();
+        }
+    } // Function undo()
+
+    /**
+     * 重做操作
+     *
+     */
+    redo(): void {
+        if (this.status != SItemStatus.Normal) {
+            this.undoStack.redo();
+        }
+    } // Function redo()
+
+    /**
+     * 取消操作item事件
+     *
+     * */
+    cancelOperate(): void {
+        if (this.status == SItemStatus.Create) {
+            this.parent = null;
+            this.releaseItem();
+        } else if (this.status == SItemStatus.Edit) {
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+        }
+    } // Function cancelOperate()
+
+    /**
+     * Item对象边界区域
+     *
+     * @return	SRect
+     */
+    boundingRect(): SRect {
+        if (this.line.length) {
+            this.minX = this.line[0].x;
+            this.maxX = this.line[0].x;
+            this.minY = this.line[0].y;
+            this.maxY = this.line[0].y;
+            this.line.forEach(it => {
+                let x = it.x,
+                    y = it.y;
+                if (x < this.minX) {
+                    this.minX = x;
+                }
+                if (y < this.minY) {
+                    this.minY = y;
+                }
+                if (x > this.maxX) {
+                    this.maxX = x;
+                }
+                if (y > this.maxY) {
+                    this.maxY = y;
+                }
+            });
+        }
+        return new SRect(
+            this.minX,
+            this.minY,
+            this.maxX - this.minX,
+            this.maxY - this.minY
+        );
+    } // Function boundingRect()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter painter对象
+     */
+    onDraw(painter: SPainter): void {
+        this.sceneDis = painter.toPx(this.dis);
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+        painter.pen.color = this.strokeColor;
+        if (this.line.length == 2) {
+            // 绘制直线
+            painter.pen.color = new SColor(this.strokeColor);
+            if (this.lineStyle == SLineStyle.Dashed) {
+                painter.pen.lineDash = [
+                    painter.toPx(this.lineWidth * 3),
+                    painter.toPx(this.lineWidth * 7)
+                ];
+            } else if (this.lineStyle == SLineStyle.Dotted) {
+                painter.pen.lineDash = [
+                    painter.toPx(this.lineWidth),
+                    painter.toPx(this.lineWidth)
+                ];
+            }
+            if (this.selected && this.status == SItemStatus.Normal) {
+                painter.pen.lineWidth = painter.toPx(this.lineWidth * 2);
+                painter.shadow.shadowBlur = 10;
+                painter.shadow.shadowColor = new SColor(`#00000033`);
+                painter.shadow.shadowOffsetX = 5;
+                painter.shadow.shadowOffsetY = 5;
+            }
+            painter.drawLine(this.line[0], this.line[1]);
+            if (
+                this.status == SItemStatus.Edit ||
+                this.status == SItemStatus.Create
+            ) {
+                // 绘制端点
+                this.line.forEach((p, i): void => {
+                    painter.brush.color = this.fillColor;
+                    if (i == this.curIndex) {
+                        painter.brush.color = this.activeFillColor;
+                    }
+                    painter.drawCircle(p.x, p.y, painter.toPx(5));
+                });
+            }
+        } else if (this.line.length == 1) {
+            if (
+                this.status == SItemStatus.Edit ||
+                this.status == SItemStatus.Create
+            ) {
+                // 绘制端点
+                painter.brush.color = this.fillColor;
+                painter.drawCircle(
+                    this.line[0].x,
+                    this.line[0].y,
+                    painter.toPx(5)
+                );
+            }
+        }
+    } // Function onDraw()
+} // Class SLineItem

+ 808 - 0
docs/guides/edit/items/src/EditPolygonItem.ts

@@ -0,0 +1,808 @@
+import {
+    SGraphItem,
+    SGraphPointListDelete,
+    SGraphPointListInsert,
+    SGraphPointListUpdate,
+    SLineStyle
+} from "@persagy-web/graph/lib";
+import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base/";
+import {
+    SColor,
+    SLine,
+    SLineCapStyle,
+    SPainter,
+    SPoint,
+    SPolygonUtil,
+    SRect
+} from "@persagy-web/draw";
+import { SItemStatus } from "..";
+import { SMathUtil } from "../utils/SMathUtil";
+
+/**
+ * 编辑多边形
+ *
+ * @author  韩耀龙
+ */
+export class SPolygonItem extends SGraphItem {
+    /** 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;
+
+    /** 轮廓线坐标  */
+    private pointList: SPoint[] = [];
+    // 获取当前状态
+    get getPointList(): SPoint[] {
+        return this.pointList;
+    }
+    // 编辑当前状态
+    set setPointList(arr: SPoint[]) {
+        this.pointList = arr;
+        this.update();
+    }
+
+    // 当前状态
+    protected _status: number = SItemStatus.Normal;
+    // 获取当前状态
+    get status(): SItemStatus {
+        return this._status;
+    }
+    // 编辑当前状态
+    set status(value: SItemStatus) {
+        this._status = value;
+        this.undoStack.clear();
+        this.update();
+    }
+
+    /** 边框颜色 */
+    _strokeColor: SColor = new SColor("#0091FF");
+    /**  画笔颜色 */
+    get strokeColor(): SColor {
+        return this._strokeColor;
+    }
+    set strokeColor(v: SColor) {
+        this._strokeColor = v;
+        this.update();
+    }
+
+    /** 填充颜色 */
+    _fillColor: SColor = new SColor("#1EE887");
+    get fillColor(): SColor {
+        return this._fillColor;
+    }
+    set fillColor(v: SColor) {
+        this._fillColor = v;
+        this.update();
+    }
+
+    /** 边框样式 */
+    _lineStyle: SLineStyle = SLineStyle.Solid;
+    get lineStyle(): SLineStyle {
+        return this._lineStyle;
+    }
+    set lineStyle(v: SLineStyle) {
+        this._lineStyle = v;
+        this.update();
+    }
+
+    /** 边框的宽 只可输入像素宽*/
+    _lineWidth: number = 4;
+    get lineWidth(): number {
+        return this._lineWidth;
+    }
+    set lineWidth(v: number) {
+        this._lineWidth = v;
+        this.update();
+    }
+
+    /** 是否闭合    */
+    closeFlag: boolean = false;
+    /** 鼠标移动点  */
+    private lastPoint: SPoint | null = null;
+    /** 当前鼠标获取顶点对应索引 */
+    private curIndex: number = -1;
+    /** 当前鼠标获取顶点对应坐标 */
+    private curPoint: null | SPoint = null;
+    /** 灵敏像素 */
+    private len: number = 10;
+    /** 场景像素 内部将灵敏像素换算为场景实际距离  */
+    private scenceLen: number = 15;
+    /** 场景像素  */
+    private isAlt: boolean = false;
+    /** undoredo堆栈 */
+    protected undoStack: SUndoStack = new SUndoStack();
+
+    /**
+     * 构造函数
+     *
+     * @param parent    指向父对象
+     */
+    constructor(parent: SGraphItem | null) {
+        super(parent);
+    }
+
+    //////////////////
+    //  以下为对pointList 数组的操作方法
+
+    /**
+     * 储存新的多边形顶点
+     *
+     * @param x   点位得x坐标
+     * @param y   点位得y坐标
+     * @param i   储存所在索引
+     * @return SPoint。添加的顶点
+     */
+    insertPoint(x: number, y: number, i: number | null = null): SPoint {
+        const point = new SPoint(x, y);
+        if (i == null) {
+            this.pointList.push(point);
+        } else {
+            this.pointList.splice(i, 0, point);
+        }
+        this.update();
+        return point;
+    }
+
+    /**
+     * 删除点位
+     *
+     * @param i   删除点所在的索引
+     * @return    SPoint|null。索引不在数组范围则返回null
+     */
+    deletePoint(i: number | null = null): SPoint | null {
+        let point = null;
+        if (i != null) {
+            if (i >= this.pointList.length || i < 0) {
+                point = null;
+            } else {
+                point = new SPoint(this.pointList[i].x, this.pointList[i].y);
+                this.pointList.splice(i, 1);
+            }
+        } else {
+            if (this.pointList.length) {
+                point = this.pointList[this.pointList.length - 1];
+                this.pointList.pop();
+            } else {
+                point = null;
+            }
+        }
+        this.curIndex = -1;
+        this.curPoint = null;
+        this.update();
+        return point;
+    }
+
+    /**
+     * 多边形顶点的移动位置
+     *
+     * @param x   点位得x坐标
+     * @param y   点位得y坐标
+     * @param i   点位得i坐标
+     * @return    移动对应得点。如果索引无法找到移动顶点,则返回null
+     */
+    movePoint(x: number, y: number, i: number): SPoint | null {
+        let point = null;
+        if (i >= this.pointList.length || i < 0) {
+            return null;
+        }
+        if (this.pointList.length) {
+            this.pointList[i].x = x;
+            this.pointList[i].y = y;
+        }
+        point = this.pointList[i];
+        return point;
+    }
+
+    /**
+     * 打印出多边形数组
+     *
+     * @return  顶点数组
+     */
+    PrintPointList(): SPoint[] {
+        return this.pointList;
+    }
+
+    ////////////
+    //  以下为三种状态下的绘制多边形方法
+
+    /**
+     * 展示状态 --绘制多边形数组
+     *
+     * @param painter      绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawShowPolygon(painter: SPainter, pointList: SPoint[]): void {
+        painter.save();
+        painter.pen.lineCapStyle = SLineCapStyle.Square;
+        painter.pen.color = this.strokeColor;
+        painter.brush.color = this.fillColor;
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+        if (this.lineStyle == SLineStyle.Dashed) {
+            painter.pen.lineDash = [
+                painter.toPx(this.lineWidth * 3),
+                painter.toPx(this.lineWidth * 7)
+            ];
+        } else if (this.lineStyle == SLineStyle.Dotted) {
+            painter.pen.lineDash = [
+                painter.toPx(this.lineWidth),
+                painter.toPx(this.lineWidth)
+            ];
+        }
+        if (this.selected) {
+            painter.shadow.shadowBlur = 10;
+            painter.shadow.shadowColor = new SColor(`#00000033`);
+            painter.shadow.shadowOffsetX = 5;
+            painter.shadow.shadowOffsetY = 5;
+        } else {
+            painter.shadow.shadowColor = SColor.Transparent;
+        }
+        painter.drawPolygon([...pointList]);
+        painter.restore();
+    }
+
+    /**
+     * 创建状态 --绘制多边形数组
+     *
+     * @param painter      绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawCreatePolygon(painter: SPainter, pointList: SPoint[]): void {
+        painter.pen.lineCapStyle = SLineCapStyle.Square;
+        painter.pen.color = this.strokeColor;
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+        if (this.lastPoint && pointList.length) {
+            painter.drawLine(
+                pointList[pointList.length - 1].x,
+                pointList[pointList.length - 1].y,
+                this.lastPoint.x,
+                this.lastPoint.y
+            );
+        }
+        painter.drawPolyline(pointList);
+        painter.pen.color = SColor.Transparent;
+        painter.brush.color = new SColor(this.fillColor.value);
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+
+        if (this.lastPoint) {
+            painter.drawPolygon([...pointList, this.lastPoint]);
+            // 绘制顶点块
+            painter.pen.color = SColor.Black;
+            painter.brush.color = SColor.White;
+            pointList.forEach(item => {
+                painter.drawCircle(item.x, item.y, painter.toPx(this.len / 2));
+            });
+            // 如果最后一个点在第一个点的灵敏度范围内,第一个点填充变红
+            if (this.pointList.length) {
+                if (
+                    SMathUtil.pointDistance(
+                        this.lastPoint.x,
+                        this.lastPoint.y,
+                        this.pointList[0].x,
+                        this.pointList[0].y
+                    ) < this.scenceLen
+                ) {
+                    // 绘制第一个点的顶点块
+                    painter.pen.color = SColor.Black;
+                    painter.brush.color = SColor.Red;
+                    painter.drawCircle(
+                        this.pointList[0].x,
+                        this.pointList[0].y,
+                        painter.toPx(this.len / 2)
+                    );
+                }
+            }
+        } else {
+            painter.drawPolygon(pointList);
+        }
+    }
+
+    /**
+     *
+     * 编辑状态 --绘制多边形数组
+     *
+     * @param painter    绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawEditPolygon(painter: SPainter, pointList: SPoint[]): void {
+        // 展示多边形
+        painter.pen.lineCapStyle = SLineCapStyle.Square;
+        painter.pen.color = this.strokeColor;
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+        painter.brush.color = new SColor(this.fillColor.value);
+        painter.drawPolygon([...pointList]);
+        // 绘制顶点块
+        painter.pen.color = SColor.Black;
+        painter.brush.color = SColor.White;
+        pointList.forEach((item, index) => {
+            painter.brush.color = SColor.White;
+            if (index == this.curIndex) {
+                painter.brush.color = new SColor("#2196f3");
+            }
+            painter.drawCircle(item.x, item.y, painter.toPx(this.len / 2));
+        });
+    }
+
+    /**
+     * 编辑状态操作多边形数组
+     *
+     * @param event    鼠标事件
+     *
+     *
+     */
+    protected editPolygonPoint(event: SMouseEvent): void {
+        //  判断是否为删除状态 isAlt = true为删除状态
+        if (this.isAlt) {
+            // 1 判断是否点击在多边形顶点
+            let lenIndex = -1; // 当前点击到的点位索引;
+            let curenLen = this.scenceLen; // 当前的灵敏度
+            this.pointList.forEach((item, index) => {
+                let dis = SMathUtil.pointDistance(
+                    event.x,
+                    event.y,
+                    item.x,
+                    item.y
+                );
+                if (dis < curenLen) {
+                    curenLen = dis;
+                    lenIndex = index;
+                }
+            });
+            // 若点击到,对该索引对应的点做删除
+            if (lenIndex != -1) {
+                if (this.pointList.length <= 3) {
+                    return;
+                }
+                const delePoint = new SPoint(
+                    this.pointList[lenIndex].x,
+                    this.pointList[lenIndex].y
+                );
+                this.deletePoint(lenIndex);
+                // 记录顶点操作记录压入堆栈
+                this.recordAction(SGraphPointListDelete, [
+                    this.pointList,
+                    delePoint,
+                    lenIndex
+                ]);
+            }
+        } else {
+            // 1 判断是否点击在多边形顶点
+            this.curIndex = -1;
+            this.curPoint = null;
+            let lenIndex = -1; // 当前点击到的点位索引;
+            let curenLen = this.scenceLen; // 当前的灵敏度
+            this.pointList.forEach((item, index) => {
+                let dis = SMathUtil.pointDistance(
+                    event.x,
+                    event.y,
+                    item.x,
+                    item.y
+                );
+                if (dis < curenLen) {
+                    curenLen = dis;
+                    lenIndex = index;
+                }
+            });
+            this.curIndex = lenIndex;
+            // 2判断是否点击在多边形得边上
+            if (-1 == lenIndex) {
+                let len = SMathUtil.pointToLine(
+                        new SPoint(event.x, event.y),
+                        new SLine(this.pointList[0], this.pointList[1])
+                    ),
+                    index = 0;
+                if (this.pointList.length > 2) {
+                    for (let i = 1; i < this.pointList.length; i++) {
+                        let dis = SMathUtil.pointToLine(
+                            new SPoint(event.x, event.y),
+                            new SLine(this.pointList[i], this.pointList[i + 1])
+                        );
+                        if (i + 1 == this.pointList.length) {
+                            dis = SMathUtil.pointToLine(
+                                new SPoint(event.x, event.y),
+                                new SLine(this.pointList[i], this.pointList[0])
+                            );
+                        }
+                        if (dis.MinDis < len.MinDis) {
+                            len = dis;
+                            index = i;
+                        }
+                    }
+                }
+                // 判断是否有点
+                if (len.Point) {
+                    // 点在了多边形的边上
+                    if (len.MinDis <= this.scenceLen) {
+                        this.pointList.splice(index + 1, 0, len.Point);
+                        // 记录新增顶点操作记录压入堆栈
+                        this.recordAction(SGraphPointListInsert, [
+                            this.pointList,
+                            len.Point,
+                            index + 1
+                        ]);
+                    } else {
+                        //没点在多边形边上也没点在多边形顶点上
+                        super.onMouseDown(event);
+                    }
+                }
+            } else {
+                // 当捕捉到顶点后 ,记录当前点的xy坐标,用于undo、redo操作
+                this.curPoint = new SPoint(
+                    this.pointList[this.curIndex].x,
+                    this.pointList[this.curIndex].y
+                );
+            }
+            // 刷新视图
+            this.update();
+        }
+    }
+
+    /////////////////////
+    // undo、redo相关操作
+
+    /**
+     * 记录相关动作并推入栈中
+     * @param	SGraphCommand         相关命令类
+     * @param	any                    对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const sgraphcommand = new SGraphCommand(this.scene, this, ...any);
+        this.undoStack.push(sgraphcommand);
+    }
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        if (this.status == SItemStatus.Normal) {
+            return;
+        }
+        this.undoStack.undo();
+    }
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        if (this.status == SItemStatus.Normal) {
+            return;
+        }
+        this.undoStack.redo();
+    }
+
+    ///////////////////////////////
+    // 以下为鼠标事件
+
+    /**
+     * 鼠标双击事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onDoubleClick(event: SMouseEvent): boolean {
+        // 如果位show状态 双击改对象则需改为编辑状态
+        if (SItemStatus.Normal == this.status) {
+            this.status = SItemStatus.Edit;
+            this.grabItem(this);
+        } else if (SItemStatus.Edit == this.status) {
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+        }
+        this.update();
+        return true;
+    } // Function onDoubleClick()
+
+    /**
+     * 键盘事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
+    onKeyDown(event: KeyboardEvent): boolean {
+        if (this.status == SItemStatus.Normal) {
+            return false;
+        } else if (this.status == SItemStatus.Create) {
+            if (event.code == "Enter") {
+                // 当顶点大于二个时才又条件执行闭合操作并清空堆栈
+                if (this.pointList.length > 2) {
+                    this.status = SItemStatus.Normal;
+                    //3 传递完成事件状态
+                    this.$emit("finishCreated");
+                    //1 grabItem 置为null
+                    this.releaseItem();
+                }
+            }
+        } else if (this.status == SItemStatus.Edit) {
+            if (event.key == "Alt") {
+                this.isAlt = true;
+            }
+        }
+        this.update();
+        return true;
+    } // Function onKeyDown()
+
+    /**
+     * 键盘键抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onKeyUp(event: KeyboardEvent): void {
+        if (this.status == SItemStatus.Edit) {
+            if (event.key == "Alt") {
+                this.isAlt = false;
+            } else if (event.keyCode == SKeyCode.Delete) {
+                // 当多边形的顶点大于三个允许删除点
+                if (this.pointList.length > 3) {
+                    this.deletePoint(this.curIndex);
+                }
+            }
+        }
+        this.update();
+    } // Function onKeyUp()
+
+    /**
+     * 鼠标按下事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseDown(event: SMouseEvent): boolean {
+        if (event.shiftKey) {
+            event = this.compare(event);
+        }
+        // 如果状态为编辑状态则添加点
+        if (this.status == SItemStatus.Create) {
+            // 新增顶点
+            let len = -1;
+            if (this.pointList.length) {
+                len = SMathUtil.pointDistance(
+                    event.x,
+                    event.y,
+                    this.pointList[0].x,
+                    this.pointList[0].y
+                );
+            }
+            if (this.pointList.length > 2 && len > 0 && len < this.scenceLen) {
+                this.status = SItemStatus.Normal;
+                //3 传递完成事件状态
+                this.$emit("finishCreated");
+                //1 grabItem 置为null
+                this.releaseItem();
+            } else {
+                this.insertPoint(event.x, event.y);
+                // 记录新增顶点操作记录压入堆栈
+                let pos = new SPoint(event.x, event.y);
+                this.recordAction(SGraphPointListInsert, [this.pointList, pos]);
+            }
+        } else if (this.status == SItemStatus.Edit) {
+            // 对多边形数组做编辑操作
+            this.editPolygonPoint(event);
+        } else {
+            return super.onMouseDown(event);
+        }
+        return true;
+    } // Function onMouseDown()
+
+    /**
+     * 鼠标移入事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseEnter(event: SMouseEvent): boolean {
+        return true;
+    } // Function onMouseEnter()
+
+    /**
+     * 鼠标移出事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
+    onMouseLeave(event: SMouseEvent): boolean {
+        return true;
+    } // Function onMouseLeave()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
+    onMouseMove(event: SMouseEvent): boolean {
+        if (event.shiftKey) {
+            event = this.compare(event);
+        }
+        if (this.status == SItemStatus.Create) {
+            this.lastPoint = new SPoint();
+            this.lastPoint.x = event.x;
+            this.lastPoint.y = event.y;
+            this.update();
+        } else if (this.status == SItemStatus.Edit) {
+            if (event.buttons == 1) {
+                if (-1 != this.curIndex) {
+                    this.pointList[this.curIndex].x = event.x;
+                    this.pointList[this.curIndex].y = event.y;
+                }
+            }
+            this.update();
+        } else {
+            return super.onMouseMove(event);
+        }
+        return true;
+    } // Function onMouseMove()
+
+    /**
+     * shift垂直水平创建或编辑
+     *
+     * @param   event   事件
+     * */
+    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;
+    } // Function compare()
+
+    /**
+     * 鼠标抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseUp(event: SMouseEvent): boolean {
+        if (this.status == SItemStatus.Edit) {
+            if (-1 != this.curIndex) {
+                const pos = new SPoint(
+                    this.pointList[this.curIndex].x,
+                    this.pointList[this.curIndex].y
+                );
+                this.recordAction(SGraphPointListUpdate, [
+                    this.pointList,
+                    this.curPoint,
+                    pos,
+                    this.curIndex
+                ]);
+            }
+        } else if (this.status == SItemStatus.Normal) {
+            this.moveToOrigin(this.x, this.y);
+            return super.onMouseUp(event);
+        }
+        return true;
+    } // Function onMouseUp()
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     *
+     * @param   x   x坐标
+     * @param   y   y坐标
+     * */
+    moveToOrigin(x: number, y: number): void {
+        super.moveToOrigin(x, y);
+        this.pointList = this.pointList.map(t => {
+            t.x = t.x + x;
+            t.y = t.y + y;
+            return t;
+        });
+        this.x = 0;
+        this.y = 0;
+    } // Function moveToOrigin()
+
+    /**
+     * 适配事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onResize(event: SMouseEvent): boolean {
+        return true;
+    } // Function onResize()
+
+    /**
+     * 取消操作
+     *
+     */
+    cancelOperate(): void {
+        // 当状态为展示状态
+        if (this.status == SItemStatus.Create) {
+            // 闭合多边形
+            this.parent = null;
+        } else if (this.status == SItemStatus.Edit) {
+            // 编辑状态
+            this.status = SItemStatus.Normal;
+        }
+        this.update();
+    } // Function cancelOperate()
+
+    /**
+     * Item对象边界区域
+     *
+     * @return SRect
+     */
+    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 => {
+                let x = it.x,
+                    y = it.y;
+                if (x < this.minX) {
+                    this.minX = x;
+                }
+                if (y < this.minY) {
+                    this.minY = y;
+                }
+                if (x > this.maxX) {
+                    this.maxX = x;
+                }
+                if (y > this.maxY) {
+                    this.maxY = y;
+                }
+            });
+        }
+        return new SRect(
+            this.minX,
+            this.minY,
+            this.maxX - this.minX,
+            this.maxY - this.minY
+        );
+    } // Function boundingRect()
+
+    /**
+     * 判断点是否在区域内
+     *
+     * @param x
+     * @param y
+     */
+    contains(x: number, y: number): boolean {
+        let arr = this.pointList;
+        if (arr.length < 3 || !SPolygonUtil.pointIn(x, y, arr)) {
+            return false;
+        }
+        return true;
+    } // Function contains()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter       painter对象
+     */
+    onDraw(painter: SPainter): void {
+        this.scenceLen = painter.toPx(this.len);
+        // 当状态为展示状态
+        if (this.status == SItemStatus.Normal) {
+            // 闭合多边形
+            this.drawShowPolygon(painter, this.pointList);
+        } else if (this.status == SItemStatus.Create) {
+            // 创建状态
+            this.drawCreatePolygon(painter, this.pointList);
+        } else {
+            // 编辑状态
+            this.drawEditPolygon(painter, this.pointList);
+        }
+    } // Function onDraw()
+}

+ 11 - 8
docs/guides/index.js

@@ -1,5 +1,4 @@
-const content = [
-    {
+const content = [{
         title: "安装",
         path: "/guides/base/"
     },
@@ -19,8 +18,7 @@ const content = [
     },
     {
         title: "场景管理",
-        children: [
-            {
+        children: [{
                 title: "图元素实例",
                 children: [
                     ["/guides/scene/items/clock", "时钟"],
@@ -38,8 +36,7 @@ const content = [
     },
     {
         title: "建筑信息图",
-        children: [
-            {
+        children: [{
                 title: "底图 Item 示例",
                 children: [
                     ["/guides/big/items/wall", "墙"],
@@ -70,10 +67,16 @@ const content = [
         children: [
             ["/guides/edit/clipboard/", "剪切板"],
             ["/guides/edit/selector/", "选择器"],
-            ["/guides/edit/items/", "编辑器Item示例"],
+            {
+                title: "编辑器Item示例",
+                children: [
+                    ["/guides/edit/items/editLine", "编辑线"],
+                    ["/guides/big/items/editPolygonItem", "编辑多边形"],
+                ]
+            },
             ["/guides/edit/undo/undo", "Undo示例"],
         ]
     },
 ];
 
-module.exports = content;
+module.exports = content;

+ 4 - 2
package.json

@@ -23,12 +23,14 @@
   },
   "devDependencies": {
     "@vuepress/plugin-back-to-top": "^1.2.0",
-    "vuepress-plugin-mathjax": "^1.2.8",
+    "less": "^3.12.2",
+    "less-loader": "^6.2.0",
     "node-ssh": "^6.0.0",
+    "polybooljs": "^1.2.0",
     "ts-loader": "^7.0.4",
     "typescript": "^3.9.2",
     "vuepress": "^1.2.0",
-    "polybooljs": "^1.2.0",
+    "vuepress-plugin-mathjax": "^1.2.8",
     "vuepress-plugin-typescript": "^0.2.0",
     "vuepress-types": "^0.9.2"
   }