Sfoglia il codice sorgente

Merge branch 'develop' of http://39.106.8.246:3003/web/wanda-editer into dev

haojianlong 4 anni fa
parent
commit
7df6efaf99

File diff suppressed because it is too large
+ 0 - 12581
package-lock.json


+ 393 - 0
src/components/Transfer/Button.vue

@@ -0,0 +1,393 @@
+<template>
+    <div :class="`p-button p-button-${type} p-button-${size} ${minWidth ? 'min-size' : ''} ${disabled ? ('p-button-' + type + '-disabled') : ''} `" @click="handleClick">
+        <Icon v-if="type === 'icon' || type === 'icon-border' || type === 'icon-text'" type="action" :name='icon' />
+        <template>
+            <div v-if="text && type !== 'icon' && type !== 'icon-border'" ref="text" class="p-btn-text">{{text}}</div>
+            <div v-else-if="type !== 'icon' && type !== 'icon-border'" ref="text" class="p-btn-text"><slot/></div>
+        </template>
+        <section class="p-button-loading" v-if="loading && type !== 'icon' && type !== 'icon-border' && type !== 'icon-text' && type !== 'text' && type !== 'text-red' && type !== 'text-blue'">
+            <Loading16px />
+        </section>
+    </div>
+</template>
+
+<script>
+    import LoadingIcon from '../static/iconSvg/loading.svg';
+    import Loading16px from '../Loading16px';
+    import Icon from '../Icon';
+    export default {
+        name: 'Button',
+        components: { LoadingIcon, Loading16px, Icon },
+        props: {
+            /**
+             * 按钮文本
+             */
+            text: {
+                type: String,
+                default: ''
+            },
+            /**
+             * 按钮类型
+             * 可选值 【default primary error disabled text text-blue text-red icon icon-text icon-border】
+             */
+            type: {
+                type: String,
+                required: true,
+                default: 'default'
+            },
+            /**
+             * icon类型
+             */
+            icon: {
+                type: String,
+                default: ''
+            },
+            /**
+             * 按钮loading状态
+             */
+            loading: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 按钮大小
+             * 可选值【large medium small】
+             */
+            size: {
+                type: String,
+                default: 'medium'
+            },
+            /**
+             * 是否禁用
+             * 可选值 【true, false】
+             */
+            disabled: {
+                type: Boolean,
+                default: false
+            }
+        },
+        data () {
+            return {
+                minWidth: false //小尺寸按钮内容宽度小于28px时,设置按钮最小宽度60px
+            }
+        },
+        watch: {
+            text () {
+                this.minWidthAction()
+            }
+        },
+        mounted () {
+            this.minWidthAction()
+        },
+        methods: {
+            minWidthAction () {
+                if (this.type === 'icon' || this.type === 'icon-text') return;
+                if (this.$refs.text) this.minWidth = this.$refs.text.innerHTML.length === 1
+            },
+            /**
+             * 点击按钮的回调
+             */
+            handleClick() {
+                if (this.type === 'disabled' || this.disabled) return;
+                this.$emit('click')
+            }
+        }
+    }
+</script>
+
+<style lang="stylus">
+    .p-button
+        position relative
+        display inline-flex
+        align-items center
+        justify-content center
+        padding-left 8px
+        padding-right 8px
+        background-color $white
+        border-width 1px
+        border-style solid
+        border-radius 4px
+        transition all .36s
+        font-size 0
+        text-align center
+        &:after
+            content ''
+            color transparent
+        &:before
+            content ''
+            color transparent
+        &+.p-button
+            margin-left 12px
+        .p-btn-text
+            overflow hidden
+            white-space nowrap
+            text-overflow ellipsis
+            position relative
+            /*display ruby*/
+            //vertical-align middle
+            z-index 1
+            user-select none
+        .p-button-loading
+            display inline-block
+            margin-left 4px
+            vertical-align middle
+    .p-button-large
+        max-width 128px
+        min-width 80px
+        height 40px
+        line-height @height - 2
+        .p-btn-text
+            font-size 16px
+        .p-button-loading
+            width 20px
+            height 20px
+    .p-button-medium
+        max-width 116px
+        min-width 80px
+        height 32px
+        line-height @height - 2
+        .p-btn-text
+            font-size 14px
+        .p-button-loading
+            width 16px
+            height 16px
+    .p-button-small
+        max-width 108px
+        min-width 60px
+        height 28px
+        line-height @height - 2
+        .p-btn-text
+            font-size 14px
+        .p-button-loading
+            width 12px
+            height 12px
+    .p-button-default, .p-button-icon-text
+        background-color $white
+        border-color $grey-400
+        color $grey-900
+        cursor pointer
+        .p-button-loading
+            path
+                fill $grey-900
+        &:hover
+            border-color $blue-500
+            color $blue-500
+        &:active
+            border-color $blue-600
+            color $blue-600
+        &::after
+            background radial-gradient(circle, $grey-200 10%, transparent 10%)
+        .loading
+            path
+                stroke $grey-400
+    .p-button-primary
+        background-color $blue-500
+        border-color $blue-500
+        color $white
+        cursor pointer
+        &:hover
+            background-color $blue-400
+            border-color $blue-400
+        &:active
+            background-color $blue-600
+            border-color $blue-600
+        &::after
+            background radial-gradient(circle, $blue-300 10%, transparent 10%)
+    .p-button-error
+        background-color $red-500
+        border-color $red-500
+        color $white
+        cursor pointer
+        &:hover
+            background-color $red-400
+            border-color $red-400
+        &:active
+            background-color $red-600
+            border-color $red-600
+        &::after
+            background radial-gradient(circle, $red-300 10%, transparent 10%)
+    .p-button-disabled,
+    .p-button-default-disabled,
+    .p-button-primary-disabled,
+    .p-button-error-disabled
+        background-color $red-200
+        color $grey-400
+        cursor not-allowed
+        border-color $grey-200
+        &:hover
+            background-color $red-200
+            border-color $red-200
+            color $grey-400
+        &:active
+            background-color $red-200
+            border-color $red-200
+            color $grey-400
+    .p-button-text-blue
+        border-style none
+        color $blue-500
+        cursor pointer
+        &:hover
+            color $blue-500
+        &:active
+            color $blue-600
+    .p-button-text-red
+        border-style none
+        color $red-500
+        cursor pointer
+        &:hover
+            color $red-400
+        &:active
+            color $red-600
+    .min-size
+        min-width 0
+    .p-button-icon,.p-button-icon-border
+        border-color $grey-400
+        padding 0
+        cursor pointer
+        path
+            fill $grey-900
+            transition all .36s
+        .p-icon
+            svg
+                width 100%!important
+                height 100%!important
+    .p-button-icon
+        border-color transparent
+        &:hover
+            color $blue-500
+            path
+                fill $blue-500
+        &:active
+            color $blue-600
+            path
+                fill $blue-600
+        &::after
+            color $blue-500
+            path
+                fill $blue-500
+    .p-button-icon-border
+        &:hover
+            border-color $blue-500
+            color $blue-500
+            path
+                fill $blue-500
+        &:active
+            border-color $blue-600
+            color $blue-600
+            path
+                fill $blue-600
+        &::after
+            border-color $blue-500
+            color $blue-500
+            path
+                fill $blue-500
+    .p-button-icon.p-button-large,.p-button-icon-border.p-button-large
+        width 40px
+        min-width 40px
+        .p-icon
+            width 24px
+            height 24px
+            line-height 24px
+    .p-button-icon.p-button-medium,.p-button-icon-border.p-button-medium
+        width 32px
+        min-width 32px
+        .p-icon
+            width 16px
+            height 16px
+            line-height 16px
+    .p-button-icon.p-button-small,.p-button-icon-border.p-button-small
+        width 28px
+        min-width 28px
+        .p-icon
+            width 12px
+            height 12px
+            line-height 12px
+    .p-button-icon-text
+        border-color transparent
+        &:hover
+            border-color transparent
+        path
+            fill $grey-900
+            transition all .36s
+        &:hover
+            color $blue-500
+            path
+                fill $blue-500
+        &:active
+            color $blue-600
+            path
+                fill $blue-600
+        &::after
+            color $blue-500
+            path
+                fill $blue-500
+        .p-icon
+            margin-right 4px
+            svg
+                width 100%!important
+                height 100%!important
+    .p-button-icon-text.p-button-large
+        .p-icon
+            width 20px
+            height 20px
+            line-height 20px
+    .p-button-icon-text.p-button-medium
+        .p-icon
+            width 16px
+            height 16px
+            line-height 16px
+    .p-button-icon-text.p-button-small
+        .p-icon
+            width 12px
+            height 12px
+            line-height 12px
+    .p-button-icon-border-disabled
+        background-color $red-200
+        color $grey-400
+        cursor not-allowed
+        border-color $red-200
+        svg
+            cursor not-allowed
+            path
+                fill $grey-400
+        &:hover
+            border-color $red-200
+            path
+                fill $grey-400
+    .p-button-icon-disabled
+        color $grey-400
+        cursor not-allowed
+        svg
+            cursor not-allowed
+            path
+                fill $grey-400
+        &:hover
+            path
+                fill $grey-400
+    .p-button-icon-text-disabled
+        color $grey-400
+        cursor not-allowed
+        svg
+            cursor not-allowed
+            path
+                fill $grey-400
+        &:hover
+            color $grey-400
+            path
+                fill $grey-400
+    .p-button-text
+        border-color transparent
+        cursor pointer
+        color $grey-900
+        &:hover
+            color $blue-500
+            border-color transparent
+        &:active
+            color $blue-600
+    .p-button-text-disabled, .p-button-text-blue-disabled, .p-button-text-red-disabled
+        cursor not-allowed
+        color $grey-400
+        &:hover
+            color $grey-400
+
+</style>

+ 405 - 0
src/components/Transfer/Input.vue

