Bläddra i källkod

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

haojianlong 4 år sedan
förälder
incheckning
ec3603c1b3

+ 9 - 0
persagy-web-edit/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+end_of_line =lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 58 - 0
persagy-web-edit/.eslintrc.js

@@ -0,0 +1,58 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+module.exports = {
+    root: true,
+    parser: '@typescript-eslint/parser',
+    extends: [
+        'plugin:@typescript-eslint/recommended',
+        // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
+        'prettier/@typescript-eslint',
+        // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
+        // 此行必须在最后
+        'plugin:prettier/recommended'
+    ],
+    env: {
+        es6: true,
+        node: true
+    },
+    parserOptions: {
+        // 支持最新 JavaScript
+        ecmaVersion: 2018,
+        sourceType: 'module'
+    },
+    rules: {
+        // 缩进
+        'indent': ['error', 4, { SwitchCase: 1 }],      // 缩进控制4空格
+        'max-len': ['error', 120],                      // 每行字符不超过120
+        'no-mixed-spaces-and-tabs': 'error',            // 禁止使用空格和tab混合缩进
+        // 语句
+        'curly': ["error", "multi-line"],               // if、else if、else、for、while强制使用大括号,但允许在单行中省略大括号。
+        'semi': ['error', 'always'],                    //不得省略语句结束的分号
+        '@typescript-eslint/no-unused-vars': 'off',     // 取消未使用变量检查
+        '@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }]       // public访问不需加访问修饰符
+    }
+};

+ 13 - 0
persagy-web-edit/.npmignore

@@ -0,0 +1,13 @@
+# 发布时排除
+api/
+docs/
+src/
+.editorconfig
+.eslintrc.js
+*.iml
+coverage
+jest.config.js
+tsconfig.json
+typedoc.json
+publish.js
+package-lock.json

+ 3 - 0
persagy-web-edit/README.md

@@ -0,0 +1,3 @@
+## 依赖包版本号
+
+"@persagy-web/graph": "2.2.1"

+ 38 - 0
persagy-web-edit/jest.config.js

@@ -0,0 +1,38 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+module.exports = {
+    preset: "ts-jest",
+    moduleFileExtensions: ["js", "ts"],
+    transform: {
+        "^.+\\.tsx?$": "ts-jest"
+    },
+    transformIgnorePatterns: ["/node_modules/"],
+    moduleNameMapper: {
+        "^@/(.*)$": "<rootDir>/src/$1"
+    },
+    collectCoverage: true
+};

+ 46 - 0
persagy-web-edit/package.json

@@ -0,0 +1,46 @@
+{
+    "name": "@persagy-web/edit",
+    "version": "2.2.1",
+    "description": "博锐尚格二维图形引擎。",
+    "main": "lib/index.js",
+    "types": "lib/index.d.js",
+    "remote": {
+        "host": "47.94.89.44",
+        "path": "/opt/tomcat9/webapps/api/web/edit",
+        "user": "user1",
+        "password": "@)!^sagacloud",
+        "local": "api"
+    },
+    "scripts": {
+        "build": "tsc",
+        "publish": "npm publish",
+        "lint": "eslint src/**/*.{js,ts,tsx}",
+        "test": "echo \"Error: no test specified\" && exit 1",
+        "typedoc": "typedoc --out api --hideGenerator src",
+        "publish-api": "node publish.js"
+    },
+    "keywords": [
+        "graph-edit"
+    ],
+    "author": "韩耀龙(han_yao_long@163.com)",
+    "license": "ISC",
+    "publishConfig": {
+        "registry": "http://dev.dp.sagacloud.cn:8082/repository/npm-hosted/"
+    },
+    "devDependencies": {
+        "@typescript-eslint/eslint-plugin": "^1.12.0",
+        "@typescript-eslint/parser": "^1.12.0",
+        "eslint": "^6.0.1",
+        "eslint-config-prettier": "^6.0.0",
+        "eslint-plugin-prettier": "^3.1.0",
+        "node-ssh": "^6.0.0",
+        "prettier": "^1.18.2",
+        "ts-jest": "^24.0.2",
+        "typedoc": "^0.17.4",
+        "typescript": "^3.5.3"
+    },
+    "dependencies": {
+        "@persagy-web/graph": "2.2.1",
+        "@types/uuid": "^8.0.0"
+    }
+}

+ 58 - 0
persagy-web-edit/publish.js

@@ -0,0 +1,58 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+const Client = require("node-ssh");
+const ssh = new Client();
+
+ssh.connect({
+    host: process.env.npm_package_remote_host,
+    port: "22",
+    username: process.env.npm_package_remote_user,
+    password: process.env.npm_package_remote_password
+}).then(() => {
+    const failedList = [];
+    ssh.putDirectory(
+        process.env.npm_package_remote_local,
+        process.env.npm_package_remote_path,
+        {
+            recursive: true,
+            concurrency: 1,
+            tick: function(localPath, remotePath, error) {
+                if (error) {
+                    failedList.push(localPath);
+                }
+            }
+        }
+    ).then(status => {
+        if (failedList.length > 0) {
+            console.log("发布失败");
+            console.log("failed transfers", failedList.join(", "));
+        } else {
+            console.log(status ? "发布成功" : "发布失败");
+        }
+        ssh.dispose();
+    });
+});

