You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

751 lines
22 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. <template>
  2. <div class="app-container">
  3. <el-card class="box-card">
  4. <el-row :gutter="20">
  5. <!--部门数据-->
  6. <el-col :span="3" :xs="24">
  7. <div class="head-container">
  8. <el-input
  9. v-model="deptName"
  10. placeholder="请输入分类名称"
  11. clearable
  12. size="small"
  13. prefix-icon="el-icon-search"
  14. style="margin-bottom: 20px"
  15. :default-expand-all="false"
  16. />
  17. </div>
  18. <div class="head-container">
  19. <el-tree
  20. ref="tree"
  21. :data="categoryOptions"
  22. highlight-current
  23. :props="defaultProps"
  24. :expand-on-click-node="false"
  25. :filter-node-method="filterNode"
  26. @node-click="handleNodeClick"
  27. />
  28. </div>
  29. </el-col>
  30. <!--用户数据-->
  31. <el-col :span="20" :xs="24">
  32. <el-form
  33. ref="queryForm"
  34. :model="listQuery"
  35. :inline="true"
  36. label-width="68px"
  37. >
  38. <el-form-item label="文章标题" prop="title">
  39. <el-input
  40. v-model="listQuery.title"
  41. placeholder="请输入文章标题"
  42. clearable
  43. size="small"
  44. style="width: 160px"
  45. @keyup.enter.native="getList"
  46. />
  47. </el-form-item>
  48. <el-form-item label="文章标签" prop="label_name">
  49. <el-input
  50. v-model="listQuery.label_name"
  51. placeholder="请输入文章标签"
  52. clearable
  53. size="small"
  54. style="width: 160px"
  55. @keyup.enter.native="getList"
  56. />
  57. </el-form-item>
  58. <el-form-item label="状态" prop="is_offline">
  59. <el-select
  60. v-model="listQuery.is_offline"
  61. placeholder="文章状态"
  62. clearable
  63. size="small"
  64. style="width: 160px"
  65. >
  66. <el-option
  67. v-for="dict in statusOptions"
  68. :key="dict.value"
  69. :label="dict.label"
  70. :value="dict.value"
  71. />
  72. </el-select>
  73. </el-form-item>
  74. <el-form-item>
  75. <el-button
  76. type="primary"
  77. icon="el-icon-search"
  78. size="mini"
  79. @click="getList"
  80. >搜索</el-button
  81. >
  82. </el-form-item>
  83. </el-form>
  84. <el-row :gutter="10" class="mb8">
  85. <el-col :span="1.5">
  86. <el-button
  87. type="primary"
  88. icon="el-icon-plus"
  89. size="mini"
  90. @click="handleCreate"
  91. >新增</el-button
  92. >
  93. </el-col>
  94. </el-row>
  95. <el-table
  96. v-loading="listLoading"
  97. :data="list"
  98. border
  99. fit
  100. highlight-current-row
  101. style="width: 100%"
  102. >
  103. <el-table-column label="ID" prop="id" align="center">
  104. <template slot-scope="{ row }">
  105. <span>{{ row.article_id }}</span>
  106. </template>
  107. </el-table-column>
  108. <el-table-column label="分类" align="center">
  109. <template slot-scope="{ row }">
  110. <span>{{ row.category_id | getCateName(categoriesJson) }}</span>
  111. </template>
  112. </el-table-column>
  113. <el-table-column label="标题" align="center">
  114. <template slot-scope="{ row }">
  115. <span>{{ row.title }}</span>
  116. </template>
  117. </el-table-column>
  118. <el-table-column label="封面图" align="center">
  119. <template slot-scope="{ row }">
  120. <img :src="row.img" height="100" />
  121. </template>
  122. </el-table-column>
  123. <el-table-column label="外链" align="center">
  124. <template slot-scope="{ row }">
  125. <span>{{ row.url }}</span>
  126. </template>
  127. </el-table-column>
  128. <el-table-column label="文章状态" align="center">
  129. <template slot-scope="{ row }">
  130. <div>
  131. <el-switch
  132. style="display: block"
  133. v-model="row.is_offline"
  134. active-color="#409EFF"
  135. inactive-color="#ff4949"
  136. :active-value="2"
  137. :inactive-value="1"
  138. @change="changeSwitch($event, row)"
  139. >
  140. </el-switch>
  141. </div>
  142. </template>
  143. </el-table-column>
  144. <el-table-column label="标签详情">
  145. <template slot-scope="{ row }">
  146. <span>{{ row.label_name | getTagDetail }}</span>
  147. </template>
  148. </el-table-column>
  149. <el-table-column label="创建时间" align="center">
  150. <template slot-scope="{ row }">
  151. <span>{{ row.create_time }}</span>
  152. </template>
  153. </el-table-column>
  154. <el-table-column
  155. label="操作"
  156. align="center"
  157. class-name="small-padding fixed-width"
  158. >
  159. <template slot-scope="scope">
  160. <el-button
  161. size="mini"
  162. type="text"
  163. icon="el-icon-edit"
  164. @click="handleUpdate(scope.row)"
  165. >修改</el-button
  166. >
  167. <el-button
  168. size="mini"
  169. type="text"
  170. icon="el-icon-delete"
  171. @click="handleDelete(scope.row)"
  172. >删除</el-button
  173. >
  174. </template>
  175. </el-table-column>
  176. </el-table>
  177. <pagination
  178. v-show="total > 0"
  179. :total="total"
  180. :page.sync="listQuery.page"
  181. :limit.sync="listQuery.limit"
  182. @pagination="getList"
  183. />
  184. </el-col>
  185. </el-row>
  186. </el-card>
  187. <!-- 添加或修改分类对话框 -->
  188. <el-drawer
  189. ref="drawer"
  190. :title="textMap[dialogStatus]"
  191. :visible.sync="dialogFormVisible"
  192. direction="rtl"
  193. custom-class="demo-drawer"
  194. size="950px"
  195. >
  196. <div class="demo-drawer__content" v-if="dialogFormVisible">
  197. <el-form
  198. ref="dataForm"
  199. :model="temp"
  200. label-position="left"
  201. label-width="70px"
  202. style
  203. >
  204. <el-form-item label="类型">
  205. <treeselect
  206. v-model="temp.category_id"
  207. :options="categoryOptions"
  208. :normalizer="normalizer"
  209. :show-count="true"
  210. :disabled="dialogStatus == 'create'"
  211. placeholder="选择分类"
  212. />
  213. </el-form-item>
  214. <template v-if="tagData.length">
  215. <el-form-item
  216. :label="item.label"
  217. v-for="(item, index) in tagData"
  218. :key="index"
  219. >
  220. <el-select
  221. v-model="chooseTagData[index]"
  222. multiple
  223. placeholder="请选择"
  224. style="width: 100%"
  225. >
  226. <el-option
  227. v-for="(sItem, sIndex) in item.value"
  228. :key="sIndex"
  229. :label="sItem"
  230. :value="sItem"
  231. ></el-option>
  232. </el-select>
  233. </el-form-item>
  234. </template>
  235. <el-form-item label="标题" prop="title">
  236. <el-input v-model="temp.title" />
  237. </el-form-item>
  238. <el-form-item label="作者" prop="title">
  239. <el-input
  240. v-model="temp.author"
  241. placeholder="请上传视频时填写作者"
  242. />
  243. </el-form-item>
  244. <el-form-item label="日期" prop="time">
  245. <el-date-picker
  246. v-model="temp.time"
  247. value-format="yyyy-MM-dd HH:mm:ss"
  248. type="date"
  249. placeholder="选择日期"
  250. ></el-date-picker>
  251. </el-form-item>
  252. <el-form-item label="外链" prop="url">
  253. <el-input v-model="temp.url" />
  254. </el-form-item>
  255. <el-form-item label="描述" prop="description">
  256. <el-input type="textarea" v-model="temp.description" />
  257. </el-form-item>
  258. <el-form-item label="状态">
  259. <el-radio-group v-model="temp.is_offline" @change="changeRadio">
  260. <el-radio :label="2">上线</el-radio>
  261. <el-radio :label="1">下线</el-radio>
  262. </el-radio-group>
  263. </el-form-item>
  264. <el-form-item label="封面" prop="img" required>
  265. <img
  266. v-if="temp.img"
  267. :src="temp.img"
  268. class="el-upload el-upload--picture-card"
  269. style="float: left; width: auto"
  270. />
  271. <el-upload
  272. ref="sys_app_logo"
  273. :on-success="uploadSuccess"
  274. :on-error="uploadError"
  275. :action="uploadAction"
  276. style="float: left"
  277. list-type="picture-card"
  278. :show-file-list="false"
  279. :before-upload="validationImages"
  280. >
  281. <i class="el-icon-plus" />
  282. </el-upload>
  283. <div
  284. v-if="validation === false"
  285. style="padding-left: 10px; color: red"
  286. >
  287. 提示宽高{{ dislabelSize.width }}x{{ dislabelSize.height }}
  288. </div>
  289. </el-form-item>
  290. <el-form-item prop="file" label="上传脚本">
  291. <el-upload
  292. class="upload-demo"
  293. :action="uploadAction"
  294. :limit="1"
  295. :on-exceed="handleFileExceed"
  296. :onSuccess="uploadFileSuccess"
  297. :file-list="temp.script_url ? [JSON.parse(temp.script_url)] : []"
  298. >
  299. <el-button size="small" type="primary">点击上传</el-button>
  300. <!-- <div slot="tip" class="el-upload__tip">
  301. 只能上传jpg/png文件且不超过500kb
  302. </div>-->
  303. </el-upload>
  304. </el-form-item>
  305. <el-form-item label="视频时长" prop="video_time">
  306. <el-time-picker
  307. v-model="temp.video_time"
  308. value-format="HH:mm:ss"
  309. type="time"
  310. placeholder="选择时间"
  311. ></el-time-picker>
  312. </el-form-item>
  313. <el-form-item label="内容" class="editor-box">
  314. <vue-ueditor-wrap
  315. v-model="temp.content"
  316. :config="ueditorConfig"
  317. :destroy="true"
  318. v-if="dialogFormVisible"
  319. />
  320. </el-form-item>
  321. </el-form>
  322. <div class="demo-drawer__footer">
  323. <el-button type="primary" @click="submitForm"> </el-button>
  324. <el-button @click="dialogFormVisible = false"> </el-button>
  325. </div>
  326. </div>
  327. </el-drawer>
  328. </div>
  329. </template>
  330. <script>
  331. import { validPhone, isEmpty } from "@/utils/validate";
  332. import {
  333. fetchList,
  334. createArticle,
  335. updateArticle,
  336. delArticle,
  337. fetchArticleDetails,
  338. } from "@/api/article";
  339. import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
  340. import VueUeditorWrap from "vue-ueditor-wrap";
  341. import { listCategory } from "@/api/category";
  342. import Treeselect from "@riophae/vue-treeselect";
  343. import "@riophae/vue-treeselect/dist/vue-treeselect.css";
  344. import { log } from "console";
  345. export default {
  346. name: "articleIndex",
  347. components: { Pagination, Treeselect, VueUeditorWrap },
  348. data() {
  349. return {
  350. deptName: "",
  351. defaultProps: {
  352. children: "children",
  353. label: "category_name",
  354. },
  355. // 状态数据字典
  356. statusOptions: [
  357. { value: "", label: "全部" },
  358. { value: 2, label: "上线" },
  359. { value: 1, label: "下线" },
  360. ],
  361. categoryOptionsLeft: undefined,
  362. tableKey: 0,
  363. list: null,
  364. total: 0,
  365. listLoading: true,
  366. listQuery: {
  367. page: 1,
  368. limit: 20,
  369. is_offline: "",
  370. title: "",
  371. label_name: "",
  372. category_id: undefined,
  373. author: "", //作者
  374. },
  375. // accountRules: {
  376. // phone: [{ required: true, trigger: 'blur', validator: RulePhone }],
  377. // wam_name: [{ required: true, trigger: 'blur', validator: RuleEmpty }]
  378. // },
  379. ueditorConfig: {
  380. UEDITOR_HOME_URL: "/UEditor/",
  381. serverUrl: process.env.VUE_APP_BASE_API + "/files/baiduUp",
  382. initialFrameHeight: 300,
  383. scaleEnabled: true,
  384. },
  385. uploadAction: process.env.VUE_APP_BASE_API + "/files/uploadFile",
  386. temp: {
  387. id: undefined,
  388. category_id: undefined,
  389. title: "",
  390. time: "",
  391. img: "",
  392. script_url: "",
  393. content: "",
  394. description: "",
  395. video_time: "",
  396. is_offline: 2,
  397. },
  398. dialogFullscreen: true,
  399. dialogFormVisible: false,
  400. dialogStatus: "",
  401. textMap: {
  402. update: "编辑",
  403. create: "添加",
  404. },
  405. rules: {
  406. title: [
  407. { required: true, message: "title is required", trigger: "blur" },
  408. ],
  409. },
  410. categoryOptions: [],
  411. // categoriesJson:[],
  412. downloadLoading: false,
  413. // tagData:[],
  414. chooseTagData: [],
  415. // 是否允许上传
  416. validation: true,
  417. // 正确的图片宽高
  418. dislabelSize: {},
  419. };
  420. },
  421. mounted() {
  422. this.getList();
  423. this.getTreeselect();
  424. },
  425. computed: {
  426. categoriesJson() {
  427. var res = this.handleCategory(this.categoryOptions);
  428. return res;
  429. },
  430. tagData() {
  431. let matchData = null;
  432. this.categoriesJson.forEach((item) => {
  433. if (item.id == this.temp.category_id) {
  434. matchData = item.label_name;
  435. }
  436. });
  437. return matchData ? JSON.parse(matchData) : [];
  438. },
  439. },
  440. watch: {
  441. dialogFormVisible(newVal) {
  442. if (newVal === false) {
  443. this.validation = true;
  444. }
  445. },
  446. tagData(newVal) {
  447. console.log("tagDAta");
  448. console.log(newVal);
  449. if (!newVal.length) {
  450. this.chooseTagData = [];
  451. }
  452. },
  453. chooseTagData(newVal) {
  454. console.log("chooseTagData");
  455. console.log(newVal);
  456. },
  457. // 根据名称筛选部门树
  458. deptName(val) {
  459. this.$refs.tree.filter(val);
  460. },
  461. },
  462. filters: {
  463. getCateName(id, categoriesJson) {
  464. for (var i = 0; i < categoriesJson.length; i++) {
  465. if (id == categoriesJson[i].id) {
  466. return categoriesJson[i].name;
  467. }
  468. }
  469. },
  470. getTagDetail(data) {
  471. if (!data) {
  472. return;
  473. }
  474. let str = "";
  475. data = JSON.parse(data);
  476. data.forEach((item) => {
  477. if (item.value.length) {
  478. str += `${item.label}${item.value.join("、")}`;
  479. }
  480. });
  481. return str;
  482. },
  483. },
  484. methods: {
  485. changeRadio(e) {
  486. this.temp.is_offline = Number(e);
  487. },
  488. // 文章开关发生变化
  489. changeSwitch(e, item) {
  490. item.is_offline = Number(e);
  491. updateArticle(item).then((r) => {
  492. this.$message({
  493. type: "success",
  494. message: `${e == 2 ? "通知:文章上线成功" : "通知:文章下线成功"}`,
  495. });
  496. });
  497. },
  498. // 验证图片宽高
  499. async validationImages(file) {
  500. let res = await this.$getImgWidth(file, this.temp.category_id);
  501. this.validation = res.validation;
  502. this.dislabelSize = res;
  503. return res.validation;
  504. },
  505. // 节点单击事件
  506. handleNodeClick(data) {
  507. this.listQuery.category_id = data.id;
  508. this.getList();
  509. },
  510. handleCategory(array) {
  511. var res = [];
  512. array.forEach((item, index) => {
  513. let obj = {};
  514. obj.id = item.category_id;
  515. obj.name = item.category_name;
  516. obj.label_name = item.label_name;
  517. res.push(obj);
  518. if (item.children) {
  519. res.push(...this.handleCategory(item.children));
  520. }
  521. });
  522. return res;
  523. },
  524. // 筛选节点
  525. filterNode(value, data) {
  526. if (!value) return true;
  527. return data.category_name.indexOf(value) !== -1;
  528. },
  529. getList() {
  530. this.listLoading = true;
  531. fetchList(this.listQuery).then((response) => {
  532. console.log(response);
  533. this.list = response.data.list;
  534. this.total = response.data.count;
  535. // Just to simulate the time of the request
  536. setTimeout(() => {
  537. this.listLoading = false;
  538. }, 1.5 * 1000);
  539. });
  540. },
  541. handleFilter() {
  542. this.listQuery.page = 1;
  543. this.getList();
  544. },
  545. handleModifyStatus(row, status) {
  546. this.$message({
  547. message: "操作Success",
  548. type: "success",
  549. });
  550. row.status = status;
  551. },
  552. resetTemp() {
  553. this.temp = {
  554. id: undefined,
  555. category_id: undefined,
  556. title: "",
  557. time: "",
  558. img: "",
  559. is_offline: 2,
  560. script_url: "",
  561. content: "",
  562. description: "",
  563. video_time: "",
  564. };
  565. },
  566. submitForm() {
  567. let matchData = [...this.tagData];
  568. this.chooseTagData.forEach((item, index) => {
  569. matchData[index]["value"] = item;
  570. });
  571. this.temp.label_name = JSON.stringify(matchData);
  572. if (this.dialogStatus == "create") {
  573. this.createData();
  574. } else if (this.dialogStatus == "update") {
  575. this.updateData();
  576. }
  577. },
  578. handleCreate() {
  579. this.resetTemp();
  580. this.temp.category_id = this.listQuery.category_id;
  581. this.dialogStatus = "create";
  582. this.dialogFormVisible = true;
  583. this.$nextTick(() => {
  584. this.$refs["dataForm"].clearValidate();
  585. });
  586. },
  587. createData() {
  588. // this.$refs['dataForm'].validate((valid) => {
  589. // if (valid) {
  590. createArticle(this.temp).then(() => {
  591. this.dialogFormVisible = false;
  592. this.$message({
  593. message: "添加成功",
  594. type: "success",
  595. duration: 2 * 1000,
  596. });
  597. this.getList();
  598. });
  599. // }
  600. // })
  601. },
  602. handleUpdate(row) {
  603. let article_id = row.article_id;
  604. this.dialogStatus = "update";
  605. this.resetTemp();
  606. this.dialogFormVisible = true;
  607. fetchArticleDetails({ article_id }).then((res) => {
  608. if (!res.data.is_offline) {
  609. res.data.is_offline = 2;
  610. }
  611. this.temp = res.data;
  612. if (this.temp.label_name) {
  613. let data = JSON.parse(this.temp.label_name);
  614. data.forEach((item, index) => {
  615. this.chooseTagData[index] = item.value;
  616. });
  617. }
  618. this.$nextTick(() => {
  619. this.$refs["dataForm"].clearValidate();
  620. });
  621. });
  622. },
  623. updateData() {
  624. // this.$refs['dataForm'].validate((valid) => {
  625. // if (valid) {
  626. updateArticle(this.temp).then(() => {
  627. this.dialogFormVisible = false;
  628. this.$message({
  629. message: "更新成功",
  630. type: "success",
  631. duration: 2 * 1000,
  632. });
  633. this.getList();
  634. });
  635. // }
  636. // })
  637. },
  638. handleDelete({ title, article_id }) {
  639. this.$confirm('是否确认删除名称为"' + title + '"的数据项?', "警告", {
  640. confirmButtonText: "确定",
  641. cancelButtonText: "取消",
  642. type: "warning",
  643. })
  644. .then(function () {
  645. console.log("del");
  646. return delArticle({ article_id });
  647. })
  648. .then((response) => {
  649. if (response.code === 200) {
  650. this.$message({
  651. message: "删除分类成功",
  652. type: "success",
  653. duration: 2 * 1000,
  654. });
  655. this.open = false;
  656. this.getList();
  657. } else {
  658. this.$message({
  659. message: "删除分类失败",
  660. type: "error",
  661. duration: 2 * 1000,
  662. });
  663. }
  664. })
  665. .catch(function () {});
  666. },
  667. uploadSuccess(response, file) {
  668. if (response.state == "SUCCESS" && this.validation) {
  669. this.temp.img = response.url;
  670. }
  671. // const uid = file.uid;
  672. // const objKeyArr = Object.keys(this.listObj);
  673. // for (let i = 0, len = objKeyArr.length; i < len; i++) {
  674. // if (this.listObj[objKeyArr[i]].uid === uid) {
  675. // this.listObj[objKeyArr[i]].url = this.config.qiniuHost + response.key;
  676. // this.listObj[objKeyArr[i]].hasSuccess = true;
  677. // return;
  678. // }
  679. // }
  680. },
  681. uploadFileSuccess(response, file) {
  682. let obj = {};
  683. obj.name = response.original;
  684. obj.url = response.url;
  685. this.temp.script_url = JSON.stringify(obj);
  686. },
  687. handleFileExceed(files, fileList) {
  688. this.$message.warning("只允许上传一个脚本");
  689. },
  690. uploadRemove(file) {
  691. const uid = file.uid;
  692. const objKeyArr = Object.keys(this.listObj);
  693. for (let i = 0, len = objKeyArr.length; i < len; i++) {
  694. if (this.listObj[objKeyArr[i]].uid === uid) {
  695. delete this.listObj[objKeyArr[i]];
  696. return;
  697. }
  698. }
  699. },
  700. uploadError(err) {
  701. this.$alert(err, "发生错误", {
  702. confirmButtonText: "确定",
  703. callback: (action) => {},
  704. });
  705. },
  706. /** 查询分类下拉树结构 */
  707. getTreeselect() {
  708. listCategory().then((response) => {
  709. this.categoryOptions = [];
  710. this.categoryOptions.push(...response.data);
  711. });
  712. },
  713. /** 转换分类数据结构 */
  714. normalizer(node) {
  715. if (node.children && !node.children.length) {
  716. delete node.children;
  717. }
  718. return {
  719. id: node.category_id,
  720. label: node.category_name,
  721. children: node.children,
  722. };
  723. },
  724. // 选择类别,获取对应的标签数据
  725. chooseCategory(node) {
  726. this.tagData = node.label_name ? JSON.parse(node.label_name) : [];
  727. },
  728. },
  729. };
  730. </script>
  731. <style lang="scss" scoped>
  732. .app-container {
  733. .filter-container {
  734. margin-left: 5px;
  735. margin-bottom: 6px;
  736. }
  737. ::v-deep .el-drawer__body {
  738. padding: 20px;
  739. }
  740. .editor-box {
  741. ::v-deep .el-form-item__content {
  742. line-height: 20px;
  743. }
  744. }
  745. }
  746. </style>