@@ -0,0 +1,405 @@
+<template>
+  <div :class="[type === 'text' && 'p-input', type === 'textarea' && 'p-textarea',disabled && 'p-input-disabled', errorInfo && 'p-input-error', focus && 'p-input-focus', inputFinish && 'p-input-finish']"
+       :style=" width > 0 && type === 'text' ? 'width: ' + width + 'px' : ''"
+  >
+    <template v-if="type === 'text'">
+      <div class="left-button" v-if="leftButton.length > 0">
+        <div class="left-button-text">{{leftButtonText}}</div>
+      </div>
+      <i class="p-input-icon-search" v-if="iconType === 'search'">
+        <Icon name="search"/>
+      </i>
+      <div class="p-input-box">
+        <div class="p-placeholder" v-if="!value || disabled">{{disabled && value ? value : placeholder}}</div>
+        <input :maxlength="maxLength" :name="name" :autofocus="autofocus" ref="input" v-if="!disabled" type="text" autocomplete="off"
+               :value="value" @focus="inputFocus" @input="searchInput" @blur="inputBlur" @keypress="inputComplete"/>
+      </div>
+      <i class="p-input-icon-clear" v-if="iconClose" @click="clear">
+        <Close />
+      </i>
+      <div class="right-button" v-if="rightButton.length > 0">
+        <div class="right-button-text">{{rightButtonText}}</div>
+      </div>
+    </template>
+    <template v-else>
+      <textarea :class="[width && width > 0 && 'p-textarea-resize']" :style=" width > 0 ? 'width: ' + width + 'px' : ''" :maxlength="maxLength" :disabled="disabled" :name="name" :autofocus="autofocus" ref="input" class="p-textarea-box" :rows="rows" @focus="inputFocus" @input="searchInput" @blur="inputBlur" @keypress="inputComplete"/>
+    </template>
+    <div class="p-input-error-info" v-if="errorInfo">{{errorInfo}}</div>
+  </div>
+</template>
+<script>
+    import Close from '../static/iconSvg/clear2.svg';
+    import Search from '../static/iconSvg/sreach.svg';
+    import Icon from "../Icon/Icon";
+    export default {
+        name: "Input",
+        components: { Close, Search, Icon },
+        props: {
+            /**
+             * 类型 可选值(text, textarea)
+             */
+            type: {
+                type: String,
+                default: 'text'
+            },
+            /**
+             * 是否自动聚焦
+             */
+            autofocus: {
+                default: false
+            },
+            /**
+             * 最大字符长度
+             */
+            maxLength: {
+                type: Number,
+                default: 100
+            },
+            /**
+             * 原生name属性
+             */
+            name: {
+                type: String,
+                default: ''
+            },
+            /**
+             * 右按钮
+             */
+            rightButton: {
+                type: Array,
+                default: () => []
+            },
+            /**
+             * 左按钮
+             */
+            leftButton: {
+                type: Array,
+                default: () => []
+            },
+            /**
+             * 输入框值
+             */
+            value: {
+                type: String,
+                default: ''
+            },
+            /**
+             * icon类型
+             */
+            iconType: {
+                type: String,
+                default: ''
+            },
+            /**
+             * 是否显示清楚按钮
+             */
+            /*iconClose: {
+                type: Boolean,
+                default: false
+            },*/
+            /**
+             * 占位符
+             */
+            placeholder: {
+                type: String,
+                default: 'Please enter'
+            },
+            /**
+             * 是否禁用状态
+             */
+            disabled: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 错误信息提示
+             */
+            errorInfo: {
+                type: String,
+                default: ''
+            },
+            /**
+             * 显示几行
+             */
+            rows: {
+                type: [Number, String],
+                default: 4
+            },
+            /**
+             * 显示几行
+             */
+            width: {
+                type: Number,
+                default: 0
+            }
+        },
+        data () {
+            return {
+                iconClose: false, // 是否显示清理按钮
+                inputFinish: true, // 输入完成
+                focus: false, // 输入框获取焦点
+                leftButtonText: '', // 左边按钮文本内容
+                rightButtonText: '' // 右边按钮文本内容
+            }
+        },
+        created () {
+            if (this.value && !this.iconClose) {
+                this.iconClose = true
+            } else if (!this.value && this.iconClose) {
+                this.iconClose = false
+            }
+            if (this.leftButton.length > 0) {
+                this.leftButtonText = this.leftButton.find(v => v.checked === 'checked').name;
+            } else if (this.rightButton.length > 0) {
+                this.rightButtonText = this.rightButton.find(v => v.checked === 'checked').name;
+            }
+        },
+        mounted () {
+            if (this.autofocus) {
+                setTimeout(() => { this.focusM() }, 100)
+            }
+        },
+        methods: {
+            focusM () {
+                if (this.$refs.input) this.$refs.input.focus();
+            },
+            // 清楚输入框内容
+            clear () {
+                this.$emit('input', '');
+                this.iconClose = false
+            },
+            // 输入框获取焦点
+            inputFocus(e) {
+                this.focus = true;
+                if (this.inputFinish) this.inputFinish = false;
+                this.$emit('focus', e.target.value)
+            },
+            // 输入框失去焦点
+            inputBlur(e) {
+                this.focus = false;
+                if (!this.inputFinish) this.inputFinish = true;
+                if (this.leftButton.length > 0) {
+                    this.$emit('blur', {
+                        value: e.target.value,
+                        leftButtonText: this.leftButtonText
+                    })
+                } else if (this.rightButton.length > 0) {
+                    this.$emit('blur', {
+                        value: e.target.value,
+                        rightButtonText: this.rightButtonText
+                    })
+                } else {
+                    this.$emit('blur', e.target.value)
+                }
+            },
+            // 输入框有值关闭占位符
+            searchInput(e) {
+                this.$emit('input', e.target.value);
+                if (this.type === 'text') {
+                    if (e.target.value && !this.iconClose) {
+                        this.iconClose = true
+                    } else if (!e.target.value && this.iconClose) {
+                        this.iconClose = false
+                    }
+                }
+            },
+            // 输入完成点击enter
+            inputComplete (e) {
+                if (e.key === 'Enter') {
+                    if (this.$refs.input) this.$refs.input.blur();
+                    // this.inputFinish = true;
+                    if (this.leftButton.length > 0) {
+                        this.$emit('pressEnter', {
+                            value: e.target.value,
+                            leftButtonText: this.leftButtonText
+                        })
+                    } else if (this.rightButton.length > 0) {
+                        this.$emit('pressEnter', {
+                            value: e.target.value,
+                            rightButtonText: this.rightButtonText
+                        })
+                    } else {
+                        this.$emit('pressEnter', e.target.value)
+                    }
+                }
+            }
+        }
+    }
+</script>
+<style lang="stylus">
+  .p-textarea
+    & + .p-textarea
+      margin-left 28px
+    background-color $white
+    display inline-flex
+    position relative
+    .p-textarea-box
+      transition all 0.3s
+      padding 8px
+      border 1px solid $grey-400
+      border-radius 4px
+      min-width 240px
+      width 600px
+      max-width 600px
+      margin 0
+      outline none
+      caret-color $blue-500
+      color $grey-900
+      background none
+      overflow-y auto
+      &.p-textarea-resize
+        resize: none;
+      &:hover
+          border-color $blue-500
+      &:active
+          border-color $blue-600
+    &.p-input-focus
+      box-shadow none !important
+      .p-textarea-box
+        border-color $blue-500
+        //box-shadow $box-shadow-blue
+  .p-input
+    background-color $white
+    display inline-flex
+    align-items center
+    padding-left 8px
+    padding-right 8px
+    border 1px solid $grey-400
+    border-radius 4px
+    min-width 180px
+    width 240px
+    max-width 600px
+    height 32px
+    font-size 0
+    transition border 0.3s , box-shadow 0.3s
+    & + .p-input
+      margin-left 28px
+    .left-button
+      margin-left -8px
+      margin-right 8px
+      border-top-left-radius 4px
+      border-bottom-left-radius 4px
+      border-right: 1px solid $grey-400
+    .right-button
+      margin-left 8px
+      margin-right -8px
+      border-top-right-radius 4px
+      border-bottom-right-radius 4px
+      border-left: 1px solid $grey-400
+    .left-button,.right-button
+      width 76px
+      height 30px
+      line-height @height
+      background-color $red-200
+      cursor pointer
+      .left-button-text,.right-button-text
+        color $grey-900
+        font-size 14px
+        text-align center
+        overflow hidden
+        text-overflow ellipsis
+        white-space nowrap
+    &:hover
+      border-color $blue-500
+    &:active
+      border-color $blue-600
+    &:after
+      content ' '
+      color transparent
+    &:before
+      content ' '
+      color transparent
+    .p-input-box
+      flex 1
+      font-size 14px
+      position relative
+      height: 30px
+      input, .p-placeholder
+        height 30px
+        line-height @height
+      input
+        margin 0
+        padding 0
+        outline none
+        background none
+        border 0
+        color $grey-900
+        width 100%
+        position relative
+        z-index 2
+        caret-color $blue-500
+      .p-placeholder
+        position absolute
+        left 0
+        top 0
+        color $grey-400
+        z-index 1
+        width 100%
+        overflow hidden
+    .p-input-icon-clear
+      padding-left 8px
+      cursor pointer
+      height 30px
+      line-height @height
+      svg
+        width 14px
+        height 14px
+        transition all 0.3s
+        vertical-align middle
+        path
+          transition all 0.3s
+      &:hover
+        path
+
+          fill $blue-500
+      &:active
+        path
+          fill $blue-600
+    .p-input-icon-search
+      padding-right 8px
+      text-align center
+      .p-icon
+        width 16px
+        height 30px
+        line-height @height
+        svg
+          vertical-align middle
+  .p-input-focus
+    border-color $blue-500
+    //box-shadow $box-shadow-blue
+  .p-input-disabled.p-input,
+  .p-input-disabled.p-textarea
+    background-color $red-200
+    border-color $grey-400
+    cursor not-allowed
+    textarea.p-textarea-box
+      cursor not-allowed
+      &:hover
+        border-color $grey-400
+    &:hover
+      border-color $grey-400
+  .p-input-error.p-input,
+  .p-input-error.p-textarea
+    position relative
+    border-color $red-500
+    //box-shadow none
+    .p-input-box
+      input
+        caret-color $red-500
+    textarea.p-textarea-box
+      caret-color $red-500
+      border-color $red-400
+      &:hover
+        border-color $red-400
+    .p-input-error-info
+      position absolute
+      left 0
+      bottom -20px
+      color $red-500
+      font-size 14px
+    &:hover
+      border-color $red-500
+  .p-input-finish
+    input, textarea
+      cursor pointer
+</style>

+ 645 - 0
src/components/Transfer/Transfer.vue