+ 66 - 0
persagy-web-edit/src/SGraphEdit.ts

@@ -0,0 +1,66 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SGraphItem } from "@persagy-web/graph/";
+import { SItemStatus } from "@persagy-web/big";
+
+/**
+ * 编辑基类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class SGraphEdit extends SGraphItem {
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //属性
+    /**编辑状态 */
+    protected _status: SItemStatus = SItemStatus.Normal;
+    get status(): SItemStatus {
+        return this._status;
+    }
+    set status(value: SItemStatus) {
+        const oldStatus = this._status;
+        const newStatus = value;
+        this._status = value;
+        //状态变更触发事件
+        this.$emit('StatusChange', oldStatus, newStatus)
+        this.update();
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //函数
+    /**
+     * 将类中得数据转换为方便存储格式的方法
+     *
+     * return   any         针对item类型保持相应的格式
+     */
+    toData(): any {
+
+    }
+
+    constructor(parent: SGraphItem | null, ) {
+        super(parent)
+    }
+}

+ 87 - 0
persagy-web-edit/src/commands/SGraphAddCommand.ts

@@ -0,0 +1,87 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SGraphCommand } from "./SGraphCommand";
+import { SGraphItem,SGraphScene } from "@persagy-web/graph/";
+import { SUndoCommand } from "@persagy-web/base";
+
+
+/**
+ * 添加item命令
+ *
+ * @author  panglixiang
+ * */
+export class SGraphAddCommand extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /** 命令item对象    */
+    item: SGraphItem;
+    /** 命令item的父类   */
+    parent: SGraphItem | null;
+
+    /**
+     * 构造函数
+     *
+     * @param   scene   item所在场景
+     * @param   item    命令item对象
+     * */
+    constructor(scene: SGraphScene, item: SGraphItem) {
+        super(scene);
+        this.item = item;
+        this.parent = item.parent;
+        this.command = "SGraphAddCommand";
+        this.desc = `添加对象=${item.id}`;
+    } // Constructor
+
+    /**
+     * 合并命令
+     *
+     * @return  boolean 是否已执行合并
+     * */
+    mergeWith(command: SUndoCommand): boolean {
+        return false;
+    } // Function mergeWith()
+
+    /**
+     * 重做
+     *
+     * */
+    redo(): void {
+        this.item.parent = this.parent;
+        // @ts-ignore
+        this.parent.update();
+    } // Function redo()
+
+    /**
+     * 撤销
+     *
+     * */
+    undo(): void {
+        this.item.parent = null;
+        // @ts-ignore
+        this.parent.update();
+    } // Function undo()
+} // Class SGraphAddCommand

+ 48 - 0
persagy-web-edit/src/commands/SGraphCommand.ts

@@ -0,0 +1,48 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SUndoCommand } from "@persagy-web/base";
+import { SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * Graph命令基类
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export abstract class SGraphCommand extends SUndoCommand {
+    /** 命令所属的场景类 */
+    scene: SGraphScene | null = null;
+
+    /**
+     * 构造函数
+     *
+     * @param   scene       命令所属的场景类
+     */
+    protected constructor(scene: SGraphScene | null) {
+        super();
+        this.scene = scene;
+    } // Constructor
+} // Class SGraphCommand

+ 112 - 0
persagy-web-edit/src/commands/SGraphMoveCommand.ts

@@ -0,0 +1,112 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SGraphCommand } from "./SGraphCommand";
+import { SGraphItem } from "@persagy-web/graph/";
+import { SPoint } from "@persagy-web/draw";
+import { SUndoCommand } from "@persagy-web/base";
+import { SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * item移动命令
+ *
+ * @author  panglixiang
+ *
+ * */
+export class SGraphMoveCommand extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /** 执行命令的item   */
+    item: SGraphItem;
+    /** 移动前位置    */
+    old: SPoint;
+    /** 移动后位置   */
+    pos: SPoint;
+
+    /**
+     * 构造函数
+     *
+     * @param   scene   当前场景
+     * @param   item    item对象
+     * @param   old     移动前位置
+     * @param   pos     移动后位置
+     * */
+    constructor(
+        scene: SGraphScene,
+        item: SGraphItem,
+        old: SPoint,
+        pos: SPoint
+    ) {
+        super(scene);
+        this.item = item;
+        this.old = old;
+        this.pos = pos;
+        this.command = "SGraphMoveCommand";
+        this.desc = `移动对象=${item.id}`;
+    } // Constructor
+
+    /**
+     * 合并命令
+     *
+     * @param   command 命令
+     * @return  boolean 是否已执行合并命令
+     * */
+    mergeWith(command: SUndoCommand): boolean {
+        if (command instanceof SGraphMoveCommand && command.item == this.item) {
+            command.pos = this.pos;
+            return true;
+        }
+        return false;
+    } // Function mergeWith()
+
+    /**
+     * 重做
+     *
+     * */
+    redo(): void {
+        this.item.pos = new SPoint(this.pos.x, this.pos.y);
+        this.item.update();
+    } // Function redo()
+
+    /**
+     * 撤销
+     *
+     * */
+    undo(): void {
+        this.item.pos = new SPoint(this.old.x, this.old.y);
+        this.item.update();
+    } // Function undo()
+
+    /**
+     * 命令细节描述
+     *
+     * */
+    toString(): string {
+        return `oldPos=${JSON.stringify(this.old)};\nnewPos=${JSON.stringify(
+            this.pos
+        )}`;
+    } // Function toString()
+} // Class SGraphMoveCommand

