Pārlūkot izejas kodu

guides:format:拓扑图格式调整

haojianlong 4 gadi atpakaļ
vecāks
revīzija
6d10aa1714

+ 261 - 0
docs/.vuepress/components/edit/items/editpolyline/editCirclePolyline.vue

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

+ 71 - 0
docs/.vuepress/components/scene/items/polyline.vue

@@ -0,0 +1,71 @@
+<template>
+    <div>
+        <canvas id="editPolyline" width="740" height="400" tabindex="0"></canvas>
+    </div>
+</template>
+
+<script>
+import { SGraphScene, SGraphView } from "@persagy-web/graph/lib";
+import { SItemStatus, SCircleCornerPolylineItem } from "@persagy-web/big/lib";
+
+/**
+ * 可编辑折线示例
+ *
+ * @author 郝洁 <haojie@persagy.com>
+ */
+export default {
+    name: "editPolyline",
+    data() {
+        return {
+            /** 命令所属的场景类 */
+            scene: null,
+            /** 实例化 view */
+            view: null,
+            item: null,
+        };
+    },
+    /**
+     * 页面挂载
+     */
+    mounted() {
+        this.view = new SGraphView("editPolyline");
+        this.scene = new SGraphScene();
+        this.view.scene = this.scene;
+        this.init()
+    },
+    methods: {
+        /**
+         * 初始化加载
+         */
+        init() {
+            this.scene.root.children = [];
+            this.item = new SCircleCornerPolylineItem(null);
+            // [15, 20],
+            //     [20, 200],
+            //     [200, 300],
+            //     [300, 100],
+            //     [200, 20]
+            this.item.pointList = [
+                { x: 15000, y: 20000 },
+                { x: 20000, y: 20000 },
+                { x: 20000, y: 30000 },
+                { x: 30000, y: 10000 },
+                { x: 20000, y: 10000 },
+            ];
+            // this.item.generatePath();
+            this.scene.addItem(this.item);
+            this.scene.grabItem = this.item;
+            this.view.update();
+            console.log(this.item)
+            this.view.fitSceneToView()
+        },
+    }
+}
+</script>
+
+<style scoped>
+    canvas {
+        border: 1px solid #eeeeee;
+        outline: none;
+    }
+</style>

+ 70 - 0
docs/guides/edit/items/editCirclePolyline.md

@@ -0,0 +1,70 @@
+# 折线编辑类 EditPolylineItem 示例
+::: details 目录
+[[toc]]
+:::
+
+## 绘制示例
+
+<edit-items-editpolyline-editCirclePolyline/>
+
+::: details 示例代码
+
+<<< @/docs/.vuepress/components/edit/items/editpolyline/editCirclePolyline.vue
+
+:::
+
+## 源代码
+
+::: details 查看代码
+
+<<< @/docs/guides/edit/items/src/EditLineItem.ts
+
+:::
+
+## 代码说明
+
+### 1 当 EditPolylineItem 为编辑状态时,需要将 EditPolylineItem 赋给 grabItem
+``` js {4}
+ // 编辑状态时的 LineItem
+ this.polylineItem = new EditPolylineItem(null);
+ this.polylineItem.status = SItemStatus.Create;
+ this.scene.grabItem = this.polylineItem;
+```
+### 2 当 EditPolylineItem 为正常状态时,需要将 grabItem 置为 null
+``` js {4}
+ // 正常状态时的 LineItem
+ this.polylineItem = new EditPolylineItem(null);
+ this.polylineItem.status = SItemStatus.Normal;
+ this.scene.grabItem = null;
+```
+### 3 当 EditPolylineItem 为试图垂直水平绘制时需要修改 verAndLeve 属性
+``` js {3}
+ // 编辑状态时的 LineItem
+ this.polylineItem = new EditPolylineItem(null,);
+ this.polylineItem.status = SItemStatus.Create;
+ this.polylineItem.verAndLeve = true;
+ this.scene.grabItem = this.polylineItem;
+```
+### 4 当 EditPolylineItem 修改属性但是画板尚未变化时需要刷新
+``` js {4}
+ // 编辑状态时的 LineItem
+ this.polylineItem = new EditPolylineItem(null);
+ this.polylineItem.status = SItemStatus.Create;
+ this.view.update();
+```
+### 5 当 EditPolylineItem 需要拖动时设置 moveable = true
+``` js {4}
+ // 编辑状态时的 LineItem
+  this.polylineItem = new EditPolylineItem(null);
+  this.polylineItem.status = SItemStatus.Create;
+  this.polylineItem.moveable = true;
+```
+
+## 交互要求
+*  esc键:若大于等于两个个顶点则为完成,否则为取消操作;
+*  Ctrl+鼠标点入边线为增加点;Ctrl+鼠标点到顶点为选择多个点,删除顶点用delete键(若剩余顶点小于两个个则不能删除);
+*  鼠标左键选择单点可移动;
+*  双击进入编辑态;
+*  选择多个点可拖动;
+*  绘制点时线与线不能交叉(放开鼠标左键后,如果出现交叉,则该点位回到原来位置,如果为创建状态,该点无法生成);
+*  选中某条边拖动;拖动中不可交叉;(补图,两种线得拖动方式)

