diff --git a/seller/src/views/shop/hotzone/assets/styles/icons8-edit-64.png b/seller/src/views/shop/hotzone/assets/styles/icons8-edit-64.png
new file mode 100644
index 00000000..72d50088
Binary files /dev/null and b/seller/src/views/shop/hotzone/assets/styles/icons8-edit-64.png differ
diff --git a/seller/src/views/shop/hotzone/assets/styles/main.css b/seller/src/views/shop/hotzone/assets/styles/main.css
new file mode 100644
index 00000000..da6a3052
--- /dev/null
+++ b/seller/src/views/shop/hotzone/assets/styles/main.css
@@ -0,0 +1,657 @@
+.hz-m-wrap {
+ position: relative;
+ /*overflow: hidden;*/
+}
+
+.hz-m-wrap .hz-u-img {
+ display: block;
+ width: 100%;
+ max-width: 100%;
+ height: auto;
+ /* max-height: 100%; */
+ user-select: none;
+}
+
+.hz-m-wrap .hz-m-area {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ cursor: crosshair;
+}
+
+.hz-m-wrap .hz-m-item {
+ position: absolute;
+ display: block;
+}
+
+.hz-m-wrap .hz-m-box {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ box-shadow: 0 0 6px #000;
+ background-color: #e31414;
+ font-size: 12px;
+ cursor: pointer;
+ color: #fff;
+ opacity: 0.8;
+}
+.hz-m-box{
+ overflow: hidden;
+}
+
+.hz-m-wrap .hz-m-box>li {
+ position: absolute;
+ text-align: center;
+ user-select: none;
+}
+
+.hz-m-wrap .hz-m-box.hz-z-hidden>li {
+ display: none;
+}
+
+.hz-m-wrap .hz-m-box.hz-m-hoverbox:hover {
+ box-shadow: 0 0 0 2px #373950;
+}
+
+.hz-m-wrap .hz-m-box.hz-m-hoverbox .hz-icon:hover {
+ background-color: #373950;
+}
+
+.hz-m-wrap .hz-m-box .hz-icon {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+ font-size: 20px;
+ text-align: center;
+}
+
+.hz-m-wrap .hz-m-box .hz-icon:hover {
+ background-color: #e31414;
+ opacity: 0.8;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-index {
+ top: 0;
+ left: 0;
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+ background-color: #000;
+ z-index: 100;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-close {
+ top: 0;
+ right: 0;
+ z-index: 10;
+}
+
+.hz-m-wrap .hz-m-box .hz-m-copy {
+ display: inline-block;
+}
+
+.hz-m-wrap .hz-m-box .hz-small-icon {
+ border: 0;
+ border-radius: 0;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square {
+ width: 8px;
+ height: 8px;
+ opacity: 0.8;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square:after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 4px;
+ height: 4px;
+ border-radius: 4px;
+ background-color: #fff;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-tl {
+ top: -4px;
+ left: -4px;
+ cursor: nw-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-tc {
+ top: -4px;
+ left: 50%;
+ transform: translateX(-50%);
+ cursor: n-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-tr {
+ top: -4px;
+ right: -4px;
+ cursor: ne-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-cl {
+ top: 50%;
+ left: -4px;
+ transform: translateY(-50%);
+ cursor: w-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-cr {
+ top: 50%;
+ right: -4px;
+ transform: translateY(-50%);
+ cursor: w-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-bl {
+ bottom: -4px;
+ left: -4px;
+ cursor: sw-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-bc {
+ bottom: -4px;
+ left: 50%;
+ transform: translateX(-50%);
+ cursor: s-resize;
+}
+
+.hz-m-wrap .hz-m-box .hz-u-square-br {
+ bottom: -4px;
+ right: -4px;
+ cursor: se-resize;
+}
+
+/* reset */
+.hz-m-modal,
+.hz-m-wrap {
+ font-size: 12px;
+ /* 清除内外边距 */
+ /* 重置列表元素 */
+ /* 重置文本格式元素 */
+ /* 初始化 input */
+}
+
+.hz-m-modal ul,
+.hz-m-wrap ul,
+.hz-m-modal ol,
+.hz-m-wrap ol,
+.hz-m-modal li,
+.hz-m-wrap li {
+ margin: 0;
+ padding: 0;
+}
+
+.hz-m-modal ul,
+.hz-m-wrap ul,
+.hz-m-modal ol,
+.hz-m-wrap ol {
+ list-style: none;
+}
+
+.hz-m-modal a,
+.hz-m-wrap a {
+ text-decoration: none;
+}
+
+.hz-m-modal a:hover,
+.hz-m-wrap a:hover {
+ text-decoration: underline;
+}
+
+.hz-m-modal p,
+.hz-m-wrap p {
+ -webkit-margin-before: 0;
+ -webkit-margin-after: 0;
+}
+
+.hz-m-modal input[type="checkbox"],
+.hz-m-wrap input[type="checkbox"] {
+ cursor: pointer;
+}
+
+/* basic */
+/* modal 样式 */
+.hz-m-modal {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1000;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ touch-action: cross-slide-y pinch-zoom double-tap-zoom;
+ text-align: center;
+ overflow: hidden;
+}
+
+.hz-m-modal:before {
+ content: "";
+ display: inline-block;
+ vertical-align: middle;
+ height: 100%;
+}
+
+.hz-m-modal .hz-modal_dialog {
+ display: inline-block;
+ vertical-align: middle;
+ text-align: left;
+ border-radius: 3px;
+}
+
+.hz-m-modal .hz-modal_title {
+ margin: 0;
+}
+
+.hz-m-modal .hz-modal_close {
+ float: right;
+ margin: -6px -4px 0 0;
+}
+
+@media (max-width: 767px) {
+ .hz-m-modal .hz-modal_dialog {
+ width: auto;
+ }
+}
+
+html.z-modal,
+html.z-modal body {
+ overflow: hidden;
+}
+
+.hz-m-modal {
+ background: rgba(0, 0, 0, 0.6);
+}
+
+.hz-m-modal .hz-modal_dialog {
+ width: 450px;
+ background: #fff;
+ -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);
+ box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);
+}
+
+.hz-m-modal .hz-modal_hd {
+ padding: 15px;
+ border-bottom: 1px solid #f4f4f4;
+}
+
+.hz-m-modal .hz-modal_title {
+ font-size: 18px;
+}
+
+.hz-m-modal .hz-modal_close {
+ margin: -15px -15px 0 0;
+ padding: 6px;
+ color: #bbb;
+ cursor: pointer;
+}
+
+.hz-m-modal .hz-modal_close:hover {
+ color: #888;
+}
+
+.hz-m-modal .hz-modal_close .hz-u-icon-close {
+ font-size: 18px;
+ transition: transform 500ms ease-in-out;
+ transform: rotate(0deg);
+ width: 18px;
+ text-align: center;
+}
+
+.hz-m-modal .hz-modal_close:hover .hz-u-icon-close {
+ transform: rotate(270deg);
+}
+
+.hz-m-modal .hz-modal_bd {
+ padding: 15px 15px 0 15px;
+ min-height: 10px;
+}
+
+.hz-m-modal .hz-modal_ft {
+ padding: 15px;
+ text-align: center;
+ border-top: 1px solid #f4f4f4;
+}
+
+.hz-m-modal .hz-modal_ft .hz-u-btn {
+ margin: 0 10px;
+}
+
+@media (max-width: 767px) {
+ .hz-m-modal .hz-modal_dialog {
+ margin: 10px;
+ }
+}
+
+/* 基本按钮样式 btn */
+.hz-u-btn {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-appearance: none;
+ border: none;
+ overflow: visible;
+ font: inherit;
+ text-transform: none;
+ text-decoration: none;
+ cursor: pointer;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ background: none;
+ display: inline-block;
+ vertical-align: middle;
+ text-align: center;
+ font-size: 12px;
+}
+
+.hz-u-btn:hover,
+.hz-u-btn:focus {
+ outline: none;
+ text-decoration: none;
+}
+
+.hz-u-btn:disabled {
+ cursor: not-allowed;
+}
+
+.hz-u-btn-block {
+ display: block;
+ width: 100%;
+}
+
+.hz-u-btn {
+ padding: 0 16px;
+ height: 28px;
+ line-height: 26px;
+ background: #f4f4f4;
+ color: #444;
+ border: 1px solid #ddd;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.hz-u-btn:hover,
+.hz-u-btn:focus {
+ background: #e5e5e5;
+ border: 1px solid #adadad;
+}
+
+.hz-u-btn:active {
+ background: #e5e5e5;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.hz-u-btn:disabled {
+ background: #fff;
+ border: 1px solid #ccc;
+ filter: alpha(opacity=65);
+ opacity: 0.65;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+/* 按钮类型 */
+.hz-u-btn-primary {
+ background: #67739b;
+ color: #fff;
+ border: 1px solid #67739b;
+}
+
+.hz-u-btn-primary:hover,
+.hz-u-btn-primary:focus {
+ background: #31384b;
+ color: #fff;
+ border: 1px solid #31384b;
+}
+
+.hz-u-btn-primary:active {
+ background: #367fa9;
+ color: #fff;
+ border: 1px solid #367fa9;
+}
+
+.hz-u-btn-primary:disabled {
+ background: #444;
+ color: #fff;
+ border: 1px solid #444;
+}
+
+/* input */
+.hz-u-input {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 0;
+ border: 0;
+ padding: 0;
+ border-radius: 0;
+ font: inherit;
+ color: inherit;
+ vertical-align: middle;
+}
+
+.hz-u-input {
+ position: relative;
+ z-index: 0;
+ padding: 5px 6px;
+ border: 1px solid #d2d6de;
+ color: #555;
+ background: #fff;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.hz-u-input::-webkit-input-placeholder {
+ color: #bbb;
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
+.hz-u-input::-moz-placeholder {
+ color: #bbb;
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
+.hz-u-input:-moz-placeholder {
+ color: #bbb;
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
+.hz-u-input:-ms-placeholder {
+ color: #bbb;
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
+.hz-u-input:focus {
+ outline: 0;
+ background: #fff;
+ color: #555;
+ border: 1px solid #3c8dbc;
+}
+
+.hz-u-input:disabled {
+ cursor: not-allowed;
+ background: #eee;
+ color: #999;
+ border: 1px solid #d2d6de;
+}
+
+.hz-u-input {
+ width: 280px;
+ height: 34px;
+}
+
+.hz-u-input.hz-u-input-success {
+ color: #00a65a;
+ border-color: #00a65a;
+}
+
+.hz-u-input.hz-u-input-warning {
+ color: #f39c12;
+ border-color: #f39c12;
+}
+
+.hz-u-input.hz-u-input-error {
+ color: #dd4b39;
+ border-color: #dd4b39;
+}
+
+.hz-u-input.hz-u-input-blank {
+ border-color: transparent;
+ border-style: dashed;
+ background: none;
+}
+
+.hz-u-input.hz-u-input-blank:focus {
+ border-color: #ddd;
+}
+
+/* formItem */
+.hz-u-formitem {
+ display: inline-block;
+ *zoom: 1;
+ margin-bottom: 1em;
+}
+
+.hz-u-formitem:before,
+.hz-u-formitem:after {
+ display: table;
+ content: "";
+ line-height: 0;
+}
+
+.hz-u-formitem:after {
+ clear: both;
+}
+
+.hz-u-formitem .hz-formitem_tt {
+ display: block;
+ float: left;
+ text-align: right;
+}
+
+.hz-u-formitem .hz-formitem_ct {
+ display: block;
+}
+
+.hz-u-formitem .hz-formitem_rqr {
+ line-height: 28px;
+ color: #dd4b39;
+}
+
+.hz-u-formitem .hz-formitem_tt {
+ line-height: 34px;
+ width: 100px;
+}
+
+.hz-u-formitem .hz-formitem_ct {
+ line-height: 34px;
+ margin-left: 108px;
+}
+
+/* icon */
+.hz-u-icon {
+ display: inline-block;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* label */
+.hz-u-label {
+ display: inline-block;
+ cursor: pointer;
+}
+
+/* margin */
+.hz-f-ml0 {
+ margin-bottom: 0;
+}
+
+/* replicator */
+.hz-u-copy input[data-for-copy] {
+ transform: translateZ(0);
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ width: 1px;
+ height: 1px;
+ opacity: 0;
+ overflow: hidden;
+ z-index: -999;
+ color: transparent;
+ background-color: transparent;
+ border: none;
+ outline: none;
+}
+
+@font-face {
+ font-family: 'iconfont';
+ /* project id 525460 */
+ src: url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.eot');
+ src: url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.woff') format('woff'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.ttf') format('truetype'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.svg#iconfont') format('svg');
+}
+
+.hz-icon {
+ font-family: "iconfont" !important;
+ font-size: 20px;
+ font-style: normal;
+ text-align: center;
+ user-select: none;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.hz-icon-edit {
+ position: absolute;
+ top: -4px;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.hz-flex-img {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.hz-flex-img img {
+ width: 100%;
+ height: 100%;
+}
+
+.hz-icon-trash:before {
+ content: "\e605";
+}
+
+.hz-edit-img {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+
+}
+
+.hz-edit-img img {
+ max-width: 300px;
+ max-height: 200px;
+ margin-bottom: 10px;
+}
+
+.hz-edit-del {
+ width: 100%;
+ display: flex;
+ justify-content: flex-end;
+}
\ No newline at end of file
diff --git a/seller/src/views/shop/hotzone/components/Hotzone.vue b/seller/src/views/shop/hotzone/components/Hotzone.vue
new file mode 100644
index 00000000..6df13e4c
--- /dev/null
+++ b/seller/src/views/shop/hotzone/components/Hotzone.vue
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ index + 1 }}
+
+
+ {{ showZoneText(zone) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seller/src/views/shop/hotzone/components/Zone.vue b/seller/src/views/shop/hotzone/components/Zone.vue
new file mode 100644
index 00000000..d9d2df9e
--- /dev/null
+++ b/seller/src/views/shop/hotzone/components/Zone.vue
@@ -0,0 +1,244 @@
+
+
+
+ - {{ index + 1 }}
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seller/src/views/shop/hotzone/directives/addItem.js b/seller/src/views/shop/hotzone/directives/addItem.js
new file mode 100644
index 00000000..9832b00f
--- /dev/null
+++ b/seller/src/views/shop/hotzone/directives/addItem.js
@@ -0,0 +1,101 @@
+import _ from '../utils'
+
+export default {
+ bind: function (el, binding, vnode) {
+ const MIN_LIMIT = _.MIN_LIMIT
+
+ el.addEventListener('mousedown', handleMouseDown, { passive: false })
+
+ function handleMouseDown(e) {
+ // console.log('additem', e)
+ e && e.preventDefault()
+
+ let itemInfo = {
+ top: _.getDistanceY(e, el),
+ left: _.getDistanceX(e, el),
+ width: 0,
+ height: 0
+ }
+ let container = _.getOffset(el)
+
+ // Only used once at the beginning of init
+ let setting = {
+ topPer: _.decimalPoint(itemInfo.top / container.height),
+ leftPer: _.decimalPoint(itemInfo.left / container.width),
+ widthPer: 0,
+ heightPer: 0
+ }
+ let preX = _.getPageX(e)
+ let preY = _.getPageY(e)
+
+ vnode.context.addItem(setting)// 这里去添加并发送了add通知,不应该发送通知
+
+ window.addEventListener('mousemove', handleChange, { passive: false })
+ window.addEventListener('mouseup', handleMouseUp, { passive: false })
+
+ function handleChange(e) {
+ e && e.preventDefault()
+
+ let moveX = _.getPageX(e) - preX
+ let moveY = _.getPageY(e) - preY
+ preX = _.getPageX(e)
+ preY = _.getPageY(e)
+
+ // Not consider the direction of movement first, consider only the lower right drag point
+ let minLimit = 0
+ // 添加热区时,判定鼠标释放时,满足(热区大于48*48时)条件时生效
+ let styleInfo = _.dealBR(itemInfo, moveX, moveY, minLimit)
+
+ // Boundary value processing 改变热区大小时边界条件的处理
+ itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container, vnode.context.zones)
+
+ Object.assign(el.lastElementChild.style, {
+ top: `${itemInfo.top}px`,
+ left: `${itemInfo.left}px`,
+ width: `${itemInfo.width}px`,
+ height: `${itemInfo.height}px`
+ })
+ }
+
+ function handleMouseUp() {
+ let perInfo = {
+ topPer: _.decimalPoint(itemInfo.top / container.height),
+ leftPer: _.decimalPoint(itemInfo.left / container.width),
+ widthPer: _.decimalPoint(itemInfo.width / container.width),
+ heightPer: _.decimalPoint(itemInfo.height / container.height),
+ img: "",
+ link: "",
+ type: "",
+ title: ""
+ }
+
+ if (vnode.context.isOverRange()) {
+ vnode.context.overRange() // 判断超出个数限制,给overRange钩子抛回调
+ } else if (container.height < MIN_LIMIT && itemInfo.width > MIN_LIMIT) {
+ vnode.context.changeItem(Object.assign(perInfo, {
+ topPer: 0,
+ heightPer: 1
+ }), true)
+ } else if (container.width < MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
+ vnode.context.changeItem(Object.assign(perInfo, {
+ leftper: 0,
+ widthPer: 1
+ }), true)
+ } else if (itemInfo.width > MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
+ vnode.context.changeItem(perInfo, true)
+ } else {
+ // 当添加区域超出范围或小于最小区域(48*48)时触发,删除当亲绘制的热区并发送erase事件通知
+ vnode.context.eraseItem()
+ }
+
+ window.removeEventListener('mousemove', handleChange)
+ window.removeEventListener('mouseup', handleMouseUp)
+ }
+ }
+
+ el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
+ },
+ unbind: function (el) {
+ el.$destroy()
+ }
+}
diff --git a/seller/src/views/shop/hotzone/directives/changeSize.js b/seller/src/views/shop/hotzone/directives/changeSize.js
new file mode 100644
index 00000000..a98c139c
--- /dev/null
+++ b/seller/src/views/shop/hotzone/directives/changeSize.js
@@ -0,0 +1,91 @@
+import _ from '../utils'
+
+export default {
+ bind: function (el, binding, vnode) {
+ el.addEventListener('mousedown', handleMouseDown,{ passive: false })
+
+ function handleMouseDown (e) {
+ let pointer = e.target.dataset.pointer //元素上绑定的方法名
+
+ if (!pointer) {
+ return
+ }
+
+ e && e.stopPropagation()
+
+ let zone = el.parentNode
+ let setting = vnode.context.setting
+ let currentIndex = vnode.context.index
+ let container = _.getOffset(zone.parentNode)
+ let itemInfo = {
+ width: _.getOffset(zone).width || 0,
+ height: _.getOffset(zone).height || 0,
+ top: setting.topPer * container.height || 0,
+ left: setting.leftPer * container.width || 0
+ }
+ let preX = _.getPageX(e)
+ let preY = _.getPageY(e)
+ let flag
+
+ // Hide the info displayed by hover
+ vnode.context.handlehideZone(true)
+
+ window.addEventListener('mousemove', handleChange,{ passive: false })
+ window.addEventListener('mouseup', handleMouseUp,{ passive: false })
+
+ function handleChange (e) {
+ e && e.preventDefault()
+ flag = true
+
+ let moveX = _.getPageX(e) - preX
+ let moveY = _.getPageY(e) - preY
+
+ preX = _.getPageX(e)
+ preY = _.getPageY(e)
+
+ // Handling the situation when different dragging points are selected
+ let styleInfo = _[pointer](itemInfo, moveX, moveY)//调用对应的方法
+ // Boundary value processing
+ itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container, vnode.context.$parent.zones, currentIndex)
+
+ Object.assign(zone.style, {
+ top: `${itemInfo.top}px`,
+ left: `${itemInfo.left}px`,
+ width: `${itemInfo.width}px`,
+ height: `${itemInfo.height}px`
+ })
+ }
+
+ function handleMouseUp () {
+ if (flag) {
+ flag = false
+ let perInfo = {
+ topPer: _.decimalPoint(itemInfo.top / container.height),
+ leftPer: _.decimalPoint(itemInfo.left / container.width),
+ widthPer: _.decimalPoint(itemInfo.width / container.width),
+ heightPer: _.decimalPoint(itemInfo.height / container.height)
+ }
+ vnode.context.changeInfo(perInfo)
+
+ // 兼容数据无变更情况下导致 computed 不更新,数据仍为 px 时 resize 出现的问题
+ Object.assign(zone.style, {
+ top: `${itemInfo.top}px`,
+ left: `${itemInfo.left}px`,
+ width: `${itemInfo.width}px`,
+ height: `${itemInfo.height}px`
+ })
+ }
+ // Show the info
+ vnode.context.handlehideZone(false)
+
+ window.removeEventListener('mousemove', handleChange)
+ window.removeEventListener('mouseup', handleMouseUp)
+ }
+ }
+
+ el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
+ },
+ unbind: function (el) {
+ el.$destroy()
+ }
+}
diff --git a/seller/src/views/shop/hotzone/directives/dragItem.js b/seller/src/views/shop/hotzone/directives/dragItem.js
new file mode 100644
index 00000000..5557d7a5
--- /dev/null
+++ b/seller/src/views/shop/hotzone/directives/dragItem.js
@@ -0,0 +1,108 @@
+import _ from '../utils'
+
+export default {
+ bind: function (el, binding, vnode) {
+ el.addEventListener('mousedown', handleMouseDown)
+ let collision
+ function handleMouseDown (e) {
+ e && e.stopPropagation()
+ let container = _.getOffset(el.parentNode)
+ let preX = _.getPageX(e)
+ let preY = _.getPageY(e)
+ let topPer
+ let leftPer
+ let flag
+
+ window.addEventListener('mousemove', handleChange,{ passive: false })
+ window.addEventListener('mouseup', handleMouseUp,{ passive: false })
+
+ function handleChange (e) {
+ e && e.preventDefault()
+ flag = true
+ collision = false
+ // Hide the info displayed by hover
+ vnode.context.handlehideZone(true)
+
+ let setting = vnode.context.setting
+ let currentIndex = vnode.context.index
+ let moveX = _.getPageX(e) - preX
+ let moveY = _.getPageY(e) - preY
+
+ setting.topPer = setting.topPer || 0
+ setting.leftPer = setting.leftPer || 0
+ topPer = _.decimalPoint(moveY / container.height + setting.topPer)
+ leftPer = _.decimalPoint(moveX / container.width + setting.leftPer)
+
+ // Hotzone moving boundary processing
+ if (topPer < 0) {
+ topPer = 0
+ moveY = -container.height * setting.topPer
+ }
+
+ if (leftPer < 0) {
+ leftPer = 0
+ moveX = -container.width * setting.leftPer
+ }
+
+ if (topPer + setting.heightPer > 1) {
+ topPer = 1 - setting.heightPer
+ moveY = container.height * (topPer - setting.topPer)
+ }
+
+ if (leftPer + setting.widthPer > 1) {
+ leftPer = 1 - setting.widthPer
+ moveX = container.width * (leftPer - setting.leftPer)
+ }
+ // 拖拽碰撞检测
+ if (vnode.context.$parent.zones.length > 1) {
+ let currentzones = JSON.parse(JSON.stringify(vnode.context.$parent.zones)).map((zone) => {
+ return {
+ left: (zone.leftPer || 0) * container.width,
+ top: (zone.topPer || 0) * container.height,
+ width: (zone.widthPer || 0) * container.width,
+ height: (zone.heightPer || 0) * container.height
+ }
+ })
+ // 矫正
+ let changeSetting = {}
+ changeSetting.left = setting.leftPer * container.width + moveX
+ changeSetting.top = setting.topPer * container.height + moveY
+ changeSetting.width = setting.widthPer * container.width
+ changeSetting.height = setting.heightPer * container.height
+ // 碰撞检测
+ for (let i = 0, len = currentzones.length; i < len; i++) {
+ if (currentIndex !== i && _.handleEgdeCollisions(currentzones[i], changeSetting)) {
+ collision = true
+ break
+ }
+ }
+ }
+ el.style.transform = `translate(${moveX}px, ${moveY}px)`
+ }
+
+ function handleMouseUp () {
+ if (flag) {
+ flag = false
+ el.style.transform = 'translate(0, 0)'
+ if (!collision) {
+ vnode.context.changeInfo({
+ topPer,
+ leftPer
+ })
+ }
+ }
+
+ // Show the info
+ vnode.context.handlehideZone(false)
+
+ window.removeEventListener('mousemove', handleChange)
+ window.removeEventListener('mouseup', handleMouseUp)
+ }
+ }
+
+ el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
+ },
+ unbind: function (el) {
+ el.$destroy()
+ }
+}
diff --git a/seller/src/views/shop/hotzone/index.js b/seller/src/views/shop/hotzone/index.js
new file mode 100644
index 00000000..c818a357
--- /dev/null
+++ b/seller/src/views/shop/hotzone/index.js
@@ -0,0 +1,7 @@
+import hotzone from './index.vue'
+
+hotzone.install = (Vue) => {
+ Vue.component(hotzone.name, hotzone)
+}
+
+export default hotzone
diff --git a/seller/src/views/shop/hotzone/index.vue b/seller/src/views/shop/hotzone/index.vue
new file mode 100644
index 00000000..90d75467
--- /dev/null
+++ b/seller/src/views/shop/hotzone/index.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
diff --git a/seller/src/views/shop/hotzone/utils/index.js b/seller/src/views/shop/hotzone/utils/index.js
new file mode 100644
index 00000000..91cd936d
--- /dev/null
+++ b/seller/src/views/shop/hotzone/utils/index.js
@@ -0,0 +1,274 @@
+let _ = {
+ MIN_LIMIT: 48, // Min size of zone
+ DECIMAL_PLACES: 4 // Hotzone positioning decimal point limit number of digits
+}
+
+/**
+ * Get a power result of 10 for the power of the constant
+ * @return {Number}
+ */
+_.getMultiple = (decimalPlaces = _.DECIMAL_PLACES) => {
+ return Math.pow(10, decimalPlaces)
+}
+
+/**
+ * Limit decimal places
+ * @param {Number} num
+ * @return {Number}
+ */
+_.decimalPoint = (val = 0) => { // 处理js小数点计算不精确问题,先放再缩小
+ return Math.round(val * _.getMultiple()) / _.getMultiple() || 0
+}
+
+/**
+ * Get element width and height
+ * @param {Object} elem
+ * @return {Object}
+ */
+_.getOffset = (elem = {}) => ({
+ width: elem.clientWidth || 0,
+ height: elem.clientHeight || 0
+})
+
+/**
+ * Get pageX
+ * @param {Object} e
+ * @return {Number}
+ */
+_.getPageX = (e) => ('pageX' in e) ? e.pageX : e.touches[0].pageX
+
+/**
+ * Get pageY
+ * @param {Object} e
+ * @return {Number}
+ */
+_.getPageY = (e) => ('pageY' in e) ? e.pageY : e.touches[0].pageY
+
+/**
+ * Gets the abscissa value of the mouse click relative to the target node
+ * @param {Object} e
+ * @param {Object} container
+ * @return {Number}
+ */
+_.getDistanceX = (e, container) =>
+ _.getPageX(e) - (container.getBoundingClientRect().left + window.pageXOffset)
+
+/**
+ * Gets the ordinate value of the mouse click relative to the target node
+ * @param {Object} e
+ * @param {Object} container
+ * @return {Number}
+ */
+_.getDistanceY = (e, container) =>
+ _.getPageY(e) - (container.getBoundingClientRect().top + window.pageYOffset)
+
+// 检测区域是否有碰撞 true 有碰撞交集 ,false 无碰撞
+_.handleEgdeCollisions = (rect1, rect2) => {
+ const l1 = { left: rect1.left, top: rect1.top }
+ const r1 = { left: rect1.left + rect1.width, top: rect1.top + rect1.height }
+ const l2 = { left: rect2.left, top: rect2.top }
+ const r2 = { left: rect2.left + rect2.width, top: rect2.top + rect2.height }
+ return !(
+ l1.left > r2.left ||
+ l2.left > r1.left ||
+ l1.top > r2.top ||
+ l2.top > r1.top
+ )
+}
+/**
+ * Treatment of boundary conditions when changing the size of the hotzone 改变热区大小时边界条件的处理(如果要避免热区重叠,代码要加载这里)
+ * @param {Object} itemInfo
+ * @param {Object} styleInfo
+ * @param {Object} container
+ */
+_.dealEdgeValue = (itemInfo, styleInfo, container, zones, currentIndex = zones.length - 1) => {
+
+ if (Object.prototype.hasOwnProperty.call(styleInfo, "left") && styleInfo.left < 0) {
+ styleInfo.left = 0
+ styleInfo.width = itemInfo.width + itemInfo.left
+ }
+
+ if (Object.prototype.hasOwnProperty.call(styleInfo, "top") && styleInfo.top < 0) {
+ styleInfo.top = 0
+ styleInfo.height = itemInfo.height + itemInfo.top
+ }
+
+ if (!Object.prototype.hasOwnProperty.call(styleInfo, "left") && Object.prototype.hasOwnProperty.call(styleInfo, "width")) {
+ if (itemInfo.left + styleInfo.width > container.width) {
+ styleInfo.width = container.width - itemInfo.left
+ }
+ }
+
+ if (!Object.prototype.hasOwnProperty.call(styleInfo, "top") && Object.prototype.hasOwnProperty.call(styleInfo, "height")) {
+ if (itemInfo.top + styleInfo.height > container.height) {
+ styleInfo.height = container.height - itemInfo.top
+ }
+ }
+ // 与其他热区重叠,则修正 检测是否发生碰撞
+ if (zones.length > 1) {
+ let currentzones = JSON.parse(JSON.stringify(zones)).map((zone) => {
+ return {
+ left: (zone.leftPer || 0) * container.width,
+ top: (zone.topPer || 0) * container.height,
+ width: (zone.widthPer || 0) * container.width,
+ height: (zone.heightPer || 0) * container.height
+ }
+ })
+ let current = { ...itemInfo, ...styleInfo }
+ for (let i = 0, len = currentzones.length; i < len; i++) {
+ if (currentIndex !== i && _.handleEgdeCollisions(currentzones[i], current)) {
+ return itemInfo
+ }
+ }
+ }
+
+ return Object.assign(itemInfo, styleInfo)
+}
+
+/**
+ * Handle different drag points, capital letters mean: T-top,L-left,C-center,R-right,B-bottom
+ * @param {Object} itemInfo
+ * @param {Number} moveX
+ * @param {Number} moveY
+ * @return {Object}
+ */
+_.dealTL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width - moveX
+ let height = itemInfo.height - moveY
+
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ Object.assign(styleInfo, {
+ width,
+ left: itemInfo.left + moveX
+ })
+ }
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ Object.assign(styleInfo, {
+ height,
+ top: itemInfo.top + moveY
+ })
+ }
+
+ return styleInfo
+}
+
+_.dealTC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let height = itemInfo.height - moveY
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ styleInfo = {
+ height,
+ top: itemInfo.top + moveY
+ }
+ }
+
+ return styleInfo
+}
+
+_.dealTR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width + moveX
+ let height = itemInfo.height - moveY
+
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ Object.assign(styleInfo, {
+ width
+ })
+ }
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ Object.assign(styleInfo, {
+ height,
+ top: itemInfo.top + moveY
+ })
+ }
+
+ return styleInfo
+}
+
+_.dealCL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width - moveX
+
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ Object.assign(styleInfo, {
+ width,
+ left: itemInfo.left + moveX
+ })
+ }
+
+ return styleInfo
+}
+
+_.dealCR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width + moveX
+
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ Object.assign(styleInfo, {
+ width
+ })
+ }
+
+ return styleInfo
+}
+
+_.dealBL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width - moveX
+ let height = itemInfo.height + moveY
+
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ Object.assign(styleInfo, {
+ width,
+ left: itemInfo.left + moveX
+ })
+ }
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ Object.assign(styleInfo, {
+ height
+ })
+ }
+
+ return styleInfo
+}
+
+_.dealBC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let height = itemInfo.height + moveY
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ Object.assign(styleInfo, {
+ height
+ })
+ }
+
+ return styleInfo
+}
+// 添加热区时,判定鼠标释放点满足一下条件时生效
+_.dealBR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
+ let styleInfo = {}
+ let width = itemInfo.width + moveX
+ let height = itemInfo.height + moveY
+ if (width >= Math.min(minLimit, itemInfo.width)) {
+ // 改变后的宽度 >= min(之前宽度,内置的最小宽度标准),即生效
+ Object.assign(styleInfo, {
+ width
+ })
+ }
+
+ if (height >= Math.min(minLimit, itemInfo.height)) {
+ // 改变后的高度 大于等于 Min(最小高度,之前高度)时,生效
+ Object.assign(styleInfo, {
+ height
+ })
+ }
+
+ return styleInfo
+}
+
+export default _
diff --git a/seller/src/views/shop/wap/config.js b/seller/src/views/shop/wap/config.js
index 063b5ec8..740d946f 100644
--- a/seller/src/views/shop/wap/config.js
+++ b/seller/src/views/shop/wap/config.js
@@ -17,7 +17,26 @@ export let homeData = {};
* notImg: true 没有选择图片功能
* close:true 右侧关闭按钮
*/
+
export const modelData = [
+ {
+ type: "flexOne",
+ name: "图片",
+ notAdd: true,
+ onlyImg: true,
+ img: "md-image",
+ options: {
+ list: [
+ {
+ img: "https://i.loli.net/2020/12/05/8wSNWbnqujDh6HL.png",
+ url: "",
+ link: "",
+ size: "750*280",
+ model: "link"
+ }
+ ]
+ }
+ },
{
type: "carousel",
name: "图片轮播",
diff --git a/seller/src/views/shop/wap/decorate.vue b/seller/src/views/shop/wap/decorate.vue
index 3f92af66..497aa6e9 100644
--- a/seller/src/views/shop/wap/decorate.vue
+++ b/seller/src/views/shop/wap/decorate.vue
@@ -118,44 +118,89 @@
-
+
+
+
选择模式
+
+
+
+ 链接
+ 热区
+
+
+
+
选择链接
-
+
已选链接:
- {{
+
+
+ {{ item.url.name }}
- {{ item.url.goodsName }}
+
+ {{ item.url.goodsName }}
- {{ item.url.name }}
+
+ {{ item.url.name }}
- {{ item.url.memberName }}
+
+ {{ item.url.memberName }}
- {{ item.url.title }}
+
+ {{ item.url.title }}
+
+
+ {{ item.url.name }}
+
秒杀
- 满减
+
+ 满减
+
拼团
{{ item.url.title || item.url.goodsName }}
- {{ item.url.title }}
+
+ {{ item.url.title }}
-
+ {{ item.model === "hotzone" ? "绘制热区" : "选择链接" }}
+
@@ -174,7 +219,7 @@
@selectedLink="selectedLink"
@selectedGoodsData="selectedGoodsData"
>
-
+
@@ -182,11 +227,13 @@