Drawing.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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="drawing-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}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', margin: 0 }">背景图</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.querySelectorAll('.drawing-background-img').forEach((item) => {
  93. item.addEventListener('mousemove', this.mouseMove);
  94. });
  95. document.body.addEventListener('mouseup', this.mouseUp);
  96. },
  97. beforeDestroy() {
  98. document.querySelectorAll('.drawing-background-img').forEach((item) => {
  99. item.removeEventListener('mousemove', this.mouseMove);
  100. });
  101. // document.querySelectorAll('.drawing-background-img')?.removeEventListener('mousemove', this.mouseMove);
  102. document.body.removeEventListener('mouseup', this.mouseUp);
  103. },
  104. methods: {
  105. updateFileList({ file_list, file_id_list, file_info_list }) {
  106. if (this.property.isSilentPropertyWatch) return;
  107. if (JSON.stringify(file_id_list) === JSON.stringify(this.data.file_id_list)) {
  108. if (this.data.image_list.length > 0) {
  109. const img = new Image();
  110. img.src = this.data.image_list[0].file_url;
  111. this.file_url = this.data.image_list[0].file_url;
  112. }
  113. } else {
  114. this.data.image_list = file_list;
  115. this.data.image_id_list = file_id_list;
  116. this.data.image_info_list = file_info_list;
  117. this.data.file_id_list = file_id_list;
  118. if (file_list.length > 0) {
  119. const img = new Image();
  120. img.src = file_list[0].file_url;
  121. this.file_url = file_list[0].file_url;
  122. img.onload = () => {
  123. const { width, height } = img;
  124. if (width > this.data.image_width || height > this.data.image_height) {
  125. const wScale = width / this.data.image_width;
  126. const hScale = height / this.data.image_height;
  127. const scale = wScale > hScale ? this.data.image_width / 2 / width : this.data.image_height / 2 / height;
  128. this.data.imgData = {
  129. width: width * scale,
  130. height: height * scale,
  131. top: 0,
  132. left: 0,
  133. };
  134. } else {
  135. this.data.imgData = {
  136. width,
  137. height,
  138. top: 0,
  139. left: 0,
  140. };
  141. }
  142. };
  143. }
  144. }
  145. },
  146. handleData() {
  147. // this.data.image_list.forEach((item) => {
  148. // GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
  149. // this.file_url = url_map[item.file_id];
  150. // // this.$set(item, 'file_url', url_map[item.file_id]);
  151. // });
  152. // });
  153. this.handleMindMap();
  154. },
  155. handleMindMap() {
  156. // 思维导图数据
  157. let node_list = [];
  158. this.data.image_list.forEach((item) => {
  159. node_list.push({
  160. name: item.file_name,
  161. id: Math.random().toString(36).substring(2, 12),
  162. });
  163. });
  164. this.data.mind_map.node_list = node_list;
  165. },
  166. /**
  167. * 拖拽开始
  168. * @param {MouseEvent} event
  169. * @param {string} cursor
  170. * @param {string} type
  171. */
  172. dragStart(event, cursor, type) {
  173. const { clientX, clientY } = event;
  174. this.data.drag = {
  175. dragging: true,
  176. startX: clientX,
  177. startY: clientY,
  178. type,
  179. };
  180. document.querySelectorAll('.drawing-background-img').forEach((item) => {
  181. item.style.cursor = cursor;
  182. });
  183. // document.querySelectorAll('.drawing-background-img').style.cursor = cursor;
  184. },
  185. /**
  186. * 鼠标移动
  187. * @param {MouseEvent} event
  188. */
  189. mouseMove(event) {
  190. if (!this.data.drag.dragging) return;
  191. const { clientX, clientY } = event;
  192. const { startX, startY, type } = this.data.drag;
  193. const widthDiff = clientX - startX;
  194. const heightDiff = clientY - startY;
  195. if (type === 'top-left') {
  196. this.data.imgData.width = Math.min(this.data.image_width, Math.max(0, this.data.imgData.width - widthDiff));
  197. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  198. this.data.imgData.top = Math.min(
  199. this.data.image_height - this.data.imgData.height,
  200. Math.max(0, this.data.imgData.top + heightDiff),
  201. );
  202. this.data.imgData.left = Math.min(
  203. this.data.image_width - this.data.imgData.width,
  204. Math.max(0, this.data.imgData.left + widthDiff),
  205. );
  206. } else if (type === 'top-right') {
  207. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  208. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  209. this.data.imgData.top = Math.min(
  210. this.data.image_height - this.data.imgData.height,
  211. Math.max(0, this.data.imgData.top + heightDiff),
  212. );
  213. this.data.imgData.left = Math.min(
  214. this.data.image_width - this.data.imgData.width,
  215. Math.max(0, this.data.imgData.left),
  216. );
  217. } else if (type === 'bottom-left') {
  218. this.data.imgData.width = Math.min(this.data.image_width, Math.max(0, this.data.imgData.width - widthDiff));
  219. this.data.imgData.height = Math.min(this.data.image_height, Math.max(this.data.imgData.height + heightDiff));
  220. this.data.imgData.top = Math.min(
  221. this.data.image_height - this.data.imgData.height,
  222. Math.max(0, this.data.imgData.top),
  223. );
  224. this.data.imgData.left = Math.min(
  225. this.data.image_width - this.data.imgData.width,
  226. Math.max(0, this.data.imgData.left + widthDiff),
  227. );
  228. } else if (type === 'bottom-right') {
  229. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  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. this.data.imgData.left = Math.min(
  236. this.data.image_width - this.data.imgData.width,
  237. Math.max(0, this.data.imgData.left),
  238. );
  239. }
  240. if (type === 'top') {
  241. this.data.imgData.height = Math.min(this.data.image_height, Math.max(0, this.data.imgData.height - heightDiff));
  242. this.data.imgData.top = Math.min(
  243. this.data.image_height - this.data.imgData.height,
  244. Math.max(0, this.data.imgData.top + heightDiff),
  245. );
  246. } else if (type === 'bottom') {
  247. this.data.imgData.height = Math.min(this.data.image_height, Math.max(this.data.imgData.height + heightDiff));
  248. this.data.imgData.top = Math.min(
  249. this.data.image_height - this.data.imgData.height,
  250. Math.max(0, this.data.imgData.top),
  251. );
  252. } else if (type === 'left') {
  253. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width - widthDiff));
  254. this.data.imgData.left = Math.min(
  255. this.data.image_width - this.data.imgData.width,
  256. Math.max(0, this.data.imgData.left + widthDiff),
  257. );
  258. } else if (type === 'right') {
  259. this.data.imgData.width = Math.min(this.data.image_width, Math.max(this.data.imgData.width + widthDiff));
  260. this.data.imgData.left = Math.min(
  261. this.data.image_width - this.data.imgData.width,
  262. Math.max(0, this.data.imgData.left),
  263. );
  264. }
  265. if (type === 'move') {
  266. this.data.imgData.top = Math.min(
  267. this.data.image_height - this.data.imgData.height,
  268. Math.max(0, this.data.imgData.top + heightDiff),
  269. );
  270. this.data.imgData.left = Math.min(
  271. this.data.image_width - this.data.imgData.width,
  272. Math.max(0, this.data.imgData.left + widthDiff),
  273. );
  274. }
  275. this.data.drag.startX = clientX;
  276. this.data.drag.startY = clientY;
  277. },
  278. /**
  279. * 鼠标抬起
  280. */
  281. mouseUp() {
  282. this.data.drag.dragging = false;
  283. // document.querySelectorAll('.drawing-background-img').style.cursor = 'auto';
  284. document.querySelectorAll('.drawing-background-img').forEach((item) => {
  285. item.style.cursor = 'auto';
  286. });
  287. },
  288. },
  289. };
  290. </script>
  291. <style lang="scss" scoped>
  292. .upload-file {
  293. // display: flex;
  294. column-gap: 12px;
  295. align-items: center;
  296. margin: 8px 0;
  297. .file-name {
  298. display: flex;
  299. column-gap: 14px;
  300. align-items: center;
  301. justify-content: space-between;
  302. max-width: 360px;
  303. padding: 8px 12px;
  304. font-size: 14px;
  305. color: #1d2129;
  306. background-color: #f7f8fa;
  307. span {
  308. display: flex;
  309. column-gap: 14px;
  310. align-items: center;
  311. }
  312. }
  313. .svg-icon {
  314. cursor: pointer;
  315. }
  316. }
  317. .image-size {
  318. display: flex;
  319. gap: 10px;
  320. align-items: center;
  321. padding: 20px 0;
  322. }
  323. .drawing-background-img {
  324. height: 310px;
  325. border: 1px dashed rgba(0, 0, 0, 8%);
  326. .img-set {
  327. position: relative;
  328. display: inline-grid;
  329. grid-template:
  330. ' . . . ' 2px
  331. ' . img . ' auto
  332. ' . . . ' 2px
  333. / 2px auto 2px;
  334. img {
  335. object-fit: contain;
  336. }
  337. .horizontal-line,
  338. .vertical-line {
  339. background-color: $main-color;
  340. }
  341. .horizontal-line {
  342. width: 100%;
  343. height: 2px;
  344. cursor: ns-resize;
  345. }
  346. .vertical-line {
  347. width: 2px;
  348. height: 100%;
  349. cursor: ew-resize;
  350. }
  351. .dot {
  352. z-index: 1;
  353. width: 6px;
  354. height: 6px;
  355. background-color: $main-color;
  356. &.top-left {
  357. top: -2px;
  358. left: -2px;
  359. }
  360. &.top-right {
  361. top: -2px;
  362. right: 2px;
  363. }
  364. &.bottom-left {
  365. bottom: 2px;
  366. left: -2px;
  367. }
  368. &.bottom-right {
  369. right: 2px;
  370. bottom: 2px;
  371. }
  372. &.top-left,
  373. &.bottom-right {
  374. position: relative;
  375. cursor: nwse-resize;
  376. }
  377. &.top-right,
  378. &.bottom-left {
  379. position: relative;
  380. cursor: nesw-resize;
  381. }
  382. }
  383. }
  384. }
  385. </style>