ArticleCategory.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <template>
  2. <div class="article-category admin-page">
  3. <el-card>
  4. <div slot="header" class="admin-page-header">
  5. <span class="admin-page-title">文章分类管理</span>
  6. <div class="admin-page-actions">
  7. <el-input v-model="keyword" placeholder="搜索分类..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
  8. <el-button type="primary" size="small" @click="handleCreate">+ 新增分类</el-button>
  9. </div>
  10. </div>
  11. <div class="table-scroll-wrap-sm">
  12. <el-table :data="filteredData" v-loading="loading" border stripe :max-height="tableHeight">
  13. <el-table-column prop="id" label="ID" width="70" />
  14. <el-table-column label="图标" width="80">
  15. <template slot-scope="{ row }">
  16. <span style="font-size: 22px">{{ row.icon || '📄' }}</span>
  17. </template>
  18. </el-table-column>
  19. <el-table-column prop="name" label="名称" min-width="140" />
  20. <el-table-column label="颜色" width="100">
  21. <template slot-scope="{ row }">
  22. <span v-if="row.color" :style="{ display: 'inline-block', width: '20px', height: '20px', backgroundColor: row.color, borderRadius: '4px', verticalAlign: 'middle', border: '1px solid #ddd' }"></span>
  23. <span style="margin-left: 6px; color: #999;">{{ row.color || '-' }}</span>
  24. </template>
  25. </el-table-column>
  26. <el-table-column prop="sortOrder" label="排序" width="70" />
  27. <el-table-column label="状态" width="80">
  28. <template slot-scope="{ row }">
  29. <el-tag :type="row.status === 1 || row.status === 'ACTIVE' ? 'success' : 'danger'" size="small">
  30. {{ row.status === 1 || row.status === 'ACTIVE' ? '启用' : '停用' }}
  31. </el-tag>
  32. </template>
  33. </el-table-column>
  34. <el-table-column label="操作" width="180" fixed="right">
  35. <template slot-scope="{ row }">
  36. <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
  37. <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
  38. <el-button size="mini">
  39. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  40. </el-button>
  41. <el-dropdown-menu slot="dropdown">
  42. <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
  43. </el-dropdown-menu>
  44. </el-dropdown>
  45. </template>
  46. </el-table-column>
  47. </el-table></div>
  48. </el-card>
  49. <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑分类' : '新增分类'" width="500px" :close-on-click-modal="false">
  50. <el-form :model="form" label-width="80px">
  51. <el-form-item label="名称">
  52. <el-input v-model="form.name" placeholder="请输入分类名称" />
  53. </el-form-item>
  54. <el-form-item label="图标">
  55. <el-input v-model="form.icon" placeholder="如: 📚 或 emoji 图标" />
  56. </el-form-item>
  57. <el-form-item label="颜色">
  58. <el-color-picker v-model="form.color" />
  59. </el-form-item>
  60. <el-form-item label="排序">
  61. <el-input-number v-model="form.sortOrder" :min="0" :max="999" />
  62. </el-form-item>
  63. <el-form-item label="状态">
  64. <el-switch v-model="form.statusBool" active-text="启用" inactive-text="停用" />
  65. </el-form-item>
  66. </el-form>
  67. <div slot="footer">
  68. <el-button @click="dialogVisible = false">取消</el-button>
  69. <el-button type="primary" @click="handleSubmit" :loading="submitting">保存</el-button>
  70. </div>
  71. </el-dialog>
  72. <el-dialog title="操作成功" :visible.sync="successDialogVisible" width="400px" :close-on-click-modal="false">
  73. <div style="text-align:center;padding:10px 0">
  74. <i class="el-icon-success" style="color:#67C23A;font-size:32px"></i>
  75. <p style="margin:12px 0 0;font-size:16px;color:#303133">保存成功</p>
  76. </div>
  77. <div slot="footer">
  78. <el-button type="primary" @click="handleContinue">继续添加</el-button>
  79. <el-button @click="handleClose">关闭</el-button>
  80. </div>
  81. </el-dialog>
  82. </div>
  83. </template>
  84. <script>
  85. import {
  86. adminArticleCategoriesList,
  87. adminArticleCategoryCreate,
  88. adminArticleCategoryUpdate,
  89. adminArticleCategoryDelete
  90. } from '@/api/article.js'
  91. export default {
  92. name: 'ArticleCategory',
  93. data() {
  94. return {
  95. loading: false,
  96. submitting: false,
  97. categories: [],
  98. dialogVisible: false,
  99. successDialogVisible: false,
  100. isEdit: false,
  101. keyword: '',
  102. form: {
  103. id: null,
  104. name: '',
  105. icon: '',
  106. color: '',
  107. sortOrder: 0,
  108. statusBool: true
  109. }
  110. }
  111. },
  112. created() {
  113. this.loadData()
  114. },
  115. computed: {
  116. tableHeight() {
  117. return window.innerHeight - 300
  118. },
  119. filteredData() {
  120. if (!this.keyword) return this.categories
  121. const kw = this.keyword.toLowerCase()
  122. return this.categories.filter(item =>
  123. [item.name, item.id+''].some(v => (v||'').toLowerCase().includes(kw))
  124. )
  125. }
  126. },
  127. methods: {
  128. async loadData() {
  129. this.loading = true
  130. try {
  131. const res = await adminArticleCategoriesList()
  132. if (res.data) {
  133. this.categories = res.data
  134. }
  135. } catch (e) {
  136. this.$message.error('加载分类列表失败')
  137. } finally {
  138. this.loading = false
  139. }
  140. },
  141. handleSearch() {
  142. this.keyword = this.keyword
  143. },
  144. handleCreate() {
  145. this.isEdit = false
  146. this.form = { id: null, name: '', icon: '', color: '', sortOrder: 0, statusBool: true }
  147. this.dialogVisible = true
  148. },
  149. handleEdit(row) {
  150. this.isEdit = true
  151. this.form = {
  152. id: row.id,
  153. name: row.name,
  154. icon: row.icon,
  155. color: row.color,
  156. sortOrder: row.sortOrder,
  157. statusBool: row.status === 1 || row.status === 'ACTIVE'
  158. }
  159. this.dialogVisible = true
  160. },
  161. async handleSubmit() {
  162. this.submitting = true
  163. try {
  164. const data = {
  165. name: this.form.name,
  166. icon: this.form.icon,
  167. color: this.form.color,
  168. sortOrder: this.form.sortOrder,
  169. status: this.form.statusBool ? 1 : 0
  170. }
  171. if (this.isEdit) {
  172. data.id = this.form.id
  173. await adminArticleCategoryUpdate(data)
  174. } else {
  175. await adminArticleCategoryCreate(data)
  176. }
  177. this.$message.success('保存成功')
  178. this.successDialogVisible = true
  179. } catch (e) {
  180. this.$message.error(e.message || '保存失败')
  181. } finally {
  182. this.submitting = false
  183. }
  184. },
  185. async handleDelete(row) {
  186. this.$confirm(`确定删除分类「${row.name}」吗?`, '提示', {
  187. confirmButtonText: '确定',
  188. cancelButtonText: '取消',
  189. type: 'warning'
  190. }).then(async () => {
  191. try {
  192. await adminArticleCategoryDelete({ id: row.id })
  193. this.$message.success('删除成功')
  194. this.loadData()
  195. } catch (e) {
  196. this.$message.error(e.message || '删除失败')
  197. }
  198. }).catch(() => {})
  199. },
  200. handleActionCmd(row, cmd) {
  201. switch (cmd) {
  202. case 'delete':
  203. this.handleDelete(row)
  204. break
  205. }
  206. },
  207. handleContinue() {
  208. this.successDialogVisible = false
  209. this.isEdit = false
  210. this.form = { id: null, name: '', icon: '', color: '', sortOrder: 0, statusBool: true }
  211. this.dialogVisible = true
  212. },
  213. handleClose() {
  214. this.successDialogVisible = false
  215. this.dialogVisible = false
  216. this.loadData()
  217. }
  218. }
  219. }
  220. </script>
  221. <style scoped>
  222. .article-category {
  223. padding: 20px;
  224. }
  225. </style>