BlogDetail.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <template>
  2. <content-with-sidebar v-if="Object.keys(blogDetail).length" class="cws-container cws-sidebar-right blog-wrapper">
  3. <!-- content -->
  4. <div class="blog-detail-wrapper">
  5. <b-row>
  6. <!-- blogs -->
  7. <b-col cols="12">
  8. <b-card :img-src="getBannerImg(blogDetail.id)" img-top img-alt="Blog Detail Pic" :title="blogDetail.title">
  9. <b-media no-body>
  10. <b-media-aside vertical-align="center" class="mr-50">
  11. <b-avatar href="javascript:void(0)" size="24" :src="getUserAvatar(blogDetail.authorId)" />
  12. </b-media-aside>
  13. <b-media-body>
  14. <small class="text-muted mr-50">by</small>
  15. <small>
  16. <b-link class="text-body">{{ getUserFullName(blogDetail.authorId) }}</b-link>
  17. </small>
  18. <span class="text-muted ml-75 mr-50">|</span>
  19. <small class="text-muted">{{ dateFormat(blogDetail.updated_at) }}</small>
  20. </b-media-body>
  21. </b-media>
  22. <div class="my-1 py-25">
  23. <b-link v-for="tag in blogDetail.tags" :key="tag">
  24. <b-badge pill class="mr-75" :variant="tagsColor(tag)">
  25. {{ tag }}
  26. </b-badge>
  27. </b-link>
  28. </div>
  29. <!-- eslint-disable vue/no-v-html -->
  30. <div class="blog-content" v-html="blogDetail.content" />
  31. <!-- user commnets -->
  32. <b-media v-for="user in blogDetail.UserComment" :key="user.avatar" no-body>
  33. <b-media-aside>
  34. <b-avatar size="60" :src="user.avatar" />
  35. </b-media-aside>
  36. <b-media-body>
  37. <h6 class="font-weight-bolder">
  38. {{ user.fullName }}
  39. </h6>
  40. <b-card-text>
  41. {{ user.comment }}
  42. </b-card-text>
  43. </b-media-body>
  44. </b-media>
  45. <!-- eslint-enable -->
  46. <hr class="my-2">
  47. <div class="d-flex align-items-center justify-content-between" id="commentIcon">
  48. <div class="d-flex align-items-center">
  49. <div class="d-flex align-items-center mr-1">
  50. <b-link class="mr-50">
  51. <feather-icon icon="MessageSquareIcon" size="21" class="text-body" />
  52. </b-link>
  53. <b-link>
  54. <div class="text-body">
  55. {{ blogComments.length }}
  56. </div>
  57. </b-link>
  58. </div>
  59. <div class="d-flex align-items-center">
  60. <b-link class="mr-50">
  61. <feather-icon size="21" icon="BookmarkIcon" class="text-body" />
  62. </b-link>
  63. <b-link>
  64. <div class="text-body">
  65. {{ kFormatter(blogDetail.bookmarked) }}
  66. </div>
  67. </b-link>
  68. </div>
  69. </div>
  70. <!-- dropdown -->
  71. <div class="blog-detail-share">
  72. <b-dropdown variant="link" toggle-class="p-0" no-caret right>
  73. <template #button-content>
  74. <feather-icon size="21" icon="Share2Icon" class="text-body" />
  75. </template>
  76. <b-dropdown-item v-for="icon in socialShareIcons" :key="icon" href="#">
  77. <feather-icon :icon="icon" size="18" />
  78. </b-dropdown-item>
  79. </b-dropdown>
  80. </div>
  81. <!--/ dropdown -->
  82. </div>
  83. </b-card>
  84. </b-col>
  85. <!--/ blogs -->
  86. <!-- blog comment -->
  87. <b-col id="blogComment" cols="12" class="mt-2">
  88. <h6 class="section-label">
  89. Comment
  90. </h6>
  91. <b-card v-for="(comment, index) in blogComments" :key="index">
  92. <b-media no-body>
  93. <b-media-aside class="mr-75">
  94. <b-avatar :src="getUserAvatar(comment.authorId)" size="38" />
  95. </b-media-aside>
  96. <b-media-body>
  97. <h6 class="font-weight-bolder mb-25">
  98. {{ getUserFullName(comment.authorId) }}
  99. </h6>
  100. <b-card-text>{{ dateFormat(comment.updated_at) }}</b-card-text>
  101. <b-card-text>
  102. {{ comment.comment }}
  103. </b-card-text>
  104. <b-link>
  105. <div class="d-inline-flex align-items-center">
  106. <feather-icon icon="CornerUpLeftIcon" size="18" class="mr-50" />
  107. <span>Reply</span>
  108. </div>
  109. </b-link>
  110. </b-media-body>
  111. </b-media>
  112. </b-card>
  113. </b-col>
  114. <!--/ blog comment -->
  115. <!-- Leave a Blog Comment -->
  116. <b-col cols="12" class="mt-2">
  117. <h6 class="section-label">
  118. Leave a Comment
  119. </h6>
  120. <b-card>
  121. <b-form>
  122. <b-row>
  123. <b-col cols="12">
  124. <b-form-group class="mb-2">
  125. <b-form-textarea id="comment-text" v-model="commentText" name="textarea" rows="4" placeholder="請輸入內容" />
  126. </b-form-group>
  127. </b-col>
  128. <b-col cols="12">
  129. <!-- <b-form-checkbox id="checkbox-1" v-model="commentCheckmark" name="checkbox-1" class="mb-2">
  130. Save my name, email, and website in this browser for the next time I comment.
  131. </b-form-checkbox> -->
  132. </b-col>
  133. <b-col cols="12">
  134. <b-button v-ripple.400="'rgba(255, 255, 255, 0.15)'" variant="primary" @click="postComment">
  135. Post Comment
  136. </b-button>
  137. </b-col>
  138. </b-row>
  139. </b-form>
  140. </b-card>
  141. </b-col>
  142. <!--/ Leave a Blog Comment -->
  143. </b-row>
  144. <!--/ blogs -->
  145. </div>
  146. <!--/ content -->
  147. <!-- sidebar -->
  148. <div slot="sidebar" class="blog-sidebar py-2 py-lg-0">
  149. <!-- input search -->
  150. <b-form-group class="blog-search">
  151. <b-input-group class="input-group-merge">
  152. <b-form-input id="search-input" v-model="search_query" placeholder="Search here" />
  153. <b-input-group-append class="cursor-pointer" is-text>
  154. <feather-icon icon="SearchIcon" />
  155. </b-input-group-append>
  156. </b-input-group>
  157. </b-form-group>
  158. <!--/ input search -->
  159. <!-- recent posts -->
  160. <div class="blog-recent-posts mt-3">
  161. <h6 class="section-label mb-75">
  162. Recent Posts
  163. </h6>
  164. <b-media v-for="(recentpost, index) in blogSidebar.recentPosts" :key="index" no-body
  165. :class="index ? 'mt-2' : ''">
  166. <b-media-aside class="mr-2">
  167. <b-link :to="{ name: 'blog-detail', params: { id: recentpost.id } }">
  168. <b-img :src="getBannerImg(recentpost.id)" :alt="getBannerImg(recentpost.id).slice(6)" width="100" rounded
  169. height="70" />
  170. </b-link>
  171. </b-media-aside>
  172. <b-media-body>
  173. <h6 class="blog-recent-post-title">
  174. <b-link :to="{ name: 'blog-detail', params: { id: recentpost.id } }" class="text-body-heading">
  175. {{ recentpost.title }}
  176. </b-link>
  177. </h6>
  178. <span class="text-muted mb-0">
  179. {{ dateFormat(recentpost.updated_at) }}
  180. </span>
  181. </b-media-body>
  182. </b-media>
  183. </div>
  184. <!--/ recent posts -->
  185. <!-- categories -->
  186. <div class="blog-categories mt-3">
  187. <h6 class="section-label mb-1">
  188. Categories
  189. </h6>
  190. <div v-for="category in blogSidebar.categories" :key="category.icon"
  191. class="d-flex justify-content-start align-items-center mb-75">
  192. <b-link>
  193. <b-avatar rounded size="32" :variant="tagsColor(category.category)" class="mr-75">
  194. <feather-icon :icon="category.icon" size="16" />
  195. </b-avatar>
  196. </b-link>
  197. <b-link>
  198. <div class="blog-category-title text-body">
  199. {{ category.category }}
  200. </div>
  201. </b-link>
  202. </div>
  203. </div>
  204. <!--/ categories -->
  205. </div>
  206. </content-with-sidebar>
  207. </template>
  208. <script>
  209. import {
  210. BFormInput,
  211. BMedia,
  212. BAvatar,
  213. BMediaAside,
  214. BMediaBody,
  215. BImg,
  216. BLink,
  217. BFormGroup,
  218. BInputGroup,
  219. BInputGroupAppend,
  220. BCard,
  221. BRow,
  222. BCol,
  223. BBadge,
  224. BCardText,
  225. BDropdown,
  226. BDropdownItem,
  227. BForm,
  228. BFormTextarea,
  229. BFormCheckbox,
  230. BButton,
  231. } from 'bootstrap-vue'
  232. import Ripple from 'vue-ripple-directive'
  233. import { kFormatter } from '@core/utils/filter'
  234. import ContentWithSidebar from '@core/layouts/components/content-with-sidebar/ContentWithSidebar.vue'
  235. import useJwt from '@/auth/jwt/useJwt'
  236. import { format } from 'date-fns'
  237. import { zhTW } from 'date-fns/locale'
  238. import { getUserData } from '@/auth/utils'
  239. export default {
  240. components: {
  241. BFormInput,
  242. BMedia,
  243. BAvatar,
  244. BMediaAside,
  245. BMediaBody,
  246. BLink,
  247. BCard,
  248. BRow,
  249. BCol,
  250. BFormGroup,
  251. BInputGroup,
  252. BInputGroupAppend,
  253. BImg,
  254. BBadge,
  255. BCardText,
  256. BDropdown,
  257. BForm,
  258. BDropdownItem,
  259. BFormTextarea,
  260. BFormCheckbox,
  261. BButton,
  262. ContentWithSidebar,
  263. },
  264. directives: {
  265. Ripple,
  266. },
  267. data() {
  268. return {
  269. blogId: this.$route.params.id,
  270. search_query: '',
  271. commentCheckmark: '',
  272. blogDetail: {},
  273. blogImageList: [],
  274. blogComments: [],
  275. blogSidebar: {},
  276. commentCount: [],
  277. commentText: '',
  278. socialShareIcons: ['GithubIcon', 'GitlabIcon', 'FacebookIcon', 'TwitterIcon', 'LinkedinIcon'],
  279. userData: getUserData(),
  280. }
  281. },
  282. created() {
  283. useJwt.getPost('/api/user/index').then(res => {
  284. this.userList = res.data;
  285. });
  286. useJwt.getPost('/api/blog-image/index').then(res => {
  287. this.blogImageList = res.data;
  288. });
  289. useJwt.getPost('/api/blog/show', { blogId: this.blogId }).then(res => {
  290. this.blogDetail = res.data;
  291. });
  292. useJwt.getPost('/api/blog/sidebar').then(res => {
  293. this.blogSidebar = res.data;
  294. });
  295. useJwt.getPost('/api/comment/index-by-blog', { blogId: this.blogId }).then(res => {
  296. this.blogComments = res.data;
  297. });
  298. useJwt.getPost('/api/comment/index-count', { blogId: this.blogId }).then(res => {
  299. this.commentCount = res.data;
  300. });
  301. setTimeout(() => {
  302. if (this.$route.hash == '#blogComment') {
  303. var el = document.getElementById('commentIcon');
  304. el.scrollIntoView({ behavior: "smooth" });
  305. }
  306. }, 500);
  307. },
  308. watch: {
  309. $route(to, from) {
  310. useJwt.getPost('/api/blog/detail', { blogId: this.$route.params.id }).then(res => {
  311. this.blogDetail = res.data
  312. })
  313. }
  314. },
  315. methods: {
  316. kFormatter,
  317. tagsColor(tag) {
  318. if (tag === 'Quote') return 'light-info'
  319. if (tag === 'Gaming') return 'light-danger'
  320. if (tag === 'Fashion') return 'light-primary'
  321. if (tag === 'Video') return 'light-warning'
  322. if (tag === 'Food') return 'light-success'
  323. return 'light-primary'
  324. },
  325. dateFormat(date) {
  326. return format(new Date(date), 'yyyy-MM-dd hh:mm', { locale: zhTW });
  327. },
  328. getUserAvatar(userId) {
  329. return ('/images/_/_/_/_/MessagePractise/resources/js/src/assets/images/avatars/' + userId + '-small.png');
  330. },
  331. getUserFullName(userId) {
  332. for (var i = 0; i < this.userList.length; i++) {
  333. if (this.userList[i].id == userId) {
  334. return this.userList[i].name;
  335. }
  336. }
  337. return 'none';
  338. },
  339. getBannerImg(blogId) {
  340. var imagePath;
  341. for (var i = 0; i < this.blogImageList.length; i++) {
  342. if (this.blogImageList[i].blogId == blogId) {
  343. imagePath = this.blogImageList[i].saveName;
  344. }
  345. }
  346. return ('/images/_/_/_/_/MessagePractise/storage/app/blog/banner/' + imagePath);
  347. },
  348. getCommentCount(blogId) {
  349. for (var i = 0; i < this.commentCount.length; i++) {
  350. if (this.commentCount[i].blogId == blogId) {
  351. return this.commentCount[i].total;
  352. }
  353. }
  354. return 0;
  355. },
  356. getCommentRank() {
  357. var rankingList = [];
  358. for (var i = 0; i < 4; i++) {
  359. rankingList.push(this.commentCount[i].blogId);
  360. }
  361. return rankingList;
  362. },
  363. postComment() {
  364. useJwt.getPost('/api/comment/store', {
  365. comment: this.commentText,
  366. blogId: this.$route.params.id,
  367. })
  368. .then(res => {
  369. this.blogComments.push({
  370. userId: this.userData.id,
  371. userFullName: this.userData.name,
  372. commented_at: new Date(),
  373. commentText: this.commentText,
  374. })
  375. this.commentText = ''
  376. })
  377. },
  378. }
  379. }
  380. </script>
  381. <style lang="scss">
  382. @import '~@resources/scss/vue/pages/page-blog.scss';
  383. </style>