@@ -0,0 +1,645 @@
+<template>
+    <div class="p-transfer">
+        <div class="p-transfer-main">
+            <div class="p-transfer-main-child p-transfer-left">
+                <section class="p-transfer-left-input">
+                    <Input iconType="search" :iconClose="iconClose" :placeholder="placeholder" v-model="inputVal" @close="iconCloseHandle" />
+                </section>
+                <section class="p-transfer-left-content" v-show="!inputVal">
+                    <!--树形结构数据-->
+                    <Tree
+                            v-if="flat==='mt'"
+                            :multiple="true"
+                            :linkage="linkage"
+                            :lastStage="lastStage"
+                            :notNull="notNull"
+                            :data="treeData"
+                            :leftPosition="28"
+                            @change="treeChange"
+                    />
+                    <!--一维多选列表-->
+                    <SelectOptionMultiple
+                            v-else
+                            :data="treeData"
+                            @change="optionChange"
+                    />
+                </section>
+                <section class="p-transfer-left-content" v-show="inputVal">
+                    <!--搜索数据列表 -s-->
+                    <SelectOptionMultiple
+                            v-if="notFound"
+                            :data="searchData"
+                            @change="optionSearchChange"
+                    />
+                    <!--搜索数据列表 -e-->
+                    <div class="p-transfer-left-content-none" v-else>没有找到任何内容</div>
+                </section>
+            </div>
+            <div class="p-transfer-main-child p-transfer-right">
+                <section :class="['p-transfer-right-title', 'p-transfer-right-title-border']">
+                    <article class="p-transfer-right-title-text">已选<span class="p-transfer-right-title-text-num" v-show="selectedData&&selectedData.length">{{selectedData.length}}</span></article>
+                    <article :class="['p-transfer-right-clear', (selectedData&&selectedData.length)&&'p-transfer-right-clear-active']" @click="handleEmpty">清空</article>
+                </section>
+                <section class="p-transfer-selected">
+                    <article class="p-transfer-selected-item" v-for="(sd, i) in selectedData" :key="'sd-'+sd.id">
+                        <span @mouseenter="itemEnter">{{sd.name}}</span>
+                        <IconClear @click="removeItem(i, sd.id)" />
+                    </article>
+                </section>
+                <section :class="['p-transfer-btn', shadowShow&&'p-transfer-btn-shadow']">
+                    <Button type="default" size="medium" @click="handleCancel">取消</Button>
+                    <Button :type="confirmBtnType" size="medium" @click="handleConfirm">确定</Button>
+                </section>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    import Input from './Input';
+    import Tree from './Tree/Tree';
+    import Button from './Button';
+    import IconClear from '../static/iconSvg/clear2.svg';
+	import SelectOptionMultiple from '../SelectOptionMultiple/SelectOptionMultiple';
+	import cloneDeep from '../static/utils/CloneDeep'
+    import { TileTool, FilterTool, ChangeStatus, GetParentIdById, Unique, PackageTool, initTreeData } from '../static/utils/TreeTool';
+
+    export default {
+        name: "Transfer",
+        components: {
+            Input, Tree, Button,
+            IconClear,
+            SelectOptionMultiple
+        },
+        props: {
+            data: {
+                type: Array,
+                default: () => []
+            },
+            // 上下级联动
+            linkage: {
+                type: Boolean,
+                default: true
+            },
+            // 只能选择末级
+            lastStage: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 是否返回半选状态的id
+             */
+            notNull: {
+                type: Boolean,
+                default: false
+            },
+            // 占位符
+            placeholder: {
+                type: String,
+                default: '请选择'
+            }
+        },
+        data() {
+            return {
+                inputVal: '', // 输入的值
+                selectedData: [], // 选中的数据
+                searchData: [], // 搜索出的数据
+                isEmpty: false, // 是否点击了清空按钮
+                confirmData: [], // 点击确定选择的数据
+				confirmBtnType: 'disabled', // 确定按钮状态
+				treeData: []
+            }
+		},
+        computed: {
+            /*
+            *   mt-多选树形结构
+            *   mn-多选一维结构
+            */
+            flat() {
+                const tree=this.data.some(item => item.children && item.children.length); // 判断数组是一维还是多维 返回true表示多维
+                if (tree) return 'mt';
+                else return 'mn'
+            },
+            // 是否显示空状态
+            notFound() {
+                return this.searchData.length
+            },
+            // 树形结构数据
+            mulData: {
+                get() {
+					let treeData = initTreeData(PackageTool(TileTool([], this.treeData, '-1')))
+                    return treeData
+                },
+                set(newData) {
+                    return newData;
+                }
+            },
+            // 设置按钮区是否显示投影
+            shadowShow() {
+                const len=this.selectedData.length;
+                const h=480-127;
+                if (len) return h/len < 40;
+                return false;
+            },
+            // 清除按钮显示
+            iconClose() {
+                return !!this.inputVal
+            }
+        },
+        watch: {
+            // 监听搜索框输入的内容
+            inputVal(n, o) {
+                if (n === o) return;
+                if (n) {
+                    this.setSearchData(n);
+                } else {
+                    this.searchData=[];
+                }
+            },
+            // 监听选中数据改变-确定按钮状态
+            selectedData(n) {
+                const cd=this.confirmData;
+                this.checkArrDiff(cd, n);
+			},
+			data: {
+				handler(newVal, oldVal) {
+					this.treeData = this.flat === 'mt' ? initTreeData(PackageTool(TileTool([], newVal, '-1'))) : newVal
+				},
+				deep: true
+			},
+		},
+		mounted() {
+			this.treeData = this.flat === 'mt' ? initTreeData(PackageTool(TileTool([], this.data, '-1'))) : this.data
+		},
+        methods: {
+            // 清除按钮
+            iconCloseHandle() {
+                this.inputVal='';
+            },
+            /**
+             * 检查两个数组是否一样
+             * @param cd - confirmData 数组1
+             * @param sd - selectedData 数组2
+             * @return {boolean} 返回值
+             */
+            checkArrDiff(cd, sd) {
+                if (cd.length) {
+                    if (sd.length) {
+                        const assign=Unique([...sd, ...cd]);
+                        if (assign.length!==cd.length || assign.length!==sd.length) {
+                            this.confirmBtnType='primary';
+                            return true;
+                        } else {
+                            this.confirmBtnType='disabled';
+                            return false;
+                        }
+                    } else {
+                        this.confirmBtnType='primary';
+                        return true;
+                    }
+                } else {
+                    if (sd.length) {
+                        this.confirmBtnType='primary';
+                        return true;
+                    } else {
+                        this.confirmBtnType='disabled';
+                        return false;
+                    }
+                }
+            },
+            // 鼠标hover
+            itemEnter(e) {
+                const target=e.target;
+                const {clientWidth, scrollWidth, title}=target;
+                if (!title && scrollWidth > clientWidth) target.title=target.innerText;
+			},
+			getParentName(parentId) {
+				let tilingTree = TileTool([], cloneDeep(this.treeData), '-1');
+				let parent = tilingTree.find(item => item.id === parentId)
+				if(parent) {
+					return `${this.getParentName(parent.parentId)}${parent.name}/`
+				} else {
+					return ''
+				}
+			},
+            // 选择的项
+            treeChange({id, checkedIds, obj}) {
+				// console.log('选择的项::::', id, checkedIds, obj);
+                if (this.linkage) {
+                    // 联动
+                    this.selectedData=this.setSelectedData([], this.treeData);
+                } else {
+                    // 不联动
+                    /**
+                     * lastStage为true表示只能选择末级,处理方式与不联动一样
+                     */
+                    const { checked, name, parentId } = obj;
+                    if (checked === 'checked') {
+                        this.selectedData.push({id, name: `${this.getParentName(parentId)}${name}`})
+                    } else {
+                        this.selectedData=this.selectedData.filter(d => d.id !== id)
+                    }
+                }
+            },
+            /**
+             * 联动-设置选中的数据
+             * @param data 接收的结果
+             * @param tree 遍历列表
+             */
+            setSelectedData(data, tree) {
+                tree.forEach(d => {
+                    if (this.notNull) {
+                        if (d.checked==='checked' || d.checked==='notNull') data.push({id: d.id, name: `${this.getParentName(d.parentId)}${d.name}`});
+                    } else {
+                        if (d.checked==='checked') data.push({id: d.id, name: `${this.getParentName(d.parentId)}${d.name}`});
+                    }
+                    if (d.children && d.children.length) this.setSelectedData(data, d.children);
+                });
+
+                return data;
+            },
+            /**
+             * 移除选中项
+             * @param i 索引
+             * @param id id
+             */
+            removeItem(i, id) {
+                this.selectedData.splice(i, 1);
+                this.treeData=this.removeChecked(id, this.treeData);
+                // 取消搜索的数据被选中
+                const sd=this.searchData;
+                if (sd && sd.length) {
+                    if (this.flat === 'mt') {
+                        this.searchData=this.removeSearchDataChecked(id, 'lId', sd);
+                    } else {
+                        this.searchData=this.removeSearchDataChecked(id, 'id', sd);
+                    }
+                }
+            },
+            /**
+             * 取消树形结构选中的项
+             * @param id 需要取消选中的id
+             * @param mulData 数据
+             * @return {*}
+             */
+            removeChecked(id, mulData) {
+                if (this.flat === 'mt') {
+                    if (this.linkage) {
+                        /* 上下级联动 */
+                        return this.linkageSetUpperAndLowerStatus(mulData, id, 'uncheck');
+                    } else {
+                        // 上下级不联动
+                        return mulData.map(d => {
+                            if (d.id === id) {
+                                d.checked='uncheck';
+                            } else {
+                                if (d.children && d.children.length) this.removeChecked(id, d.children);
+                            }
+                            return d;
+                        });
+                    }
+                } else {
+                    return mulData.map(d => {
+                        if (d.id === id) d.selected=false;
+                        return d;
+                    });
+                }
+            },
+            /**
+             * 联动-设置上下级状态
+             * @param mulData 需要遍历的数据
+             * @param id 当前被移除选中的id
+             * @param cStatus 需要设置的子集状态
+             */
+            linkageSetUpperAndLowerStatus(mulData, id, cStatus) {
+                const tileData=TileTool([], JSON.parse(JSON.stringify(mulData)), '-1');
+                // 通过子级id查找父级数据
+                const parentIds=GetParentIdById([], id, tileData);
+
+                // 设置子项取消选中
+                const data=this.recursionMulData(mulData, id, cStatus);
+                // 接收父级数据
+                const pArr=this.getParentData([], data, parentIds);
+                // 设置父级checked状态
+                pArr.forEach(d => {
+                    if (d.children && d.children.length) {
+                        d.checked=ChangeStatus(d.children);
+                    }
+                });
+                // 设置选中的数据
+                this.selectedData=this.setSelectedData([], data);
+                return data;
+            },
+            /**
+             * 不联动-设置上下级状态
+             * @param mulData 需要遍历的数据
+             * @param id 当前被移除选中的id
+             * @param cStatus 需要设置的子集状态
+             */
+            notLinkageSetUpperAndLowerStatus(mulData, id, cStatus) {
+                return mulData.map(d => {
+                    if (d.id === id) {
+                        d.checked=cStatus;
+                    } else {
+                        if (d.children && d.children.length) d.children=this.notLinkageSetUpperAndLowerStatus(d.children, id, cStatus);
+                    }
+                    return d;
+                });
+            },
+            /**
+             * 联动-设置子项取消选中
+             * @param data 需要遍历的数据
+             * @param id 当前被移除选中的id
+             * @param cStatus 需要设置的子集状态
+             */
+            recursionMulData(data, id, cStatus) {
+                return data.map(d => {
+                    if (d.id === id) {
+                        // 设置当前id的子级选中状态
+                        d.checked=cStatus;
+                        if (d.children && d.children.length) d.children=this.removeChildChecked(d.children, cStatus);
+                    } else {
+                        if (d.children && d.children.length) d.children=this.recursionMulData(d.children, id, cStatus);
+                    }
+                    return d;
+                });
+            },
+            /**
+             * 联动-查找父级数据
+             * @param pArr 接收找到的数据
+             * @param data 需要遍历的数据
+             * @param pIds 父级id组
+             */
+            getParentData(pArr, data, pIds) {
+                data.forEach(d => {
+                    if (pIds.includes(d.id)) {
+                        pArr.unshift(d);
+                        if (d.children && d.children.length) this.getParentData(pArr, d.children, pIds);
+                    }
+                });
+
+                return pArr;
+            },
+            /**
+             * 联动-设置子项取消选中
+             * @param data 需要遍历的数据
+             * @param cStatus 需要设置的子集状态
+             */
+            removeChildChecked(data, cStatus) {
+                return data.map(d => {
+                    d.checked=cStatus;
+                    if (d.children && d.children.length) this.removeChildChecked(d.children, cStatus);
+                    return d;
+                })
+            },
+            /**
+             * 设置搜索数据取消选中
+             * @param id 需要取消选中的id
+             * @param flag 需要取消选中的id的key
+             * @param data 需要遍历的数据
+             */
+            removeSearchDataChecked(id, flag, data) {
+                return data.map(d => {
+                    if (d[flag] === id) d.selected=false;
+                    return d;
+                })
+            },
+            // 设置树形结构全部取消选中
+            setMulDataUncheck(data) {
+                return data.map(d => {
+                    d.checked='uncheck';
+                    if (d.children && d.children.length) d.children=this.setMulDataUncheck(d.children);
+                    return d;
+                })
+            },
+            // 设置一维结构全部取消选中
+            setMulDataUnSelected(data) {
+                return data.map(d => {
+                    d.selected=false;
+                    return d;
+                })
+            },
+            /**
+             * 设置一维结构根据条件选中
+             * @param data 一维结构数据
+             * @param ids 需要选中的ids
+             */
+            setMulDataSelectedByIds(data, ids) {
+                return data.map(d => {
+                    d.selected=ids.includes(d.id);
+                    return d;
+                })
+            },
+            /**
+             * 设置树形结构根据条件选中
+             * @param data 树形结构数据
+             * @param ids 需要选中的ids
+             */
+            setTreeCheckedByIds(data, ids) {
+                if (this.linkage) {
+                    /* 上下级联动 */
+                    return this.linkageSetStatusByIds(data, ids);
+                } else {
+                    // 上下级不联动
+                    return this.notLinkageSetStatusByIds(data, ids);
+                }
+            },
+            /**
+             * 联动-根据选中的ids设置树形结构状态
+             * @param data 树形结构数据
+             * @param ids 需要选中的ids
+             */
+            linkageSetStatusByIds(data, ids) {
+                return data.map(d => {
+                    if (ids.includes(d.id)) {
+                        d.checked='checked';
+                    } else {
+                        d.checked='uncheck';
+                    }
+                    if (d.children && d.children.length) {
+                        d.children=this.linkageSetStatusByIds(d.children, ids);
+                    }
+                    setTimeout(() => {
+                        if (!ids.includes(d.id) && d.children && d.children.length) d.checked=ChangeStatus(d.children);
+                    }, 0);
+                    return d;
+                })
+            },
+            /**
+             * 不联动-根据选中的ids设置树形结构状态
+             * @param data 树形结构数据
+             * @param ids 需要选中的ids
+             */
+            notLinkageSetStatusByIds(data, ids) {
+                return data.map(d => {
+                    if (ids.includes(d.id)) d.checked='checked';
+                    else d.checked='uncheck';
+                    if (d.children && d.children.length) d.children=this.notLinkageSetStatusByIds(d.children, ids);
+                    return d;
+                })
+            },
+            /**
+             * 一维多选列表
+             * @param selected 被选中的项
+             * @param unselect 被取消的项
+             * @param cb 当取新增/消选中时,执行的回调
+             */
+            optionChange(selected, unselect, cb) {
+                if (selected && selected.selected) {
+                    // 增加选中项
+                    const {id, name}=selected;
+                    const n=name.replace(/<[^<>]+>/g, '');
+                    this.selectedData.push({id, name: n});
+                    if (cb) cb(id, true);
+                } else {
+                    // 减少选中项
+                    const {id}=unselect;
+                    this.selectedData=this.selectedData.filter(d => d.id !== id);
+                    if (cb) cb(id, false);
+                }
+            },
+            /**
+             * 搜索列表
+             * @param selected 被选中的项
+             * @param unselect 被取消的项
+             */
+            optionSearchChange(selected, unselect) {
+                // console.log('搜索列表:::', selected, unselect);
+                if (this.flat === 'mt') {
+                    let cStatus='', id='';
+                    if (selected && selected.selected) {
+                        cStatus='checked';
+                        id=selected.lId;
+                    } else {
+                        cStatus='uncheck';
+                        id=unselect.lId;
+                    }
+                    if (this.linkage) {
+                        /* 上下级联动 */
+                        this.treeData=this.linkageSetUpperAndLowerStatus(this.treeData, id, cStatus);
+                    } else {
+                        /* 上下级不联动/只能选择末级 */
+                        const data=this.notLinkageSetUpperAndLowerStatus(this.treeData, id, cStatus);
+                        this.selectedData=this.setSelectedData([], data);
+                    }
+                } else {
+                    this.optionChange(selected, unselect, (id, status) => {
+                        this.treeData=this.treeData.map(d => {
+                            if (d.id === id) d.selected=status;
+                            return d;
+                        });
+                    });
+                }
+            },
+            /**
+             * 搜索列表
+             * @param v 搜索框输入的值
+             */
+            setSearchData(v) {
+                const md=JSON.parse(JSON.stringify(this.treeData));
+                const re=new RegExp(v, 'g');
+                if (this.flat === 'mt') {
+                    // 得到平铺的数据
+                    const tileData=TileTool([], md, '-1');
+                    if (this.lastStage) {
+                        /* 只能选择末级 */
+                        // 筛选出包涵有搜索字符的数据
+                        const fData=tileData.filter(d => {
+                            const hasChild=tileData.find(fd => fd.parentId===d.id); // 查看是否还有子级
+                            if (!hasChild && d.name.includes(v)) {
+                                d.name=d.name.replace(re, `<span style="color: #0091ff;font-size: 14px;">${v}</span>`);
+                                return d;
+                            }
+                        });
+                        // 得到筛选后的数据 一维数据列表
+                        this.searchData=FilterTool(tileData, fData);
+
+                    } else {
+                        /* 上下级联动/上下级不联动 */
+                        // 筛选出包涵有搜索字符的数据
+                        const fData=tileData.filter(d => {
+                            if (d.name.includes(v)) {
+                                d.name=d.name.replace(re, `<span style="color: #0091ff;font-size: 14px;">${v}</span>`);
+                                return d;
+                            }
+                        });
+                        // 得到筛选后的数据 一维数据列表
+                        this.searchData=FilterTool(tileData, fData);
+                    }
+                } else {
+                    this.searchData=md.filter(d => {
+                        if (d.name.includes(v)) {
+                            d.name=d.name.replace(re, `<span style="color: #0091ff;font-size: 14px;">${v}</span>`);
+                            return d;
+                        }
+                    })
+                }
+            },
+            // 清除数据
+            clearObj() {
+                if (this.flat === 'mt') this.treeData=this.setMulDataUncheck(this.treeData);
+                else this.treeData=this.setMulDataUnSelected(this.treeData);
+
+                this.selectedData=[];
+                this.searchData=[];
+                this.inputVal='';
+            },
+            // 点击清空
+            handleEmpty() {
+                // 如果已经有选择的数据,设置isEmpty状态
+                this.isEmpty=true;
+                this.confirmBtnType='primary'; // 设置确定按钮状态
+                this.clearObj();
+            },
+            // 点击取消
+            handleCancel() {
+                const cd=this.confirmData;
+                if (cd && cd.length) {
+                    // 如果 confirmData与selectedData不一样才执行以下操作
+                    if (this.checkArrDiff(cd, this.selectedData)) {
+                        this.selectedData=JSON.parse(JSON.stringify(cd));
+                        const ids=cd.map(d => d.id);
+                        if (this.flat === 'mt') {
+                            this.treeData=this.setTreeCheckedByIds(this.treeData, ids);
+                        } else {
+                            this.treeData=this.setMulDataSelectedByIds(this.treeData, ids);
+                        }
+                    }
+                    this.searchData=[];
+                    this.inputVal='';
+                } else {
+                    this.clearObj();
+                }
+                this.$emit('cancel');
+            },
+            // 点击确定
+            handleConfirm() {
+                const sd=JSON.parse(JSON.stringify(this.selectedData));
+                const ids=sd.map(d => d.id);
+                this.confirmData=sd;
+                this.$emit('confirm', ids, sd);
+                this.confirmBtnType='disabled'; // 设置确定按钮状态
+
+                setTimeout(() => {
+                    this.searchData=[];
+                    this.inputVal='';
+                }, 200);
+            }
+        }
+    }
+</script>
+
+<style lang="stylus">
+
+@import "../static/stylus/transfer/transfer.styl"
+
+.p-transfer
+    display inline-block
+    width 600px
+    font-size 0
+    .p-transfer-left-content
+        height calc(100% - 44px)
+    .p-transfer-selected
+        height calc(100% - 144px)
+
+</style>

