ArticlePanel.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <div class="article-panel">
  3. <div class="article-header">
  4. <div class="filter-container">
  5. <el-select
  6. v-model="filterForm.category_id"
  7. placeholder="全部分类"
  8. clearable
  9. @change="handleFilter"
  10. >
  11. <el-option label="全部分类" value="" />
  12. <el-option
  13. v-for="item in categories"
  14. :key="item.id"
  15. :label="item.name"
  16. :value="item.id"
  17. />
  18. </el-select>
  19. <el-select
  20. v-model="filterForm.status"
  21. placeholder="全部状态"
  22. clearable
  23. @change="handleFilter"
  24. >
  25. <el-option label="全部状态" value="" />
  26. <el-option label="草稿" :value="0" />
  27. <el-option label="已发布" :value="1" />
  28. <el-option label="已下线" :value="2" />
  29. </el-select>
  30. </div>
  31. <el-button type="primary" @click="handleAddArticle">新增文章</el-button>
  32. </div>
  33. <el-table
  34. v-loading="loading"
  35. :data="articles"
  36. border
  37. style="width: 100%">
  38. <el-table-column prop="id" label="ID" width="80" />
  39. <el-table-column label="封面" width="120">
  40. <template #default="scope">
  41. <el-image
  42. v-if="scope.row.cover"
  43. :src="scope.row.cover"
  44. :preview-src-list="[scope.row.cover]"
  45. fit="cover"
  46. style="width: 80px; height: 45px"
  47. />
  48. <span v-else>无封面</span>
  49. </template>
  50. </el-table-column>
  51. <el-table-column prop="title" label="标题" show-overflow-tooltip />
  52. <el-table-column prop="category_name" label="分类" width="120" />
  53. <el-table-column label="置顶" width="80">
  54. <template #default="scope">
  55. <el-tag :type="scope.row.is_top === 1 ? 'danger' : 'info'" size="small">
  56. {{ scope.row.is_top === 1 ? '是' : '否' }}
  57. </el-tag>
  58. </template>
  59. </el-table-column>
  60. <el-table-column label="状态" width="80">
  61. <template #default="scope">
  62. <el-tag :type="getStatusType(scope.row.status)" size="small">
  63. {{ getStatusText(scope.row.status) }}
  64. </el-tag>
  65. </template>
  66. </el-table-column>
  67. <el-table-column label="创建时间" width="160">
  68. <template #default="scope">
  69. {{ formatDateTime(scope.row.create_time) }}
  70. </template>
  71. </el-table-column>
  72. <el-table-column label="操作" width="200" fixed="right">
  73. <template #default="scope">
  74. <el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
  75. <el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
  76. </template>
  77. </el-table-column>
  78. </el-table>
  79. <div class="pagination-container">
  80. <el-pagination
  81. v-model:current-page="pagination.page"
  82. v-model:page-size="pagination.limit"
  83. :page-sizes="[10, 20, 50, 100]"
  84. layout="total, sizes, prev, pager, next, jumper"
  85. :total="pagination.total"
  86. @size-change="handleSizeChange"
  87. @current-change="handleCurrentChange"
  88. />
  89. </div>
  90. </div>
  91. </template>
  92. <script>
  93. import { defineComponent, ref, reactive, onMounted, computed } from 'vue'
  94. import { useRouter } from 'vue-router'
  95. import { ElMessageBox } from 'element-plus'
  96. import { useNews } from '../../composables/useNews'
  97. export default defineComponent({
  98. name: 'ArticlePanel',
  99. setup() {
  100. const router = useRouter()
  101. const {
  102. loading,
  103. articles,
  104. categories,
  105. pagination,
  106. getArticleList,
  107. getCategoryList,
  108. deleteArticle
  109. } = useNews()
  110. const filterForm = reactive({
  111. category_id: '',
  112. status: ''
  113. })
  114. // 获取文章列表
  115. const loadData = async () => {
  116. const params = {}
  117. if (filterForm.category_id) {
  118. params.category_id = filterForm.category_id
  119. }
  120. if (filterForm.status !== '') {
  121. params.status = filterForm.status
  122. }
  123. await getArticleList(params)
  124. }
  125. // 获取状态文本
  126. const getStatusText = (status) => {
  127. switch (status) {
  128. case 0: return '草稿'
  129. case 1: return '已发布'
  130. case 2: return '已下线'
  131. default: return '未知'
  132. }
  133. }
  134. // 获取状态类型
  135. const getStatusType = (status) => {
  136. switch (status) {
  137. case 0: return 'info'
  138. case 1: return 'success'
  139. case 2: return 'warning'
  140. default: return 'info'
  141. }
  142. }
  143. // 筛选
  144. const handleFilter = () => {
  145. pagination.page = 1
  146. loadData()
  147. }
  148. // 分页大小变化
  149. const handleSizeChange = (val) => {
  150. pagination.limit = val
  151. loadData()
  152. }
  153. // 页码变化
  154. const handleCurrentChange = (val) => {
  155. pagination.page = val
  156. loadData()
  157. }
  158. // 新增文章
  159. const handleAddArticle = () => {
  160. router.push('/news/article/create')
  161. }
  162. // 编辑文章
  163. const handleEdit = (row) => {
  164. router.push(`/news/article/edit/${row.id}`)
  165. }
  166. // 删除文章
  167. const handleDelete = (row) => {
  168. ElMessageBox.confirm('确定要删除该文章吗?删除后不可恢复', '提示', {
  169. confirmButtonText: '确定',
  170. cancelButtonText: '取消',
  171. type: 'warning'
  172. }).then(async () => {
  173. const success = await deleteArticle(row.id)
  174. if (success) {
  175. loadData()
  176. }
  177. }).catch(() => {})
  178. }
  179. // 时间格式化函数
  180. const formatDateTime = (dateTimeStr) => {
  181. if (!dateTimeStr || dateTimeStr === '0001-01-01T00:00:00Z') {
  182. return '-'
  183. }
  184. const date = new Date(dateTimeStr)
  185. // 转换为东八区时间
  186. const options = {
  187. year: 'numeric',
  188. month: '2-digit',
  189. day: '2-digit',
  190. hour: '2-digit',
  191. minute: '2-digit',
  192. second: '2-digit',
  193. timeZone: 'Asia/Shanghai'
  194. }
  195. return date.toLocaleString('zh-CN', options)
  196. }
  197. // 初始化
  198. onMounted(async () => {
  199. await getCategoryList()
  200. await loadData()
  201. })
  202. return {
  203. loading,
  204. articles,
  205. categories,
  206. pagination,
  207. filterForm,
  208. getStatusText,
  209. getStatusType,
  210. formatDateTime,
  211. handleFilter,
  212. handleSizeChange,
  213. handleCurrentChange,
  214. handleAddArticle,
  215. handleEdit,
  216. handleDelete
  217. }
  218. }
  219. })
  220. </script>
  221. <style scoped>
  222. .article-panel {
  223. padding: 20px 0;
  224. }
  225. .article-header {
  226. margin-bottom: 20px;
  227. display: flex;
  228. justify-content: space-between;
  229. }
  230. .filter-container {
  231. width: 380px;
  232. display: flex;
  233. gap: 10px;
  234. }
  235. .pagination-container {
  236. margin-top: 20px;
  237. display: flex;
  238. justify-content: flex-end;
  239. }
  240. </style>