PicturePreview.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <div ref="pictureArea" class="picture-area" :style="getAreaStyle()">
  3. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  4. <div ref="pictureAreaBox" class="main">
  5. <div class="view-area">
  6. <div class="picture-area">
  7. <template v-if="'list' === data.property.view_method">
  8. <el-carousel
  9. ref="pictureCarousel"
  10. class="view-list"
  11. indicator-position="none"
  12. :autoplay="false"
  13. :style="{ height: elementHeight - 144 - 17 + 'px' }"
  14. @change="handleChange"
  15. >
  16. <el-carousel-item v-for="(file, i) in data.file_list" :key="i">
  17. <el-image
  18. :id="file.file_id"
  19. :src="file.file_url"
  20. fit="contain"
  21. :preview-src-list="data.file_list.map((x) => x.file_url)"
  22. />
  23. </el-carousel-item>
  24. </el-carousel>
  25. <div class="container-box">
  26. <button v-if="viewLeftRightBtn" class="arrow left" @click="scroll(-1)">
  27. <i class="el-icon-arrow-left"></i>
  28. </button>
  29. <ul ref="container" class="view-list-bottom" :style="{ width: elementWidth + 'px' }">
  30. <li v-for="(file, i) in data.file_list" :key="i" @click="handleIndicatorClick(i)">
  31. <el-image :id="file.file_id" :src="file.file_url" fit="contain" />
  32. </li>
  33. </ul>
  34. <button v-if="viewLeftRightBtn" class="arrow right" @click="scroll(1)">
  35. <i class="el-icon-arrow-right"></i>
  36. </button>
  37. </div>
  38. </template>
  39. <ul v-else class="view-independent">
  40. <li v-for="(file, i) in data.file_list" :key="i" @click="handleIndicatorClick(i)">
  41. <el-image :id="file.file_id" :src="file.file_url" fit="contain" />
  42. </li>
  43. </ul>
  44. </div>
  45. <div v-if="'list' === data.property.view_method && isEnable(data.property.view_memo)" class="memo-area">
  46. <div v-for="(file, i) in data.file_info_list" :key="i">
  47. <div v-if="curPictureMemoIndex === i" class="title-div">{{ file.title ?? file.title }}</div>
  48. <div v-if="curPictureMemoIndex === i" class="memo-div">{{ file.intro ?? file.intro }}</div>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. </template>
  55. <script>
  56. import { getPictureData } from '@/views/book/courseware/data/picture';
  57. import PreviewMixin from '../common/PreviewMixin';
  58. export default {
  59. name: 'PicturePreview',
  60. mixins: [PreviewMixin],
  61. inject: ['getDragStatus'],
  62. data() {
  63. return {
  64. data: getPictureData(),
  65. curImgIndex: 0,
  66. elementWidth: 0,
  67. elementHeight: 0,
  68. viewLeftRightBtn: false,
  69. fileLen: 0,
  70. elementID: '',
  71. pictureObserversMap: {},
  72. curPictureMemoIndex: 0,
  73. };
  74. },
  75. watch: {
  76. data: {
  77. handler(val) {
  78. this.fileLen = val.file_list.length;
  79. if (this.fileLen > 0 && this.data.property.view_method === 'list') {
  80. const ele = this.$refs.pictureAreaBox;
  81. // this.elementWidth = ele.clientWidth;
  82. // this.elementHeight = ele.clientHeight;
  83. const sn_position = this.data.property.sn_position;
  84. const viewMemo = this.isEnable(this.data.property.view_memo);
  85. // 序号在左和右补齐序号高度,去掉padding(8*2)
  86. if (sn_position.includes('left') || sn_position.includes('right')) {
  87. this.elementWidth = viewMemo ? (ele.clientWidth - 16) * 0.8 : ele.clientWidth - 16;
  88. this.elementHeight = ele.clientHeight + 30;
  89. } else {
  90. this.elementWidth = viewMemo ? ele.clientWidth * 0.8 : ele.clientWidth;
  91. this.elementHeight = ele.clientHeight;
  92. }
  93. const mainEle = this.$refs.pictureArea;
  94. // 检查元素是否包含已知的类名
  95. mainEle.classList.forEach((className) => {
  96. // 排除已知的类名
  97. if (className !== 'audio-area') {
  98. // 打印另一个类名
  99. this.elementID = className;
  100. }
  101. });
  102. }
  103. },
  104. deep: true,
  105. },
  106. elementWidth() {
  107. this.isViewLeftRightBtn();
  108. },
  109. },
  110. mounted() {
  111. this.$nextTick(() => {
  112. const canvasElement = document.querySelector('.canvas');
  113. if (!canvasElement) {
  114. const ele = this.$refs.pictureAreaBox;
  115. const sn_position = this.data.property.sn_position;
  116. const viewMemo = this.isEnable(this.data.property.view_memo);
  117. // 序号在左和右补齐序号高度,去掉padding(8*2)
  118. if (sn_position.includes('left') || sn_position.includes('right')) {
  119. this.elementWidth = viewMemo ? (ele.clientWidth - 16) * 0.8 : ele.clientWidth - 16;
  120. this.elementHeight = ele.clientHeight + 30;
  121. } else {
  122. this.elementWidth = viewMemo ? ele.clientWidth * 0.8 : ele.clientWidth;
  123. this.elementHeight = ele.clientHeight;
  124. }
  125. this.fileLen = this.data.file_list.length;
  126. this.isViewLeftRightBtn();
  127. return;
  128. }
  129. const mainEle = this.$refs.pictureArea;
  130. // 检查元素是否包含已知的类名
  131. mainEle.classList.forEach((className) => {
  132. // 排除已知的类名
  133. if (className !== 'audio-area') {
  134. // 打印另一个类名
  135. this.elementID = className;
  136. }
  137. });
  138. const instanceName = `observer_${this.elementID}`;
  139. this.pictureObserversMap[instanceName] = new ResizeObserver((entries) => {
  140. if (!this.getDragStatus()) return;
  141. for (let entry of entries) {
  142. window.requestAnimationFrame(() => {
  143. const sn_position = this.data.property.sn_position;
  144. const viewMemo = this.isEnable(this.data.property.view_memo);
  145. // 序号在上方和下方减去序号高度,在左右去掉padding(8*2)
  146. if (sn_position.includes('top') || sn_position.includes('bottom')) {
  147. this.elementWidth = viewMemo ? entry.contentRect.width * 0.8 : entry.contentRect.width;
  148. this.elementHeight = entry.contentRect.height - 30;
  149. } else {
  150. this.elementWidth = viewMemo ? (entry.contentRect.width - 16) * 0.8 : entry.contentRect.width - 16;
  151. this.elementHeight = entry.contentRect.height;
  152. }
  153. });
  154. }
  155. });
  156. this.pictureObserversMap[instanceName].observe(this.$el);
  157. });
  158. },
  159. beforeDestroy() {
  160. Object.values(this.pictureObserversMap).forEach((observer) => {
  161. observer.disconnect();
  162. });
  163. },
  164. methods: {
  165. handleResize() {
  166. const width = this.$refs.pictureAreaBox.clientWidth;
  167. if (width !== this.elementWidth) {
  168. this.elementWidth = width;
  169. }
  170. },
  171. // 是否显示左右箭头
  172. isViewLeftRightBtn() {
  173. // 计算底部列表图片宽度
  174. let listWidth = this.fileLen * this.data.min_width + 13 * (this.fileLen - 1);
  175. if (listWidth > this.elementWidth) {
  176. this.viewLeftRightBtn = true;
  177. } else {
  178. this.viewLeftRightBtn = false;
  179. }
  180. },
  181. handleIndicatorClick(index) {
  182. // 获取 Carousel 实例
  183. const carousel = this.$refs.pictureCarousel;
  184. // 切换到对应索引的图片
  185. carousel.setActiveItem(index);
  186. this.curPictureMemoIndex = index;
  187. },
  188. handleChange(index) {
  189. this.curPictureMemoIndex = index;
  190. },
  191. // 滚动图片列表
  192. scroll(direction) {
  193. const container = this.$refs.container;
  194. const step = Number(this.data.min_width) + 13; // 每次滚动的距离
  195. this.scrollPosition += step * direction;
  196. container.scrollLeft += step * direction;
  197. },
  198. autoResize(entry) {
  199. window.requestAnimationFrame(() => {
  200. const sn_position = this.data.property.sn_position;
  201. const viewMemo = this.isEnable(this.data.property.view_memo);
  202. // 序号在上方和下方减去序号高度,在左右去掉padding(8*2)
  203. if (sn_position.includes('top') || sn_position.includes('bottom')) {
  204. this.elementWidth = viewMemo ? entry.contentRect.width * 0.8 : entry.contentRect.width;
  205. // this.elementHeight = entry.contentRect.height - 30;
  206. } else {
  207. this.elementWidth = viewMemo ? (entry.contentRect.width - 16) * 0.8 : entry.contentRect.width - 16;
  208. this.elementHeight = entry.contentRect.height;
  209. }
  210. });
  211. },
  212. },
  213. };
  214. </script>
  215. <style lang="scss" scoped>
  216. .picture-area {
  217. display: grid;
  218. gap: 6px;
  219. float: left;
  220. padding: 8px;
  221. > .main {
  222. display: flex;
  223. margin: 4px auto;
  224. > span {
  225. display: flex;
  226. }
  227. }
  228. .main {
  229. grid-area: main;
  230. width: 100%;
  231. .view-area {
  232. .memo-area {
  233. float: left;
  234. width: 15%;
  235. padding-left: 5px;
  236. text-align: left;
  237. border-left: 1px solid #eee;
  238. .title-div {
  239. font-size: 16px;
  240. font-weight: 600;
  241. }
  242. .memo-div {
  243. color: #706f78;
  244. overflow-wrap: break-word;
  245. }
  246. }
  247. :deep .el-carousel {
  248. margin-bottom: 17px;
  249. background-color: #d9d9d9;
  250. &__container::before {
  251. display: inline-block;
  252. padding-bottom: 55%;
  253. content: '';
  254. }
  255. &__container {
  256. height: 100%;
  257. }
  258. &__item {
  259. display: flex;
  260. justify-content: center;
  261. text-align: center;
  262. }
  263. }
  264. .container-box {
  265. position: relative;
  266. .left {
  267. left: 0;
  268. }
  269. .arrow {
  270. position: absolute;
  271. top: 0;
  272. z-index: 10;
  273. height: 144px;
  274. text-align: center;
  275. background-color: rgba(0, 0, 0, 10%);
  276. border-radius: 0;
  277. }
  278. .arrow:hover {
  279. background-color: rgba(0, 0, 0, 30%);
  280. }
  281. .right {
  282. right: 0;
  283. }
  284. .view-list-bottom {
  285. display: flex;
  286. flex-wrap: nowrap;
  287. column-gap: 13px;
  288. min-width: 144px;
  289. overflow: hidden;
  290. li {
  291. display: flex;
  292. align-items: center;
  293. justify-content: center;
  294. background-color: #d9d9d9;
  295. }
  296. .el-image {
  297. width: 144px;
  298. height: 144px;
  299. cursor: pointer;
  300. }
  301. }
  302. }
  303. .view-independent {
  304. display: flex;
  305. flex-wrap: wrap;
  306. gap: 40px;
  307. li {
  308. display: flex;
  309. align-items: center;
  310. justify-content: center;
  311. background-color: #d9d9d9;
  312. }
  313. .el-image {
  314. width: 144px;
  315. height: 144px;
  316. }
  317. }
  318. }
  319. }
  320. }
  321. </style>