+ 683 - 0
docs/guides/edit/items/src/EditCirclePolylineItem.ts

@@ -0,0 +1,683 @@
+import {
+    SColor,
+    SLine,
+    SPainter,
+    SPoint,
+    SRect,
+    SPath
+} 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/";
+
+/**
+ * 折线编辑类
+ *
+ * @author  韩耀龙 <han_yao_long@163.com>
+ */
+export default class SBaseCirclePolylineEdit extends SGraphItem {
+    /** X 坐标最小值 */
+    private minX = Number.MAX_SAFE_INTEGER;
+    /** X 坐标最大值 */
+    private maxX = Number.MIN_SAFE_INTEGER;
+    /** Y 坐标最小值 */
+    private minY = Number.MAX_SAFE_INTEGER;
+    /** Y 坐标最大值 */
+    private maxY = Number.MIN_SAFE_INTEGER;
+    /** 折点信息 */
+    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();
+    /** 圆角半径 */
+    private _radius: number = 5;
+    get radius(): number {
+        return this._radius;
+    }
+    set radius(v: number) {
+        if (v === this._radius) {
+            return;
+        }
+        this._radius = v;
+        this.update();
+    }
+    /** 圆角半径是否需要转像素值 */
+    radiusIsPx: boolean = false;
+    /** path 对象 */
+    path: SPath = new SPath();
+
+    /**
+     * 构造函数
+     *
+     * @param parent  父级
+     * @param list    第一个坐标|坐标集合
+     */
+    constructor(parent: null | SGraphItem, list: SPoint | SPoint[]) {
+        super(parent);
+        if (list instanceof SPoint) { // 实例在 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();
+    }
+
+    /**
+     * 是否可以添加点到数组中
+     *
+     * @param index     要添加到的索引
+     * @return 是否可添加
+     */
+    private canHandle(index: number): boolean {
+        return index >= 0 && index <= this.pointList.length;
+    }
+
+    /**
+     * 根据索引删除点
+     *
+     * @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();
+        }
+    }
+
+    /**
+     * 鼠标按下事件
+     *
+     * @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 == 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);
+    }
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param event   鼠标事件
+     * @return 是否处理事件
+     */
+    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);
+        }
+    }
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param event   鼠标事件
+     * @return 是否处理事件
+     */
+    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) {
+            super.onMouseUp(event);
+            return true;
+        }
+
+        return true;
+    }
+
+    /**
+     * 鼠标双击事件
+     *
+     * @param event     事件参数
+     * @return 是否处理事件
+     */
+    onDoubleClick(event: SMouseEvent): boolean {
+        // 如果为show状态 双击改对象则需改为编辑状态
+        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;
+    }
+
+    /**
+     * 键盘按键弹起事件
+     *
+     * @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);
+        }
+    }
+
+    /**
+     * 移动后处理所有坐标,并肩原点置为场景原点
+     */
+    moveToOrigin(): void {
+        this.pointList = this.pointList.map(t => {
+            t.x = t.x + this.x;
+            t.y = t.y + this.y;
+            return t;
+        });
+        this.x = 0;
+        this.y = 0;
+    }
+
+    /**
+     * 获取点击点与点集中距离最近点
+     *
+     * @param p   鼠标点击点
+     */
+    findNearestPoint(p: SPoint): void {
+        let len = this.sceneDis;
+        for (let i = 0; i < this.pointList.length; i++) {
+            let dis = SMathUtil.pointDistance(
+                p.x,
+                p.y,
+                this.pointList[i].x,
+                this.pointList[i].y
+            );
+            if (dis < len) {
+                len = dis;
+                this.curIndex = i;
+                this.curPoint = new SPoint(
+                    this.pointList[this.curIndex].x,
+                    this.pointList[this.curIndex].y
+                );
+            }
+        }
+    }
+
+    /**
+     * 计算增加点的位置
+     *
+     * @param p   鼠标点击点
+     */
+    findAddPos(p: SPoint): void {
+        let len = SMathUtil.pointToLine(
+            p,
+            new SLine(this.pointList[0], this.pointList[1])
+            ),
+            index = 0;
+        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);
+            }
+        }
+    }
+
+    /**
+     * shift 垂直水平创建或编辑
+     *
+     * @param event   事件
+     * @return 处理后的鼠标事件
+     */
+    compare(event: SMouseEvent): SMouseEvent {
+        if (this.pointList.length) {
+            let last = new SPoint(event.x, event.y);
+            if (this.status == SItemStatus.Create) {
+                last = this.pointList[this.pointList.length - 1];
+            } else if (this.status == SItemStatus.Edit) {
+                if (this.curIndex > 1) {
+                    last = this.pointList[this.curIndex - 1];
+                }
+            }
+
+            const dx = Math.abs(event.x - last.x);
+            const dy = Math.abs(event.y - last.y);
+            if (dy > dx) {
+                event.x = last.x;
+            } else {
+                event.y = last.y;
+            }
+        }
+
+        return event;
+    }
+
+    /**
+     * 记录相关动作并推入栈中
+     *
+     * @param SGraphCommand     相关命令类
+     * @param any               对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const command = new SGraphCommand(this.scene, this, ...any);
+        this.undoStack.push(command);
+    }
+
+    /**
+     * Item 对象边界区域
+     *
+     * @return 对象边界区域
+     */
+    boundingRect(): SRect {
+        if (this.pointList.length) {
+            this.minX = this.pointList[0].x;
+            this.maxX = this.pointList[0].x;
+            this.minY = this.pointList[0].y;
+            this.maxY = this.pointList[0].y;
+            this.pointList.forEach(it => {
+                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
+        );
+    }
+
+    /**
+     * 判断点是否在区域内
+     *
+     * @param x     x 坐标
+     * @param y     y 坐标
+     * @return 是否在区域内
+     */
+    contains(x: number, y: number): boolean {
+        let p = new SPoint(x, y);
+        for (let i = 1; i < this.pointList.length; i++) {
+            let PTL = SMathUtil.pointToLine(
+                p,
+                new SLine(
+                    this.pointList[i - 1].x,
+                    this.pointList[i - 1].y,
+                    this.pointList[i].x,
+                    this.pointList[i].y
+                )
+            );
+            if (PTL.MinDis < this.sceneDis) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 撤销操作
+     */
+    undo(): void {
+        if (this._status != SItemStatus.Normal) {
+            this.undoStack.undo();
+        }
+    }
+
+    /**
+     * 重做操作
+     */
+    redo(): void {
+        if (this._status != SItemStatus.Normal) {
+            this.undoStack.redo();
+        }
+    }
+
+    /**
+     * 取消操作执行
+     */
+    cancelOperate(): void {
+        if (this.status == SItemStatus.Create) {
+            this.parent = null;
+            this.releaseItem();
+        } else if (this.status == SItemStatus.Edit) {
+            this.status = SItemStatus.Normal;
+            this.releaseItem();
+        }
+    }
+
+    /**
+     * 根据点集生成 path 对象
+     *
+     * @param r 拐点处圆角半径
+     */
+    generatePath(r: number = 0, list: SPoint[]): void {
+        const len = list.length;
+        if (len) {
+            this.path = new SPath();
+            this.path.moveTo(list[0].x, list[0].y);
+            for (let i = 1; i < len - 1; i++) {
+                const temp = list[i];
+                const next = list[i + 1];
+                this.path.arcTo(temp.x, temp.y, next.x, next.y, r);
+            }
+            const last = list[len - 1];
+            this.path.lineTo(last.x, last.y);
+        }
+    }
+
+    /**
+     * 绘制基本图形
+     *
+     * @param painter   绘制对象
+     */
+    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)
+            ];
+        }
+
+        const temp = JSON.parse(JSON.stringify(this.pointList));
+        if (this.status == SItemStatus.Create && this.lastPoint) {
+            temp.push(this.lastPoint);
+        }
+
+        if (temp.length > 2) {
+            let radius = this.radius;
+            if (this.radiusIsPx) {
+                radius = painter.toPx(this.radius);
+            }
+            this.generatePath(radius, temp);
+            painter.drawPath(this.path);
+        } else {
+            painter.drawPolyline(temp);
+        }
+    }
+
+    /**
+     * 返回对象储存的相关数据
+     *
+     * @return 对象储存的相关数据
+     */
+    toData(): any {
+        this.moveToOrigin();
+        if (!this.data) return;
+        const Line = this.pointList.map(pos => {
+            return {
+                x: pos.x,
+                y: pos.y
+            };
+        });
+        this.data.style.outLine = Line;
+        this.data.style.default.lineWidth = this.lineWidth;
+        this.data.style.default.lineStyle = this.lineStyle;
+        this.data.style.default.strokeColor = this.strokeColor.value;
+        return this.data;
+    }
+
+    /**
+     * Item 绘制操作
+     *
+     * @param 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);
+            // 编辑状态
+            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);
+        }
+    }
+}

+ 2 - 0
docs/guides/index.js

@@ -48,6 +48,7 @@ const content = [
                     ["/guides/scene/items/image", "图片"],
                     ["/guides/scene/items/svg", "SVG 图"],
                     ["/guides/scene/items/imgText", "图例 item"],
+                    ["/guides/scene/items/polyline", "折线 item (拐角为圆角)"],
                 ]
             },
             {
@@ -124,6 +125,7 @@ const content = [
                     ["/guides/edit/items/editLine", "编辑线"],
                     ["/guides/edit/items/editPolygon", "编辑多边形"],
                     ["/guides/edit/items/editPolyline", "编辑折线"],
+                    ["/guides/edit/items/editCirclePolyline", "编辑折线(拐点为圆角)"],
                     ["/guides/edit/items/editImage", "编辑图片"],
                     ["/guides/edit/items/editText", "编辑文字"],
                     ["/guides/edit/items/editCircle", "编辑圆"],

+ 9 - 0
docs/guides/scene/items/polyline.md

@@ -0,0 +1,9 @@
+# 折线 item (拐角为圆角) 
+
+::: details 目录
+[[toc]]
+:::
+
+## item 实例
+
+<scene-items-polyline />

+ 3 - 3
package.json

@@ -15,9 +15,9 @@
   "dependencies": {
     "@persagy-vue/doc": "1.1.36",
     "@persagy-web/base": "2.2.1",
-    "@persagy-web/big": "2.2.32",
-    "@persagy-web/draw": "2.2.8",
-    "@persagy-web/graph": "2.2.33",
+    "@persagy-web/big": "2.2.36",
+    "@persagy-web/draw": "2.2.9",
+    "@persagy-web/graph": "2.2.36",
     "axios": "^0.19.2",
     "element-ui": "^2.12.0",
     "vue": "^2.6.10",