+ 107 - 0
persagy-web-edit/src/commands/SGraphPointListDelete.ts

@@ -0,0 +1,107 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SPoint } from "@persagy-web/draw";
+import { SGraphCommand, SGraphItem, SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * 多边形、折线等相关顶点的位置删除命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SGraphPointListDelete extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /**  指向item对象 */
+    item: SGraphItem;
+    /** 索引 */
+    index: number | null;
+    /**  删除位置 */
+    pos: SPoint | null;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   scene       item所在场景
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   pos         删除的点
+     * @param   index       索引
+     */
+    constructor(
+        scene: SGraphScene,
+        item: SGraphItem,
+        pointList: SPoint[],
+        pos: SPoint,
+        index: number | null = null
+    ) {
+        super(scene);
+        this.item = item;
+        this.index = index;
+        this.pointList = pointList;
+        this.pos = pos;
+        this.command = "SGraphPointListDelete";
+        this.desc = `删除折点=${item.id}`;
+    } // Constructor
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        if (this.index == null) {
+            this.pointList.pop();
+        } else {
+            this.pointList.splice(this.index, 1);
+        }
+        this.item.update();
+    } // Function redo()
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        if (this.pos == null) return;
+        if (this.index == null) {
+            this.pointList.push(this.pos);
+        } else {
+            this.pointList.splice(this.index, 0, this.pos);
+        }
+        this.item.update();
+    } // Function undo()
+
+    /**
+     * 命令细节描述
+     *
+     * */
+    toString(): string {
+        const pointList = `pointList=${JSON.stringify(this.pointList)}`;
+        const pos = `pos=${JSON.stringify(this.pos)}`;
+        const index = `index=${this.index}`;
+        return `${index};\n${pos};\n${pointList}`;
+    } // Function toString()
+} // Class SGraphPointListDelete

+ 107 - 0
persagy-web-edit/src/commands/SGraphPointListInsert.ts

@@ -0,0 +1,107 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SPoint } from "@persagy-web/draw";
+import { SGraphCommand, SGraphItem, SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * 多边形、折线等相关顶点的位置插入命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SGraphPointListInsert extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /**  指向item对象 */
+    item: SGraphItem;
+    /**  当前位置 */
+    pos: SPoint;
+    /** 索引 */
+    index: number | null;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   scene       item所在场景
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   pos         当前位置
+     * @param   index       索引
+     */
+    constructor(
+        scene: SGraphScene,
+        item: SGraphItem,
+        pointList: SPoint[],
+        pos: SPoint,
+        index: number | null = null
+    ) {
+        super(scene);
+        this.item = item;
+        this.pos = pos;
+        this.index = index;
+        this.pointList = pointList;
+        this.command = "SGraphPointListInsert";
+        this.desc = `添加折点=${item.id}`;
+    } // Constructor
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        const point = new SPoint(this.pos.x, this.pos.y);
+        if (this.index == null) {
+            this.pointList.push(point);
+        } else {
+            this.pointList.splice(this.index, 0, point);
+        }
+        this.item.update();
+    } // Function redo()
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        if (this.index == null) {
+            this.pointList.pop();
+        } else {
+            this.pointList.splice(this.index, 1);
+        }
+        this.item.update();
+    } // Function undo()
+
+    /**
+     * 命令细节描述
+     *
+     * */
+    toString(): string {
+        const pointList = `pointList=${JSON.stringify(this.pointList)}`;
+        const pos = `pos=${JSON.stringify(this.pos)}`;
+        const index = `index=${this.index}`;
+        return `${index};\n${pos};\n${pointList}`;
+    } // Function toString()
+} // Class SGraphPointListInsert()

+ 106 - 0
persagy-web-edit/src/commands/SGraphPointListUpdate.ts

@@ -0,0 +1,106 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SPoint } from "@persagy-web/draw";
+import { SGraphCommand, SGraphItem, SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * 多边形、折线等相关顶点的位置更新命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SGraphPointListUpdate extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /**  指向item对象 */
+    item: SGraphItem;
+    /**  原位置 */
+    old: SPoint;
+    /**  当前位置 */
+    pos: SPoint;
+    /** 索引 */
+    index: number;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   scene       item所在场景
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   old         原位置
+     * @param   pos         当前位置
+     * @param   index       索引
+     */
+    constructor(
+        scene: SGraphScene,
+        item: SGraphItem,
+        pointList: SPoint[],
+        old: SPoint,
+        pos: SPoint,
+        index: number
+    ) {
+        super(scene);
+        this.item = item;
+        this.old = old;
+        this.pos = pos;
+        this.index = index;
+        this.pointList = pointList;
+        this.command = "SGraphPointListUpdate";
+        this.desc = `更新折点=${item.id}`;
+    } // Constructor
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        this.pointList[this.index].x = this.pos.x;
+        this.pointList[this.index].y = this.pos.y;
+        this.item.update();
+    } // Function redo()
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        this.pointList[this.index].x = this.old.x;
+        this.pointList[this.index].y = this.old.y;
+        this.item.update();
+    } // Function undo()
+
+    /**
+     * 命令细节描述
+     *
+     * */
+    toString(): string {
+        const pointList = `pointList=${JSON.stringify(this.pointList)}`;
+        const old = `old=${JSON.stringify(this.old)}`;
+        const pos = `pos=${JSON.stringify(this.pos)}`;
+        const index = `index=${this.index}`;
+        return `${index};\n${old};\n${pos};\n${pointList}`;
+    } // Function toString()
+} // Class SGraphPointListUpdate