+ 250 - 0
src/components/Transfer/Tree/Tree.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="p-tree">
+    <TreeNode
+      :multiple="multiple"
+      :linkage="linkage"
+      :lastStage="lastStage"
+      v-for="(item, ind) in treeData"
+      :key="item.id+'-'+ind"
+      :treeItem="item"
+      :triangleShow="!!(item.children&&item.children.length)"
+      :index="String(ind)"
+      :change="change"
+      :leftPosition="leftPosition"
+    />
+  </div>
+</template>
+
+<script>
+import TreeNode from "./depend/treeNode";
+export default {
+  name: "Tree",
+  components: { TreeNode },
+  props: {
+    /**
+     * 是否开启多选
+     */
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 是否联动选择
+     */
+    linkage: {
+      type: Boolean,
+      default: true
+    },
+    /**
+     * 只能选择末级
+     */
+    lastStage: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 是否返回半选状态的id
+     */
+    notNull: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 树形结构数据列表
+     */
+    data: {
+      type: Array,
+      default: () => []
+    },
+    // 左侧距离
+    leftPosition: {
+      type: Number,
+      default: 12
+    }
+  },
+  computed: {
+    treeData: {
+      get() {
+        return this.data;
+      },
+      set(newData) {
+        return newData;
+      }
+    }
+  },
+  methods: {
+    /**
+     * 点击某项
+     * @param obj 返回当前点击对象
+     * @param index 索引串
+     */
+    change(obj, index) {
+      const { id } = obj;
+      if (this.multiple) {
+        const iArr = index.split("-"); // 拿到索引值
+        iArr.pop(); // 这里不需要遍历最后一个索引的数据
+        const data = this.treeData;
+        if (this.linkage) this.changeParentChecked(data, iArr);
+        const checkedObj = this.filterIds(data);
+        const checkedIds = checkedObj.map(d => d.id);
+        /**
+         * 点击某项返回的数据
+         * @param id
+         * @param checkedIds 选择的id组,多选时才返回
+         * @param obj 选择的当前对象,多选时才返回
+         * @param checkedObj 选择的对象组,多选时才返回
+         * @type Function
+         */
+        this.$emit("change", { id, checkedIds, obj, checkedObj });
+      } else {
+        this.treeData = this.changeCheckedItem(this.treeData, id);
+        /**
+         * 点击某项返回的数据
+         * @param id
+         * @type Function
+         */
+        this.$emit("change", obj);
+      }
+    },
+    /**
+     * 单选改变状态
+     * @param data
+     * @param id
+     * @return {*}
+     */
+    changeCheckedItem(data, id) {
+      return data.map(d => {
+        if (d.id === id) {
+          d.checked = "checked";
+        } else {
+          d.checked = "uncheck";
+        }
+        if (d.children && d.children.length)
+          d.children = this.changeCheckedItem(d.children, id);
+        return d;
+      });
+    },
+    /**
+     * 递归筛选子项有选中的数据
+     * @param data
+     * @param iArr
+     * @param curr 筛选到的数据放入数组
+     */
+    currentData(data, iArr, curr) {
+      const ind = iArr.shift();
+      data.forEach((d, i) => {
+        if (ind && i === Number(ind)) {
+          curr.unshift(d);
+          if (d.children && d.children.length)
+            this.currentData(d.children, iArr, curr);
+        }
+      });
+    },
+    /**
+     *  筛选选中的id
+     */
+    filterIds(data) {
+      const arr = [];
+      this.recursionIds(data, arr);
+
+      return arr;
+    },
+    /**
+     * 筛选选中的id
+     * @param data
+     * @param arr
+     */
+    recursionIds(data, arr) {
+      data.forEach(d => {
+        if (this.notNull) {
+          if (d.checked === "checked" || d.checked === "notNull") arr.push(d);
+        } else {
+          if (d.checked === "checked") arr.push(d);
+        }
+        if (d.children && d.children.length) this.recursionIds(d.children, arr);
+      });
+    }
+  }
+};
+</script>
+
+<style lang="less">
+.p-tree {
+  width: 100%;
+  overflow-x: hidden;
+  font-size: 0;
+}
+.p-tree-node {
+  width: 100%;
+}
+
+.p-tree-node-content {
+  position: relative;
+  width: 100%;
+  cursor: pointer;
+  &:hover {
+    background-color: #f5f6f7;
+  }
+  &.p-tree-node-content-checked {
+    background-color: #e1f2ff;
+    .p-tree-node-check {
+      .p-tree-node-title {
+        .p-tree-node-name {
+          color: #0091ff;
+        }
+      }
+    }
+  }
+}
+
+.p-tree-svg {
+  display: inline-block;
+  vertical-align: middle;
+  margin-right: 8px;
+  width: 16px;
+  height: 16px;
+  line-height: 16px;
+  text-align: center;
+  .p-tree-icon-svg {
+    vertical-align: text-bottom;
+    transform: rotate(90deg);
+    transition: transform 0.3s;
+  }
+
+  .p-tree-icon-rotate {
+    transform: rotate(180deg);
+  }
+}
+.p-tree-node-check {
+  display: inline-block;
+  vertical-align: middle;
+  width: calc(100% - 24px);
+  .p-tree-node-title {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+    user-select: none;
+    width: calc(100% - 24px);
+    .p-tree-node-name {
+      width: 100%;
+      height: 38px;
+      line-height: 38px;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      font-size: 14px;
+      color: #1F2329;
+    }
+  }
+}
+.p-tree-node-content-disabled {
+  // pointer-events none
+  cursor: pointer;
+  .p-tree-node-name {
+    color: #004275 !important;
+  }
+}
+.p-tree-child {
+  width: 100%;
+}
+</style>

