From 4ace1a9ccac88bc88b1237137126e41df9606e50 Mon Sep 17 00:00:00 2001 From: mahe Date: Sat, 6 May 2023 15:57:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BA=97=E9=93=BA=E6=A5=BC?= =?UTF-8?q?=E5=B1=82=E8=A3=85=E4=BF=AE=E5=9B=BE=E7=89=87=E7=83=AD=E5=8C=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hotzone/assets/styles/icons8-edit-64.png | Bin 0 -> 1179 bytes .../views/shop/hotzone/assets/styles/main.css | 657 ++++++++++++++++++ .../views/shop/hotzone/components/Hotzone.vue | 293 ++++++++ .../views/shop/hotzone/components/Zone.vue | 244 +++++++ .../views/shop/hotzone/directives/addItem.js | 101 +++ .../shop/hotzone/directives/changeSize.js | 91 +++ .../views/shop/hotzone/directives/dragItem.js | 108 +++ seller/src/views/shop/hotzone/index.js | 7 + seller/src/views/shop/hotzone/index.vue | 69 ++ seller/src/views/shop/hotzone/utils/index.js | 274 ++++++++ seller/src/views/shop/wap/config.js | 19 + seller/src/views/shop/wap/decorate.vue | 91 ++- 12 files changed, 1939 insertions(+), 15 deletions(-) create mode 100644 seller/src/views/shop/hotzone/assets/styles/icons8-edit-64.png create mode 100644 seller/src/views/shop/hotzone/assets/styles/main.css create mode 100644 seller/src/views/shop/hotzone/components/Hotzone.vue create mode 100644 seller/src/views/shop/hotzone/components/Zone.vue create mode 100644 seller/src/views/shop/hotzone/directives/addItem.js create mode 100644 seller/src/views/shop/hotzone/directives/changeSize.js create mode 100644 seller/src/views/shop/hotzone/directives/dragItem.js create mode 100644 seller/src/views/shop/hotzone/index.js create mode 100644 seller/src/views/shop/hotzone/index.vue create mode 100644 seller/src/views/shop/hotzone/utils/index.js 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 0000000000000000000000000000000000000000..72d5008830b50b5a8079b5a679e337fe22c92991 GIT binary patch literal 1179 zcmV;M1Z4Y(P);!ZRW6AT(b3>r705rbfih-NZTGkIy$_(&X`{4VNFsJUI$eXHu$ zqjCOKSJye`p6~D5RkymUU_t+HS=$Z7lB8>aM}QlF6~Gx_Kd=+nW@dl4*j{~-v|Q45 zNoSR>DM@#?-D`A78k02H2RyB_lAdh4=V+2NF6nR}eA31y~PU3Y=Fz z5+{L|%xr2oo8N@FlPu*Z{2YVh#agW;T;oe66I<3pyK1KS~-I1h4E* zeo~1s>DUoTTQXud5sbT0=2Fr{N*`0BkDa)yp|QXRMPjgq7Bw@Nf*U}Pvt)A zTB|C@|{f{sepsG;t378F|X>|_#0mZqVAJ56TPSQR}zew6twC}khF$~z61MmObo|j)3-sOGj#jh>edu0P8 zEt7Pn0{mzhc$a(9%Xu)g??s`30lE*kAl(;mkD2{2DAAd;8@R&TH4RLd**67y3BK_o zrU4Io+n$el{Njh zr8kr;f*v55DL2u3ly2mJm!%j6WKRD8=s7h$?bE7cfTWA*o&HGL16&_aqU?D-nySu# ztm%J4K;8`SHt=-FU14>qssaAur=MnlPe|4WubbJ4iepDnX#RKbECPO~_lBd+L#I{R05hAF^f>Sea3QdZA%RY-(lNly-jcKfSZrqhMCz$g zRbB^}nSh~$hq#x+L#YJ=+HRn2Ef|n`J?riBm$;pjTacaaz5Q=!e%gSe-oAUwHr22u z0RJJ2BcaLZ)8KC4=G69`lHLQ}p_$v|%V-Vog!kTWz}}45Or5(%m$VZ27FZgkwf6L! znQaaYKKB(cvuWU|T3TvOUjWY+NtkyQFte9|jlf?Kn`%nCNPbrPvq)0jV{?}Qj{&!m tJf_mbV{>J63fKp1F|(a*H?yE&)PLCE`=1R%7UTc`002ovPDHLkV1hW0F311? literal 0 HcmV?d00001 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 @@ + + + + + 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 @@ + + + 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 @@ - + +
+
选择模式
+ +
+ + 链接 + 热区 + +
+
+
选择链接
- +
@@ -174,7 +219,7 @@ @selectedLink="selectedLink" @selectedGoodsData="selectedGoodsData" > - + @@ -182,11 +227,13 @@