diy-preview.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. <template id="diy-preview">
  2. <div style="flex:1;position:relative">
  3. <el-scrollbar class="dp-el-scrollbar" ref="scrollbar">
  4. <div id="diy-preview-contain" class="dp-contain" ref="diyPreview" :style="mainStyle">
  5. <div id="diy-test">
  6. <div style="position: relative" data-html2canvas-ignore>
  7. <!-- :style="{ 'background': topStatusBar.bg}" -->
  8. <div class="dp-header-placeholder"></div>
  9. </div>
  10. <!-- 头部 -->
  11. <div class="drag-scroll" :data-id="item.id" v-for="(item,index) in headerArr" :key="item.id" @click="onClick(item, index)">
  12. <div :class="computedItem(item,index)">
  13. <components :is="getName(item)" :active-item="item"></components>
  14. <div class="dp-close" v-if="deleteBtn(item)" @click.stop>
  15. <el-popover placement="bottom" v-model="item.visible">
  16. <p class="diy-del-text">确定删除吗?</p>
  17. <div flex="cross:center">
  18. <el-button class="diy-del-btn" size="mini" plain @click.stop="deleteItem">删除</el-button>
  19. <el-button size="mini" plain @click="item.visible = false">取消</el-button>
  20. </div>
  21. <i class="el-icon-close" slot="reference"></i>
  22. </el-popover>
  23. </div>
  24. <div class="dp-hover"></div>
  25. <!-- 前端是否显示 -->
  26. <div class="show" v-if="!item.isShow">
  27. <div>已隐藏</div>
  28. </div>
  29. </div>
  30. </div>
  31. <!-- 拖拽容器 - 文档流元素 -->
  32. <draggable v-model="prevArr" :animation="300" :options="{filter:'.undraggable',preventOnFilter:false}" @start="start" @end="onEnd">
  33. <transition-group>
  34. <!-- 请保证生成key唯一 -->
  35. <!-- v-if 会影响滚动精准 -->
  36. <div class="drag-scroll" :data-id="item.id" v-if="filterArr(item)" v-for="(item,index) in prevArr" :class="[ item.is_drag !== false ? '' : 'undraggable']" :key="item.id" @click="onClick(item, index)">
  37. <div :class="computedItem(item,index)">
  38. <components :is="getName(item)" :active-item="item"></components>
  39. <!-- <div class="dp-close" v-if="deleteBtn(item)" @click.stop>
  40. <el-popover placement="bottom" v-model="item.visible">
  41. <p class="diy-del-text">确定删除吗?</p>
  42. <div flex="cross:center">
  43. <el-button class="diy-del-btn" size="mini" plain @click.stop="deleteItem">删除</el-button>
  44. <el-button size="mini" plain @click="item.visible = false">取消</el-button>
  45. </div>
  46. <i class="el-icon-close" slot="reference"></i>
  47. </el-popover>
  48. </div> -->
  49. <div class="dp-hover"></div>
  50. <!-- 前端是否显示 -->
  51. <div class="show" v-if="!item.isShow">
  52. <div>已隐藏</div>
  53. </div>
  54. </div>
  55. </div>
  56. </transition-group>
  57. </draggable>
  58. <!-- 脱离文档流元素 - 选中效果需要子元素添加.diy-solid-line类名 -->
  59. <div id="float">
  60. <div class="drag-scroll" :data-id="item.id" v-if="filterFloatArr(item)" v-for="(item,index) in prevArr" :key="item.id" @click="onClick(item, index)" v-show="index != 0">
  61. <div :class="computedFloatItem(item,index)">
  62. <components :is="getName(item)" :active-item="item">
  63. <!-- 容器里面必须有position不为默认值的情况 - 才生效 -->
  64. <div class="dp-close" v-if="deleteBtn(item)" @click.stop>
  65. <el-popover placement="bottom" v-model="item.visible">
  66. <p class="diy-del-text">确定删除吗?</p>
  67. <div flex="cross:center">
  68. <el-button class="diy-del-btn" size="mini" plain @click.stop="deleteItem">删除</el-button>
  69. <el-button size="mini" plain @click="item.visible = false">取消</el-button>
  70. </div>
  71. <i class="el-icon-close" slot="reference"></i>
  72. </el-popover>
  73. </div>
  74. <div class="dp-hover"></div>
  75. <!-- 前端是否显示 -->
  76. <div class="show" v-if="!item.isShow">
  77. <div>已隐藏</div>
  78. </div>
  79. </components>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. </div>
  85. </el-scrollbar>
  86. </div>
  87. </template>
  88. <script src="/resources/js/Sortable.min.js"></script>
  89. <script src="<?= Yii::$app->request->baseUrl ?>/resources/unpkg/vuedraggable@2.18.1/dist/vuedraggable.umd.min.js"></script>
  90. <script>
  91. Vue.component('diy-preview', {
  92. name: "diy-preview",
  93. template: '#diy-preview',
  94. props: {
  95. list: {
  96. type: Array,
  97. default () {
  98. return []
  99. }
  100. },
  101. activeIndex: {
  102. type: Number,
  103. default () {
  104. return 0
  105. }
  106. },
  107. },
  108. watch: {
  109. list: {
  110. handler(newValue) {
  111. this.$nextTick(() => {
  112. if (newValue && newValue.length) {
  113. this.headerArr = newValue.filter(item => {
  114. const e1 = item.funcListItem || {}
  115. return e1.is_top
  116. })
  117. this.prevArr = newValue.slice(0)
  118. }
  119. })
  120. },
  121. deep: true,
  122. immediate: true
  123. },
  124. activeIndex(newVal, oldVal) {
  125. if (newVal === oldVal) return false
  126. var el = this.$refs.scrollbar.wrap; // 滚动容器
  127. var currentTop = this.$refs.scrollbar.wrap.scrollTop;
  128. var containHeight = el.offsetHeight;
  129. var halfHeight = containHeight / 2 - 200;
  130. clearTimeout(this.timerID)
  131. this.timerID = setTimeout(() => {
  132. this.$nextTick(() => {
  133. var itemDom = this.getTargetDom(newVal)
  134. if (!itemDom) {
  135. return
  136. }
  137. var clientTop = itemDom.offsetTop - currentTop;
  138. if (clientTop < halfHeight) { // 往下
  139. var addTop = halfHeight - clientTop
  140. this.toTop(currentTop, currentTop - addTop)
  141. } else { // 往上
  142. this.toTop(currentTop, currentTop + clientTop - halfHeight)
  143. }
  144. })
  145. }, 200);
  146. }
  147. },
  148. data() {
  149. return {
  150. visible: false,
  151. components: [],
  152. headerArr: [],
  153. prevArr: [],
  154. duration: 0,
  155. floatArr: ["order-broadcast"],
  156. timerID: 0
  157. };
  158. },
  159. computed: {
  160. content() {
  161. var item = this.list[this.activeIndex]
  162. return item && item.isShow ? "隐藏" : "显示"
  163. },
  164. mainStyle() {
  165. if (!this.prevArr[0]) {
  166. return {}
  167. }
  168. var mainPage = this.prevArr[0].data.mainPage || {}
  169. var style = {
  170. backgroundSize: "100%",
  171. backgroundRepeat: "no-repeat",
  172. backgroundPosition: "left top",
  173. backgroundColor: mainPage.defColor
  174. } // 基类
  175. if (mainPage.imgUrl) {
  176. style.backgroundImage = `url(${mainPage.imgUrl})`
  177. }
  178. return style
  179. }
  180. },
  181. methods: {
  182. getTargetDom(newVal) {
  183. // 通过id查询对应dom - drag-scroll类名必须对应prevArr顺序
  184. const domList = document.getElementsByClassName("drag-scroll");
  185. const item = this.prevArr[newVal]
  186. const id = item && item.id
  187. let domTarget = null;
  188. for (let index = 0; index < domList.length; index++) {
  189. let dom = domList[index];
  190. if (dom.dataset.id === id) {
  191. domTarget = dom
  192. }
  193. }
  194. return domTarget
  195. },
  196. deleteBtn(item) {
  197. var typeCom = ['nav'] // 过滤肯定不要删除的组件
  198. return !(typeCom.indexOf(item.funcListItem.type) !== -1) && (!item.disabled || !(item.is_delete === false))
  199. },
  200. toTop(startTop = 100, endTop = 0, duration = 400) {
  201. var start = 0;
  202. var el = this.$refs.scrollbar;
  203. const frameFunc = (t) => {
  204. if (!start) {
  205. start = t
  206. }
  207. var time = t - start
  208. el.wrap.scrollTop = linear(time, startTop, endTop - startTop, duration)
  209. if (time > duration) {
  210. return false
  211. }
  212. requestAnimationFrame(frameFunc)
  213. };
  214. requestAnimationFrame(frameFunc)
  215. // 辅助函数
  216. /** 运动算法
  217. * t:动画已经消耗的时间
  218. * b:小球初始位置
  219. * c:小球的需要移动额距离
  220. * d:动画持续的总时间
  221. * */
  222. function linear(t, b, c, d) {
  223. return c * t / d + b;
  224. }
  225. },
  226. filterArr(item) {
  227. var boo = false
  228. try {
  229. if (item.funcListItem.is_top) { // 头部元素
  230. boo = false
  231. } else { // 浮动元素
  232. boo = !this.filterFloatArr(item)
  233. }
  234. } catch (error) {
  235. console.log(error);
  236. }
  237. return boo
  238. },
  239. filterFloatArr(item) {
  240. var boo = false;
  241. if (!item) {
  242. return boo
  243. }
  244. return this.floatArr.some(type => {
  245. return type === item.funcListItem.type
  246. })
  247. },
  248. start(e) {
  249. console.log(e);
  250. this.duration = 300
  251. },
  252. onEnd(e) {
  253. this.$emit("change-move", {
  254. newIndex: e.newIndex,
  255. list: this.prevArr
  256. })
  257. this.duration = 0
  258. },
  259. getName(target) {
  260. try {
  261. return target ? target.funcListItem.type + "-preview" : ''
  262. } catch (e) {
  263. console.log(e);
  264. return ""
  265. }
  266. },
  267. computedItem(item, index) {
  268. return {
  269. 'dp-list-item': true,
  270. 'dp-active-item': this.activeIndex == index,
  271. 'dp-is-show': item.isShow
  272. }
  273. },
  274. computedFloatItem(item, index) {
  275. return {
  276. 'float-dp-list-item': true,
  277. 'float-dp-active-item': this.activeIndex == index,
  278. 'float-dp-is-show': item.isShow
  279. }
  280. },
  281. onClick(item, index) {
  282. this.$emit("select-item", this.list[index], index)
  283. },
  284. /**
  285. * 操作相关
  286. */
  287. copy() {
  288. if (this.operAuth("copy")) {
  289. return
  290. }
  291. this.$eventBus.$emit("diy-preview-copy")
  292. },
  293. deleteItem() {
  294. this.visible = false
  295. this.operateFn('delete')
  296. },
  297. operAuth(type) {
  298. var item = this.prevArr[this.activeIndex] || {
  299. funcListItem: {}
  300. }
  301. var typeCom = ['nav'] // 过滤肯定禁用的组件
  302. var boo = false
  303. if (item) {
  304. boo = (typeCom.indexOf(item.funcListItem.type) !== -1) || this.activeIndex === -1 || item.disabled === false;
  305. }
  306. var obj = {
  307. show: boo,
  308. up: boo || item.is_up === false,
  309. down: boo || item.is_down === false,
  310. copy: boo || item.is_copy === false,
  311. delete: boo || item.is_delete === false
  312. }
  313. return obj[type]
  314. },
  315. operateFn(type, index) {
  316. // up - down - show - delete
  317. if (this.operAuth(type)) {
  318. return
  319. }
  320. this.$eventBus.$emit("diy-preview-operateFn", {
  321. type,
  322. index: this.activeIndex
  323. })
  324. },
  325. setBg() {
  326. this.$eventBus.$emit("diy-back-set")
  327. },
  328. },
  329. });
  330. </script>
  331. <style>
  332. .dp-contain {
  333. position: relative;
  334. box-sizing: initial;
  335. display: flex;
  336. flex-direction: column;
  337. width: 375px;
  338. margin: 45px auto 0;
  339. border: 1px solid #ddd;
  340. min-height: 900px;
  341. }
  342. .dp-el-scrollbar {
  343. height: 100%;
  344. }
  345. .dp-header-placeholder {
  346. width: 100%;
  347. height: 44px;
  348. background-size: 100% 100%;
  349. z-index: 99;
  350. background: url("resources/img/decorate/nav/light1.png") no-repeat;
  351. }
  352. #float {
  353. position: absolute;
  354. top: 0;
  355. left: 0;
  356. right: 0;
  357. bottom: 0;
  358. pointer-events: none;
  359. margin-top: 88px;
  360. }
  361. .dp-nav-placeholder {
  362. position: relative;
  363. height: 44px;
  364. }
  365. .dp-list-item {
  366. position: relative;
  367. }
  368. .float-dp-list-item {
  369. /* position: relative; */
  370. float: left;
  371. pointer-events: auto;
  372. }
  373. .dp-list-item .dp-close,
  374. .float-dp-list-item .dp-close {
  375. display: none;
  376. }
  377. .dp-active-item:before,
  378. .float-dp-active-item .diy-solid-line:before {
  379. box-sizing: border-box;
  380. content: "";
  381. position: absolute;
  382. top: 0;
  383. left: 0;
  384. right: 0;
  385. bottom: 0;
  386. border: 2px solid var(--diy-theme);
  387. height: 100%;
  388. z-index: 100;
  389. pointer-events: none;
  390. cursor: move;
  391. }
  392. .dp-active-item .dp-close,
  393. .float-dp-active-item .dp-close {
  394. display: block;
  395. }
  396. .slide-right {
  397. position: absolute;
  398. width: 42px;
  399. height: 200px;
  400. top: 0;
  401. bottom: 0;
  402. left: 50%;
  403. background: #73767D;
  404. margin: auto 0 auto 220px;
  405. /* margin-left: ; */
  406. border-radius: 2px;
  407. z-index: 999;
  408. }
  409. .slide-right .iconfont-el {
  410. color: #fff;
  411. display: block;
  412. text-align: center;
  413. font-size: 24px;
  414. line-height: 40px;
  415. cursor: pointer;
  416. position: relative;
  417. }
  418. .slide-right .iconfont-el>p {
  419. display: none;
  420. }
  421. .dp-close {
  422. position: absolute;
  423. top: 0;
  424. right: 0;
  425. width: 16px;
  426. height: 16px;
  427. background-color: var(--diy-theme);
  428. color: #fff;
  429. font-size: 12px;
  430. text-align: center;
  431. border-bottom-left-radius: 40px;
  432. z-index: 99;
  433. }
  434. .dp-close .el-icon-close {
  435. transform: translate(2px, -2px);
  436. }
  437. .flip-list-move {
  438. transition: transform 1s;
  439. }
  440. .dp-hover:before {
  441. box-sizing: border-box;
  442. content: "";
  443. position: absolute;
  444. top: 0;
  445. left: 0;
  446. right: 0;
  447. bottom: 0;
  448. border: 1px dashed var(--diy-theme);
  449. height: 100%;
  450. z-index: 100;
  451. pointer-events: none;
  452. cursor: move;
  453. display: none;
  454. }
  455. .dp-list-item:hover .dp-hover:before,
  456. .float-dp-list-item .diy-solid-line:hover .dp-hover:before {
  457. display: block;
  458. }
  459. .mask {
  460. position: absolute;
  461. top: 0;
  462. left: 0;
  463. right: 0;
  464. bottom: 0;
  465. background-color: rgba(0, 0, 0, 0.2);
  466. }
  467. .show {
  468. position: absolute;
  469. top: 0;
  470. left: 0;
  471. right: 0;
  472. bottom: 0;
  473. background-color: rgba(0, 0, 0, 0.2);
  474. z-index: 10;
  475. }
  476. .show div {
  477. position: absolute;
  478. top: 0;
  479. left: 0;
  480. right: 0;
  481. bottom: 0;
  482. width: 68px;
  483. height: 24px;
  484. line-height: 24px;
  485. margin: auto;
  486. text-align: center;
  487. background-color: #000;
  488. border-radius: 50px;
  489. color: #fff;
  490. }
  491. </style>