+ 14 - 0
src/components/Transfer/Tree/depend/arrow_triangle.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <path
+            fill="#C3C6CB"
+            d="
+                M7.36397105,6.47904167
+                C7.71544566,6.05570759 8.29167643,6.06338938 8.63677321,6.47904167
+                L10.9236602,9.23348584
+                C11.2751348,9.65681992 11.1147123,10 10.555128,10
+                L5.44561621,10 C4.89060692,10 4.7319873,9.64913813 5.07708407,9.23348584
+                L7.36397105,6.47904167
+                Z"
+    ></path>
+</svg>

+ 172 - 0
src/components/Transfer/Tree/depend/treeNode.vue

@@ -0,0 +1,172 @@
+<template>
+	<div class="p-tree-node">
+		<div
+			:class="['p-tree-node-content', !multiple&&treeItem.checked==='checked'&&'p-tree-node-content-checked', treeItem.disabled&&'p-tree-node-content-disabled']"
+			:style="{paddingLeft: paddingLeft+'px'}"
+		>
+			<section class="p-tree-svg" @click="openChild" >
+				<ArrowTriangle
+					:class="['p-tree-icon-svg', (treeItem.open)&&'p-tree-icon-rotate']"
+					v-if="triangleShow"
+				/>
+			</section>
+			<div class="p-tree-node-check" @click="handleCheck(treeItem, index)">
+					<!-- v-if="multiple&&(!lastStage||!triangleShow)" -->
+				<section class="p-tree-node-title">
+					<article class="p-tree-node-name" @mouseenter="treeItemEnter" v-html="treeItem.name" />
+				</section>
+			</div>
+		</div>
+		<div class="p-tree-child" v-if="triangleShow" v-show="treeItem.open">
+			<TreeNode
+				:multiple="multiple"
+				:linkage="linkage"
+				:lastStage="lastStage"
+				v-for="(item, ind) in treeItem.children"
+				:key="item.id+'-'+ind"
+				:treeItem="item"
+				:triangleShow="!!(item.children&&item.children.length)"
+				:index="`${index}-${ind}`"
+				:change="change"
+				:leftPosition="leftPosition"
+			/>
+		</div>
+	</div>
+</template>
+
+<script>
+    import ArrowTriangle from './arrow_triangle.svg';
+    export default {
+        name: 'TreeNode',
+        components: { ArrowTriangle},
+        props: {
+            /**
+             * 是否开启多选
+             */
+            multiple: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 是否联动选择
+             */
+            linkage: {
+                type: Boolean,
+                default: true
+            },
+            /**
+             * 只能选择末级
+             */
+            lastStage: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 树形结构子项数据列表
+             */
+            treeItem: {
+                type: Object,
+                default: {}
+            },
+            /**
+             * 点击某项
+             */
+            change: {
+                type: Function,
+                default: () => false
+            },
+            /**
+             * 下拉三角形是否显示
+             */
+            triangleShow: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 索引
+             */
+            index: {
+                type: String,
+                default: ''
+			},
+			leftPosition: {
+				type: Number,
+				default: 12
+			}
+        },
+        computed: {
+            // 左边内边距
+            paddingLeft() {
+                return (this.index.split('-').length-1)*24+Number(this.leftPosition);
+			},
+
+			checkboxShow() {
+                const item = this.treeItem
+                debugger
+				// const flag = item.hasOwnProperty('isleaf') ? item.isleaf : true
+				return this.multiple && (!this.triangleShow || !this.lastStage) && flag
+			}
+		},
+        methods: {
+            // 展开/收起
+            openChild() {
+                // if (!this.disabledOpen && this.treeItem.disabled ) return
+                this.treeItem.open=!this.treeItem.open
+            },
+            // 鼠标hover
+            treeItemEnter(e) {
+                const target=e.target;
+                const {clientWidth, scrollWidth, title}=target;
+                if (!title && scrollWidth > clientWidth) target.title=target.innerText;
+            },
+            // 选择
+            handleCheck(obj, index) {
+                if (obj.disabled) return;
+                if (this.multiple) {
+					if(this.checkboxShow) {
+						let status='';
+						const treeItem=this.treeItem;
+						const {checked, children}=treeItem;
+
+						if (checked === 'checked') {
+							status='uncheck';
+						} else {
+							//  if (checked === 'uncheck' || checked === 'notNull')
+							status='checked';
+						}
+
+						if (this.linkage) {
+							// 上下级联动
+							if (children && children.length) treeItem.children=this.setCheckedStatus(children, status);
+
+							treeItem.checked=status;
+							this.treeItem=treeItem;
+							this.change(obj, index);
+						} else {
+							// 上下级不联动 this.lastStage为true-表示只能选择末级节点
+							if (this.lastStage && children && children.length) return;
+							this.treeItem=treeItem;
+							treeItem.checked=status;
+
+							this.change(obj, index);
+						}
+					}
+                } else {
+					// const notLastNode = obj.hasOwnProperty('children') || (obj.hasOwnProperty('isleaf') ? !obj.isleaf : false)
+					if(this.lastStage && notLastNode) return
+                    // 执行父级的函数
+                    this.change(obj, index);
+                }
+            },
+            // 设置checked状态
+            setCheckedStatus(data, status) {
+                return data.map(d => {
+                    d.checked=status;
+                    if (d.children && d.children.length)
+                        d.children=this.setCheckedStatus(d.children, status);
+                    return d;
+                })
+            }
+        }
+    }
+</script>

+ 61 - 49
src/components/baseEditertest.vue

@@ -2,6 +2,8 @@
   <div id="baseEditer">
     <div id="fengMap"></div>
     <div class="canvas-container" ref="graphy">
+      <a-button type="primary" @click="undo">取消</a-button>
+      <a-button type="primary" @click="redo">回复</a-button>
       <canvas id="canvas" :width="canvasWidth" :height="canvasHeight" ref="canvas" tabindex="0"></canvas>
     </div>
   </div>
@@ -9,10 +11,10 @@
 <script>
 import { SFengParser } from "@saga-web/feng-map";
 import { SFloorParser } from "@saga-web/big";
-import { DirectRelationItem } from "@saga-web/topology/lib/items/DirectRelationItem.js";
+import { SPoint } from "@saga-web/draw";
 import { SGraphyView, SGraphyScene } from "@saga-web/graphy/lib";
 import { SPolygonItem } from "./mapClass/SPolygonItem";