+ 102 - 0
persagy-web-edit/src/commands/SGraphPropertyCommand.ts

@@ -0,0 +1,102 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SGraphCommand } from "./SGraphCommand";
+import { SGraphItem } from "@persagy-web/graph/";
+import { SGraphScene } from "@persagy-web/graph/";
+
+/**
+ * 属性修改命令类
+ *
+ * @author  张宇(taohuzy@163.com)
+ */
+
+export class SGraphPropertyCommand<T> extends SGraphCommand {
+    /** 命令名称    */
+    readonly command: string;
+    /**	指向item对象	*/
+    item: SGraphItem;
+    /** 属性名称 */
+    propName: string;
+    /** 属性旧值 */
+    oldProp: T;
+    /** 属性新值 */
+    newProp: T;
+
+    /**
+     * 构造函数
+     *
+     * @param   scene       命令所属的场景类
+     * @param   item        命令所属的item类
+     * @param   propName    修改的属性名称
+     * @param   oldProp     修改前的属性值
+     * @param   newProp     修改后的属性值
+     */
+    constructor(
+        scene: SGraphScene,
+        item: SGraphItem,
+        propName: string,
+        oldProp: T,
+        newProp: T
+    ) {
+        super(scene);
+        this.item = item;
+        this.propName = propName;
+        this.oldProp = oldProp;
+        this.newProp = newProp;
+        this.command = "SGraphPropertyCommand";
+        this.command = `更新属性=${item.id}`;
+    } // Constructor
+
+    /**
+     * redo操作
+     */
+    redo(): void {
+        // @ts-ignore
+        this.item[this.propName] = this.newProp;
+        this.item.update();
+    } // Function redo()
+
+    /**
+     * undo操作
+     */
+    undo(): void {
+        // @ts-ignore
+        this.item[this.propName] = this.oldProp;
+        this.item.update();
+    } // Function undo()
+
+    /**
+     * 命令细节描述
+     *
+     * */
+    toString(): string {
+        const propName = `propName=${this.propName}`;
+        const oldProp = `oldProp=${JSON.stringify(this.oldProp)}`;
+        const newProp = `newProp=${JSON.stringify(this.newProp)}`;
+        return `${propName};\n${oldProp};\n${newProp}`;
+    } // Function toString()
+} // Class SGraphPropertyCommand

+ 49 - 0
persagy-web-edit/src/enums/SGraphLayoutType.ts

@@ -0,0 +1,49 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+/**
+ * 布局对齐方式
+ *
+ * @author  郝建龙
+ */
+export enum SGraphLayoutType {
+    /** 垂直分布    */
+    Vertical,
+    /** 垂直分布    */
+    Horizontal,
+    /** 左对齐 */
+    Left,
+    /** 右对齐 */
+    Right,
+    /** 顶对齐 */
+    Top,
+    /** 底对齐 */
+    Bottom,
+    /** 水平居中    */
+    Center,
+    /** 垂直居中    */
+    Middle
+} // Enum SGraphLayoutType

+ 39 - 0
persagy-web-edit/src/enums/SImageShowType.ts

@@ -0,0 +1,39 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+/**
+ * 图片item展示方式
+ *
+ * @author  haojianlong
+ */
+export enum SImageShowType {
+    /** 铺满  */
+    Full,
+    /** 自适应 */
+    AutoFit,
+    /** 等比缩放 */
+    Equivalency
+} // Enum SImageShowType

+ 41 - 0
persagy-web-edit/src/enums/SLineStyle.ts

@@ -0,0 +1,41 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+/**
+ * 线样式
+ *
+ * @author  张宇
+ */
+export enum SLineStyle {
+    /** 实线    */
+    Solid,
+    /** 虚线    */
+    Dashed,
+    /** 点线    */
+    Dotted,
+    /** 无    */
+    None
+} // Enum SLineStyle

+ 41 - 0
persagy-web-edit/src/enums/SOrderSetType.ts

@@ -0,0 +1,41 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+/**
+ * 图层操作
+ *
+ * @author  郝建龙
+ */
+export enum SOrderSetType {
+    /** 置顶  */
+    Top,
+    /** 置底    */
+    Bottom,
+    /** 上移一层    */
+    After,
+    /** 下移一层    */
+    Before
+} // Enum SOrderSetType

+ 37 - 0
persagy-web-edit/src/enums/STextOrigin.ts

