index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. <template>
  2. <div
  3. :class="['widgets-home', customizing === 1 ? 'customizing' : '']"
  4. ref="main"
  5. >
  6. <div class="widgets-content">
  7. <div class="widgets-top">
  8. <div class="widgets-top-actions">
  9. <el-button
  10. v-if="customizing"
  11. icon="el-icon-check"
  12. round
  13. @click="save"
  14. >完成</el-button
  15. >
  16. <el-button
  17. v-if="customizing && !adding"
  18. type="primary"
  19. icon="el-icon-plus"
  20. round
  21. @click="custom"
  22. ></el-button>
  23. <el-button
  24. v-if="!customizing"
  25. icon="el-icon-edit"
  26. round
  27. @click="layout"
  28. ></el-button>
  29. </div>
  30. </div>
  31. <div class="widgets" ref="widgets">
  32. <div class="widgets-wrapper">
  33. <div v-if="nowCompsList.length <= 0" class="no-widgets">
  34. <el-empty
  35. image="img/no-widgets.svg"
  36. description="没有部件啦"
  37. :image-size="280"
  38. ></el-empty>
  39. </div>
  40. <div>
  41. <grid-layout
  42. :layout="grid"
  43. :col-num="colNum"
  44. :row-height="30"
  45. :is-draggable="customizing"
  46. :is-resizable="customizing"
  47. :responsive="false"
  48. >
  49. <grid-item
  50. v-for="(item, index) in grid"
  51. :key="index"
  52. :static="item.static"
  53. :x="item.x"
  54. :y="item.y"
  55. :w="item.w"
  56. :h="item.h"
  57. :i="item.i"
  58. >
  59. <div class="widgets-item">
  60. <component
  61. :is="allComps[item.name]"
  62. ></component>
  63. <div
  64. v-if="customizing"
  65. :class="[
  66. 'customize-overlay',
  67. customizing ? 'layout-overlay' : '',
  68. ]"
  69. >
  70. <el-button
  71. class="close"
  72. type="danger"
  73. v-if="customizing"
  74. icon="el-icon-close"
  75. size="small"
  76. @click="removeItem(item.i)"
  77. ></el-button>
  78. <!-- <label v-if="customizing">{{item.w}}{{item.h}}<el-icon><component :is="allComps[item.name].icon" /></el-icon>{{ allComps[item.name].title }}</label> -->
  79. </div>
  80. </div>
  81. </grid-item>
  82. </grid-layout>
  83. </div>
  84. </div>
  85. </div>
  86. </div>
  87. <div v-if="adding" class="widgets-aside">
  88. <el-container>
  89. <el-header>
  90. <div class="widgets-aside-title">
  91. <el-icon><el-icon-circle-plus-filled /></el-icon
  92. >添加部件
  93. </div>
  94. <div class="widgets-aside-close" @click="close()">
  95. <el-icon><el-icon-close /></el-icon>
  96. </div>
  97. </el-header>
  98. <el-main class="nopadding">
  99. <div class="widgets-list">
  100. <div
  101. v-if="myCompsList.length <= 0"
  102. class="widgets-list-nodata"
  103. >
  104. <el-empty
  105. description="没有部件啦"
  106. :image-size="60"
  107. ></el-empty>
  108. </div>
  109. <div
  110. v-for="item in myCompsList"
  111. :key="item.title"
  112. class="widgets-list-item"
  113. >
  114. <div class="item-logo">
  115. <el-icon><component :is="item.icon" /></el-icon>
  116. </div>
  117. <div class="item-info">
  118. <h2>{{ item.title }}</h2>
  119. <p>{{ item.description }}</p>
  120. </div>
  121. <div class="item-actions">
  122. <el-button
  123. type="primary"
  124. icon="el-icon-plus"
  125. size="small"
  126. @click="push(item)"
  127. ></el-button>
  128. </div>
  129. </div>
  130. </div>
  131. </el-main>
  132. <el-footer style="height: 51px">
  133. <el-button
  134. size="small"
  135. type="default"
  136. @click="backDefault()"
  137. >恢复默认</el-button
  138. >
  139. </el-footer>
  140. </el-container>
  141. </div>
  142. </div>
  143. </template>
  144. <script>
  145. import deskService from "@/api/sys/deskService";
  146. import draggable from "vuedraggable/src/vuedraggable";
  147. import allComps from "./components";
  148. import { createApp } from "vue";
  149. import VueGridLayout from "vue-grid-layout";
  150. const app = createApp().use(VueGridLayout);
  151. const GridLayout = app.component("grid-layout");
  152. const GridItem = app.component("grid-item");
  153. export default {
  154. components: {
  155. draggable,
  156. GridLayout,
  157. GridItem,
  158. },
  159. data() {
  160. return {
  161. customizing: false,
  162. adding: false,
  163. allComps: allComps,
  164. selectedComps: [],
  165. selectLayout: [],
  166. defaultGrid: [
  167. {
  168. x: 6,
  169. y: 9,
  170. w: 6,
  171. h: 15,
  172. i: 1,
  173. name: "MyNoticePageList",
  174. moved: false,
  175. },
  176. // { x: 0, y: 4, w: 12, h: 5, i: 2, name: "Myapp", moved: false },MyNoticePageList
  177. { x: 8, y: 0, w: 4, h: 4, i: 4, name: "Time", moved: false },
  178. {
  179. x: 0,
  180. y: 0,
  181. w: 8,
  182. h: 4,
  183. i: 6,
  184. name: "Flowable",
  185. moved: false,
  186. },
  187. {
  188. x: 0,
  189. y: 9,
  190. w: 6,
  191. h: 15,
  192. i: 8,
  193. name: "TodoList",
  194. moved: false,
  195. },
  196. {
  197. x: 0,
  198. y: 9,
  199. w: 6,
  200. h: 15,
  201. i: 1,
  202. name: "MyNotifyList",
  203. moved: false,
  204. },
  205. ],
  206. // grid: [],
  207. grid: [],
  208. draggable: true,
  209. resizable: true,
  210. responsive: true,
  211. colNum: 12,
  212. index: 0,
  213. };
  214. },
  215. created() {
  216. deskService.getGridInfo().then((data) => {
  217. if (data.grid) {
  218. this.grid = JSON.parse(data.grid);
  219. } else {
  220. // this.grid = this.$TOOL.data.get("grid") || JSON.parse(JSON.stringify(this.defaultGrid))
  221. this.grid = JSON.parse(JSON.stringify(this.defaultGrid));
  222. }
  223. });
  224. },
  225. mounted() {
  226. this.$emit("on-mounted");
  227. this.index = this.grid.length;
  228. },
  229. computed: {
  230. allCompsList() {
  231. var allCompsList = [];
  232. for (var key in this.allComps) {
  233. allCompsList.push({
  234. key: key,
  235. title: allComps[key].title,
  236. icon: allComps[key].icon,
  237. description: allComps[key].description,
  238. layout: allComps[key].layout,
  239. });
  240. }
  241. for (let comp of allCompsList) {
  242. const _item = this.grid.find((item) => {
  243. return item.name === comp.key;
  244. });
  245. if (_item) {
  246. comp.disabled = true;
  247. }
  248. }
  249. return allCompsList;
  250. },
  251. myCompsList() {
  252. return this.allCompsList.filter((item) => !item.disabled);
  253. },
  254. nowCompsList() {
  255. return this.grid;
  256. },
  257. },
  258. methods: {
  259. addItem: function (item) {
  260. // Add a new item. It must have a unique key!
  261. this.grid.push({
  262. x: (this.grid.length * 2) % (this.colNum || 12),
  263. y: this.grid.length + (this.colNum || 12), // puts it at the bottom
  264. w: item.layout.w,
  265. h: item.layout.h,
  266. i: this.index,
  267. name: item.key,
  268. });
  269. // Increment the counter to ensure key is always unique.
  270. this.index++;
  271. },
  272. removeItem: function (val) {
  273. const index = this.grid.map((item) => item.i).indexOf(val);
  274. this.grid.splice(index, 1);
  275. },
  276. //开启自定义
  277. custom() {
  278. this.adding = true;
  279. },
  280. layout() {
  281. this.customizing = true;
  282. },
  283. //追加
  284. push(item) {
  285. this.addItem(item);
  286. },
  287. //隐藏组件
  288. remove(item) {
  289. var newCopmsList = this.grid.copmsList;
  290. newCopmsList.forEach((obj, index) => {
  291. var newObj = obj.filter((o) => o != item);
  292. newCopmsList[index] = newObj;
  293. });
  294. },
  295. //保存
  296. save() {
  297. this.customizing = false;
  298. this.adding = false;
  299. this.$TOOL.data.set("grid", this.grid);
  300. deskService.save({ grid: JSON.stringify(this.grid) });
  301. },
  302. //恢复默认
  303. backDefault() {
  304. this.customizing = true;
  305. this.grid = JSON.parse(JSON.stringify(this.defaultGrid));
  306. deskService.save({ grid: JSON.stringify(this.defaultGrid) });
  307. },
  308. //关闭
  309. close() {
  310. this.adding = false;
  311. },
  312. },
  313. };
  314. </script>
  315. <style scoped lang="scss">
  316. .el-header {
  317. background: #fff;
  318. border-bottom: 1px solid var(--el-border-color-light);
  319. padding: 13px 15px;
  320. display: flex;
  321. justify-content: space-between;
  322. align-items: center;
  323. }
  324. .el-footer {
  325. background: #fff;
  326. border-top: 1px solid var(--el-border-color-light);
  327. padding: 13px 15px;
  328. height: 51px;
  329. }
  330. .el-main {
  331. flex-basis: 100%;
  332. }
  333. .widgets-top-actions {
  334. position: fixed;
  335. // right: 10px;
  336. // top: 200px;
  337. right: 30px;
  338. margin-top: -25px;
  339. z-index: 2;
  340. }
  341. .widgets-home {
  342. display: flex;
  343. flex-direction: row;
  344. flex: 1;
  345. }
  346. .widgets-content {
  347. flex: 1;
  348. overflow: auto;
  349. overflow-x: hidden;
  350. padding: 5px;
  351. }
  352. .widgets-aside {
  353. margin: -10px -10px -10px 0;
  354. width: 360px;
  355. background: #fff;
  356. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  357. position: relative;
  358. overflow: auto;
  359. }
  360. .widgets-aside-title {
  361. font-size: 14px;
  362. display: flex;
  363. align-items: center;
  364. justify-content: center;
  365. }
  366. .widgets-aside-title i {
  367. margin-right: 10px;
  368. font-size: 18px;
  369. }
  370. .widgets-aside-close {
  371. font-size: 18px;
  372. width: 30px;
  373. height: 30px;
  374. display: flex;
  375. align-items: center;
  376. justify-content: center;
  377. border-radius: 3px;
  378. cursor: pointer;
  379. }
  380. .widgets-aside-close:hover {
  381. background: rgba(180, 180, 180, 0.1);
  382. }
  383. .widgets-top {
  384. display: flex;
  385. justify-content: space-between;
  386. align-items: center;
  387. }
  388. .widgets-top-title {
  389. font-size: 18px;
  390. font-weight: bold;
  391. }
  392. .draggable-box {
  393. height: 100%;
  394. }
  395. .widgets-item {
  396. position: relative;
  397. height: 100%;
  398. // margin-bottom: 15px;
  399. }
  400. .customizing .widgets-wrapper {
  401. margin-right: -360px;
  402. }
  403. .customizing .widgets-wrapper .el-col {
  404. padding-bottom: 15px;
  405. }
  406. .customizing .widgets-wrapper .draggable-box {
  407. border: 1px dashed var(--el-color-primary);
  408. padding: 15px;
  409. }
  410. .customizing .widgets-wrapper .no-widgets {
  411. display: none;
  412. }
  413. // .customizing .widgets-item {position: relative;margin-bottom: 15px;}
  414. .customize-overlay {
  415. position: absolute;
  416. top: 0;
  417. right: 0;
  418. bottom: 0;
  419. left: 0;
  420. z-index: 1;
  421. display: flex;
  422. flex-direction: column;
  423. align-items: center;
  424. justify-content: center;
  425. background: rgba(255, 255, 255, 0.9);
  426. cursor: move;
  427. }
  428. .customize-overlay label {
  429. background: var(--el-color-primary);
  430. color: #fff;
  431. height: 40px;
  432. padding: 0 30px;
  433. border-radius: 40px;
  434. font-size: 18px;
  435. display: flex;
  436. align-items: center;
  437. justify-content: center;
  438. cursor: move;
  439. }
  440. .customize-overlay label i {
  441. margin-right: 15px;
  442. font-size: 24px;
  443. }
  444. .customize-overlay .close {
  445. position: absolute;
  446. top: 15px;
  447. right: 15px;
  448. }
  449. // .customize-overlay .close:focus, .customize-overlay .close:hover {background: var(--el-button-hover-color);}
  450. .layout-overlay {
  451. border: 2px dotted #2d8cf0;
  452. background: transparent !important;
  453. }
  454. .widgets-list {
  455. }
  456. .widgets-list-item {
  457. display: flex;
  458. flex-direction: row;
  459. padding: 15px;
  460. align-items: center;
  461. }
  462. .widgets-list-item .item-logo {
  463. width: 40px;
  464. height: 40px;
  465. border-radius: 50%;
  466. background: rgba(180, 180, 180, 0.1);
  467. display: flex;
  468. align-items: center;
  469. justify-content: center;
  470. font-size: 18px;
  471. margin-right: 15px;
  472. color: #6a8bad;
  473. }
  474. .widgets-list-item .item-info {
  475. flex: 1;
  476. }
  477. .widgets-list-item .item-info h2 {
  478. font-size: 16px;
  479. font-weight: normal;
  480. cursor: default;
  481. }
  482. .widgets-list-item .item-info p {
  483. font-size: 12px;
  484. color: #999;
  485. cursor: default;
  486. }
  487. .widgets-list-item:hover {
  488. background: rgba(180, 180, 180, 0.1);
  489. }
  490. .widgets-wrapper .sortable-ghost {
  491. opacity: 0.5;
  492. }
  493. .selectLayout {
  494. width: 100%;
  495. display: flex;
  496. }
  497. .selectLayout-item {
  498. width: 60px;
  499. height: 60px;
  500. border: 2px solid var(--el-border-color-light);
  501. padding: 5px;
  502. cursor: pointer;
  503. margin-right: 15px;
  504. }
  505. .selectLayout-item span {
  506. display: block;
  507. background: var(--el-border-color-light);
  508. height: 46px;
  509. }
  510. .selectLayout-item.item02 span {
  511. height: 30px;
  512. }
  513. .selectLayout-item.item02 .el-col:nth-child(1) span {
  514. height: 14px;
  515. margin-bottom: 2px;
  516. }
  517. .selectLayout-item.item03 span {
  518. height: 14px;
  519. margin-bottom: 2px;
  520. }
  521. .selectLayout-item:hover {
  522. border-color: var(--el-color-primary);
  523. }
  524. .selectLayout-item.active {
  525. border-color: var(--el-color-primary);
  526. }
  527. .selectLayout-item.active span {
  528. background: var(--el-color-primary);
  529. }
  530. .dark {
  531. .widgets-aside {
  532. background: #2b2b2b;
  533. }
  534. .customize-overlay {
  535. background: rgba(43, 43, 43, 0.9);
  536. }
  537. }
  538. @media (max-width: 992px) {
  539. .customizing .widgets-aside {
  540. width: 100%;
  541. position: absolute;
  542. top: 50%;
  543. right: 0;
  544. bottom: 0;
  545. }
  546. .customizing .widgets-wrapper {
  547. margin-right: 0;
  548. }
  549. }
  550. .layoutJSON {
  551. background: #ddd;
  552. border: 1px solid black;
  553. margin-top: 10px;
  554. padding: 10px;
  555. }
  556. .columns {
  557. -moz-columns: 120px;
  558. -webkit-columns: 120px;
  559. columns: 120px;
  560. }
  561. /*************************************/
  562. .remove {
  563. position: absolute;
  564. right: 2px;
  565. top: 0;
  566. cursor: pointer;
  567. }
  568. .vue-grid-layout {
  569. // background: #eee;
  570. // min-height: 500px
  571. }
  572. .vue-grid-item:not(.vue-grid-placeholder) {
  573. // background: #ccc;
  574. // border: 1px solid black;
  575. }
  576. .vue-resizable-handle {
  577. z-index: 300;
  578. }
  579. .vue-grid-item .resizing {
  580. opacity: 0.9;
  581. }
  582. .vue-grid-item .static {
  583. background: #cce;
  584. }
  585. .vue-grid-item .text {
  586. font-size: 24px;
  587. text-align: center;
  588. position: absolute;
  589. top: 0;
  590. bottom: 0;
  591. left: 0;
  592. right: 0;
  593. margin: auto;
  594. height: 100%;
  595. width: 100%;
  596. }
  597. .vue-grid-item .no-drag {
  598. height: 100%;
  599. width: 100%;
  600. }
  601. .vue-grid-item .minMax {
  602. font-size: 12px;
  603. }
  604. .vue-grid-item .add {
  605. cursor: pointer;
  606. }
  607. .vue-draggable-handle {
  608. position: absolute;
  609. width: 20px;
  610. height: 20px;
  611. top: 0;
  612. left: 0;
  613. background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>")
  614. no-repeat;
  615. background-position: bottom right;
  616. padding: 0 8px 8px 0;
  617. background-repeat: no-repeat;
  618. background-origin: content-box;
  619. box-sizing: border-box;
  620. cursor: pointer;
  621. }
  622. </style>