com-hotspot.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /**
  3. * @link:http://www.gdqijianshi.com/
  4. * @copyright: Copyright (c) 2020 广东七件事集团
  5. * Created by PhpStorm
  6. * Author: ganxiaohao
  7. * Date: 2020-04-23
  8. * Time: 16:17
  9. */
  10. $static = Yii::$app->request->hostInfo . Yii::$app->request->baseUrl;
  11. ?>
  12. <style>
  13. .com-hotspot .shadow {
  14. position: absolute;
  15. left: 0;
  16. top: 0;
  17. width: 100%;
  18. height: 100%;
  19. background-color: rgba(0, 0, 0, 0);
  20. border: 1px solid #2E9FFF;
  21. }
  22. .com-hotspot .hotspot {
  23. position: absolute;
  24. left: 0;
  25. top: 0;
  26. border: 1px dashed #5CB3FD;
  27. z-index: 100;
  28. }
  29. .com-hotspot .hotspot .close {
  30. position: absolute;
  31. right: -16px;
  32. top: -16px;
  33. z-index: 101
  34. }
  35. .com-hotspot .el-input {
  36. width: 70px;
  37. }
  38. .com-hotspot .right label {
  39. padding: 0 10px;
  40. }
  41. .com-hotspot .inside {
  42. position: relative;
  43. width: 750px;
  44. min-height: 1334px;
  45. background-color: #eee;
  46. zoom: 0.5;
  47. }
  48. .com-hotspot .pic {
  49. width: auto;
  50. height: auto;
  51. max-width: 750px;
  52. max-height: 1334px;
  53. transform: scale(0.5);
  54. }
  55. .com-hotspot .pic-list {
  56. position: relative;
  57. }
  58. .com-hotspot .pic-list div {
  59. position: absolute;
  60. left: 0;
  61. top: 0;
  62. }
  63. .com-hotspot {
  64. -moz-user-select: none;
  65. -khtml-user-select: none;
  66. user-select: none;
  67. }
  68. .com-hotspot .el-input-group__append {
  69. background-color: #fff;
  70. }
  71. </style>
  72. <template id="com-hotspot">
  73. <div class="com-hotspot">
  74. <div @click="dialogVisible = !dialogVisible" style="display: inline-block">
  75. <slot></slot>
  76. </div>
  77. <el-dialog append-to-body title="热区划分" :visible.sync="dialogVisible" :close-on-click-modal="false"
  78. class="com-hotspot" width="900px">
  79. <div flex="dir:left box:first">
  80. <div class="inside" flex="dir:left main:center cross:center" ref="inside">
  81. <div style="position: relative;" ref="box">
  82. <div class="shadow" :style="{zIndex: zIndex}"
  83. @click="click" @mousedown="mousedown" @mouseup="mouseup" @mousemove="mousemove"
  84. @mouseout="mouseout "></div>
  85. <div class="hotspot"
  86. :style="{width:item.width+'px',height:item.height+'px',left:item.left+'px',
  87. top:item.top+'px', maxWidth:'calc('+width+' - '+item.left+'px)',
  88. maxHeight:'calc('+height+' - '+item.top+'px)', backgroundColor: index == key ? 'rgba(92, 179, 253, 0.2)' : ''}"
  89. @click="select(key)" v-for="(item, key) in hotspotList"
  90. :data-index="key">
  91. <div class="close" @click.stop="del(key)" v-if="item.is_close">
  92. <img style="width: 32px;height: 32px"
  93. src="<?= $static ?>/resources/img/mall/icon-close.png">
  94. </div>
  95. </div>
  96. <div class="pic-list" :style="style" v-if="picList && picList.length > 0">
  97. <template v-for="(item, index) in picList">
  98. <div :style="item">
  99. <img :src="item.pic_url" style="width: 100%;visibility: hidden;display: block;">
  100. </div>
  101. <img :src="item.pic_url" style="width: 100%;visibility: hidden;display: block;">
  102. </template>
  103. </div>
  104. <div :style="style" flex="dir:left main:center cross:center" v-else>
  105. <img style="width: auto;height: auto;max-width: 100%;max-height: 100%" :src="picUrl">
  106. </div>
  107. </div>
  108. </div>
  109. <el-form label-width="120px">
  110. <div class="right" v-if="index > -1">
  111. <el-form-item label="热区尺寸">
  112. <el-row type="flex">
  113. <el-col :span="12">
  114. <div flex="dir:left">
  115. <label>W</label>
  116. <el-input size="small" type="number" v-model.number="hotspotList[index].width" :max="maxWidth">
  117. </el-input>
  118. <label>px</label>
  119. </div>
  120. </el-col>
  121. <el-col :span="12">
  122. <div flex="dir:left">
  123. <label>H</label>
  124. <el-input size="small" type="number" v-model.number="hotspotList[index].height" :max="maxHeight">
  125. </el-input>
  126. <label>px</label>
  127. </div>
  128. </el-col>
  129. </el-row>
  130. </el-form-item>
  131. <el-form-item label="热区位置">
  132. <el-row type="flex">
  133. <el-col :span="12">
  134. <div flex="dir:left">
  135. <label>X</label>
  136. <el-input size="small" type="number" v-model.number="hotspotList[index].left">
  137. </el-input>
  138. <label>px</label>
  139. </div>
  140. </el-col>
  141. <el-col :span="12">
  142. <div flex="dir:left">
  143. <label>Y</label>
  144. <el-input size="small" type="number" v-model.number="hotspotList[index].top">
  145. </el-input>
  146. <label>px</label>
  147. </div>
  148. </el-col>
  149. </el-row>
  150. </el-form-item>
  151. <el-form-item label="热区链接" v-if="isLink">
  152. <com-pick-link style="margin-left: 10px" title="选择链接" @selected="selectLink">
  153. <el-input size="small" style="width: 100%;" :disabled="true" v-model="hotspotList[index].link.name">
  154. <template slot="append">
  155. <el-button>选择链接</el-button>
  156. </template>
  157. </el-input>
  158. </com-pick-link>
  159. </el-form-item>
  160. <el-form-item label="热区属性" v-if="mode == 'auth'">
  161. <el-radio-group v-model="hotspotList[index].open_type">
  162. <el-radio label="login" :disabled="radioDisabled('login')">登录按钮</el-radio>
  163. <el-radio label="cancel" :disabled="radioDisabled('cancel')">不登录按钮</el-radio>
  164. </el-radio-group>
  165. </el-form-item>
  166. </div>
  167. <div v-if="index == -1 && hotspotList.length == 0" style="padding: 20px 40px;">请先在左侧蓝框内用鼠标划出热区范围</div>
  168. <el-form-item v-else label="">
  169. <el-button style="margin-left: 10px" type="primary" size="small" @click="confirm">保存</el-button>
  170. <el-button size="small" @click="clearAll">重置</el-button>
  171. </el-form-item>
  172. </el-form>
  173. </div>
  174. </el-dialog>
  175. </div>
  176. </template>
  177. <script>
  178. Vue.component('com-hotspot', {
  179. template: '#com-hotspot',
  180. props: {
  181. multiple: Boolean,
  182. picUrl: String,
  183. width: {
  184. type: String,
  185. default: '750px'
  186. },
  187. height: {
  188. type: String,
  189. default: '1334px'
  190. },
  191. hotspotArray: {
  192. type: Array,
  193. default: []
  194. },
  195. isLink: Boolean,
  196. picList: Array,
  197. max: Number,
  198. mode: String
  199. },
  200. data() {
  201. return {
  202. dialogVisible: false,
  203. is_mousedown: false,
  204. is_mousemove: false,
  205. is_close: false,
  206. hotspot: {
  207. height: 0,
  208. width: 0,
  209. left: 0,
  210. top: 0,
  211. defaultX: 0,
  212. defaultY: 0,
  213. is_close: false,
  214. link: '',
  215. open_type: ''
  216. },
  217. hotspotList: [],
  218. index: -1,
  219. zIndex: 99,
  220. }
  221. },
  222. watch: {
  223. dialogVisible(oldVal, newVal) {
  224. this.hotspotList = JSON.parse(JSON.stringify(this.hotspotArray))
  225. this.index = -1;
  226. }
  227. },
  228. computed: {
  229. style() {
  230. return {
  231. width: this.width,
  232. height: this.height
  233. };
  234. },
  235. maxWidth() {
  236. return parseFloat(this.width) - this.hotspotList[this.index].left;
  237. },
  238. maxHeight() {
  239. return parseFloat(this.height) - this.hotspotList[this.index].top;
  240. },
  241. },
  242. methods: {
  243. click(e) {
  244. this.is_mousedown = false;
  245. },
  246. position(e) {
  247. function getElementPosition(e) {
  248. var x = 0, y = 0;
  249. while (e != null) {
  250. x += e.offsetLeft;
  251. y += e.offsetTop;
  252. e = e.offsetParent;
  253. }
  254. return {x: x, y: y};
  255. }
  256. // 缩放比例
  257. let zoom = 2;
  258. // 获取图片距离浏览器的x/y(真实的尺寸,不受zoom属性影响)
  259. // let position = getElementPosition(this.$refs.box);
  260. // 获取父容器距离浏览器的x/y(真实的尺寸,不受zoom属性影响)
  261. // let inside = getElementPosition(this.$refs.inside);
  262. // 图片距离缩放容器左边和顶部
  263. // let picX = (position.x - inside.x) / zoom;
  264. // let picY = (position.y - inside.y) / zoom;
  265. // 鼠标点下时距离浏览器的x/y(需要计算zoom属性的影响)
  266. // position.x = e.clientX + (e.offsetX + picX) * (zoom - 1) - position.x;
  267. // position.y = e.clientY + (e.offsetY + picY) * (zoom - 1) - position.y;
  268. // position.x = (e.clientX - inside.x) * zoom - (position.x - inside.x);
  269. // position.y = (e.clientY - inside.y) * zoom - (position.y - inside.y);
  270. let position = {
  271. x: e.offsetX * zoom,
  272. y: e.offsetY * zoom
  273. };
  274. return position;
  275. },
  276. mousedown(e) {
  277. if (!this.multiple) {
  278. if (this.hotspotList.length > 0) {
  279. return;
  280. }
  281. } else {
  282. if (this.max && this.hotspotList.length === this.max) {
  283. return ;
  284. }
  285. }
  286. let position = this.position(e);
  287. this.is_mousedown = true;
  288. let hotspot = JSON.parse(JSON.stringify(this.hotspot));
  289. hotspot.left = position.x;
  290. hotspot.top = position.y;
  291. hotspot.defaultX = position.x;
  292. hotspot.defaultY = position.y;
  293. this.hotspotList.push(hotspot);
  294. this.index = this.hotspotList.length - 1;
  295. this.zIndex = 102;
  296. },
  297. mousemove(e) {
  298. if (this.is_mousedown) {
  299. if (this.index === -1) {
  300. return;
  301. }
  302. let position = this.position(e);
  303. let hotspot = this.hotspotList[this.index];
  304. this.hotspotList[this.index].left = Math.min(position.x, hotspot.defaultX);
  305. this.hotspotList[this.index].top = Math.min(position.y, hotspot.defaultY);
  306. this.hotspotList[this.index].width = Math.abs(hotspot.defaultX - position.x);
  307. this.hotspotList[this.index].height = Math.abs(hotspot.defaultY - position.y);
  308. this.is_mousemove = true;
  309. }
  310. },
  311. mouseout(e) {
  312. if (this.index === -1) {
  313. return;
  314. }
  315. this.is_mousedown = false;
  316. this.hotspotList[this.index].is_close = true;
  317. this.is_mousemove = false;
  318. this.zIndex = 99;
  319. },
  320. mouseup(e) {
  321. if (this.index === -1) {
  322. return;
  323. }
  324. this.is_mousedown = false;
  325. this.hotspotList[this.index].is_close = true;
  326. this.is_mousemove = false;
  327. this.zIndex = 99;
  328. },
  329. del(index) {
  330. this.hotspotList.splice(index, 1);
  331. this.index = -1;
  332. },
  333. select(index) {
  334. if (this.is_mousemove) {
  335. return;
  336. }
  337. this.index = index;
  338. },
  339. clearAll() {
  340. this.index = -1;
  341. this.hotspotList = [];
  342. },
  343. selectLink(e) {
  344. if (this.index > -1 && this.hotspotList.length > 0) {
  345. this.hotspotList[this.index].link = e[0]
  346. }
  347. },
  348. confirm() {
  349. this.dialogVisible = false;
  350. this.index = -1;
  351. this.$emit('confirm', this.hotspotList);
  352. },
  353. radioDisabled(name) {
  354. for (let i in this.hotspotList) {
  355. if (this.hotspotList[i].open_type && this.hotspotList[i].open_type == name && i != this.index) {
  356. return true;
  357. }
  358. }
  359. return false;
  360. }
  361. }
  362. })
  363. </script>