@@ -0,0 +1,37 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+/**
+ * 文本原点位置
+ *
+ * @author  张宇
+ */
+export enum STextOrigin {
+  /** 左上点    */
+  LeftTop,
+  /** 中心点    */
+  Centrum,
+} // Enum STextOrigin

+ 27 - 0
persagy-web-edit/src/index.ts

@@ -0,0 +1,27 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+import {SGraphEdit} from "./SGraphEdit";
+export {SGraphEdit}

+ 93 - 0
persagy-web-edit/src/items/ImageEdit.ts

@@ -0,0 +1,93 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SPainter, SRect, SSize, SColor, SPoint } from "@persagy-web/draw";
+import { SImageShowType, STextOrigin } from "@persagy-web/graph";
+import { SGraphItem, SImageItem } from "@persagy-web/graph";
+import { SItemStatus } from "@persagy-web/big";
+
+/**
+ * 图片编辑类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class ImageEdit extends SImageItem {
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //属性
+    /**编辑状态 */
+    protected _status: SItemStatus = SItemStatus.Normal;
+    get status(): SItemStatus {
+        return this._status;
+    }
+    set status(value: SItemStatus) {
+        const oldStatus = this._status;
+        const newStatus = value;
+        this._status = value;
+        //状态变更触发事件
+        this.$emit('StatusChange', oldStatus, newStatus)
+        this.update();
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //函数
+    /**
+     * 构造函数
+     *
+     * @param   parent      指向父对象
+     */
+    constructor(parent: SGraphItem | null, url: string) {
+        super(parent);
+        this.url = url;
+        // this.initItem();
+        this.origin = new SPoint(this.width / 2, this.height / 2);
+    }
+
+    /**
+     * 将类中得数据转换为方便存储格式的方法
+     *
+     * return   any         针对item类型保持相应的格式
+     */
+    toData(): any {
+
+    }
+    // 初始化item
+    // initItem() {
+    //     if (!this.url) {
+    //         this.strokeColor = SColor.Black;
+    //         this.width = 100;
+    //         this.height = 100
+    //     }
+    // }
+
+    /**
+     * 根据显示模式计算图片的宽高
+     */
+    computeImgSize(): void {
+        super.computeImgSize();
+        this.origin = new SPoint(this.width / 2, this.height / 2);
+        this.update();
+    } // Function computeImgSize()
+} // Class SImageItem

+ 541 - 0
persagy-web-edit/src/items/LineEdit.ts

@@ -0,0 +1,541 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+
+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 {
+    SGraphPointListInsert,
+    SGraphPointListUpdate,
+    SLineStyle,
+    SGraphItem
+} from "@persagy-web/graph/";
+import { SGraphEdit } from "./../"
+
+/**
+ * 直线编辑类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class LineEdit extends SGraphEdit {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //属性
+    /** 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

+ 843 - 0
persagy-web-edit/src/items/PolygonEdit.ts

@@ -0,0 +1,843 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import {
+    SGraphPointListDelete,
+    SGraphPointListInsert,
+    SGraphPointListUpdate,
+    SLineStyle,
+    SGraphItem
+} from "@persagy-web/graph";
+import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base/";
+import {
+    SColor,
+    SLine,
+    SLineCapStyle,
+    SPainter,
+    SPoint,
+    SPolygonUtil,
+    SRect
+} from "@persagy-web/draw";
+import { SItemStatus } from "@persagy-web/big";
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
+import {SGraphEdit} from "./../"
+
+/**
+ * 多边形编辑类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class PolygonEdit extends SGraphEdit {
+    /** 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();
+    }
+    /** 是否垂直水平绘制   */
+    private _verAndLeve: Boolean = false;
+    get verAndLeve(): Boolean {
+        return this._verAndLeve;
+    }
+    set verAndLeve(bool: Boolean) {
+        this._verAndLeve = bool;
+        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 || this.verAndLeve) {
+            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 || this.verAndLeve) {
+            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()
+}

+ 629 - 0
persagy-web-edit/src/items/PolylineEdit.ts

