article.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <template>
  2. <div class="tenant">
  3. <byTable
  4. :source="sourceList.data"
  5. :pagination="sourceList.pagination"
  6. :config="config"
  7. :loading="loading"
  8. highlight-current-row
  9. :selectConfig="selectConfig"
  10. :table-events="{
  11. select: select,
  12. }"
  13. :action-list="[
  14. {
  15. text: '添加栏目文章',
  16. action: () => openModal('add'),
  17. },
  18. ]"
  19. @get-list="getList">
  20. </byTable>
  21. <el-dialog z-index="100" :title="modalType == 'add' ? '添加栏目文章' : '编辑栏目文章'" v-if="dialogVisible" v-model="dialogVisible" width="600" v-loading="loading">
  22. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="byform">
  23. <template #coverList>
  24. <el-row style="width: 100%">
  25. <el-col :span="6">
  26. <el-form-item prop="coverList">
  27. <el-upload
  28. class="avatar-uploader"
  29. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  30. :data="uploadDataOne"
  31. :show-file-list="false"
  32. accept=".gif, .jpeg, .jpg, .png"
  33. :on-success="coverListSuccess"
  34. :before-upload="uploadFileOne">
  35. <el-image
  36. v-if="formData.data.coverList && formData.data.coverList.length > 0"
  37. :src="formData.data.coverList[0].fileUrl"
  38. fit="scale-down"
  39. class="avatar" />
  40. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  41. </el-upload>
  42. <el-button
  43. class="delete-btn"
  44. type="danger"
  45. v-if="formData.data.coverList && formData.data.coverList.length > 0"
  46. @click="formData.data.coverList = []">
  47. 删除
  48. </el-button>
  49. </el-form-item>
  50. </el-col>
  51. </el-row>
  52. </template>
  53. <template #videoList>
  54. <el-row style="width: 100%">
  55. <el-col :span="6">
  56. <el-form-item prop="videoList">
  57. <el-upload
  58. class="avatar-uploader"
  59. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  60. :data="uploadDataTwo"
  61. :show-file-list="false"
  62. accept="video/*"
  63. :on-success="videoListSuccess"
  64. :before-upload="uploadFileTwo">
  65. <video
  66. v-if="formData.data.videoList && formData.data.videoList.length > 0"
  67. :src="formData.data.videoList[0].fileUrl"
  68. style="width: 300px; height: 300px"
  69. controls></video>
  70. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  71. </el-upload>
  72. <el-button
  73. class="delete-btn"
  74. type="danger"
  75. v-if="formData.data.videoList && formData.data.videoList.length > 0"
  76. @click="formData.data.videoList = []">
  77. 删除
  78. </el-button>
  79. </el-form-item>
  80. </el-col>
  81. </el-row>
  82. </template>
  83. <template #detailsContent>
  84. <div style="width: 100%" v-if="dialogVisible">
  85. <TinymceEditor
  86. :value="formData.data.content"
  87. @updateValue="updateHandover"
  88. ref="contentEditor"
  89. />
  90. </div>
  91. </template>
  92. </byForm>
  93. <template #footer>
  94. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  95. <el-button type="primary" v-no-double-click="submitForm" size="large" :loading="submitLoading">确 定</el-button>
  96. </template>
  97. </el-dialog>
  98. </div>
  99. </template>
  100. <script setup>
  101. import { ElMessage, ElMessageBox } from "element-plus";
  102. import byTable from "@/components/byTable/index";
  103. import byForm from "@/components/byForm/index";
  104. import {computed, nextTick, reactive, ref} from "vue";
  105. import TinymceEditor from "@/components/Editor/TinymceEditor.vue";
  106. import {getDictOneByXmhjc, getFileList,getFileStr} from "@/api/XMHJC/common";
  107. import {addColumnArticle, editColumnArticle, findColumnArticleList, getColumnArticle} from "@/api/XMHJC/column";
  108. import {deleteAboutUsCulture} from "@/api/XMHJC/aboutUs";
  109. const loading = ref(false);
  110. const submitLoading = ref(false);
  111. const sourceList = ref({
  112. data: [],
  113. pagination: {
  114. total: 0,
  115. pageNum: 1,
  116. pageSize: 10,
  117. },
  118. });
  119. let dialogVisible = ref(false);
  120. let modalType = ref("add");
  121. let rules = ref({
  122. title: [{ required: true, message: "请输入标题", trigger: "blur" }],
  123. subTitle: [{ required: true, message: "请输入副标题", trigger: "blur" }],
  124. briefly: [{ required: true, message: "请输入简述", trigger: "blur" }],
  125. bodyType: [{ required: true, message: "请输入列表类型", trigger: "change" }],
  126. videoUrl: [{ required: true, message: "请输入第三方链接", trigger: "blur" }],
  127. sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
  128. status: [{ required: true, message: "请选择启用状态", trigger: "change" }],
  129. });
  130. const { proxy } = getCurrentInstance();
  131. const bodyListType = ref([]);
  132. const enableStatus = ref([]);
  133. const selectConfig = [];
  134. const config = computed(() => {
  135. return [
  136. {
  137. attrs: {
  138. label: "标题",
  139. prop: "title",
  140. },
  141. },
  142. {
  143. attrs: {
  144. label: "副标题",
  145. prop: "subTitle",
  146. },
  147. },
  148. {
  149. attrs: {
  150. label: "列表类型",
  151. prop: "bodyType",
  152. width: '90'
  153. },
  154. render(type) {
  155. return proxy.dictValueLabel(type, bodyListType.value);
  156. },
  157. },
  158. {
  159. attrs: {
  160. label: "状态",
  161. prop: "status",
  162. width: '80'
  163. },
  164. render(type) {
  165. return proxy.dictValueLabel(type, enableStatus.value);
  166. },
  167. },
  168. {
  169. attrs: {
  170. label: "排序",
  171. prop: "sort",
  172. width: '80'
  173. },
  174. },
  175. {
  176. attrs: {
  177. label: "发表时间",
  178. prop: "time",
  179. width: '160'
  180. },
  181. },
  182. {
  183. attrs: {
  184. label: "操作",
  185. width: "120",
  186. align: "right",
  187. },
  188. renderHTML(row) {
  189. return [
  190. {
  191. attrs: {
  192. label: "修改",
  193. type: "primary",
  194. text: true,
  195. },
  196. el: "button",
  197. click() {
  198. getDtl(row);
  199. },
  200. },
  201. {
  202. attrs: {
  203. label: "删除",
  204. type: "danger",
  205. text: true,
  206. },
  207. el: "button",
  208. click() {
  209. ElMessageBox.confirm("此操作将永久删除该数据, 是否继续?", "提示", {
  210. confirmButtonText: "确定",
  211. cancelButtonText: "取消",
  212. type: "warning",
  213. }).then(() => {
  214. deleteAboutUsCulture({
  215. id: row.id,
  216. })
  217. .then((res) => {
  218. ElMessage({
  219. message: "删除成功",
  220. type: "success",
  221. });
  222. getList();
  223. });
  224. });
  225. },
  226. },
  227. ];
  228. },
  229. },
  230. ];
  231. });
  232. let formData = reactive({
  233. data: {
  234. coverList:[],
  235. videoList:[],
  236. },
  237. });
  238. const formOption = reactive({
  239. inline: true,
  240. labelWidth: 100,
  241. itemWidth: 100,
  242. rules: [],
  243. });
  244. const byform = ref(null);
  245. const formConfig = computed(() => {
  246. return [
  247. {
  248. type: "input",
  249. prop: "title",
  250. label: "标题",
  251. required: true,
  252. },
  253. {
  254. type: "input",
  255. prop: "subTitle",
  256. label: "副标题",
  257. required: true,
  258. },
  259. {
  260. type: "input",
  261. prop: "briefly",
  262. label: "简述",
  263. itemType: "textarea",
  264. required: true,
  265. },
  266. {
  267. type: "slot",
  268. slotName: "coverList",
  269. prop: "coverList",
  270. label: "封面",
  271. required: true,
  272. },
  273. {
  274. type: "select",
  275. prop: "bodyType",
  276. label: "正文类型",
  277. data: bodyListType.value,
  278. required: true,
  279. },
  280. {
  281. type: "slot",
  282. slotName: "detailsContent",
  283. prop: "detailsContent",
  284. label: "详情内容",
  285. isShow: formData.data.bodyType == "1"
  286. },
  287. // {
  288. // type: "input",
  289. // prop: "videoUrl",
  290. // label: "视频链接",
  291. // required: true,
  292. // isShow: formData.data.bodyType == "2"
  293. // },
  294. {
  295. type: "slot",
  296. slotName: "videoList",
  297. prop: "videoList",
  298. label: "视频",
  299. required: true,
  300. isShow: formData.data.bodyType == "2"
  301. },
  302. {
  303. label: "启用状态",
  304. prop: "status",
  305. type: "select",
  306. data: enableStatus.value,
  307. required: true,
  308. },
  309. {
  310. type: "input",
  311. prop: "sort",
  312. label: "排序",
  313. itemType: "number",
  314. precision: 0,
  315. max: 999,
  316. required: true,
  317. },
  318. ];
  319. });
  320. const getDictlist = async () => {
  321. const res = await getDictOneByXmhjc(["enable_status","column_body_type"]);
  322. enableStatus.value = res["enable_status"].map((x) => ({
  323. label: x.dictValue,
  324. value: x.dictKey,
  325. }));
  326. bodyListType.value = res["column_body_type"].map((x) => ({
  327. label: x.dictValue,
  328. value: x.dictKey,
  329. }));
  330. };
  331. const getList = async (req) => {
  332. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  333. loading.value = true;
  334. const res = await findColumnArticleList(sourceList.value.pagination);
  335. sourceList.value.data = res.data.rows;
  336. sourceList.value.pagination.total = res.data.total;
  337. setTimeout(() => {
  338. loading.value = false;
  339. }, 200);
  340. };
  341. const openModal = () => {
  342. dialogVisible.value = true;
  343. modalType.value = "add";
  344. formData.data = {
  345. content:'',
  346. };
  347. };
  348. const selection = ref({
  349. data: [],
  350. });
  351. const select = (_selection, row) => {
  352. selection.value.data = _selection;
  353. };
  354. const submitForm = () => {
  355. byform.value.handleSubmit(() => {
  356. if (!formData.data.coverList ) {
  357. ElMessage({message: "请上传封面",type: "error",});
  358. return
  359. }
  360. if (formData.data.bodyType == '2' && !formData.data.videoList) {
  361. ElMessage({message: "请上传视频",type: "error",});
  362. return
  363. }
  364. if (formData.data.bodyType == '1' &&
  365. !formData.data.content) {
  366. ElMessage({message: "请填写详情内容",type: "error",});
  367. return
  368. }
  369. submitLoading.value = true;
  370. if (modalType.value === 'add'){
  371. addColumnArticle(formData.data).then(response => {
  372. ElMessage({
  373. message: "添加成功",
  374. type: "success",
  375. });
  376. dialogVisible.value = false;
  377. submitLoading.value = false;
  378. getList();
  379. }).catch(()=>{
  380. submitLoading.value = false;
  381. });
  382. }else {
  383. editColumnArticle(formData.data).then(response => {
  384. ElMessage({
  385. message: "编辑成功",
  386. type: "success",
  387. });
  388. dialogVisible.value = false;
  389. submitLoading.value = false;
  390. getList();
  391. }).catch(()=>{
  392. submitLoading.value = false;
  393. });
  394. }
  395. });
  396. };
  397. const getDtl = async (row) => {
  398. modalType.value = "edit";
  399. const response = await getColumnArticle({ id: row.id })
  400. formData.data = response.data;
  401. const resFile = await getFileList({businessIdList: [response.data.id], fileType: 1})
  402. formData.data.coverList = resFile.data[response.data.id];
  403. const resFile1 = await getFileList({businessIdList: [response.data.id], fileType: 2})
  404. formData.data.videoList = resFile1.data[response.data.id];
  405. dialogVisible.value = true;
  406. };
  407. const updateHandover = (val) => {
  408. formData.data.content = val;
  409. };
  410. const uploadDataOne = ref({});
  411. const uploadFileOne = async (file) => {
  412. const res = await getFileStr({ fileName: file.name });
  413. uploadDataOne.value = res.data.uploadBody;
  414. file.id = res.data.id;
  415. file.fileName = res.data.fileName;
  416. file.fileUrl = res.data.fileUrl;
  417. return true;
  418. };
  419. const uploadDataTwo = ref({});
  420. const uploadFileTwo = async (file) => {
  421. const res = await getFileStr({ fileName: file.name });
  422. uploadDataTwo.value = res.data.uploadBody;
  423. file.id = res.data.id;
  424. file.fileName = res.data.fileName;
  425. file.fileUrl = res.data.fileUrl;
  426. return true;
  427. };
  428. const coverListSuccess = (response, uploadFile) => {
  429. formData.data.coverList = [
  430. {
  431. id: uploadFile.raw.id,
  432. fileName: uploadFile.raw.fileName,
  433. fileUrl: uploadFile.raw.fileUrl,
  434. },
  435. ];
  436. };
  437. const videoListSuccess = (response, uploadFile) => {
  438. formData.data.videoList = [
  439. {
  440. id: uploadFile.raw.id,
  441. fileName: uploadFile.raw.fileName,
  442. fileUrl: uploadFile.raw.fileUrl,
  443. },
  444. ];
  445. };
  446. getDictlist()
  447. getList()
  448. </script>
  449. <style lang="scss" scoped>
  450. .tenant {
  451. padding: 20px;
  452. .delete-btn{
  453. margin-top: 10px;
  454. margin-left: 25px;
  455. }
  456. }
  457. .avatar-uploader .avatar {
  458. width: 110px;
  459. height: 110px;
  460. display: block;
  461. background-color: black;
  462. }
  463. .avatar-uploader .el-upload {
  464. border: 1px dashed var(--el-border-color);
  465. border-radius: 6px;
  466. cursor: pointer;
  467. position: relative;
  468. overflow: hidden;
  469. transition: var(--el-transition-duration-fast);
  470. }
  471. .avatar-uploader .el-upload:hover {
  472. border-color: var(--el-color-primary);
  473. }
  474. .el-icon.avatar-uploader-icon {
  475. font-size: 28px;
  476. color: #8c939d;
  477. width: 110px;
  478. height: 110px;
  479. text-align: center;
  480. border: 1px dashed var(--el-border-color);
  481. }
  482. </style>