creatTopic.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <template>
  2. <div class="mainBox">
  3. <div class="layerBox">
  4. <tableTitle :name="tableDivTitle"/>
  5. <el-form :model="form" ref="form" :rules="formRules" label-position="left" label-width="120px">
  6. <div class="formDiv">
  7. <div>
  8. <el-form-item label="课题分类:" prop="type" class="custom-align-right">
  9. <el-select v-model="form.type" placeholder="请选择课题分类..">
  10. <!-- <el-option label="科研" value="1"></el-option>
  11. <el-option label="维权" value="2"></el-option> -->
  12. <el-option :label="item.label" :value="item.value" v-for="item in topicType"></el-option>
  13. </el-select>
  14. </el-form-item>
  15. <el-form-item label="课题标题:" prop="title" class="custom-align-right">
  16. <el-input v-model="form.title" autocomplete="off" placeholder="请输入课题标题"></el-input>
  17. </el-form-item>
  18. <div class="QuillTitle">
  19. <span>* </span>课题内容:
  20. <div @click="toggleSourceMode" class="QuillModelBtn">
  21. {{ showHtml ? '切换到编辑模式' : '切换到源码模式' }}
  22. </div>
  23. </div>
  24. <el-form-item label="">
  25. <div class="editor-container">
  26. <div v-if="showHtml">
  27. <textarea v-model="editorHtml" style="width: 100%; height: 400px;"></textarea>
  28. </div>
  29. <div v-else>
  30. <quill-editor ref="quillEditor" v-model="form.content" :options="editorOptions" class="my-quill-editor"/>
  31. </div>
  32. <!-- 多图上传隐藏的input -->
  33. <input type="file" ref="multiFileInput" @change="handleMultipleFiles" multiple hidden accept="image/jpeg, image/png" />
  34. </div>
  35. </el-form-item>
  36. <el-form-item label="建立群聊:" prop="is_group" class="custom-align-right">
  37. <el-radio-group v-model="form.is_group" :disabled="groupStatus">
  38. <el-radio :label="'1'">是</el-radio>
  39. <el-radio :label="'0'">否</el-radio>
  40. </el-radio-group>
  41. </el-form-item>
  42. <div>
  43. <el-form-item label="群聊名称:" prop="group_name" class="custom-align-right" v-if="form.is_group=='1'">
  44. <el-input v-model="form.group_name" autocomplete="off" placeholder="请输入群聊名称" :disabled="groupStatus" maxlength="20"></el-input>
  45. </el-form-item>
  46. <el-form-item label="作者:" class="custom-align-right">
  47. <el-input v-model="form.author" autocomplete="off" placeholder="请输入作者名"></el-input>
  48. </el-form-item>
  49. </div>
  50. </div>
  51. </div>
  52. </el-form>
  53. </div>
  54. <div class="bottomBtnBox">
  55. <el-button type="info" @click="returnPage">返回</el-button>
  56. <el-button type="primary" @click="editToServe" v-if="editStatus==true">修改</el-button>
  57. <el-button type="primary" @click="addToServe" v-else>创建</el-button>
  58. </div>
  59. </div>
  60. </template>
  61. <script>
  62. //表格标题
  63. import tableTitle from './components/tableTitle';
  64. //引入公用样式
  65. import '@/styles/global.less';
  66. import { quillEditor } from 'vue-quill-editor';
  67. import 'quill/dist/quill.snow.css';
  68. import ImageResize from 'quill-image-resize-module';
  69. import Quill from 'quill'; // 引入 Quill
  70. import Delta from 'quill-delta'; // 引入 Delta,用于手动修改文档
  71. // 注册 Image Resize 模块
  72. Quill.register('modules/imageResize', ImageResize);
  73. export default {
  74. components: {
  75. quillEditor,
  76. tableTitle
  77. },
  78. data() {
  79. //0.全局操作 start ------------------------------------------------------------>
  80. //表单验证
  81. const validateEmpty = (rule,value,callback) => {
  82. if (value.length == 0) {
  83. callback(new Error('该项不能为空!'))
  84. } else {
  85. callback()
  86. }
  87. }
  88. const validateArray = (rule,value,callback) => {
  89. if (value.length == 0) {
  90. callback(new Error('该项不能为空!'))
  91. } else {
  92. callback()
  93. }
  94. }
  95. let self = this;
  96. //0.全局操作 end ------------------------------------------------------------>
  97. return {
  98. //1.表单项 start ------------------------------------------------------------>
  99. editStatus:false,//是否为编辑状态
  100. groupStatus:false,//是否可以编辑群信息
  101. tableDivTitle:"编辑课题",
  102. disclaimer:true,//免责声明
  103. //提交表单
  104. form: {
  105. type:"",//课题分类
  106. title:"",//课题标题
  107. content:"",//内容
  108. is_group:"0",//是否创建群聊
  109. group_name:"",//群聊名称
  110. author:"",//作者
  111. },
  112. topicType:[],//课题分类
  113. topicStatus:[],//课题状态
  114. //1.2 表单验证规则
  115. formRules: {
  116. title:[{required:true,trigger:'blur',validator:validateEmpty}],
  117. type:[{required:true,trigger:'blur',validator:validateArray}],
  118. is_group:[{required:true,trigger:'blur',validator:validateEmpty}],
  119. group_name:[{required:true,trigger:'blur',validator:validateEmpty}],
  120. },
  121. //1.3富文本编辑器配置
  122. showHtml: false, //用于保存源码内容
  123. editorHtml: '',
  124. editorOptions: {
  125. placeholder: '请输入内容...',
  126. theme: 'snow', // 主题样式
  127. modules: {
  128. toolbar: {
  129. container: [
  130. [{ 'font': [] }], // 字体
  131. [{ 'header': [1, 2, 3, 4, 5, 6, false] }], // 标题
  132. [{ 'size': ['small', false, 'large', 'huge'] }], // 字体大小
  133. ['bold', 'italic', 'underline', 'strike'], // 加粗、斜体、下划线、删除线
  134. [{ 'color': [] }, { 'background': [] }], // 文字颜色、背景颜色
  135. [{ 'script': 'sub' }, { 'script': 'super' }], // 上标、下标
  136. [{ 'list': 'ordered'}, { 'list': 'bullet' }], // 列表
  137. [{ 'indent': '-1'}, { 'indent': '+1' }], // 缩进
  138. [{ 'align': [] }], // 对齐方式
  139. ['blockquote', 'code-block'], // 引用、代码块
  140. ['link', 'image', 'video'], // 链接、图片、视频
  141. ['clean'],
  142. [{ 'html': true }] // 添加自定义按钮的占位符
  143. ],
  144. handlers: {
  145. image: () => {
  146. this.handleImageClick();
  147. },
  148. showHtml: function() {
  149. this.$emit('toggleSourceMode');
  150. }
  151. }
  152. },
  153. imageResize: {
  154. displayStyles: {
  155. backgroundColor: 'black',
  156. border: 'none',
  157. color: 'white'
  158. },
  159. modules: ['Resize', 'DisplaySize', 'Toolbar'] // 启用不同的调整方式
  160. }
  161. }
  162. },
  163. //表单项 end ------------------------------------------------------------>
  164. };
  165. },
  166. methods: {
  167. //1.提交表单 start ------------------------------------------------------------>
  168. //1.1 直接上传图片
  169. beforeAvatarUpload(file) {
  170. const isJPG = file.type === 'image/jpeg';
  171. const isPNG = file.type === 'image/png';
  172. const isLt2M = file.size / 1024 / 1024 < 2;
  173. if (!isJPG && !isPNG) {
  174. this.$message.error('上传缩略图只能是 JPG 或 PNG 格式!');
  175. return false;
  176. }
  177. if (!isLt2M) {
  178. this.$message.error('上传缩略图大小不能超过 2MB!');
  179. return false;
  180. }
  181. const formData = new FormData();
  182. formData.append('file', file);
  183. this.$store.dispatch('pool/uploadFile',formData).then(res=> {
  184. this.imgUrl = res.data.imgUrl;//显示缩略图
  185. this.form.imgurl = res.data.imgUrl;//提供表单地址
  186. console.log(res.data.imgUrl)
  187. }).catch(() => {
  188. this.$message({
  189. type: 'info',
  190. message: '网络错误,请重试!'
  191. });
  192. })
  193. // 阻止默认的上传行为
  194. return false;
  195. },
  196. //1.2 提交表单
  197. addToServe(){
  198. //先进行验证
  199. this.$refs.form.validate(valid => {
  200. if (valid) {
  201. //console.log(this.form)
  202. this.$store.dispatch('chat/addTopic',this.form).then(res=> {
  203. //汇报结果
  204. this.$message({
  205. type: 'success',
  206. message: '已成功创建商圈!'
  207. });
  208. this.cleatForm();
  209. //返回列表页
  210. this.returnPage()
  211. }).catch(() => {
  212. this.$message({
  213. type: 'info',
  214. message: '网络错误,请重试!'
  215. });
  216. })
  217. }
  218. })
  219. },
  220. //1.3 清理表单
  221. cleatForm(){
  222. this.form.type = "";
  223. this.form.title = "";
  224. this.form.content = "";
  225. this.form.is_group = "";
  226. this.form.group_name = "";
  227. this.form.author = "";
  228. },
  229. //1.4 查询商圈分类
  230. getTopicType(){
  231. this.$store.dispatch('chat/topicType',this.getApiData).then(res=> {
  232. this.topicType = res.data;
  233. console.log(this.topicType)
  234. }).catch(() => {
  235. this.$message.error("查询商圈分类失败!");
  236. })
  237. },
  238. //1.5 查询商圈状态
  239. getTopicStatus(){
  240. this.$store.dispatch('chat/topicStatus',this.getApiData).then(res=> {
  241. this.topicStatus = res.data;
  242. console.log(res)
  243. }).catch(() => {
  244. this.$message.error("查询商圈状态失败!");
  245. })
  246. },
  247. //提交表单 end ------------------------------------------------------------>
  248. //2.跳转操作 start ------------------------------------------------------------>
  249. returnPage(){
  250. this.$router.push({
  251. path: '/topic',
  252. });
  253. },
  254. //跳转操作 end ------------------------------------------------------------>
  255. //3.回显操作 ------------------------------------------------------------>
  256. //3.1回显数据
  257. getMainData() {
  258. let data = {
  259. id: this.$route.query.id + ""
  260. };
  261. this.$store.dispatch('chat/getTopicInfo', data).then(res => {
  262. console.log(res);
  263. this.form.title = res.data.title;
  264. this.form.type = res.data.type;
  265. this.form.content = res.data.content;
  266. this.form.is_group = res.data.is_group;
  267. this.form.group_name = res.data.group_name;
  268. this.form.author = res.data.author;
  269. //如果已经创建了群聊,阻止其修改
  270. if(res.data.is_group=="1"){
  271. this.groupStatus = true;
  272. }
  273. }).catch(() => {
  274. this.$message({
  275. type: 'info',
  276. message: '网络错误,请重试!'
  277. });
  278. });
  279. },
  280. async loadCascaderPath(path) {
  281. for (let i = 0; i < path.length; i++) {
  282. const parentId = path[i - 1] || 0; // 获取当前层级的父级ID
  283. const level = i; // 当前层级的索引
  284. await this.$store.dispatch('pool/categoryList', { pid: parentId })
  285. .then((res) => {
  286. const nodes = res.data.map(item => ({
  287. value: item.id,
  288. label: item.name,
  289. leaf: level >= 3, // 假设4层结构,设置叶子节点标记
  290. }));
  291. // 级联选择器加载数据
  292. if (level === path.length - 1) {
  293. this.form.cat_arr_id = path; // 确保最后一级路径正确设置
  294. this.parentKey += 1; // 强制刷新 cascader
  295. }
  296. });
  297. }
  298. },
  299. //1.3提交修改
  300. editToServe(){
  301. //添加要修改的id
  302. this.form.id = this.editId + "";
  303. //先进行验证
  304. this.$refs.form.validate(valid => {
  305. if (valid) {
  306. //console.log(this.form)
  307. this.$store.dispatch('chat/updateTopic',this.form).then(res=> {
  308. //汇报结果
  309. this.$message({
  310. type: 'success',
  311. message: '已成功修改课题信息!'
  312. });
  313. this.cleatForm();
  314. //返回列表页
  315. this.returnPage()
  316. }).catch(() => {
  317. this.$message({
  318. type: 'info',
  319. message: '网络错误,请重试!'
  320. });
  321. })
  322. }
  323. })
  324. },
  325. //跳转操作 end ------------------------------------------------------------>
  326. //4.富文本编辑器 start ------------------------------------------------------------>
  327. //4.1 编辑器点击上传图片
  328. handleImageClick() {
  329. this.$refs.multiFileInput.click(); // 打开文件选择框
  330. },
  331. handleMultipleFiles(event) {
  332. const files = event.target.files;
  333. if (files.length) {
  334. this.uploadMultipleImages(files); // 处理多图片上传
  335. }
  336. },
  337. uploadMultipleImages(files) {
  338. const uploadPromises = [];
  339. for (let i = 0; i < files.length; i++) {
  340. uploadPromises.push(this.uploadImage(files[i]));
  341. }
  342. Promise.all(uploadPromises).then(urls => {
  343. const quillEditor = this.$refs.quillEditor.quill;
  344. urls.forEach(url => {
  345. const range = quillEditor.getSelection();
  346. quillEditor.insertEmbed(range.index, 'image', url); // 在编辑器中插入图片
  347. });
  348. }).catch(error => {
  349. this.$message.error('图片上传失败,请重试!');
  350. });
  351. },
  352. uploadImage(file) {
  353. const formData = new FormData();
  354. formData.append('file', file);
  355. return this.$store.dispatch('pool/uploadFile', formData)
  356. .then(res => {
  357. if (res && res.data && res.data.imgUrl) {
  358. return res.data.imgUrl;
  359. } else {
  360. throw new Error('图片上传失败');
  361. }
  362. })
  363. .catch(error => {
  364. this.$message.error('图片上传失败,请重试!');
  365. throw error;
  366. });
  367. },
  368. //4.2 图片粘贴上传
  369. // 处理从网页粘贴的图片 URL
  370. handleImageFromWeb(imageUrl) {
  371. return new Promise((resolve) => {
  372. console.log('开始下载图片:', imageUrl);
  373. this.fetchImageAsBlob(imageUrl).then((blob) => {
  374. console.log('图片已下载为 Blob:', blob);
  375. const formData = new FormData();
  376. formData.append('file', blob, 'image.jpg');
  377. this.$store.dispatch('pool/uploadFile', formData).then((res) => {
  378. if (res && res.data && res.data.imgUrl) {
  379. console.log('图片上传成功:', res.data.imgUrl);
  380. resolve(res.data.imgUrl);
  381. } else {
  382. console.log('图片上传失败,保留原 URL:', imageUrl);
  383. resolve(imageUrl);
  384. }
  385. }).catch((error) => {
  386. console.error('图片上传时出现错误:', error);
  387. resolve(imageUrl);
  388. });
  389. }).catch((error) => {
  390. console.error('图片下载失败:', error);
  391. resolve(imageUrl);
  392. });
  393. });
  394. },
  395. fetchImageAsBlob(url) {
  396. return fetch(url)
  397. .then(response => {
  398. if (!response.ok) {
  399. throw new Error('Failed to fetch image');
  400. }
  401. return response.blob();
  402. });
  403. },
  404. //编辑源码
  405. toggleSourceMode() {
  406. if (!this.showHtml) {
  407. // 切换到源码模式,将编辑器内容同步到 textarea 中
  408. this.editorHtml = this.$refs.quillEditor.quill.root.innerHTML;
  409. this.showHtml = true; // 显示 textarea
  410. } else {
  411. // 切换回富文本模式,将 textarea 内容同步回编辑器
  412. this.showHtml = false; // 显示 Quill 编辑器
  413. // Quill 编辑器可能被销毁,所以使用 $nextTick 确保 DOM 渲染完成后再操作编辑器
  414. this.$nextTick(() => {
  415. if (this.$refs.quillEditor) {
  416. this.$refs.quillEditor.quill.root.innerHTML = this.editorHtml;
  417. } else {
  418. console.error('Quill 编辑器实例未找到');
  419. }
  420. });
  421. }
  422. }
  423. //富文本编辑器 end ------------------------------------------------------------>
  424. },
  425. mounted(){
  426. //查询课题分类
  427. this.getTopicType();
  428. this.form.author = this.$store.state.user.name;
  429. //1.判断是新建还是回显
  430. if(this.$route.query.id!=undefined){
  431. this.editId = this.$route.query.id;
  432. this.editStatus = true;
  433. console.log("编辑商圈!")
  434. this.getMainData();
  435. }else{
  436. this.editStatus = false;
  437. console.log("新建商圈!")
  438. }
  439. //复制内容到富文本 start ------------------------------------------------------------>
  440. this.$nextTick(() => {
  441. const quillEditor = this.$refs.quillEditor.quill;
  442. if (quillEditor) {
  443. console.log('Quill 编辑器已初始化');
  444. // 在粘贴事件触发时,记录所有 img 的 src
  445. quillEditor.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
  446. if (node.tagName === 'IMG') {
  447. const imageUrl = node.getAttribute('src');
  448. console.log('检测到粘贴的图片 URL:', imageUrl);
  449. if (imageUrl && !imageUrl.startsWith('data:') && !imageUrl.startsWith('file://')) {
  450. // 先处理图片上传
  451. this.handleImageFromWeb(imageUrl).then((uploadedImageUrl) => {
  452. // 查找编辑器中所有 img 标签并替换 src
  453. const imgs = quillEditor.root.querySelectorAll('img');
  454. imgs.forEach((img) => {
  455. if (img.getAttribute('src') === imageUrl) {
  456. img.setAttribute('src', uploadedImageUrl); // 替换 src
  457. console.log('图片 src 已替换为:', uploadedImageUrl);
  458. }
  459. });
  460. });
  461. }
  462. }
  463. return delta; // 返回原始 delta
  464. });
  465. } else {
  466. console.error('Quill 初始化失败');
  467. }
  468. });
  469. //复制富文本 end ------------------------------------------------------------>
  470. },
  471. };
  472. </script>
  473. <style scoped lang="less">
  474. //文本编辑器
  475. .QuillTitle {
  476. line-height: 36px;
  477. font-size: 14px;
  478. color: #606266;
  479. font-weight:bold;
  480. padding-left: 30px;
  481. span{
  482. color: #ff4949
  483. }
  484. .QuillModelBtn {
  485. display: inline-block;
  486. margin-left: 10px;
  487. font-size: 12px;
  488. color: #999;
  489. cursor: pointer;
  490. }
  491. }
  492. .editor-container {
  493. height: 420px;
  494. padding-bottom:20px;
  495. }
  496. .my-quill-editor {
  497. height: 320px;
  498. }
  499. .ql-editor {
  500. height: 320px;
  501. }
  502. //执行v-deep穿透scope选择器 start------------------------------------------------------------>*/
  503. ::v-deep .custom-form-item > .el-form-item__label {
  504. line-height: 140px !important;
  505. }
  506. ::v-deep .custom-textarea .el-textarea__inner {
  507. resize: none; /* 禁止用户拖拽调整大小 */
  508. }
  509. ::v-deep .custom-align-right .el-form-item__label {
  510. text-align: right; /* 设置标签文字右对齐 */
  511. }
  512. ::v-deep .el-select {
  513. width: 100%; /* 禁止用户拖拽调整大小 */
  514. }
  515. //执行v-deep穿透scope选择器 end------------------------------------------------------------>*/
  516. </style>