@@ -0,0 +1,629 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+
+import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw";
+import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base";
+import { SItemStatus } from "@persagy-web/big";;
+import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";;
+import {
+    SGraphPointListDelete,
+    SGraphPointListInsert,
+    SGraphPointListUpdate,
+    SLineStyle,
+    SGraphItem
+} from "@persagy-web/graph/";
+import {SGraphEdit} from "./../"
+
+/**
+ * 折线编辑类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class PolylineEdit extends SGraphEdit {
+    /** 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();
+    }
+    /** 鼠标移动时的点 */
+    private lastPoint: SPoint | null = null;
+    /** 线条颜色    */
+    _strokeColor: SColor = SColor.Black;
+    get strokeColor(): SColor {
+        return this._strokeColor;
+    }
+    set strokeColor(v: SColor) {
+        this._strokeColor = v;
+        this.update();
+    }
+    /** 填充色 */
+    _fillColor: SColor = new SColor("#2196f3");
+    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 = 1;
+    get lineWidth(): number {
+        return this._lineWidth;
+    }
+    set lineWidth(v: number) {
+        this._lineWidth = v;
+        this.update();
+    }
+    /** 是否垂直水平绘制   */
+    private _verAndLeve: Boolean = false;
+    get verAndLeve(): Boolean {
+        return this._verAndLeve;
+    }
+    set verAndLeve(bool: Boolean) {
+        this._verAndLeve = bool;
+        this.update();
+    }
+    /** 全局灵敏度   */
+    dis: number = 10;
+    /** 灵敏度转换为场景长度  */
+    private sceneDis: number = 10;
+    /** 当前点索引   */
+    private 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);
+        if (list instanceof SPoint) {
+            this.pointList.push(list);
+        } else {
+            this.pointList = list;
+        }
+    } // Constructor
+
+    /**
+     * 添加点至数组中
+     *
+     * @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();
+    } // Function addPoint()
+
+    /**
+     * 是否可以添加点到数组中
+     *
+     * @param   index   要添加到的索引
+     * @return  boolean 是否可添加
+     * */
+    private canHandle(index: number): boolean {
+        return index >= 0 && index <= this.pointList.length;
+    } // Function canHandle()
+
+    /**
+     * 根据索引删除点
+     *
+     * @param   index   删除点
+     * */
+    deletePoint(index: number): void {
+        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(SGraphPointListDelete, [
+                this.pointList,
+                p,
+                index
+            ]);
+            this.curIndex = -1;
+            this.curPoint = null;
+            this.update();
+        }
+    } // Function deletePoint
+
+    /**
+     * 鼠标按下事件
+     *
+     * @param   event   鼠标事件
+     * @return  boolean 是否处理事件
+     * */
+    onMouseDown(event: SMouseEvent): boolean {
+        this.curIndex = -1;
+        this.curPoint = null;
+        if (event.shiftKey || this.verAndLeve) {
+            event = this.compare(event);
+        }
+        if (event.buttons == 1) {
+            if (this.status == SItemStatus.Create) {
+                this.addPoint(new SPoint(event.x, event.y));
+                return true;
+            } else if (this.status == SItemStatus.Edit) {
+                // 查询鼠标最近的索引
+                this.findNearestPoint(new SPoint(event.x, event.y));
+                // 增加点
+                if (this.curIndex < 0) {
+                    this.findAddPos(new SPoint(event.x, event.y));
+                }
+                // 删除点
+                if (event.altKey && this.canHandle(this.curIndex)) {
+                    this.deletePoint(this.curIndex);
+                }
+                this.update();
+                return true;
+            } else {
+                return super.onMouseDown(event);
+            }
+        }
+        return super.onMouseDown(event);
+    } // Function onMouseDown()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param   event   鼠标事件
+     * @return  boolean 是否处理事件
+     * */
+    onMouseMove(event: SMouseEvent): boolean {
+        if (event.shiftKey || this.verAndLeve) {
+            event = this.compare(event);
+        }
+        if (this.status == SItemStatus.Create) {
+            if (this.lastPoint) {
+                this.lastPoint.x = event.x;
+                this.lastPoint.y = event.y;
+            } else {
+                this.lastPoint = new SPoint(event.x, event.y);
+            }
+            this.update();
+            return true;
+        } else if (this.status == SItemStatus.Edit) {
+            if (event.buttons == 1) {
+                if (this.canHandle(this.curIndex)) {
+                    this.pointList[this.curIndex].x = event.x;
+                    this.pointList[this.curIndex].y = event.y;
+                }
+            }
+            this.update();
+            return true;
+        } else {
+            return super.onMouseMove(event);
+        }
+    } // Function onMouseMove()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param   event   鼠标事件
+     * @return  boolean 是否处理事件
+     * */
+    onMouseUp(event: SMouseEvent): boolean {
+        if (this.status == SItemStatus.Edit) {
+            if (this.curIndex > -1) {
+                const p = new SPoint(
+                    this.pointList[this.curIndex].x,
+                    this.pointList[this.curIndex].y
+                );
+                this.recordAction(SGraphPointListUpdate, [
+                    this.pointList,
+                    this.curPoint,
+                    p,
+                    this.curIndex
+                ]);
+            }
+        } else if (this.status == SItemStatus.Normal) {
+            this.moveToOrigin(this.x, this.y);
+            return super.onMouseUp(event);
+        }
+        return true;
+    } // Function onMouseUp()
+
+    /**
+     * 鼠标双击事件
+     *
+     * @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();
+        } else if (this.status == SItemStatus.Create) {
+            if (this.pointList.length > 1) {
+                this.status = SItemStatus.Normal;
+                this.releaseItem();
+                this.$emit("finishCreated");
+            }
+        }
+        this.$emit("onDoubleClick", event);
+        return true;
+    } // Function onDoubleClick()
+
+    /***
+     * 键盘按键弹起事件
+     *
+     * @param	event         事件参数
+     */
+    onKeyUp(event: KeyboardEvent): void {
+        if (event.keyCode == SKeyCode.Enter) {
+            if (this.pointList.length > 1) {
+                if (this.status == SItemStatus.Create) {
+                    this.$emit("finishCreated");
+                }
+                this.status = SItemStatus.Normal;
+                this.releaseItem();
+            }
+        }
+        // delete删除点
+        if (
+            event.keyCode == SKeyCode.Delete &&
+            this.status == SItemStatus.Edit
+        ) {
+            this.deletePoint(this.curIndex);
+        }
+    } // Function onKeyUp()
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     *
+     * @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   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
+                );
+            }
+        }
+    } // Function findNearestPoint()
+
+    /**
+     * 计算增加点的位置
+     *
+     * @param   p   鼠标点击点
+     * */
+    findAddPos(p: SPoint): void {
+        let len = SMathUtil.pointToLine(
+            p,
+            new SLine(this.pointList[0], this.pointList[1])
+        ),
+            index = 0;
+        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) {
+            if (len.Point) {
+                this.addPoint(len.Point, index + 1);
+            }
+        }
+    } // Function findAddPos()
+
+    /**
+     * 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	SGraphCommand   相关命令类
+     * @param	any             对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const command = new SGraphCommand(this.scene, this, ...any);
+        this.undoStack.push(command);
+    } // Function recordAction()
+
+    /**
+     * 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
+     * @return  true-是
+     */
+    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;
+    } // 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()
+
+    /**
+     * 取消操作执行
+     *
+     * */
+    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()
+
+    /**
+     * 绘制基本图形
+     * */
+    drawBaseLine(painter: SPainter): void {
+        // 绘制基本图形
+        painter.pen.color = 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)
+            ];
+        }
+        painter.drawPolyline(this.pointList);
+    } // Function drawBaseLine()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter painter对象
+     */
+    onDraw(painter: SPainter): void {
+        // 缓存场景长度
+        this.sceneDis = painter.toPx(this.dis);
+        // 创建状态
+        painter.pen.lineWidth = painter.toPx(this.lineWidth);
+        if (this.status == SItemStatus.Create && this.lastPoint) {
+            // 绘制基本图形
+            this.drawBaseLine(painter);
+            painter.drawLine(
+                this.pointList[this.pointList.length - 1],
+                this.lastPoint
+            );
+            // 编辑状态
+            this.pointList.forEach((t, i): void => {
+                painter.brush.color = SColor.White;
+                if (i == this.curIndex) {
+                    painter.brush.color = this.fillColor;
+                }
+                painter.drawCircle(t.x, t.y, painter.toPx(5));
+            });
+        } else if (this.status == SItemStatus.Edit) {
+            // 绘制基本图形
+            this.drawBaseLine(painter);
+            // 编辑状态
+            this.pointList.forEach((t, i): void => {
+                painter.brush.color = SColor.White;
+                if (i == this.curIndex) {
+                    painter.brush.color = this.fillColor;
+                }
+                painter.drawCircle(t.x, t.y, painter.toPx(5));
+            });
+        } else {
+            // 查看状态
+            if (this.selected) {
+                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;
+            }
+            // 绘制基本图形
+            this.drawBaseLine(painter);
+        }
+    } // Function onDraw()
+} // Class SPolylineItem

