news.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. <template>
  2. <div class="app-container">
  3. <!-- 查询和其他操作 -->
  4. <div class="filter-container">
  5. <el-select
  6. v-model="listQuery.category"
  7. clearable
  8. placeholder="请选择类别"
  9. class="filter-item"
  10. size="small"
  11. >
  12. <el-option
  13. v-for="dict in categoryMap"
  14. :key="dict.value"
  15. :label="dict.label"
  16. :value="dict.value"
  17. />
  18. </el-select>
  19. <el-input
  20. v-model="listQuery.title"
  21. clearable
  22. class="filter-item"
  23. style="width: 200px;"
  24. placeholder="请输入标题"
  25. size="small"
  26. />
  27. <el-input
  28. v-model="listQuery.from"
  29. clearable
  30. class="filter-item"
  31. style="width: 200px;"
  32. placeholder="请输入来源"
  33. size="small"
  34. />
  35. <el-button
  36. v-permission="['news:news:list']"
  37. class="filter-item"
  38. type="primary"
  39. size="mini"
  40. icon="el-icon-search"
  41. @click="handleFilter"
  42. >查找</el-button
  43. >
  44. <el-button
  45. v-permission="['news:news:create']"
  46. class="filter-item"
  47. type="primary"
  48. size="mini"
  49. icon="el-icon-edit"
  50. @click="handleCreate"
  51. >添加</el-button
  52. >
  53. </div>
  54. <!-- 查询结果 -->
  55. <el-table
  56. v-loading="listLoading"
  57. :data="list"
  58. size="small"
  59. element-loading-text="正在查询中。。。"
  60. border
  61. fit
  62. highlight-current-row
  63. >
  64. <el-table-column
  65. :formatter="typeFormat"
  66. align="center"
  67. label="类别"
  68. prop="category"
  69. />
  70. <el-table-column align="center" label="标题" prop="title" />
  71. <el-table-column
  72. align="center"
  73. label="标题图片"
  74. prop="titleImg"
  75. width="120px"
  76. >
  77. <template slot-scope="scope">
  78. <img
  79. v-if="scope.row.titleImg"
  80. :src="scope.row.titleImg"
  81. width="100px"
  82. height="100px"
  83. >
  84. </template>
  85. </el-table-column>
  86. <el-table-column align="center" label="来源" prop="from" />
  87. <el-table-column align="center" label="更新时间" prop="gmtUpdate">
  88. <template slot-scope="scope">{{
  89. scope.row.gmtUpdate | formatTime
  90. }}</template>
  91. </el-table-column>
  92. <el-table-column
  93. align="center"
  94. label="操作"
  95. class-name="small-padding fixed-width"
  96. >
  97. <template slot-scope="scope">
  98. <el-button
  99. v-permission="['news:news:edit']"
  100. type="primary"
  101. size="mini"
  102. @click="handleUpdate(scope.row)"
  103. >编辑</el-button
  104. >
  105. <el-button
  106. v-permission="['news:news:delete']"
  107. type="danger"
  108. size="mini"
  109. @click="handleDelete(scope.row)"
  110. >删除</el-button
  111. >
  112. </template>
  113. </el-table-column>
  114. </el-table>
  115. <pagination
  116. v-show="total > 0"
  117. :total="total"
  118. :page.sync="listQuery.page"
  119. :limit.sync="listQuery.limit"
  120. @pagination="getList"
  121. />
  122. <!-- 添加或修改对话框 -->
  123. <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" :close-on-click-modal="false">
  124. <el-form
  125. ref="dataForm"
  126. :rules="rules"
  127. :model="dataForm"
  128. status-icon
  129. label-position="left"
  130. label-width="100px"
  131. style="width: 800px; margin-left:10px;"
  132. >
  133. <el-form-item label="id" prop="id" hidden>
  134. <el-input v-model="dataForm.id" />
  135. </el-form-item>
  136. <el-form-item label="类别" prop="category">
  137. <el-select v-model="dataForm.category" clearable placeholder="请选择类别">
  138. <el-option
  139. v-for="(dict, index) in options"
  140. :key="index"
  141. :label="dict.label"
  142. :value="dict.value"
  143. />
  144. </el-select>
  145. </el-form-item>
  146. <el-form-item label="标题" prop="title">
  147. <el-input v-model="dataForm.title" />
  148. </el-form-item>
  149. <el-form-item label="标题图片" prop="titleImg">
  150. <el-upload
  151. :headers="headers"
  152. :action="uploadPath"
  153. :show-file-list="false"
  154. :on-success="uploadSuccessHandle"
  155. :before-upload="onBeforeUpload"
  156. class="avatar-uploader"
  157. accept=".jpg, .jpeg, .png, .gif"
  158. >
  159. <img
  160. v-if="dataForm.titleImg"
  161. ref="adImg"
  162. :src="dataForm.titleImg"
  163. class="avatar"
  164. >
  165. <i v-else class="el-icon-plus avatar-uploader-icon" />
  166. </el-upload>
  167. </el-form-item>
  168. <el-form-item label="简介" prop="abstractContent">
  169. <textarea v-model="dataForm.abstractContent" cols="100" rows="10" />
  170. </el-form-item>
  171. <el-form-item label="来源" prop="from">
  172. <el-input v-model="dataForm.from" />
  173. </el-form-item>
  174. <el-form-item label="内容" prop="content">
  175. <editor :init="editorInit" v-model="dataForm.content" />
  176. </el-form-item>
  177. </el-form>
  178. <div slot="footer" class="dialog-footer">
  179. <el-button @click="dialogFormVisible = false">取消</el-button>
  180. <el-button
  181. v-if="dialogStatus == 'create'"
  182. :loading="submiting"
  183. type="primary"
  184. @click="createData"
  185. >确定</el-button
  186. >
  187. <el-button
  188. v-else
  189. :loading="submiting"
  190. type="primary"
  191. @click="updateData"
  192. >确定</el-button
  193. >
  194. </div>
  195. </el-dialog>
  196. </div>
  197. </template>
  198. <script>
  199. import { listNews, createNews, updateNews, deleteNews } from '@/api/news'
  200. import Pagination from '@/components/Pagination'
  201. import { uploadPath, createStorage } from '@/api/storage'
  202. import { getToken } from '@/utils/auth'
  203. import Editor from '@tinymce/tinymce-vue'
  204. const categoryMap = []
  205. export default {
  206. name: 'News',
  207. components: { Pagination, Editor },
  208. data() {
  209. return {
  210. categoryMap,
  211. uploadPath,
  212. config: {
  213. // 初始容器高度
  214. initialFrameHeight: 550,
  215. // 初始容器宽度
  216. initialFrameWidth: 750,
  217. // 关闭自动保存
  218. enableAutoSave: true
  219. },
  220. content: '请输入内容',
  221. list: null,
  222. total: 0,
  223. listLoading: true,
  224. listQuery: {
  225. page: 1,
  226. limit: 20
  227. },
  228. dataForm: {
  229. id: undefined
  230. },
  231. category: '',
  232. dialogFormVisible: false,
  233. submiting: false,
  234. dialogStatus: '',
  235. textMap: {
  236. update: '编辑',
  237. create: '创建'
  238. },
  239. rules: {
  240. category: [
  241. { required: true, message: '类别名称不能为空', trigger: 'blur' }
  242. ],
  243. title: [
  244. { required: true, message: '请输入标题', trigger: 'blur' },
  245. { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
  246. ],
  247. titleImg: [
  248. { required: true, message: '标题图片不能为空', trigger: 'blur' }
  249. ],
  250. content: [
  251. { required: true, message: '内容不能为空', trigger: 'blur' }
  252. ],
  253. from: [
  254. { required: true, message: '来源不能为空', trigger: 'blur' }
  255. ],
  256. gmtUpdate: [
  257. { required: true, message: '更新时间不能为空', trigger: 'blur' }
  258. ],
  259. abstractContent: [
  260. { required: true, message: '简介不能为空', trigger: 'blur' }
  261. ]
  262. },
  263. editorInit: {
  264. language: 'zh_CN',
  265. convert_urls: false,
  266. height: 550,
  267. width: 750,
  268. plugins: [
  269. 'lineheight',
  270. 'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount '
  271. ],
  272. toolbar: [
  273. 'lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample',
  274. 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen '
  275. ],
  276. images_upload_handler: function(blobInfo, success, failure) {
  277. const formData = new FormData()
  278. formData.append('file', blobInfo.blob())
  279. createStorage(formData)
  280. .then(res => {
  281. success(res.data.url)
  282. })
  283. .catch(() => {
  284. failure('上传失败,请重新上传')
  285. })
  286. }
  287. },
  288. options: [{
  289. value: '2',
  290. label: '行业新闻'
  291. }],
  292. value: ''
  293. }
  294. },
  295. computed: {
  296. headers() {
  297. return {
  298. accessToken: getToken()
  299. }
  300. }
  301. },
  302. created() {
  303. this.getList()
  304. this.getDicts('news_type').then(response => {
  305. response.data.data.forEach((item, index) => {
  306. response.data.data[index].value = parseInt(
  307. response.data.data[index].value
  308. )
  309. })
  310. this.categoryMap = response.data.data
  311. })
  312. },
  313. methods: {
  314. getList() {
  315. this.listLoading = true
  316. listNews(this.listQuery)
  317. .then(response => {
  318. this.list = response.data.data.items
  319. this.total = response.data.data.total
  320. this.listLoading = false
  321. })
  322. .catch(() => {
  323. this.list = []
  324. this.total = 0
  325. this.listLoading = false
  326. })
  327. },
  328. typeFormat(row, column) {
  329. return this.selectDictLabel(this.categoryMap, row.category)
  330. },
  331. handleFilter() {
  332. this.listQuery.page = 1
  333. this.getList()
  334. },
  335. resetForm() {
  336. this.dataForm = {
  337. id: undefined
  338. }
  339. },
  340. handleCreate() {
  341. this.resetForm()
  342. this.dialogStatus = 'create'
  343. this.dialogFormVisible = true
  344. this.$nextTick(() => {
  345. this.$refs['dataForm'].clearValidate()
  346. })
  347. },
  348. createData() {
  349. this.$refs['dataForm'].validate(valid => {
  350. if (valid) {
  351. this.submiting = true
  352. createNews(this.dataForm)
  353. .then(response => {
  354. this.list.unshift(response.data.data)
  355. this.dialogFormVisible = false
  356. this.$notify.success({
  357. title: '成功',
  358. message: '添加成功'
  359. })
  360. this.submiting = false
  361. })
  362. .catch(response => {
  363. this.$notify.error({
  364. title: '失败',
  365. message: response.data.errmsg
  366. })
  367. this.submiting = false
  368. })
  369. }
  370. })
  371. },
  372. handleUpdate(row) {
  373. this.dataForm = Object.assign({}, row)
  374. this.category = Number(this.dataForm.category)
  375. this.dialogStatus = 'update'
  376. this.dialogFormVisible = true
  377. this.$nextTick(() => {
  378. this.$refs['dataForm'].clearValidate()
  379. })
  380. },
  381. updateData() {
  382. this.$refs['dataForm'].validate(valid => {
  383. if (valid) {
  384. this.submiting = true
  385. updateNews(this.dataForm)
  386. .then(() => {
  387. for (const v of this.list) {
  388. if (v.id === this.dataForm.id) {
  389. const index = this.list.indexOf(v)
  390. this.list.splice(index, 1, this.dataForm)
  391. break
  392. }
  393. }
  394. this.dialogFormVisible = false
  395. this.submiting = false
  396. this.$notify.success({
  397. title: '成功',
  398. message: '更新成功'
  399. })
  400. })
  401. .catch(response => {
  402. this.$notify.error({
  403. title: '失败',
  404. message: response.data.errmsg
  405. })
  406. this.submiting = false
  407. })
  408. }
  409. })
  410. },
  411. handleDelete(row) {
  412. this.$confirm(
  413. '此操作将永久删除该记录---' + row.id + '---, 是否继续?',
  414. '提示',
  415. {
  416. confirmButtonText: '确定',
  417. cancelButtonText: '取消',
  418. type: 'warning'
  419. }
  420. )
  421. .then(() => {
  422. deleteNews(row.id)
  423. .then(response => {
  424. this.$notify.success({
  425. title: '成功',
  426. message: '删除成功'
  427. })
  428. const index = this.list.indexOf(row)
  429. this.list.splice(index, 1)
  430. })
  431. .catch(response => {
  432. this.$notify.error({
  433. title: '失败',
  434. message: response.data.errmsg
  435. })
  436. })
  437. })
  438. .catch(() => {
  439. return false
  440. })
  441. },
  442. // 上传图片了处理图片
  443. uploadSuccessHandle(e, file) {
  444. const that = this
  445. this.dataForm.titleImg = e.url
  446. this.dialogFormVisible = false
  447. this.dialogFormVisible = true
  448. },
  449. onBeforeUpload(file) {
  450. const isIMAGE =
  451. file.type === 'image/jpeg' || 'image/gif' || 'image/png' || 'image/jpg'
  452. const isLt1M = file.size / 1024 / 1024 < 1
  453. if (!isIMAGE) {
  454. this.$message.error('上传文件只能是图片格式!')
  455. }
  456. if (!isLt1M) {
  457. this.$message.error('上传文件大小不能超过 1MB!')
  458. }
  459. return isIMAGE && isLt1M
  460. }
  461. }
  462. }
  463. </script>
  464. <style lang="scss" scop>
  465. .show-content {
  466. border: 1px solid #ccc;
  467. border-radius: 5px;
  468. padding: 9px;
  469. min-height: 614px;
  470. p {
  471. margin: 5px 0;
  472. }
  473. }
  474. .tox.tox-silver-sink.tox-tinymce-aux {
  475. z-index: 9999;
  476. }
  477. .avatar-uploader .el-upload {
  478. border: 1px dashed #d9d9d9;
  479. border-radius: 6px;
  480. cursor: pointer;
  481. position: relative;
  482. overflow: hidden;
  483. }
  484. .avatar-uploader .el-upload:hover {
  485. border-color: #20a0ff;
  486. }
  487. .avatar-uploader-icon {
  488. font-size: 28px;
  489. color: #8c939d;
  490. width: 120px;
  491. height: 120px;
  492. line-height: 120px;
  493. text-align: center;
  494. }
  495. </style>