ContentShow.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <template>
  2. <div class="content-show"
  3. :style="localOptions.style">
  4. <breadcrumbs class="q-mb-md" />
  5. <div class="content-show__summery">
  6. <div class="content-show__summery-info">
  7. <div class="content-show__thumbnail">
  8. <q-img :src="content.thumbnail" />
  9. </div>
  10. <div class="content-show__meta">
  11. <div class="content-show__title">
  12. {{ content.title }}
  13. </div>
  14. <div class="content-show__attrs">
  15. <div class="content-show__attrs-title">
  16. ویژگی ها:
  17. </div>
  18. <div class="content-show__attrs-items">
  19. <div v-if="content.is_physical"
  20. class="content-show__attrs-item">
  21. <div class="content-show__attrs-item-label">
  22. نوع:
  23. </div>
  24. <div class="content-show__attrs-item-value">
  25. {{ content.is_physical_info.label }}
  26. </div>
  27. </div>
  28. <div v-if="content.physical_type"
  29. class="content-show__attrs-item">
  30. <div class="content-show__attrs-item-label">
  31. جنس:
  32. </div>
  33. <div class="content-show__attrs-item-value">
  34. {{ content.physical_type_info.label }}
  35. </div>
  36. </div>
  37. <div v-if="content.weight"
  38. class="content-show__attrs-item">
  39. <div class="content-show__attrs-item-label">
  40. وزن:
  41. </div>
  42. <div class="content-show__attrs-item-value">
  43. {{ content.weight }}
  44. گرم
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. <div v-if="content.description"
  53. class="content-show__descriptions">
  54. <q-banner class="content-show__descriptions-title">
  55. توضیحات:
  56. </q-banner>
  57. <div class="content-show__descriptions-data"
  58. v-html="content.description" />
  59. </div>
  60. <div v-if="content.medias_info"
  61. class="content-show__medias">
  62. <medias-component :medias="content.medias_info" />
  63. </div>
  64. </div>
  65. </template>
  66. <script>
  67. import ShamsiDate from 'assets/ShamsiDate'
  68. import { Content } from 'src/models/Content.js'
  69. import { APIGateway } from 'src/api/APIGateway.js'
  70. import MediasComponent from './components/Medias.vue'
  71. import { ContentCategory } from 'src/models/ContentCategory.js'
  72. import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
  73. import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
  74. let html2pdf
  75. export default {
  76. name: 'ContentShow',
  77. components: { Breadcrumbs, MediasComponent },
  78. mixins: [mixinPrefetchServerData, mixinWidget],
  79. data () {
  80. return {
  81. content: new Content(),
  82. contentCategory: new ContentCategory(),
  83. contentCategoryParent: new ContentCategory(),
  84. contentCategoryParentParent: new ContentCategory(),
  85. addToCartLoading: false
  86. }
  87. },
  88. computed: {
  89. contentId () {
  90. return this.$route.params.id
  91. },
  92. breadcrumbsPath () {
  93. const path = [
  94. {
  95. label: 'محتوا',
  96. to: { name: 'Public.ContentCategoryParentParent.List' }
  97. }
  98. ]
  99. if (this.contentCategoryParentParent.id) {
  100. path.push({
  101. label: this.contentCategoryParentParent.title,
  102. to: { name: 'Public.ContentCategoryParentParent.Show', params: { category_id: this.contentCategoryParentParent.id } }
  103. })
  104. }
  105. if (this.contentCategoryParent.id) {
  106. path.push({
  107. label: this.contentCategoryParent.title,
  108. to: { name: 'Public.ContentCategoryParent.Show', params: { category_id: this.contentCategoryParent.id } }
  109. })
  110. }
  111. if (this.contentCategory.id) {
  112. path.push({
  113. label: this.contentCategory.title,
  114. to: { name: 'Public.ContentCategory.Show', params: { category_id: this.contentCategory.id } }
  115. })
  116. }
  117. if (this.content.id) {
  118. path.push({
  119. label: this.content.title,
  120. to: { name: 'Public.Content.Show', params: { id: this.content.id } }
  121. })
  122. }
  123. return path
  124. }
  125. },
  126. methods: {
  127. prefetchServerDataPromise () {
  128. return this.getContent()
  129. },
  130. prefetchServerDataPromiseThen (content) {
  131. this.content = new Content(content)
  132. this.content.content = false
  133. this.loadContentCategory()
  134. },
  135. prefetchServerDataPromiseCatch () {
  136. this.content.content = false
  137. },
  138. getTitledDateTime (dateTime) {
  139. return ShamsiDate.getTitledDateTime(dateTime)
  140. },
  141. getTime (time) {
  142. return ShamsiDate.getDateTime(time)
  143. },
  144. getContent () {
  145. this.content.content = true
  146. return APIGateway.content.get(this.contentId)
  147. },
  148. print () {
  149. this.convertPhotosToBase64()
  150. const html2pdfConfig = {
  151. margin: [0, 0, 0, 0],
  152. image: {
  153. type: 'jpeg',
  154. quality: 0.6
  155. },
  156. filename: this.content.title,
  157. html2canvas: {
  158. dpi: 1,
  159. scale: 2.5,
  160. letterRendering: true,
  161. useCORS: true
  162. },
  163. jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
  164. }
  165. html2pdf()
  166. .set(html2pdfConfig)
  167. .from(this.$refs.printArea)
  168. .save()
  169. .thenExternal(() => {
  170. this.downloadLoading = false
  171. })
  172. },
  173. toDataURL(src, callback, outputFormat) {
  174. const img = new Image()
  175. img.crossOrigin = 'Anonymous'
  176. img.onload = function() {
  177. const canvas = document.createElement('CANVAS')
  178. const ctx = canvas.getContext('2d')
  179. canvas.height = this.naturalHeight
  180. canvas.width = this.naturalWidth
  181. ctx.drawImage(this, 0, 0)
  182. const dataURL = canvas.toDataURL(outputFormat)
  183. callback(dataURL)
  184. }
  185. if (src.includes('?')) {
  186. img.src = src
  187. } else {
  188. img.src = src + '?test=123'
  189. }
  190. if (img.complete || img.complete === undefined) {
  191. img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
  192. img.src = src
  193. }
  194. },
  195. convertPhotosToBase64 () {
  196. this.convertSvgToBase64()
  197. this.convertImagesToBase64()
  198. },
  199. convertImagesToBase64 () {
  200. if (!this.$refs.printArea) {
  201. return
  202. }
  203. const images = this.$refs.printArea.getElementsByTagName('img')
  204. images.forEach(image => {
  205. this.toDataURL(image.src, function(dataUrl) {
  206. image.src = dataUrl
  207. })
  208. })
  209. },
  210. convertSvgToBase64 () {
  211. if (!this.$refs.printArea) {
  212. return
  213. }
  214. const svgs = this.$refs.printArea.getElementsByTagName('svg')
  215. svgs.forEach(svg => {
  216. // Convert the SVG node to HTML.
  217. const div = document.createElement('div')
  218. div.appendChild(svg.cloneNode(true))
  219. // Encode the SVG as base64
  220. const b64 = 'data:image/svg+xml;base64,' + window.btoa(div.innerHTML)
  221. const url = 'url("' + b64 + '")'
  222. svg.style.backgroundImage = url
  223. svg.style.backgroundSize = 'cover'
  224. if (svg.parentElement.classList.contains('brace-left')) {
  225. svg.style.backgroundPosition = 'left'
  226. }
  227. if (svg.parentElement.classList.contains('brace-center')) {
  228. svg.style.backgroundPosition = 'bottom'
  229. }
  230. if (svg.parentElement.classList.contains('brace-right')) {
  231. svg.style.backgroundPosition = 'right'
  232. }
  233. })
  234. },
  235. getContentCategoryInfo (contentCategoryId) {
  236. return APIGateway.contentCategory.get({ data: { id: contentCategoryId } })
  237. },
  238. loadContentCategory () {
  239. this.contentCategory.loading = true
  240. this.getContentCategoryInfo(this.content.category)
  241. .then((contentCategory) => {
  242. this.contentCategory = new ContentCategory(contentCategory)
  243. this.updateBreadcrumbs()
  244. if (this.contentCategory.parent) {
  245. this.getContentCategoryInfo(this.contentCategory.parent)
  246. .then((parentContentCategory) => {
  247. this.contentCategoryParent = new ContentCategory(parentContentCategory)
  248. this.updateBreadcrumbs()
  249. if (this.contentCategoryParent.parent) {
  250. this.getContentCategoryInfo(this.contentCategoryParent.parent)
  251. .then((parentParentContentCategory) => {
  252. this.contentCategoryParentParent = new ContentCategory(parentParentContentCategory)
  253. this.updateBreadcrumbs()
  254. })
  255. .finally(() => {
  256. this.contentCategoryParentParent.loading = false
  257. })
  258. }
  259. })
  260. .finally(() => {
  261. this.contentCategoryParent.loading = false
  262. })
  263. }
  264. })
  265. .finally(() => {
  266. this.contentCategory.loading = false
  267. })
  268. },
  269. updateBreadcrumbs () {
  270. const breadcrumbs = {
  271. visible: true,
  272. loading: false,
  273. path: this.breadcrumbsPath
  274. }
  275. this.$store.commit('AppLayout/updateBreadcrumbs', breadcrumbs)
  276. }
  277. }
  278. }
  279. </script>
  280. <style scoped lang="scss">
  281. .content-show {
  282. $price-section-with: 312px;
  283. .content-show__summery {
  284. $gap: 24px;
  285. display: flex;
  286. flex-wrap: wrap;
  287. gap: $gap;
  288. align-items: stretch;
  289. justify-content: flex-start;
  290. margin-bottom: 24px;
  291. .content-show__summery-info {
  292. border-radius: 8px;
  293. background: #F6F6F6;
  294. border: 1px solid #DFE1EC;
  295. padding: 24px;
  296. width: 100%;
  297. display: flex;
  298. flex-wrap: wrap;
  299. gap: 74px;
  300. align-items: flex-start;
  301. justify-content: flex-start;
  302. .content-show__thumbnail {
  303. width: 239px;
  304. @media screen and (max-width: 600px) {
  305. & {
  306. width: 100%;
  307. }
  308. }
  309. }
  310. .content-show__meta {
  311. .content-show__title {
  312. font-size: 22px;
  313. margin-bottom: 35px;
  314. }
  315. .content-show__attrs {
  316. .content-show__attrs-title {
  317. color: #727272;
  318. font-size: 16px;
  319. margin-bottom: 16px;
  320. }
  321. .content-show__attrs-items {
  322. .content-show__attrs-item {
  323. font-size: 16px;
  324. display: flex;
  325. gap: 4px;
  326. align-items: center;
  327. margin-bottom: 20px;
  328. justify-content: flex-start;
  329. &:before {
  330. content: ' ';
  331. width: 8px;
  332. height: 8px;
  333. border-radius: 100%;
  334. background: #475F4A;
  335. margin-right: 6px;
  336. }
  337. .content-show__attrs-item-label {
  338. color: #797979;
  339. }
  340. .content-show__attrs-item-value {
  341. color: #212121;
  342. }
  343. }
  344. }
  345. }
  346. @media screen and (max-width: 600px) {
  347. & {
  348. width: 100%;
  349. }
  350. }
  351. }
  352. @media screen and (max-width: 900px) {
  353. & {
  354. width: 100%;
  355. }
  356. }
  357. @media screen and (max-width: 600px) {
  358. & {
  359. gap: 16px;
  360. padding-bottom: 0;
  361. }
  362. }
  363. }
  364. }
  365. .content-show__contents {
  366. margin-bottom: 47px;
  367. :deep(.q-expansion-item) {
  368. .q-expansion-item__container {
  369. .q-item {
  370. background: #F6F6F6;
  371. border: 1px solid #DFE1EC;
  372. border-radius: 8px;
  373. .q-item__section--avatar {
  374. padding-right: 8px;
  375. font-size: 36px;
  376. min-width: 36px;
  377. color: #AAA095;
  378. }
  379. .q-item__label {
  380. font-size: 18px;
  381. color: #212121;
  382. }
  383. }
  384. .q-expansion-item__content {
  385. border: 1px solid #DFE1EC;
  386. border-top: none;
  387. }
  388. }
  389. &.q-expansion-item--expanded {
  390. .q-expansion-item__container {
  391. .q-item {
  392. border-radius: 8px 8px 0 0;
  393. }
  394. }
  395. }
  396. }
  397. .content-item-component {
  398. border-bottom: 1px solid #DFE1EC;
  399. &:last-child {
  400. border: none;
  401. }
  402. }
  403. }
  404. .content-show__descriptions {
  405. padding: 32px 32px 70px 32px;
  406. border: 1px solid #DFE1EC;
  407. border-radius: 8px;
  408. background: #F6F6F6;
  409. margin-bottom: 20px;
  410. .content-show__descriptions-title{
  411. color: #727272;
  412. font-size: 16px;
  413. margin-bottom: 21px;
  414. }
  415. .content-show__descriptions-data {
  416. color: #212121;
  417. font-size: 16px;
  418. }
  419. }
  420. .content-show__medias {
  421. background: #FFFFFF;
  422. border: 1px solid #DFE1EC;
  423. border-radius: 8px;
  424. padding: 8px 8px;
  425. }
  426. }
  427. </style>