-import {FloorView} from "./../lib/FloorView"
+import { FloorView } from "./../lib/FloorView";
 export default {
   data() {
     return {
@@ -23,13 +25,14 @@ export default {
       fmap: null,
       canvasWidth: 700,
       canvasHeight: 800,
-      fParser: null
+      fParser: null,
+      item: null
     };
   },
   mounted() {
     this.canvasWidth = this.$refs.graphy.offsetWidth;
     this.canvasHeight = this.$refs.graphy.offsetHeight;
-    this.init()
+    this.init();
   },
   methods: {
     init() {
@@ -45,46 +48,52 @@ export default {
       // );
       // console.log(this.fmap);
       // this.fmap.parseData("1001724_29", 1, res => {
-        // console.log(res);
-        // this.fParser = new SFloorParser(null);
-        // console.log(this.fParser);
-        // this.fParser.parseData(res);
-        // this.fParser.spaceList.forEach(t => this.scene.addItem(t));
-        // this.fParser.wallList.forEach(t => this.scene.addItem(t));
-        // this.fParser.virtualWallList.forEach(t => this.scene.addItem(t));
-        // this.fParser.doorList.forEach(t => this.scene.addItem(t));
-        // this.fParser.columnList.forEach(t => this.scene.addItem(t));
-        // this.fParser.casementList.forEach(t => this.scene.addItem(t));
-        this.addPolyLine();
-        // this.DirectRelationItem();
-        this.view.scene = this.scene;
-        this.view.fitSceneToView();
+      // console.log(res);
+      // this.fParser = new SFloorParser(null);
+      // console.log(this.fParser);
+      // this.fParser.parseData(res);
+      // this.fParser.spaceList.forEach(t => this.scene.addItem(t));
+      // this.fParser.wallList.forEach(t => this.scene.addItem(t));
+      // this.fParser.virtualWallList.forEach(t => this.scene.addItem(t));
+      // this.fParser.doorList.forEach(t => this.scene.addItem(t));
+      // this.fParser.columnList.forEach(t => this.scene.addItem(t));
+      // this.fParser.casementList.forEach(t => this.scene.addItem(t));
+      this.addPolyLine();
+      // this.DirectRelationItem();
+      this.view.scene = this.scene;
+      this.view.fitSceneToView();
       // });
     },
     // 引入构建多边形类
     addPolyLine() {
-      // const data = {
-      //   PointList: [
-      //     { X: 100, Y: 100},
-      //     { X: 1000, Y: 100},
-      //     { X: 1000, Y:1100  },
-      //     { X: 100, Y:1100  },
-      //   ]
-      // };
+      const PointList = [
+        new SPoint(0, 0),
+        new SPoint(50, 0),
+        new SPoint(50, 50),
+        new SPoint(0, 50)
+      ];
       const Polylines = new SPolygonItem(null);
-      Polylines.setStatus = 2;
+      Polylines.setStatus = 3;
+      // Polylines.setPointList = PointList
       this.scene.addItem(Polylines);
       this.scene.grabItem = Polylines;
+      this.item = Polylines;
+    },
+    undo() {
+      this.item.undo();
+    },
+    redo() {
+      this.item.redo();
     },
     // 引入折线
     DirectRelationItem() {
-      // const data = {
-      //   PointList: [
-      //     { X: -138.71000000089407, Y: -17.5 },
-      //     { X: -130.31000000052154, Y: -17.5 }
-      //   ]
-      // };
-      const directRelationItem = new DirectRelationItem(null);
+      const data = {
+        PointList: [
+          { X: -138.71000000089407, Y: -17.5 },
+          { X: -130.31000000052154, Y: -17.5 }
+        ]
+      };
+      const directRelationItem = new DirectRelationItem(null, data);
       this.scene.addItem(directRelationItem);
     },
     clearGraphy() {
@@ -93,42 +102,45 @@ export default {
         return;
       }
       this.view = new FloorView("canvas");
-      document.getElementById('canvas').focus()
+      document.getElementById("canvas").focus();
     },
     spaceClick(space, event) {
       const item = new SImageItem(null, {
         Width: 32,
         Height: 32,
-        Url: "https://dss2.bdstatic.com/6Ot1bjeh1BF3odCf/it/u=1569891320,3644984535&fm=85&app=52&f=JPEG?w=121&h=75&s=891B0FD8964406EF8091EA200300E056"
-      })
-      item.moveTo(event[0].x, event[0].y)
+        Url:
+          "https://dss2.bdstatic.com/6Ot1bjeh1BF3odCf/it/u=1569891320,3644984535&fm=85&app=52&f=JPEG?w=121&h=75&s=891B0FD8964406EF8091EA200300E056"
+      });
+      item.moveTo(event[0].x, event[0].y);
       item.zOrder = 999;
-      item.connect('mousedown', this, this.iconClick)
-      this.scene.addItem(item)
-      setInterval(()=>{item.text+='魔'},1000)
+      item.connect("mousedown", this, this.iconClick);
+      this.scene.addItem(item);
+      setInterval(() => {
+        item.text += "魔";
+      }, 1000);
     },
     iconClick(item, event) {
-      console.log(2222222222222)
+      console.log(2222222222222);
     },
-    changeFloor() { },
+    changeFloor() {},
     changeStatus(name) {
       switch (name) {
-        case '选择':
+        case "选择":
           // test init
           this.init();
           break;
-        case '画线':
+        case "画线":
           this.scene.isLining = true;
-          console.log('lining')
+          console.log("lining");
           break;
-        case '画文字':
+        case "画文字":
           this.scene.isTexting = true;
           break;
-        case '画图片':
+        case "画图片":
           this.scene.isImging = true;
           break;
         default:
-          console.log(name)
+          console.log(name);
           break;
       }
     }

+ 0 - 1
src/components/edit/right_toolbar.vue

@@ -46,7 +46,6 @@
         </div>
       </a-tab-pane>
     </a-tabs>
-    <!-- <Select width="300" tipPlace="top" caption="标题:" :selectdata="dataSelect2" :placeholder="'请选择'"></Select> -->
   </div>
 </template>
 <script>

+ 370 - 221
src/components/mapClass/SPolygonItem.ts

@@ -18,29 +18,28 @@
  * ********************************************************************************************************************
  */
 
-import { SGraphyItem } from "@saga-web/graphy/";
-import { SMouseEvent } from "@saga-web/base/";;
+import { SGraphItem, SGraphCommand } from "@saga-web/graph/";
+import { SMouseEvent, SUndoStack } from "@saga-web/base/";;
 import {
     SColor,
     SLineCapStyle,
     SPainter,
-    SPath2D,
     SPoint,
-    SPolygonUtil,
     SRect,
     SLine
 } from "@saga-web/draw";
 import { PolygonData } from "./type/PolygonData";
-import { Point } from "./type/Point";
 import { SRelationState } from "@saga-web/big/lib/enums/SRelationState"
 import { SMathUtil } from "@saga-web/big/lib/utils/SMathUtil";
 
+import { SPointListInsert, SPointListInDelete, SPointListUpdate } from "./command/index"
+
 /**
  * 编辑多边形
  *
  * @author  韩耀龙
  */
-export class SPolygonItem extends SGraphyItem {
+export class SPolygonItem extends SGraphItem {
     /** X坐标最小值  */
     private minX = Number.MAX_SAFE_INTEGER;
     /** X坐标最大值  */
@@ -50,10 +49,22 @@ export class SPolygonItem extends SGraphyItem {
     /** Y坐标最大值  */
     private maxY = Number.MIN_SAFE_INTEGER;
     /** 轮廓线坐标  */
-    pointList: SPoint[] = [];
+    private pointList: SPoint[] = [];
+    // 获取当前状态
+    get getPointList(): SPoint[] {
+        return this.pointList;
+    }
+    // 编辑当前状态
+    set setPointList(arr: SPoint[]) {
+        this.pointList = arr;
+        if (arr) {
+            this._xyzListToSPointList(arr);
+        };
+        this.update();
+    };
     /** 是否闭合    */
     closeFlag: boolean = false;
-    // 当前状态 1:show 2 创建中,3 编辑中
+    // 当前状态
     _status: number = SRelationState.Normal;
     // 获取当前状态
     get getStatus(): number {
@@ -62,6 +73,12 @@ export class SPolygonItem extends SGraphyItem {
     // 编辑当前状态
     set setStatus(value: number) {
         this._status = value;
+        // 如果状态为show则需清栈
+        if (value == SRelationState.Normal) {
+            if (this.undoStack) {
+                this.undoStack.clear();
+            }
+        }
         this.update();
     };
     data: PolygonData | null = null;
@@ -75,39 +92,41 @@ export class SPolygonItem extends SGraphyItem {
     private lastPoint = new SPoint();
     /** 当前鼠标获取顶点对应索引 */
     private curIndex: number = -1;
+    /** 当前鼠标获取顶点对应坐标 */
+    private curPoint: null | SPoint = null
     /** 灵敏像素 */
-    private len: number = 15;
-    /** 场景像素  */
+    private len: number = 10;
+    /** 场景像素 内部将灵敏像素换算为场景实际距离  */
     private scenceLen: number = 15;
+    /** 场景像素  */
+    private isAlt: boolean = false;
+    /** undoredo堆栈 */
+    private undoStack: SUndoStack | null = new SUndoStack();
+
     /**
     * 构造函数
     *
     * @param parent    指向父对象
     * @param data      PolygonData数据
     */
-
-    constructor(parent: SGraphyItem | null, data: PolygonData | null) {
+    constructor(parent: SGraphItem | null) {
         super(parent);
-        this.data = data;
-
-        if (data && data.PointList) {
-            this._xyzListToSPointList(data.PointList);
-        };
-        this.zOrder = 100;
     }
 
+    //////////////////
+    //  以下为对pointList 数组的操作方法
 
     /**
-    * 储存新的多边形顶点
-    * @param x   点位得x坐标
-    * @param y   点位得y坐标
-    * @param i   储存所在索引
-    * @return SPoint
-    */
-
-    reservePoint(x: number, y: number, i?: number): SPoint {
+     * 储存新的多边形顶点
+     *
+     * @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 (typeof i == 'undefined') {
+        if (i == null) {
             this.pointList.push(point)
         } else {
             this.pointList.splice(i, 0, point);
@@ -116,39 +135,47 @@ export class SPolygonItem extends SGraphyItem {
         return point
     }
 
-
     /**
-    * 删除点位
-    * @param i   删除点所在的索引
-    * @return SPoint
-    */
-
-    deletePoint(i?: number): SPoint | null | undefined {
+     * 删除点位
+     *
+     * @param i   删除点所在的索引
+     * @return    SPoint|null。索引不在数组范围则返回null
+     */
+    deletePoint(i: number | null = null): SPoint | null {
         let point = null;
-        if (typeof i != 'undefined') {
-            if (this.pointList.length - 1 >= i) {
-                point = this.pointList[i];
-                this.pointList.splice(i, 0);
-            } else {
+        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 {
-            point = this.pointList.pop();
+            if (this.pointList.length) {
+                point = this.pointList[this.pointList.length - 1];
+                this.pointList.pop();
+            } else {
+                point = null
+            }
         }
+        console.log('删除点位', point, i)
+        this.update()
         return point
     }
 
-
     /**
-    * 多边形顶点的移动位置
-    * @param x   点位得x坐标
-    * @param y   点位得y坐标
-    * @param i   点位得i坐标
-    * @return SPoint
-    */
-
-    movePoint(x: number, y: number, i: number, ): SPoint | null | undefined {
+     * 多边形顶点的移动位置
+     *
+     * @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;
@@ -157,42 +184,25 @@ export class SPolygonItem extends SGraphyItem {
         return point
     }
 
-
     /**
-    * 打印出多边形数组
-    * @return SPoint[]
-    */
-
+     * 打印出多边形数组
+     *
+     * @return  顶点数组
+     */
     PrintPointList(): SPoint[] {
         return this.pointList
     }
 
     /**
-     * Item对象边界区域
+     * xyz数据转换为SPoint格式函数;获取外接矩阵参数
      *
-     * @return SRect
+     * @param arr    外层传入的数据PointList
      */
-    boundingRect(): SRect {
-        return new SRect(
-            this.minX,
-            this.minY,
-            this.maxX - this.minX,
-            this.maxY - this.minY
-        );
-    } // Function boundingRect()
-
-
-    /**
-    * xyz数据转换为SPoint格式函数;获取外接矩阵参数
-    *
-    * @param arr    外层传入的数据PointList
-    */
-
-    protected _xyzListToSPointList(arr: Point[]) {
+    protected _xyzListToSPointList(arr: SPoint[]): void {
         if (arr.length) {
             this.pointList = arr.map(it => {
-                let x = it.X,
-                    y = it.Y;
+                let x = it.x,
+                    y = it.y;
                 if (x < this.minX) {
                     this.minX = x;
                 }
@@ -212,117 +222,75 @@ export class SPolygonItem extends SGraphyItem {
         }
     }
 
+    ////////////
+    //  以下为三种状态下的绘制多边形方法
 
     /**
-    * 构造函数  展示多边形轮廓
-    *
-    */
-
-    protected showPolygon(painter: SPainter, pointList: SPoint[]) {
+     * 展示状态 --绘制多边形数组
+     *
+     * @param painter      绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawShowPolygon(painter: SPainter, pointList: SPoint[]): void {
         painter.pen.lineCapStyle = SLineCapStyle.Square;
         painter.pen.color = this.borderColor;
         painter.pen.lineWidth = painter.toPx(this.borderLine);
         painter.brush.color = this.brushColor;
-        painter.drawPolygon([...this.pointList]);
+        painter.drawPolygon([...pointList]);
     }
 
 
     /**
-    * 构造函数  创建多边形
-    *
-    */
-
-    protected createPolygon(painter: SPainter, pointList: SPoint[]) {
+     * 创建状态 --绘制多边形数组
+     *
+     * @param painter      绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawCreatePolygon(painter: SPainter, pointList: SPoint[]): void {
         painter.pen.lineCapStyle = SLineCapStyle.Square;
-        painter.pen.color = new SColor("#0091FF");
-        painter.pen.lineWidth = painter.toPx(4);
-        painter.drawPolyline(this.pointList);
-        if (this.lastPoint) {
-            painter.drawLine(
-                pointList[this.pointList.length - 1],
-                this.lastPoint
-            );
-        };
+        painter.pen.color = this.borderColor;
+        painter.pen.lineWidth = painter.toPx(this.borderLine);
+        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.pen.lineWidth = painter.toPx(4);
-        painter.drawPolygon([...this.pointList, this.lastPoint]);
+        painter.brush.color = this.brushColor;
+        painter.pen.lineWidth = painter.toPx(this.borderLine);
+        painter.drawPolygon([...pointList, this.lastPoint]);
     }
 
-
     /**
-    * 构造函数  创建多边形
-    *
-    */
-
-    protected editPolygon(painter: SPainter, pointList: SPoint[]) {
+     *
+     * 编辑状态 --绘制多边形数组
+     *
+     * @param painter    绘制类
+     * @param pointList    绘制多边形数组
+     */
+    protected drawEditPolygon(painter: SPainter, pointList: SPoint[]): void {
         // 展示多边形
-        this.showPolygon(painter, pointList);
+        this.drawShowPolygon(painter, pointList);
         // 绘制顶点块
+        painter.pen.color = SColor.Black;
+        painter.brush.color = SColor.White;
         pointList.forEach((item, index) => {
-            painter.drawCircle(item.x, item.y, painter.toPx(8))
+            painter.drawCircle(item.x, item.y, painter.toPx(this.len / 2))
         })
     }
 
-
-
-
-    ////////////以下为鼠标事件
-
     /**
-    * 鼠标双击事件
-    *
-    * @param	event         事件参数
-    * @return	boolean
-    */
-
-    onDoubleClick(event: SMouseEvent): boolean {
-        // 如果位show状态 双击改对象则需改为编辑状态
-        if (this._status == 1) {
-            this._status = 3;
-            this.update()
-        }
-        return true;
-    } // Function onDoubleClick()
-
-
-    /**
-    * 键盘事件
-    *
-    * @param	event         事件参数
-    * @return	boolean
-    */
-    onKeyDown(event: KeyboardEvent): boolean {
-        if (this._status == 1) {
-            return false;
-        } else if (this._status == 2) {
-            if (event.code == 'Enter') {
-                this._status = 1
-            }
-        } else if (this._status == 3) {
-            // if (event.code == 'Enter') {
-            //     this._status = 1
-            // }
-        } else {
-
-        }
-        this.update()
-        return true;
-    } // Function onKeyDown()
-    /**
-     * 鼠标双击事件
+     * 编辑状态操作多边形数组
+     *
+     * @param event    鼠标事件
+     *
      *
-     * @param	event         事件参数
-     * @return	boolean
      */
-    onMouseDown(event: SMouseEvent): boolean {
-        // 如果状态为编辑状态则添加点
-        if (this._status == 2) {
-            // 新增顶点
-            this.reservePoint(event.x, event.y)
-        } else if (this._status == 3) {
+    protected editPolygonPoint(event: SMouseEvent): void {
+        //  判断是否为删除状态 isAlt = true为删除状态
+        if (this.isAlt) {
             // 1 判断是否点击在多边形顶点
-            // 敏感度
-            this.len = 15;
+            let lenIndex = -1;  // 当前点击到的点位索引;
+            let curenLen = this.scenceLen; // 当前的灵敏度
             this.pointList.forEach((item, index) => {
                 let dis = SMathUtil.pointDistance(
                     event.x,
@@ -330,13 +298,42 @@ export class SPolygonItem extends SGraphyItem {
                     item.x,
                     item.y
                 );
-                if (dis < this.len) {
-                    this.len = dis;
-                    this.curIndex = index;
+                if (dis < curenLen) {
+                    curenLen = dis;
+                    lenIndex = index;
                 }
             });
-            // 判断是否点击在多边形得边上
-            if (-1 == this.curIndex) {
+            // 若点击到,对该索引对应的点做删除
+            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(SPointListInDelete, [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])
@@ -348,8 +345,7 @@ export class SPolygonItem extends SGraphyItem {
                             new SPoint(event.x, event.y),
                             new SLine(this.pointList[i], this.pointList[i + 1])
                         );
-                        if (i == this.pointList.length) {
-                            console.log(this.pointList.length)
+                        if ((i + 1) == this.pointList.length) {
                             dis = SMathUtil.pointToLine(
                                 new SPoint(event.x, event.y),
                                 new SLine(this.pointList[i], this.pointList[0])
@@ -361,17 +357,66 @@ export class SPolygonItem extends SGraphyItem {
                         }
                     }
                 }
+                // 如果再线上则为新增点
                 if (len.Point) {
                     if (len.MinDis <= this.scenceLen) {
                         this.pointList.splice(index + 1, 0, len.Point);
-                        this.update();
+                        // 记录新增顶点操作记录压入堆栈
+                        this.recordAction(SPointListInsert, [this.pointList, len.Point, index + 1])
                     }
                 }
+            } else {
+                // 当捕捉到顶点后 ,记录当前点的xy坐标,用于undo、redo操作
+                this.curPoint = new SPoint(this.pointList[this.curIndex].x, this.pointList[this.curIndex].y);
             }
+            // 刷新视图
+            this.update();
         }
-        return true;
-    } // Function onMouseDown()
 
+    }
+
+    /////////////////////
+    // undo、redo相关操作
+
+    /**
+     * 记录相关动作并推入栈中
+     * @param	SGraphCommand         相关命令类
+     * @param	any                    对应传入参数
+     */
+    protected recordAction(SGraphCommand: any, any: any[]): void {
+        // 记录相关命令并推入堆栈中
+        const sgraphcommand = new SGraphCommand(this, ...any);
+        if (this.undoStack) {
+            this.undoStack.push(sgraphcommand);
+        }
+    }
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        if (this._status == SRelationState.Normal) {
+            return
+        }
+        if (this.undoStack) {
+            this.undoStack.undo();
+        }
+    }
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        if (this._status == SRelationState.Normal) {
+            return
+        }
+        if (this.undoStack) {
+            this.undoStack.redo();
+        }
+    }
+
+    ///////////////////////////////
+    // 以下为鼠标事件
 
     /**
      * 鼠标双击事件
@@ -379,92 +424,196 @@ export class SPolygonItem extends SGraphyItem {
      * @param	event         事件参数
      * @return	boolean
      */
-    onMouseEnter(event: SMouseEvent): boolean {
+    onDoubleClick(event: SMouseEvent): boolean {
+        // 如果位show状态 双击改对象则需改为编辑状态
+        if (SRelationState.Normal == this._status) {
+            this._status = SRelationState.Edit;
+        } else if (SRelationState.Edit == this._status) {
+            this._status = SRelationState.Normal;
+            if (this.undoStack) {
+                this.undoStack.clear()
+            }
+        }
+        this.update()
+        return true;
+    } // Function onDoubleClick()
+
+
+    /**
+     * 键盘事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
+    onKeyDown(event: KeyboardEvent): boolean {
+        if (this._status == SRelationState.Normal) {
+            return false;
+        } else if (this._status == SRelationState.Create) {
+            if (event.code == 'Enter') {
+                // 当顶点大于二个时才又条件执行闭合操作并清空堆栈
+                if (this.pointList.length > 2) {
+                    this._status = SRelationState.Normal;
+                    if (this.undoStack) {
+                        this.undoStack.clear();
+                    }
+                }
+            }
+        } else if (this._status == SRelationState.Edit) {
+            if (event.key == 'Alt') {
+                this.isAlt = true;
+            }
+        }
+        this.update()
+        return true;
+    } // Function onKeyDown()
+
+
+    /**
+     * 键盘键抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onKeyUp(event: KeyboardEvent): boolean {
+        if (this._status == SRelationState.Edit) {
+            if (event.key == 'Alt') {
+                this.isAlt = false;
+            }
+        }
+        this.update()
+        return true;
+    } // Function onKeyUp()
+
+    /**
+     * 鼠标按下事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+    onMouseDown(event: SMouseEvent): boolean {
+        // 如果状态为编辑状态则添加点
+        if (this._status == SRelationState.Create) {
+            // 新增顶点
+            this.insertPoint(event.x, event.y);
+            // 记录新增顶点操作记录压入堆栈
+            let pos = new SPoint(event.x, event.y);
+            this.recordAction(SPointListInsert, [this.pointList, pos])
+        } else if (this._status == SRelationState.Edit) {
+            // 对多边形数组做编辑操作
+            this.editPolygonPoint(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 onMouseDown()
+    } // Function onMouseLeave()
+
+    /**
+     * 鼠标移动事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
     onMouseMove(event: SMouseEvent): boolean {
-        if (this._status == 2) {
+        if (this._status == SRelationState.Create) {
             this.lastPoint.x = event.x;
             this.lastPoint.y = event.y;
-        } else if (this._status == 3) {
+            this.update()
+        } else if (this._status == SRelationState.Edit) {
             if (-1 != this.curIndex) {
-                console.log('onMouseMove')
                 this.pointList[this.curIndex].x = event.x;
                 this.pointList[this.curIndex].y = event.y;
             }
+            this.update()
         }
-        this.update()
+
         return true;
-    } // Function onMouseDown()
+    } // Function onMouseMove()
+
+
+    /**
+     * 鼠标抬起事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
+
     onMouseUp(event: SMouseEvent): boolean {
-        console.log('onMouseUp')
-        if (this._status == 3) {
+        if (this._status == SRelationState.Edit) {
+            if (-1 != this.curIndex) {
+                const pos = new SPoint(this.pointList[this.curIndex].x, this.pointList[this.curIndex].y)
+                this.recordAction(SPointListUpdate, [this.pointList, this.curPoint, pos, this.curIndex])
+            }
             this.curIndex = -1;
+            this.curPoint = null;
         }
         return true;
     } // Function onMouseUp()
-    onResize(event: SMouseEvent): boolean {
-        return true;
-    } // Function onMouseUp()
 
+    /**
+     * 适配事件
+     *
+     * @param	event         事件参数
+     * @return	boolean
+     */
 
+    onResize(event: SMouseEvent): boolean {
+        return true;
+    } // Function onResize()
 
     /**
-    *  点击点是否在item范围内
-    *
-    */
-    // contains(x: number, y: number): boolean {
-    //     console.log('PTL.MinDis < this.scenceLen')
-    //     if (3 == this._status) {
-    //         let p = new SPoint(x, y);
-    //         //  todo差缩放
-    //         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.scenceLen) {
-    //                 console.log('PTL.MinDis < this.scenceLen', PTL.MinDis, this.scenceLen)
-    //                 return true;
-    //             }
-    //         }
-    //         return false;
-    //     }
-    //     return false;
-    // }
+     * Item对象边界区域
+     *
+     * @return SRect
+     */
+    boundingRect(): SRect {
+        return new SRect(
+            this.minX,
+            this.minY,
+            this.maxX - this.minX,
+            this.maxY - this.minY
+        );
+    } // Function boundingRect()
 
     /**
-    * Item绘制操作
-    *
-    * @param   painter       painter对象
-    */
-
+     * Item绘制操作
+     *
+     * @param   painter       painter对象
+     */
     onDraw(painter: SPainter): void {
         this.scenceLen = painter.toPx(this.len)
         // 当状态为展示状态
-        if (this._status == 1) {
+        if (this._status == SRelationState.Normal) {
             // 闭合多边形
-            this.showPolygon(painter, this.pointList);
-        } else if (this._status == 2) {
+            this.drawShowPolygon(painter, this.pointList);
+        } else if (this._status == SRelationState.Create) {
             // 创建状态
-            this.createPolygon(painter, this.pointList)
+            this.drawCreatePolygon(painter, this.pointList)
         } else {
             // 编辑状态
-            this.editPolygon(painter, this.pointList);
+            this.drawEditPolygon(painter, this.pointList);
         }
     } // Function onDraw()
 

+ 82 - 0
src/components/mapClass/command/SPointListInDelete.ts

@@ -0,0 +1,82 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *                      :*$@@%$*:                         ;:                ;;    ;;
+ *                    :@@%!  :!@@%:                       %!             ;%%@@%$ =@@@@@@@%;     @%@@@%%%%@@@@@
+ *                   :@%;       :$=                       %%$$$%$$         ;$$  ;$@=   !@$
+ *                   =@!                                  %!              @ $=;%   !@@@%:      !$$$$$$$$$$$$$$=
+ *                   =@*                                  %!              @ $= % %@=   =%@!      %=
+ *              *$%%! @@=        ;=$%%%$*:                %!              @ $= % =%%%%%%@$      *%:         =%
+ *            %@@!:    !@@@%=$@@@@%!  :*@@$:              %!              @ $= % $*     ;@      @*          :%*
+ *          ;@@!          ;!!!;:         ;@%:      =======@%========*     @ $$ % $%*****$@     :@$=*********=@$
+ *          $@*   ;@@@%=!:                *@*
+ *          =@$    ;;;!=%@@@@=!           =@!
+ *           %@$:      =@%: :*@@@*       %@=                    Copyright (c) 2016-2019.  北京上格云技术有限公司
+ *            ;%@@$=$@@%*       *@@@$=%@@%;
+ *               ::;::             ::;::                                              All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+import { SPoint } from "@saga-web/draw/lib";
+import { SGraphItem, SGraphCommand } from "@saga-web/graph";
+
+/**
+ * 多边形、折线等相关顶点的位置删除命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SPointListInDelete extends SGraphCommand {
+    /**  指向item对象 */
+    item: SGraphItem;
+    /** 索引 */
+    index: number | null;
+    /**  删除位置 */
+    pos: SPoint | null;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   index       索引
+     */
+    constructor(item: SGraphItem, pointList: SPoint[], pos: SPoint, index: number | null = null) {
+        super(null);
+        this.item = item;
+        this.index = index;
+        this.pointList = pointList;
+        this.pos = pos
+
+    }
+    // mergeWith(command: SUndoCommand): boolean {
+    //     return false;
+    // }
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        if (this.index == null) {
+            this.pointList.pop()
+        } else {
+            this.pointList.splice(this.index, 1);
+        }
+        this.item.update();
+    }
+
+    /**
+     * 执行取消操作执行
+     */
+    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();
+    }
+}

+ 83 - 0
src/components/mapClass/command/SPointListInsert.ts

@@ -0,0 +1,83 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *                      :*$@@%$*:                         ;:                ;;    ;;
+ *                    :@@%!  :!@@%:                       %!             ;%%@@%$ =@@@@@@@%;     @%@@@%%%%@@@@@
+ *                   :@%;       :$=                       %%$$$%$$         ;$$  ;$@=   !@$
+ *                   =@!                                  %!              @ $=;%   !@@@%:      !$$$$$$$$$$$$$$=
+ *                   =@*                                  %!              @ $= % %@=   =%@!      %=
+ *              *$%%! @@=        ;=$%%%$*:                %!              @ $= % =%%%%%%@$      *%:         =%
+ *            %@@!:    !@@@%=$@@@@%!  :*@@$:              %!              @ $= % $*     ;@      @*          :%*
+ *          ;@@!          ;!!!;:         ;@%:      =======@%========*     @ $$ % $%*****$@     :@$=*********=@$
+ *          $@*   ;@@@%=!:                *@*
+ *          =@$    ;;;!=%@@@@=!           =@!
+ *           %@$:      =@%: :*@@@*       %@=                    Copyright (c) 2016-2019.  北京上格云技术有限公司
+ *            ;%@@$=$@@%*       *@@@$=%@@%;
+ *               ::;::             ::;::                                              All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+import { SPoint } from "@saga-web/draw/lib";
+import { SGraphItem,SGraphCommand} from "@saga-web/graph";
+
+
+/**
+ * 多边形、折线等相关顶点的位置插入命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SPointListInsert extends SGraphCommand {
+    /**  指向item对象 */
+    item: SGraphItem;
+    /**  当前位置 */
+    pos: SPoint;
+    /** 索引 */
+    index: number | null;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   pos         当前位置
+     * @param   index       索引
+     */
+    constructor(item: SGraphItem, pointList: SPoint[], pos: SPoint, index: number | null = null) {
+        super(null);
+        this.item = item;
+        this.pos = pos;
+        this.index = index;
+        this.pointList = pointList;
+    }
+    // mergeWith(command: SUndoCommand): boolean {
+    //     return false;
+    // }
+
+    /**
+     * 执行重做操作执行
+     */
+    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();
+    }
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        if (this.index == null) {
+            this.pointList.pop()
+        } else {
+            this.pointList.splice(this.index,1);
+        }
+        this.item.update();
+    }
+}

+ 80 - 0
src/components/mapClass/command/SPointListUpdate.ts

@@ -0,0 +1,80 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *                      :*$@@%$*:                         ;:                ;;    ;;
+ *                    :@@%!  :!@@%:                       %!             ;%%@@%$ =@@@@@@@%;     @%@@@%%%%@@@@@
+ *                   :@%;       :$=                       %%$$$%$$         ;$$  ;$@=   !@$
+ *                   =@!                                  %!              @ $=;%   !@@@%:      !$$$$$$$$$$$$$$=
+ *                   =@*                                  %!              @ $= % %@=   =%@!      %=
+ *              *$%%! @@=        ;=$%%%$*:                %!              @ $= % =%%%%%%@$      *%:         =%
+ *            %@@!:    !@@@%=$@@@@%!  :*@@$:              %!              @ $= % $*     ;@      @*          :%*
+ *          ;@@!          ;!!!;:         ;@%:      =======@%========*     @ $$ % $%*****$@     :@$=*********=@$
+ *          $@*   ;@@@%=!:                *@*
+ *          =@$    ;;;!=%@@@@=!           =@!
+ *           %@$:      =@%: :*@@@*       %@=                    Copyright (c) 2016-2019.  北京上格云技术有限公司
+ *            ;%@@$=$@@%*       *@@@$=%@@%;
+ *               ::;::             ::;::                                              All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+import { SGraphyCommand } from "@saga-web/graphy/lib";
+import { SPoint } from "@saga-web/draw/lib";
+import { SGraphItem, SGraphCommand } from "@saga-web/graph";
+
+/**
+ * 多边形、折线等相关顶点的位置更新命令
+ *
+ * @author  韩耀龙
+ */
+
+export class SPointListUpdate extends SGraphCommand {
+    /**  指向item对象 */
+    item: SGraphItem;
+    /**  原位置 */
+    old: SPoint;
+    /**  当前位置 */
+    pos: SPoint;
+    /** 索引 */
+    index: number;
+    /** 顶点数组 */
+    pointList: SPoint[];
+
+    /**
+     * 构造函数
+     * @param   item        指向item对象
+     * @param   pointList   顶点数组
+     * @param   old         原位置
+     * @param   pos         当前位置
+     * @param   index       索引
+     */
+    constructor(item: SGraphItem, pointList: SPoint[], old: SPoint, pos: SPoint, index: number) {
+        super(null);
+        this.item = item;
+        this.old = old;
+        this.pos = pos;
+        this.index = index;
+        this.pointList = pointList;
+    }
+    // mergeWith(command: SUndoCommand): boolean {
+    //     return false;
+    // }
+
+    /**
+     * 执行重做操作执行
+     */
+    redo(): void {
+        this.pointList[this.index].x = this.pos.x;
+        this.pointList[this.index].y = this.pos.y;
+        this.item.update();
+    }
+
+    /**
+     * 执行取消操作执行
+     */
+    undo(): void {
+        this.pointList[this.index].x = this.old.x;
+        this.pointList[this.index].y = this.old.y;
+        this.item.update();
+    }
+}

+ 6 - 0
src/components/mapClass/command/index.ts

@@ -0,0 +1,6 @@
+import { SPointListInsert } from "./SPointListInsert"
+import { SPointListInDelete } from "./SPointListInDelete"
+import { SPointListUpdate } from "./SPointListUpdate"
+export {
+    SPointListInsert, SPointListInDelete, SPointListUpdate
+}

+ 2 - 2
src/views/editer.vue

@@ -11,8 +11,8 @@
       </div>
       <!-- 绘制界面 -->
       <div class="baseEdit">
-        <baseEditer ref="graphy"></baseEditer>
-        <!-- <baseEditertest ref="graphy"></baseEditertest> -->
+        <!-- <baseEditer ref="graphy"></baseEditer> -->
+        <baseEditertest ref="graphy"></baseEditertest>
         <!-- <baseEditerzy ref="graphy"></baseEditerzy> -->
       </div>
       <!-- 右侧工具栏 -->