Drawing.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <template>
  2. <ModuleBase ref="base" :type="data.type">
  3. <template #content>
  4. <UploadFile
  5. key="upload_image"
  6. :type="data.type"
  7. :total-size="data.total_size"
  8. :file-list="data.image_list"
  9. :file-id-list="data.image_id_list"
  10. :file-info-list="data.image_info_list"
  11. :label-text="labelText"
  12. :accept-file-type="acceptFileType"
  13. :icon-class="iconClass"
  14. :limit="1"
  15. :single-size="500"
  16. @updateFileList="updateFileList"
  17. />
  18. <div class="image-size">
  19. <span>画板大小</span
  20. ><el-input-number v-model="data.image_width" :step="100" :min="200" :max="800" /><el-input-number
  21. v-model="data.image_height"
  22. :step="100"
  23. :min="200"
  24. :max="800"
  25. />
  26. </div>
  27. <div
  28. class="background-img"
  29. :style="{
  30. width: data.image_width + 'px',
  31. height: data.image_height + 'px',
  32. border: '1px dotted #DCDFE6',
  33. background: data.image_list[0] && file_url ? '' : '#DCDFE6',
  34. }"
  35. >
  36. <div
  37. v-if="data.image_list[0] && file_url"
  38. class="img-set"
  39. :style="{ top: `${data.imgData.top - 9}px`, left: `${data.imgData.left}px` }"
  40. >
  41. <div class="dot top-left" @mousedown="dragStart($event, 'nwse-resize', 'top-left')"></div>
  42. <div class="horizontal-line" @mousedown="dragStart($event, 'ns-resize', 'top')"></div>
  43. <div class="dot top-right" @mousedown="dragStart($event, 'nesw-resize', 'top-right')"></div>
  44. <div class="vertical-line" @mousedown="dragStart($event, 'ew-resize', 'left')"></div>
  45. <img
  46. :src="file_url"
  47. draggable="false"
  48. alt="背景图"
  49. :style="{ width: `${data.imgData.width}px`, height: `${data.imgData.height}px` }"
  50. @mousedown="dragStart($event, 'move', 'move')"
  51. />
  52. <div class="vertical-line" @mousedown="dragStart($event, 'ew-resize', 'right')"></div>
  53. <div class="dot bottom-left" @mousedown="dragStart($event, 'nesw-resize', 'bottom-left')"></div>
  54. <div class="horizontal-line" @mousedown="dragStart($event, 'ns-resize', 'bottom')"></div>
  55. <div class="dot bottom-right" @mousedown="dragStart($event, 'nwse-resize', 'bottom-right')"></div>
  56. </div>
  57. <p v-else :style="{ lineHeight: data.image_height + 'px', textAlign: 'center' }">背景图</p>
  58. </div>
  59. </template>
  60. </ModuleBase>
  61. </template>
  62. <script>
  63. import ModuleMixin from '../../common/ModuleMixin';
  64. import UploadFile from '../../base/common/UploadFile.vue';
  65. import { getDrawingData } from '@/views/book/courseware/data/drawing';
  66. import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
  67. import { GetFileURLMap } from '@/api/app';
  68. export default {
  69. name: 'DrawingPage',
  70. components: { UploadFile, SelectUpload },
  71. mixins: [ModuleMixin],
  72. data() {
  73. return {
  74. data: getDrawingData(),
  75. labelText: '背景图',
  76. acceptFileType: '.jpg,.png,.jpeg',
  77. iconClass: 'picture',
  78. file_url: '',
  79. };
  80. },
  81. watch: {
  82. 'data.image_list': {
  83. handler(val) {
  84. if (val.length > 0) {
  85. this.handleData();
  86. }
  87. },
  88. immediate: true,
  89. },
  90. },
  91. mounted() {
  92. document.querySelector('.background-img').addEventListener('mousemove', this.mouseMove);
  93. document.body.addEventListener('mouseup', this.mouseUp);
  94. },
  95. beforeDestroy() {
  96. document.querySelector('.background-img')?.removeEventListener('mousemove', this.mouseMove);
  97. document.body.removeEventListener('mouseup', this.mouseUp);
  98. },
  99. methods: {
  100. updateFileList({ file_list, file_id_list, file_info_list }) {
  101. this.data.image_list = file_list;
  102. this.data.image_id_list = file_id_list;
  103. this.data.image_info_list = file_info_list;
  104. this.data.file_id_list = file_id_list;
  105. if (file_list.length > 0) {
  106. const img = new Image();
  107. img.src = file_list[0].file_url;
  108. this.file_url = file_list[0].file_url;
  109. img.onload = () => {
  110. const { width, height } = img;
  111. if (width > this.data.image_width || height > this.data.image_height) {
  112. const wScale = width / this.data.image_width;
  113. const hScale = height / this.data.image_height;
  114. const scale = wScale > hScale ? this.data.image_width / 2 / width : this.data.image_height / 2 / height;
  115. this.data.imgData = {
  116. width: width * scale,
  117. height: height * scale,
  118. top: 0,
  119. left: 0,
  120. };
  121. } else {
  122. this.data.imgData = {
  123. width,
  124. height,
  125. top: 0,
  126. left: 0,
  127. };
  128. }
  129. };
  130. }
  131. },
  132. handleData() {
  133. // this.data.image_list.forEach((item) => {
  134. // GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
  135. // this.file_url = url_map[item.file_id];
  136. // // this.$set(item, 'file_url', url_map[item.file_id]);
  137. // });
  138. // });
  139. this.handleMindMap();
  140. },
  141. handleMindMap() {
  142. // 思维导图数据
  143. let node_list = [];
  144. this.data.image_list.forEach((item) => {
  145. node_list.push({
  146. name: item.file_name,
  147. id: Math.random().toString(36).substring(2, 12),
  148. });
  149. });
  150. this.data.mind_map.node_list = node_list;
  151. },
  152. /**
  153. * 拖拽开始
  154. * @param {MouseEvent} event
  155. * @param {string} cursor
  156. * @param {string} type
  157. */
  158. dragStart(event, cursor, type) {
  159. const { clientX, clientY } = event;
  160. this.data.drag = {
  161. dragging: true,
  162. startX: clientX,
  163. startY: clientY,
  164. type,
  165. };
  166. document.querySelector('.background-img').style.cursor = cursor;
  167. },
  168. /**
  169. * 鼠标移动
  170. * @param {MouseEvent} event
  171. */
  172. mouseMove(event) {
  173. if (!this.data.drag.dragging) return;
  174. const { clientX, clientY } = event;
  175. const { startX, startY, type } = this.data.drag;
  176. const widthDiff = clientX - startX;
  177. const heightDiff = clientY - startY;
  178. if (type === 'top-left') {
  179. this.data.imgData.width = Math.min(this.data.image_width, Math.max(0, this.data.imgData.width - widthDiff));
  180. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  181. this.data.imgData.top = Math.min(
  182. this.data.image_height - this.data.imgData.height,
  183. Math.max(0, this.data.imgData.top + heightDiff),
  184. );
  185. this.data.imgData.left = Math.min(
  186. this.data.image_width - this.data.imgData.width,
  187. Math.max(0, this.data.imgData.left + widthDiff),
  188. );
  189. } else if (type === 'top-right') {
  190. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  191. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  192. this.data.imgData.top = Math.min(
  193. this.data.image_height - this.data.imgData.height,
  194. Math.max(0, this.data.imgData.top + heightDiff),
  195. );
  196. this.data.imgData.left = Math.min(
  197. this.data.image_width - this.data.imgData.width,
  198. Math.max(0, this.data.imgData.left),
  199. );
  200. } else if (type === 'bottom-left') {
  201. this.data.imgData.width = Math.min(this.data.image_width, Math.max(0, this.data.imgData.width - widthDiff));
  202. this.data.imgData.height = Math.min(this.data.image_height, Math.max(this.data.imgData.height + heightDiff));
  203. this.data.imgData.top = Math.min(
  204. this.data.image_height - this.data.imgData.height,
  205. Math.max(0, this.data.imgData.top),
  206. );
  207. this.data.imgData.left = Math.min(
  208. this.data.image_width - this.data.imgData.width,
  209. Math.max(0, this.data.imgData.left + widthDiff),
  210. );
  211. } else if (type === 'bottom-right') {
  212. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  213. this.data.imgData.height = Math.min(this.data.image_height, Math.max(this.data.imgData.height + heightDiff));
  214. this.data.imgData.top = Math.min(
  215. this.data.image_height - this.data.imgData.height,
  216. Math.max(0, this.data.imgData.top),
  217. );
  218. this.data.imgData.left = Math.min(
  219. this.data.image_width - this.data.imgData.width,
  220. Math.max(0, this.data.imgData.left),
  221. );
  222. }
  223. if (type === 'top') {
  224. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  225. this.data.imgData.top = Math.min(
  226. this.data.image_height - this.data.imgData.height,
  227. Math.max(0, this.data.imgData.top + heightDiff),
  228. );
  229. } else if (type === 'bottom') {
  230. this.data.imgData.height = Math.min(this.data.image_height, Math.max(this.data.imgData.height + heightDiff));
  231. this.data.imgData.top = Math.min(
  232. this.data.image_height - this.data.imgData.height,
  233. Math.max(0, this.data.imgData.top),
  234. );
  235. } else if (type === 'left') {
  236. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width - widthDiff));
  237. this.data.imgData.left = Math.min(
  238. this.data.image_width - this.data.imgData.width,
  239. Math.max(0, this.data.imgData.left + widthDiff),
  240. );
  241. } else if (type === 'right') {
  242. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  243. this.data.imgData.left = Math.min(
  244. this.data.image_width - this.data.imgData.width,
  245. Math.max(0, this.data.imgData.left),
  246. );
  247. }
  248. if (type === 'move') {
  249. this.data.imgData.top = Math.min(
  250. this.data.image_height - this.data.imgData.height,
  251. Math.max(0, this.data.imgData.top + heightDiff),
  252. );
  253. this.data.imgData.left = Math.min(
  254. this.data.image_width - this.data.imgData.width,
  255. Math.max(0, this.data.imgData.left + widthDiff),
  256. );
  257. }
  258. this.data.drag.startX = clientX;
  259. this.data.drag.startY = clientY;
  260. },
  261. /**
  262. * 鼠标抬起
  263. */
  264. mouseUp() {
  265. this.data.drag.dragging = false;
  266. document.querySelector('.background-img').style.cursor = 'auto';
  267. },
  268. },
  269. };
  270. </script>
  271. <style lang="scss" scoped>
  272. .upload-file {
  273. // display: flex;
  274. column-gap: 12px;
  275. align-items: center;
  276. margin: 8px 0;
  277. .file-name {
  278. display: flex;
  279. column-gap: 14px;
  280. align-items: center;
  281. justify-content: space-between;
  282. max-width: 360px;
  283. padding: 8px 12px;
  284. font-size: 14px;
  285. color: #1d2129;
  286. background-color: #f7f8fa;
  287. span {
  288. display: flex;
  289. column-gap: 14px;
  290. align-items: center;
  291. }
  292. }
  293. .svg-icon {
  294. cursor: pointer;
  295. }
  296. }
  297. .image-size {
  298. display: flex;
  299. gap: 10px;
  300. align-items: center;
  301. padding: 20px 0;
  302. }
  303. .background-img {
  304. height: 310px;
  305. border: 1px dashed rgba(0, 0, 0, 8%);
  306. .img-set {
  307. position: relative;
  308. display: inline-grid;
  309. grid-template:
  310. ' . . . ' 2px
  311. ' . img . ' auto
  312. ' . . . ' 2px
  313. / 2px auto 2px;
  314. img {
  315. object-fit: cover;
  316. }
  317. .horizontal-line,
  318. .vertical-line {
  319. background-color: $main-color;
  320. }
  321. .horizontal-line {
  322. width: 100%;
  323. height: 2px;
  324. cursor: ns-resize;
  325. }
  326. .vertical-line {
  327. width: 2px;
  328. height: 100%;
  329. cursor: ew-resize;
  330. }
  331. .dot {
  332. z-index: 1;
  333. width: 6px;
  334. height: 6px;
  335. background-color: $main-color;
  336. &.top-left {
  337. top: -2px;
  338. left: -2px;
  339. }
  340. &.top-right {
  341. top: -2px;
  342. right: 2px;
  343. }
  344. &.bottom-left {
  345. bottom: 2px;
  346. left: -2px;
  347. }
  348. &.bottom-right {
  349. right: 2px;
  350. bottom: 2px;
  351. }
  352. &.top-left,
  353. &.bottom-right {
  354. position: relative;
  355. cursor: nwse-resize;
  356. }
  357. &.top-right,
  358. &.bottom-left {
  359. position: relative;
  360. cursor: nesw-resize;
  361. }
  362. }
  363. }
  364. }
  365. </style>