main.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <!--
  2. - Copyright (C) 2018-2019
  3. - All rights reserved, Designed By www.joolun.com
  4. 芋道源码:
  5. ① 移除多余的 rep 为前缀的变量,让 message 消息更简单
  6. ② 代码优化,补充注释,提升阅读性
  7. ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
  8. ④ 支持发送【视频】消息时,支持新建视频
  9. -->
  10. <template>
  11. <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">
  12. <!-- 类型 1:文本 -->
  13. <el-tab-pane name="text">
  14. <span slot="label"><i class="el-icon-document"></i> 文本</span>
  15. <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" @input="inputContent" />
  16. </el-tab-pane>
  17. <!-- 类型 2:图片 -->
  18. <el-tab-pane name="image">
  19. <span slot="label"><i class="el-icon-picture"></i> 图片</span>
  20. <el-row>
  21. <!-- 情况一:已经选择好素材、或者上传好图片 -->
  22. <div class="select-item" v-if="objData.url">
  23. <img class="material-img" :src="objData.url">
  24. <p class="item-name" v-if="objData.name">{{objData.name}}</p>
  25. <el-row class="ope-row">
  26. <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
  27. </el-row>
  28. </div>
  29. <!-- 情况二:未做完上述操作 -->
  30. <div v-else>
  31. <el-row style="text-align: center">
  32. <!-- 选择素材 -->
  33. <el-col :span="12" class="col-select">
  34. <el-button type="success" @click="openMaterial">
  35. 素材库选择<i class="el-icon-circle-check el-icon--right"></i>
  36. </el-button>
  37. <el-dialog title="选择图片" :visible.sync="dialogImageVisible" width="90%" append-to-body>
  38. <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />
  39. </el-dialog>
  40. </el-col>
  41. <!-- 文件上传 -->
  42. <el-col :span="12" class="col-add">
  43. <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
  44. :before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
  45. <el-button type="primary">上传图片</el-button>
  46. <div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
  47. </el-upload>
  48. </el-col>
  49. </el-row>
  50. </div>
  51. </el-row>
  52. </el-tab-pane>
  53. <!-- 类型 3:语音 -->
  54. <el-tab-pane name="voice">
  55. <span slot="label"><i class="el-icon-phone"></i> 语音</span>
  56. <el-row>
  57. <div class="select-item2" v-if="objData.url">
  58. <p class="item-name">{{objData.name}}</p>
  59. <div class="item-infos">
  60. <wx-voice-player :url="objData.url" />
  61. </div>
  62. <el-row class="ope-row">
  63. <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
  64. </el-row>
  65. </div>
  66. <div v-else>
  67. <el-row style="text-align: center">
  68. <!-- 选择素材 -->
  69. <el-col :span="12" class="col-select">
  70. <el-button type="success" @click="openMaterial">
  71. 素材库选择<i class="el-icon-circle-check el-icon--right"></i>
  72. </el-button>
  73. <el-dialog title="选择语音" :visible.sync="dialogVoiceVisible" width="90%" append-to-body>
  74. <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial"></WxMaterialSelect>
  75. </el-dialog>
  76. </el-col>
  77. <!-- 文件上传 -->
  78. <el-col :span="12" class="col-add">
  79. <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
  80. :before-upload="beforeVoiceUpload" :on-success="handleUploadSuccess">
  81. <el-button type="primary">点击上传</el-button>
  82. <div slot="tip" class="el-upload__tip">格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div>
  83. </el-upload>
  84. </el-col>
  85. </el-row>
  86. </div>
  87. </el-row>
  88. </el-tab-pane>
  89. <!-- 类型 4:视频 -->
  90. <el-tab-pane name="video">
  91. <span slot="label"><i class="el-icon-share"></i> 视频</span>
  92. <el-row>
  93. <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
  94. <div style="margin: 20px 0;"></div>
  95. <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
  96. <div style="margin: 20px 0;"></div>
  97. <div style="text-align: center;">
  98. <wx-video-player v-if="objData.url" :url="objData.url" />
  99. </div>
  100. <div style="margin: 20px 0;"></div>
  101. <el-row style="text-align: center">
  102. <!-- 选择素材 -->
  103. <el-col :span="12">
  104. <el-button type="success" @click="openMaterial">
  105. 素材库选择<i class="el-icon-circle-check el-icon--right"></i>
  106. </el-button>
  107. <el-dialog title="选择视频" :visible.sync="dialogVideoVisible" width="90%" append-to-body>
  108. <wx-material-select :objData="objData" @selectMaterial="selectMaterial" />
  109. </el-dialog>
  110. </el-col>
  111. <!-- 文件上传 -->
  112. <el-col :span="12">
  113. <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
  114. :before-upload="beforeVideoUpload" :on-success="handleUploadSuccess">
  115. <el-button type="primary">新建视频<i class="el-icon-upload el-icon--right"></i></el-button>
  116. </el-upload>
  117. </el-col>
  118. </el-row>
  119. </el-row>
  120. </el-tab-pane>
  121. <!-- 类型 5:图文 -->
  122. <el-tab-pane name="news">
  123. <span slot="label"><i class="el-icon-news"></i> 图文</span>
  124. <el-row>
  125. <div class="select-item" v-if="objData.articles">
  126. <wx-news :articles="objData.articles" />
  127. <el-row class="ope-row">
  128. <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />
  129. </el-row>
  130. </div>
  131. <!-- 选择素材 -->
  132. <div v-if="!objData.content">
  133. <el-row style="text-align: center">
  134. <el-col :span="24">
  135. <el-button type="success" @click="openMaterial">{{newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'}}<i class="el-icon-circle-check el-icon--right"></i></el-button>
  136. </el-col>
  137. </el-row>
  138. </div>
  139. <el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%" append-to-body>
  140. <wx-material-select :objData="objData" @selectMaterial="selectMaterial" :newsType="newsType" />
  141. </el-dialog>
  142. </el-row>
  143. </el-tab-pane>
  144. <!-- 类型 6:音乐 -->
  145. <el-tab-pane name="music">
  146. <span slot="label"><i class="el-icon-service"></i> 音乐</span>
  147. <el-row>
  148. <el-col :span="6">
  149. <div class="thumb-div">
  150. <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl">
  151. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  152. <div class="thumb-but">
  153. <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
  154. :before-upload="beforeThumbImageUpload" :on-success="handleUploadSuccess">
  155. <el-button slot="trigger" size="mini" type="text">本地上传</el-button>
  156. <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px">素材库选择</el-button>
  157. </el-upload>
  158. </div>
  159. </div>
  160. <el-dialog title="选择图片" :visible.sync="dialogThumbVisible" width="80%" append-to-body>
  161. <wx-material-select :objData="{type:'image', accountId: objData.accountId}" @selectMaterial="selectMaterial" />
  162. </el-dialog>
  163. </el-col>
  164. <el-col :span="18">
  165. <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
  166. <div style="margin: 20px 0;"></div>
  167. <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
  168. </el-col>
  169. </el-row>
  170. <div style="margin: 20px 0;"></div>
  171. <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />
  172. <div style="margin: 20px 0;"></div>
  173. <el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" @input="inputContent" />
  174. </el-tab-pane>
  175. </el-tabs>
  176. </template>
  177. <script>
  178. import WxNews from '@/views/mp/components/wx-news/main.vue'
  179. import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
  180. import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
  181. import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
  182. import { getAccessToken } from '@/utils/auth'
  183. export default {
  184. name: "wxReplySelect",
  185. components: {
  186. WxNews,
  187. WxMaterialSelect,
  188. WxVoicePlayer,
  189. WxVideoPlayer
  190. },
  191. props: {
  192. objData: { // 消息对象。
  193. type: Object, // 设置为 Object 的原因,方便属性的传递
  194. required: true,
  195. },
  196. newsType:{ // 图文类型:1、已发布图文;2、草稿箱图文
  197. type: String,
  198. default: "1"
  199. },
  200. },
  201. data() {
  202. return {
  203. tempPlayerObj: {
  204. type: '2'
  205. },
  206. tempObj: new Map().set( // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;
  207. this.objData.type, // 消息类型
  208. Object.assign({}, this.objData)), // 消息内容
  209. // ========== 素材选择的弹窗,是否可见 ==========
  210. dialogNewsVisible: false, // 图文
  211. dialogImageVisible: false, // 图片
  212. dialogVoiceVisible: false, // 语音
  213. dialogVideoVisible: false, // 视频
  214. dialogThumbVisible: false, // 缩略图
  215. // ========== 文件上传(图片、语音、视频) ==========
  216. fileList: [], // 文件列表
  217. uploadData: {
  218. "accountId": undefined,
  219. "type": this.objData.type,
  220. "title":'',
  221. "introduction":''
  222. },
  223. actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',
  224. headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
  225. }
  226. },
  227. methods:{
  228. beforeThumbImageUpload(file){
  229. const isType = file.type === 'image/jpeg'
  230. || file.type === 'image/png'
  231. || file.type === 'image/gif'
  232. || file.type === 'image/bmp'
  233. || file.type === 'image/jpg';
  234. if (!isType) {
  235. this.$message.error('上传图片格式不对!');
  236. return false;
  237. }
  238. const isLt = file.size / 1024 / 1024 < 2;
  239. if (!isLt) {
  240. this.$message.error('上传图片大小不能超过 2M!');
  241. return false;
  242. }
  243. this.uploadData.accountId = this.objData.accountId;
  244. return true;
  245. },
  246. beforeVoiceUpload(file){
  247. // 校验格式
  248. const isType = file.type === 'audio/mp3'
  249. || file.type === 'audio/mpeg'
  250. || file.type === 'audio/wma'
  251. || file.type === 'audio/wav'
  252. || file.type === 'audio/amr';
  253. if (!isType) {
  254. this.$message.error('上传语音格式不对!' + file.type);
  255. return false;
  256. }
  257. // 校验大小
  258. const isLt = file.size / 1024 / 1024 < 2;
  259. if (!isLt) {
  260. this.$message.error('上传语音大小不能超过 2M!');
  261. return false;
  262. }
  263. this.uploadData.accountId = this.objData.accountId;
  264. return true;
  265. },
  266. beforeImageUpload(file) {
  267. // 校验格式
  268. const isType = file.type === 'image/jpeg'
  269. || file.type === 'image/png'
  270. || file.type === 'image/gif'
  271. || file.type === 'image/bmp'
  272. || file.type === 'image/jpg';
  273. if (!isType) {
  274. this.$message.error('上传图片格式不对!');
  275. return false;
  276. }
  277. // 校验大小
  278. const isLt = file.size / 1024 / 1024 < 2;
  279. if (!isLt) {
  280. this.$message.error('上传图片大小不能超过 2M!');
  281. return false;
  282. }
  283. this.uploadData.accountId = this.objData.accountId;
  284. return true;
  285. },
  286. beforeVideoUpload(file){
  287. // 校验格式
  288. const isType = file.type === 'video/mp4';
  289. if (!isType) {
  290. this.$message.error('上传视频格式不对!');
  291. return false;
  292. }
  293. // 校验大小
  294. const isLt = file.size / 1024 / 1024 < 10;
  295. if (!isLt) {
  296. this.$message.error('上传视频大小不能超过 10M!');
  297. return false;
  298. }
  299. this.uploadData.accountId = this.objData.accountId;
  300. return true;
  301. },
  302. handleUploadSuccess(response, file, fileList) {
  303. if (response.code !== 0) {
  304. this.$message.error('上传出错:' + response.msg)
  305. return false;
  306. }
  307. // 清空上传时的各种数据
  308. this.fileList = []
  309. this.uploadData.title = ''
  310. this.uploadData.introduction = ''
  311. // 上传好的文件,本质是个素材,所以可以进行选中
  312. let item = response.data
  313. this.selectMaterial(item)
  314. },
  315. /**
  316. * 切换消息类型的 tab
  317. *
  318. * @param tab tab
  319. */
  320. handleClick(tab) {
  321. // 设置后续文件上传的文件类型
  322. this.uploadData.type = this.objData.type;
  323. if (this.uploadData.type === 'music') { // 【音乐】上传的是缩略图
  324. this.uploadData.type = 'thumb';
  325. }
  326. // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData
  327. let tempObjItem = this.tempObj.get(this.objData.type)
  328. if (tempObjItem) {
  329. this.objData.content = tempObjItem.content ? tempObjItem.content : null
  330. this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null
  331. this.objData.url = tempObjItem.url ? tempObjItem.url : null
  332. this.objData.name = tempObjItem.url ? tempObjItem.name : null
  333. this.objData.title = tempObjItem.title ? tempObjItem.title : null
  334. this.objData.description = tempObjItem.description ? tempObjItem.description : null
  335. return;
  336. }
  337. // 如果获取不到,需要把 objData 复原
  338. // 必须使用 $set 赋值,不然 input 无法输入内容
  339. this.$set(this.objData, 'content', '');
  340. this.$delete(this.objData, 'mediaId');
  341. this.$delete(this.objData, 'url');
  342. this.$set(this.objData, 'title', '');
  343. this.$set(this.objData, 'description', '');
  344. },
  345. /**
  346. * 选择素材,将设置设置到 objData 变量
  347. *
  348. * @param item 素材
  349. */
  350. selectMaterial(item) {
  351. // 选择好素材,所以隐藏弹窗
  352. this.closeMaterial();
  353. // 创建 tempObjItem 对象,并设置对应的值
  354. let tempObjItem = {}
  355. tempObjItem.type = this.objData.type;
  356. if (this.objData.type === 'news') {
  357. tempObjItem.articles = item.content.newsItem
  358. this.objData.articles = item.content.newsItem
  359. } else if (this.objData.type === 'music') { // 音乐需要特殊处理,因为选择的是图片的缩略图
  360. tempObjItem.thumbMediaId = item.mediaId
  361. this.objData.thumbMediaId = item.mediaId
  362. tempObjItem.thumbMediaUrl = item.url
  363. this.objData.thumbMediaUrl = item.url
  364. // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉
  365. tempObjItem.title = this.objData.title || ''
  366. tempObjItem.introduction = this.objData.introduction || ''
  367. tempObjItem.musicUrl = this.objData.musicUrl || ''
  368. tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''
  369. } else if (this.objData.type === 'image'
  370. || this.objData.type === 'voice') {
  371. tempObjItem.mediaId = item.mediaId
  372. this.objData.mediaId = item.mediaId
  373. tempObjItem.url = item.url;
  374. this.objData.url = item.url;
  375. tempObjItem.name = item.name
  376. this.objData.name = item.name
  377. } else if (this.objData.type === 'video') {
  378. tempObjItem.mediaId = item.mediaId
  379. this.objData.mediaId = item.mediaId
  380. tempObjItem.url = item.url;
  381. this.objData.url = item.url;
  382. tempObjItem.name = item.name
  383. this.objData.name = item.name
  384. // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction
  385. if (item.title) {
  386. this.objData.title = item.title || ''
  387. tempObjItem.title = item.title || ''
  388. }
  389. if (item.introduction) {
  390. this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下
  391. tempObjItem.description = item.introduction || ''
  392. }
  393. } else if (this.objData.type === 'text') {
  394. this.objData.content = item.content || ''
  395. }
  396. // 最终设置到临时缓存
  397. this.tempObj.set(this.objData.type, tempObjItem)
  398. },
  399. openMaterial() {
  400. if (this.objData.type === 'news') {
  401. this.dialogNewsVisible = true
  402. } else if(this.objData.type === 'image') {
  403. this.dialogImageVisible = true
  404. } else if(this.objData.type === 'voice') {
  405. this.dialogVoiceVisible = true
  406. } else if(this.objData.type === 'video') {
  407. this.dialogVideoVisible = true
  408. } else if(this.objData.type === 'music') {
  409. this.dialogThumbVisible = true
  410. }
  411. },
  412. closeMaterial() {
  413. this.dialogNewsVisible = false
  414. this.dialogImageVisible = false
  415. this.dialogVoiceVisible = false
  416. this.dialogVideoVisible = false
  417. this.dialogThumbVisible = false
  418. },
  419. deleteObj() {
  420. if (this.objData.type === 'news') {
  421. this.$delete(this.objData, 'articles');
  422. } else if(this.objData.type === 'image') {
  423. this.objData.mediaId = null
  424. this.$delete(this.objData, 'url');
  425. this.objData.name = null
  426. } else if(this.objData.type === 'voice') {
  427. this.objData.mediaId = null
  428. this.$delete(this.objData, 'url');
  429. this.objData.name = null
  430. } else if(this.objData.type === 'video') {
  431. this.objData.mediaId = null
  432. this.$delete(this.objData, 'url');
  433. this.objData.name = null
  434. this.objData.title = null
  435. this.objData.description = null
  436. } else if(this.objData.type === 'music') {
  437. this.objData.thumbMediaId = null
  438. this.objData.thumbMediaUrl = null
  439. this.objData.title = null
  440. this.objData.description = null
  441. this.objData.musicUrl = null
  442. this.objData.hqMusicUrl = null
  443. } else if(this.objData.type === 'text') {
  444. this.objData.content = null
  445. }
  446. // 覆盖缓存
  447. this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
  448. },
  449. /**
  450. * 输入时,缓存每次 objData 到 tempObj 中
  451. *
  452. * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式
  453. */
  454. inputContent(str) {
  455. // 覆盖缓存
  456. this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
  457. }
  458. }
  459. };
  460. </script>
  461. <style lang="scss" scoped>
  462. .public-account-management{
  463. .el-input{
  464. width: 70%;
  465. margin-right: 2%;
  466. }
  467. }
  468. .pagination{
  469. text-align: right;
  470. margin-right: 25px;
  471. }
  472. .select-item{
  473. width: 280px;
  474. padding: 10px;
  475. margin: 0 auto 10px auto;
  476. border: 1px solid #eaeaea;
  477. }
  478. .select-item2{
  479. padding: 10px;
  480. margin: 0 auto 10px auto;
  481. border: 1px solid #eaeaea;
  482. }
  483. .ope-row{
  484. padding-top: 10px;
  485. text-align: center;
  486. }
  487. .item-name{
  488. font-size: 12px;
  489. overflow: hidden;
  490. text-overflow:ellipsis;
  491. white-space: nowrap;
  492. text-align: center;
  493. }
  494. .el-form-item__content{
  495. line-height:unset!important;
  496. }
  497. .col-select{
  498. border: 1px solid rgb(234, 234, 234);
  499. padding: 50px 0px;
  500. height: 160px;
  501. width: 49.5%;
  502. }
  503. .col-select2{
  504. border: 1px solid rgb(234, 234, 234);
  505. padding: 50px 0px;
  506. height: 160px;
  507. }
  508. .col-add{
  509. border: 1px solid rgb(234, 234, 234);
  510. padding: 50px 0px;
  511. height: 160px;
  512. width: 49.5%;
  513. float: right
  514. }
  515. .avatar-uploader-icon {
  516. border: 1px solid #d9d9d9;
  517. font-size: 28px;
  518. color: #8c939d;
  519. width: 100px!important;
  520. height: 100px!important;
  521. line-height: 100px!important;
  522. text-align: center;
  523. }
  524. .material-img {
  525. width: 100%;
  526. }
  527. .thumb-div{
  528. display: inline-block;
  529. text-align: center;
  530. }
  531. .item-infos{
  532. width: 30%;
  533. margin: auto
  534. }
  535. </style>