BlogList.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <template>
  2. <content-with-sidebar v-if="Object.keys(blogList).length" class="blog-wrapper">
  3. <!-- blogs -->
  4. <b-row class="blog-list-wrapper">
  5. <b-col md="6">
  6. <b-col v-for="(blog, index) in blogList.slice(0, Math.ceil(blogList.length / 2))" :key="index" md="12">
  7. <b-card tag="article" no-body>
  8. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }">
  9. <b-img :src="getBannerImg(blog.id)" :alt="getBannerImg(blog.id).slice(5)" class="card-img-top" />
  10. </b-link>
  11. <b-card-body>
  12. <b-card-title>
  13. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }"
  14. class="blog-title-truncate text-body-heading">
  15. {{ blog.title }}
  16. </b-link>
  17. </b-card-title>
  18. <b-media no-body>
  19. <b-media-aside vertical-align="center" class="mr-50">
  20. <b-avatar href="javascript:void(0)" size="24" :src="getUserAvatar(blog.authorId)" />
  21. </b-media-aside>
  22. <b-media-body>
  23. <small class="text-muted mr-50">by</small>
  24. <small>
  25. <b-link class="text-body">{{ getUserFullName(blog.authorId) }}</b-link>
  26. </small>
  27. <span class="text-muted ml-75 mr-50">|</span>
  28. <small class="text-muted">{{ dateFormat(blog.updated_at) }}</small>
  29. </b-media-body>
  30. </b-media>
  31. <div class="my-1 py-25">
  32. <b-link v-for="(tag, index) in JSON.parse(blog.tags)" :key="index">
  33. <b-badge pill class="mr-75" :variant="tagsColor(tag)">
  34. {{ tag }}
  35. </b-badge>
  36. </b-link>
  37. </div>
  38. <b-card-text class="blog-content-truncate">
  39. {{ blog.content }}
  40. </b-card-text>
  41. <hr>
  42. <div class="d-flex justify-content-between align-items-center">
  43. <b-link :to="{ path: `/blog/${blog.id}#blogComment` }">
  44. <div class="d-flex align-items-center text-body">
  45. <feather-icon icon="MessageSquareIcon" class="mr-50" />
  46. <span class="font-weight-bold">{{ getCommentCount(blog.id) }} Comments</span>
  47. </div>
  48. </b-link>
  49. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }" class="font-weight-bold">
  50. Read More
  51. </b-link>
  52. </div>
  53. </b-card-body>
  54. </b-card>
  55. </b-col>
  56. </b-col>
  57. <b-col md="6">
  58. <b-col v-for="(blog, index) in blogList.slice(Math.ceil(blogList.length / 2))" :key="index" md="12">
  59. <b-card tag="article" no-body>
  60. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }">
  61. <b-img :src="getBannerImg(blog.id)" :alt="getBannerImg(blog.id).slice(5)" class="card-img-top" />
  62. </b-link>
  63. <b-card-body>
  64. <b-card-title>
  65. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }"
  66. class="blog-title-truncate text-body-heading">
  67. {{ blog.title }}
  68. </b-link>
  69. </b-card-title>
  70. <b-media no-body>
  71. <b-media-aside vertical-align="center" class="mr-50">
  72. <b-avatar href="javascript:void(0)" size="24" :src="getUserAvatar(blog.authorId)" />
  73. </b-media-aside>
  74. <b-media-body>
  75. <small class="text-muted mr-50">by</small>
  76. <small>
  77. <b-link class="text-body">{{ getUserFullName(blog.authorId) }}</b-link>
  78. </small>
  79. <span class="text-muted ml-75 mr-50">|</span>
  80. <small class="text-muted">{{ dateFormat(blog.updated_at) }}</small>
  81. </b-media-body>
  82. </b-media>
  83. <div class="my-1 py-25">
  84. <b-link v-for="(tag, index) in JSON.parse(blog.tags)" :key="index">
  85. <b-badge pill class="mr-75" :variant="tagsColor(tag)">
  86. {{ tag }}
  87. </b-badge>
  88. </b-link>
  89. </div>
  90. <b-card-text class="blog-content-truncate">
  91. {{ blog.content }}
  92. </b-card-text>
  93. <hr>
  94. <div class="d-flex justify-content-between align-items-center">
  95. <b-link :to="{ path: `/blog/${blog.id}#blogComment` }">
  96. <div class="d-flex align-items-center text-body">
  97. <feather-icon icon="MessageSquareIcon" class="mr-50" />
  98. <span class="font-weight-bold">{{ getCommentCount(blog.id) }} Comments</span>
  99. </div>
  100. </b-link>
  101. <b-link :to="{ name: 'blog-detail', params: { id: blog.id } }" class="font-weight-bold">
  102. Read More
  103. </b-link>
  104. </div>
  105. </b-card-body>
  106. </b-card>
  107. </b-col>
  108. </b-col>
  109. <b-col cols="12">
  110. <!-- pagination -->
  111. <div class="my-2" @click="getBlogListPerpage">
  112. <b-pagination v-model="currentPage" align="center" :per-page="perPage" :total-rows="rows" first-number
  113. last-number prev-class="prev-item" next-class="next-item">
  114. <template #prev-text>
  115. <feather-icon icon="ChevronLeftIcon" size="18" />
  116. </template>
  117. <template #next-text>
  118. <feather-icon icon="ChevronRightIcon" size="18" />
  119. </template>
  120. </b-pagination>
  121. </div>
  122. </b-col>
  123. </b-row>
  124. <!--/ blogs -->
  125. <!-- sidebar -->
  126. <div slot="sidebar" class="blog-sidebar py-2 py-lg-0">
  127. <!-- input search -->
  128. <b-form-group class="blog-search">
  129. <b-input-group class="input-group-merge">
  130. <b-form-input id="search-input" v-model="search_query" placeholder="Search here" />
  131. <b-input-group-append class="cursor-pointer" is-text>
  132. <feather-icon icon="SearchIcon" />
  133. </b-input-group-append>
  134. </b-input-group>
  135. </b-form-group>
  136. <!--/ input search -->
  137. <!-- recent posts -->
  138. <div class="blog-recent-posts mt-3">
  139. <h6 class="section-label mb-75">
  140. Recent Posts
  141. </h6>
  142. <b-media v-for="(recentpost, index) in blogSidebar.recentPosts" :key="index" no-body
  143. :class="index ? 'mt-2' : ''">
  144. <b-media-aside class="mr-2">
  145. <b-link :to="{ name: 'blog-detail', params: { id: recentpost.id } }">
  146. <b-img :src="getBannerImg(recentpost.id)" :alt="getBannerImg(recentpost.id).slice(6)" width="100" rounded
  147. height="70" />
  148. </b-link>
  149. </b-media-aside>
  150. <b-media-body>
  151. <h6 class="blog-recent-post-title">
  152. <b-link :to="{ name: 'blog-detail', params: { id: recentpost.id } }" class="text-body-heading">
  153. {{ recentpost.title }}
  154. </b-link>
  155. </h6>
  156. <span class="text-muted mb-0">
  157. {{ dateFormat(recentpost.updated_at) }}
  158. </span>
  159. </b-media-body>
  160. </b-media>
  161. </div>
  162. <!--/ recent posts -->
  163. <!-- categories -->
  164. <div class="blog-categories mt-3">
  165. <h6 class="section-label mb-1">
  166. Categories
  167. </h6>
  168. <div v-for="category in blogSidebar.categories" :key="category.icon"
  169. class="d-flex justify-content-start align-items-center mb-75">
  170. <b-link>
  171. <b-avatar rounded size="32" :variant="tagsColor(category.category)" class="mr-75">
  172. <feather-icon :icon="category.icon" size="16" />
  173. </b-avatar>
  174. </b-link>
  175. <b-link>
  176. <div class="blog-category-title text-body">
  177. {{ category.category }}
  178. </div>
  179. </b-link>
  180. </div>
  181. </div>
  182. <!--/ categories -->
  183. </div>
  184. <!--/ sidebar -->
  185. </content-with-sidebar>
  186. </template>
  187. <script>
  188. import {
  189. BRow,
  190. BCol,
  191. BCard,
  192. BFormInput,
  193. BCardText,
  194. BCardTitle,
  195. BMedia,
  196. BAvatar,
  197. BMediaAside,
  198. BMediaBody,
  199. BImg,
  200. BCardBody,
  201. BLink,
  202. BBadge,
  203. BFormGroup,
  204. BInputGroup,
  205. BInputGroupAppend,
  206. BPagination,
  207. } from 'bootstrap-vue'
  208. import { kFormatter } from '@core/utils/filter'
  209. import ContentWithSidebar from '@core/layouts/components/content-with-sidebar/ContentWithSidebar.vue'
  210. import useJwt from '@/auth/jwt/useJwt'
  211. import { format } from 'date-fns'
  212. import { zhTW } from 'date-fns/locale'
  213. export default {
  214. components: {
  215. BRow,
  216. BCol,
  217. BCard,
  218. BFormInput,
  219. BCardText,
  220. BCardBody,
  221. BCardTitle,
  222. BMedia,
  223. BAvatar,
  224. BMediaAside,
  225. BMediaBody,
  226. BLink,
  227. BBadge,
  228. BFormGroup,
  229. BInputGroup,
  230. BInputGroupAppend,
  231. BImg,
  232. BPagination,
  233. ContentWithSidebar,
  234. },
  235. data() {
  236. return {
  237. search_query: '',
  238. userList: [],
  239. blogImageList: [],
  240. blogList: [],
  241. commentCount: [],
  242. blogSidebar: {},
  243. currentPage: 1,
  244. perPage: 4,
  245. rows: 4,
  246. }
  247. },
  248. created() {
  249. useJwt.getPost('/api/blog/total-count').then(res => {
  250. this.rows = res.data;
  251. });
  252. useJwt.getPost('/api/user/index').then(res => {
  253. this.userList = res.data;
  254. });
  255. useJwt.getPost('/api/comment/index-count').then(res => {
  256. this.commentCount = res.data;
  257. });
  258. useJwt.getPost('/api/blog-image/index').then(res => {
  259. this.blogImageList = res.data;
  260. });
  261. useJwt.getPost('/api/blog/index-page', { offset: 0, rows: this.perPage }).then(res => {
  262. this.blogList = res.data;
  263. });
  264. useJwt.getPost('/api/blog/sidebar').then(res => {
  265. this.blogSidebar = res.data;
  266. });
  267. },
  268. methods: {
  269. kFormatter,
  270. tagsColor(tag) {
  271. if (tag === 'Quote') return 'light-info'
  272. if (tag === 'Gaming') return 'light-danger'
  273. if (tag === 'Fashion') return 'light-primary'
  274. if (tag === 'Video') return 'light-warning'
  275. if (tag === 'Food') return 'light-success'
  276. return 'light-primary'
  277. },
  278. dateFormat(date) {
  279. return format(new Date(date), 'yyyy-MM-dd hh:mm', { locale: zhTW });
  280. },
  281. getUserAvatar(userId) {
  282. return ('/images/_/_/_/_/MessagePractise/resources/js/src/assets/images/avatars/' + userId + '-small.png');
  283. },
  284. getUserFullName(userId) {
  285. for (var i = 0; i < this.userList.length; i++) {
  286. if (this.userList[i].id == userId) {
  287. return this.userList[i].name;
  288. }
  289. }
  290. return 'none';
  291. },
  292. getBannerImg(blogId) {
  293. var imagePath;
  294. for (var i = 0; i < this.blogImageList.length; i++) {
  295. if (this.blogImageList[i].blogId == blogId) {
  296. imagePath = this.blogImageList[i].saveName;
  297. }
  298. }
  299. return ('/images/_/_/_/_/MessagePractise/storage/app/blog/banner/' + imagePath);
  300. },
  301. getCommentCount(blogId) {
  302. for (var i = 0; i < this.commentCount.length; i++) {
  303. if (this.commentCount[i].blogId == blogId) {
  304. return this.commentCount[i].total;
  305. }
  306. }
  307. return 0;
  308. },
  309. getCommentRank() {
  310. var rankingList = [];
  311. for (var i = 0; i < 4; i++) {
  312. rankingList.push(this.commentCount[i].blogId);
  313. }
  314. return rankingList;
  315. },
  316. getBlogListPerpage() {
  317. useJwt.getPost('/api/blog/index-page', { offset: ((this.currentPage - 1) * this.perPage), rows: this.perPage }).then(res => {
  318. this.blogList = res.data;
  319. })
  320. },
  321. },
  322. }
  323. </script>
  324. <style lang="scss">
  325. @import '~@resources/scss/vue/pages/page-blog.scss';
  326. </style>