+ 258 - 0
persagy-web-edit/src/items/TextEdit.ts

@@ -0,0 +1,258 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+import { SObjectItem } from "@persagy-web/graph/";
+import { SPainter, SRect, SColor, SFont, SPoint } from "@persagy-web/draw";
+import { SGraphItem, STextOrigin, SLineStyle } from "@persagy-web/graph/";
+import { SItemStatus } from "@persagy-web/big";
+
+/**
+ * 文本编辑类
+ *
+ * @author  韩耀龙(han_yao_long@163.com)
+ */
+export class TextEdit extends SObjectItem {
+    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //属性
+    /**编辑状态 */
+    protected _status: SItemStatus = SItemStatus.Normal;
+    get status(): SItemStatus {
+        return this._status;
+    }
+    set status(value: SItemStatus) {
+        const oldStatus = this._status;
+        const newStatus = value;
+        this._status = value;
+        //状态变更触发事件
+        this.$emit('StatusChange', oldStatus, newStatus)
+        this.update();
+    }
+    /** 记录painter */
+    private _painter: SPainter | null = null;
+
+    /** 文本内容 */
+    private _text: string = "";
+    get text(): string {
+        return this._text;
+    }
+    set text(v: string) {
+        this._text = v;
+        this._textArr = this.text.split(/\n/g);
+        this.drawFormatText();
+        this.update();
+    }
+
+    /** 切割后的文本数组 */
+    private _textArr: string[] = [];
+
+    /** 文本颜色 */
+    private _color: SColor = new SColor("#333333");
+    get color(): SColor {
+        return this._color;
+    }
+    set color(v: SColor) {
+        this._color = v;
+        this.update();
+    }
+
+    /** 字体 */
+    private _font: SFont;
+    get font(): SFont {
+        return this._font;
+    }
+    set font(v: SFont) {
+        this._font = v;
+        this.drawFormatText();
+        this.update();
+    }
+
+    /** 背景色 */
+    private _backgroundColor: SColor = SColor.Transparent;
+    get backgroundColor(): SColor {
+        return this._backgroundColor;
+    }
+    set backgroundColor(v: SColor) {
+        this._backgroundColor = v;
+        this.update();
+    }
+
+    /** 边框色 */
+    private _strokeColor: SColor = SColor.Transparent;
+    get strokeColor(): SColor {
+        return this._strokeColor;
+    }
+    set strokeColor(v: SColor) {
+        this._strokeColor = v;
+        this.update();
+    }
+
+    /** 边框宽度 */
+    private _lineWidth: number = 1;
+    get lineWidth(): number {
+        return this._lineWidth;
+    }
+    set lineWidth(v: number) {
+        this._lineWidth = v;
+        this.update();
+    }
+
+    /** 边框样式    */
+    private _borderStyle: SLineStyle = SLineStyle.None;
+    get borderStyle(): SLineStyle {
+        return this._borderStyle;
+    }
+    set borderStyle(v: SLineStyle) {
+        this._borderStyle = v;
+        this.update();
+    }
+
+    /** 原点位置    */
+    private _originPosition: STextOrigin = STextOrigin.LeftTop;
+    get originPosition(): STextOrigin {
+        return this._originPosition;
+    }
+    set originPosition(v: STextOrigin) {
+        this._originPosition = v;
+        this.update();
+    }
+
+    /** 文本绘制最大宽 */
+    maxWidth: number | undefined = undefined;
+
+    /**
+     * 构造函数
+     *
+     * @param   parent      指向父Item
+     * @param   str         文本内容
+     */
+    constructor(parent: SGraphItem | null, str: string = "") {
+        super(parent);
+        this._text = str;
+        this._font = new SFont("sans-serif", 12);
+        this.height = 12;
+    } // Constructor
+
+    /**
+     * 对象边界区域
+     *
+     * @return  边界区域
+     */
+    boundingRect(): SRect {
+        return new SRect(
+            -this.origin.x,
+            -this.origin.y,
+            this.width,
+            this.height
+        );
+    } // Function boundingRect()
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     *
+     * @param   x   x坐标
+     * @param   y   y坐标
+     * */
+    moveToOrigin(x: number, y: number): void {
+        this.moveTo(this.x + x, this.y + y);
+    } // Function moveToOrigin()
+
+    /**
+     * 绘制显示状态文本Item
+     *
+     * @param painter    绘制类
+     */
+    protected drawShowText(painter: SPainter): void {
+        painter.translate(-this.origin.x, -this.origin.y);
+        //绘制矩形轮廓
+        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.brush.color = this.backgroundColor;
+        painter.pen.lineWidth = this.lineWidth;
+        painter.pen.color = this.strokeColor;
+        painter.drawRect(0, 0, this.width, this.height);
+
+        //绘制文本
+        painter.brush.color = new SColor(this.color);
+        painter.shadow.shadowColor = SColor.Transparent;
+        painter.font = this.font;
+        this._textArr.forEach((text: string, index: number) => {
+            painter.drawText(
+                text,
+                this.font.size / 4,
+                index * (this.font.size * 1.25) + this.font.size / 4,
+                this.maxWidth
+            );
+        });
+    } // Function drawShowText()
+
+    /**
+     * 根据换行切割文本,绘制多行并计算外轮廓宽高
+     *
+     */
+    protected drawFormatText(): void {
+        if (this._painter instanceof SPainter) {
+            this._painter.save();
+            this._painter.font = this.font;
+            let textMaxWidth = 0;
+            let fontSize: number = this.font.size;
+            this._textArr.forEach((text: string, index: number) => {
+                let textWidth: number = this._painter
+                    ? this._painter.textWidth(text) + fontSize / 2
+                    : fontSize / 2;
+                if (textWidth > textMaxWidth) {
+                    textMaxWidth = textWidth;
+                }
+            });
+            // 在绘制文本后计算文本的宽高
+            this.width = textMaxWidth;
+            this.height = fontSize * 1.25 * this._textArr.length + fontSize / 8;
+            // 设置原点位置(默认左上角)
+            if (this.originPosition == STextOrigin.Centrum) {
+                this.origin = new SPoint(this.width / 2, this.height / 2);
+            }
+            this._painter.restore();
+        }
+    } // Function drawFormatText()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   painter      绘画类
+     */
+    onDraw(painter: SPainter): void {
+        if (!(this._painter instanceof SPainter)) {
+            this._painter = painter;
+            this.drawFormatText();
+        }
+        this.drawShowText(painter);
+    } // Function onDraw()
+} // Class STextItem

+ 16 - 0
persagy-web-edit/tsconfig.json

@@ -0,0 +1,16 @@
+{
+    "compilerOptions": {
+        "target": "es6",                            // Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'.
+        "module": "commonjs",                       // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
+        "outDir": "./lib",                          // 编译后生成的文件目录
+        "strict": true,                             // 开启严格的类型检测
+        "declaration": true,                        // 生成 `.d.ts` 文件
+        "experimentalDecorators": true,             // 开启装饰器
+        "removeComments": true,                     // 去除注释
+        "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错。
+        "esModuleInterop": true,                    // 支持别名导入
+        "moduleResolution": "node"                  // 此处设置为node,才能解析import xx from 'xx'
+    },
+    "include": ["./src"],
+    "exclude": ["node_modules"]
+}

+ 6 - 0
persagy-web-edit/typedoc.json

@@ -0,0 +1,6 @@
+{
+    "name": "博锐尚格绘制引擎",
+    "mode": "file",
+    "out": "doc",
+    "exclude": ["**/*+(index|.test).ts"]
+}