index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <!--
  2. MIT License
  3. Copyright (c) 2020 www.joolun.com
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. 点餐系统:
  20. ① 移除 avue 框架,使用 element-ui 重写
  21. ② 重写代码,保持和现有项目保持一致
  22. -->
  23. <template>
  24. <div class="app-container">
  25. <doc-alert title="自动回复" url="https://doc.iocoder.cn/mp/auto-reply/" />
  26. <!-- 搜索工作栏 -->
  27. <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
  28. <el-form-item label="公众号" prop="accountId">
  29. <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
  30. <el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item>
  34. <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
  35. <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
  36. </el-form-item>
  37. </el-form>
  38. <!-- tab 切换 -->
  39. <el-tabs v-model="type" @tab-click="handleClick">
  40. <!-- 操作工具栏 -->
  41. <el-row :gutter="10" class="mb8">
  42. <el-col :span="1.5">
  43. <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  44. v-hasPermi="['mp:auto-reply:create']" v-if="type !== '1' || list.length <= 0">新增
  45. </el-button>
  46. </el-col>
  47. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
  48. </el-row>
  49. <!-- tab 项 -->
  50. <el-tab-pane name="1">
  51. <span slot="label"><i class="el-icon-star-off"></i> 关注时回复</span>
  52. </el-tab-pane>
  53. <el-tab-pane name="2">
  54. <span slot="label"><i class="el-icon-chat-line-round"></i> 消息回复</span>
  55. </el-tab-pane>
  56. <el-tab-pane name="3">
  57. <span slot="label"><i class="el-icon-news"></i> 关键词回复</span>
  58. </el-tab-pane>
  59. </el-tabs>
  60. <!-- 列表 -->
  61. <el-table v-loading="loading" :data="list">
  62. <el-table-column label="请求消息类型" align="center" prop="requestMessageType" v-if="type === '2'" />
  63. <el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
  64. <el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
  65. <template v-slot="scope">
  66. <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch"/>
  67. </template>
  68. </el-table-column>
  69. <el-table-column label="回复消息类型" align="center">
  70. <template v-slot="scope">
  71. <dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType"/>
  72. </template>
  73. </el-table-column>
  74. <el-table-column label="回复内容" align="center">
  75. <template v-slot="scope">
  76. <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
  77. <div v-else-if="scope.row.responseMessageType === 'voice'">
  78. <wx-voice-player :url="scope.row.responseMediaUrl" />
  79. </div>
  80. <div v-else-if="scope.row.responseMessageType === 'image'">
  81. <a target="_blank" :href="scope.row.responseMediaUrl">
  82. <img :src="scope.row.responseMediaUrl" style="width: 100px">
  83. </a>
  84. </div>
  85. <div v-else-if="scope.row.responseMessageType === 'video' || scope.row.responseMessageType === 'shortvideo'">
  86. <wx-video-player :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
  87. </div>
  88. <div v-else-if="scope.row.responseMessageType === 'news'">
  89. <wx-news :articles="scope.row.responseArticles" />
  90. </div>
  91. <div v-else-if="scope.row.responseMessageType === 'music'">
  92. <wx-music :title="scope.row.responseTitle" :description="scope.row.responseDescription"
  93. :thumb-media-url="scope.row.responseThumbMediaUrl"
  94. :music-url="scope.row.responseMusicUrl" :hq-music-url="scope.row.responseHqMusicUrl" />
  95. </div>
  96. </template>
  97. </el-table-column>
  98. <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  99. <template v-slot="scope">
  100. <span>{{ parseTime(scope.row.createTime) }}</span>
  101. </template>
  102. </el-table-column>
  103. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  104. <template v-slot="scope">
  105. <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  106. v-hasPermi="['mp:auto-reply:update']">修改
  107. </el-button>
  108. <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  109. v-hasPermi="['mp:auto-reply:delete']">删除
  110. </el-button>
  111. </template>
  112. </el-table-column>
  113. </el-table>
  114. <!-- 添加或修改自动回复的对话框 -->
  115. <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
  116. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  117. <el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
  118. <el-select v-model="form.requestMessageType" placeholder="请选择">
  119. <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
  120. :key="dict.value" :label="dict.label" :value="dict.value"
  121. v-if="requestMessageTypes.includes(dict.value)"/>
  122. </el-select>
  123. </el-form-item>
  124. <el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
  125. <el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable size="small">
  126. <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
  127. :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
  128. </el-select>
  129. </el-form-item>
  130. <el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
  131. <el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
  132. </el-form-item>
  133. <el-form-item label="回复消息">
  134. <wx-reply-select :objData="objData" v-if="hackResetWxReplySelect" />
  135. </el-form-item>
  136. </el-form>
  137. <span slot="footer" class="dialog-footer">
  138. <el-button @click="cancel">取 消</el-button>
  139. <el-button type="primary" @click="handleSubmit">确 定</el-button>
  140. </span>
  141. </el-dialog>
  142. </div>
  143. </template>
  144. <script>
  145. import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
  146. import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
  147. import WxMsg from '@/views/mp/components/wx-msg/main.vue';
  148. import WxLocation from '@/views/mp/components/wx-location/main.vue';
  149. import WxMusic from '@/views/mp/components/wx-music/main.vue';
  150. import WxNews from '@/views/mp/components/wx-news/main.vue';
  151. import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
  152. import { getSimpleAccounts } from "@/api/mp/account";
  153. import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updateAutoReply } from "@/api/mp/autoReply";
  154. export default {
  155. name: 'MpAutoReply',
  156. components: {
  157. WxVideoPlayer,
  158. WxVoicePlayer,
  159. WxMsg,
  160. WxLocation,
  161. WxMusic,
  162. WxNews,
  163. WxReplySelect
  164. },
  165. data() {
  166. return {
  167. // tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
  168. type: '3',
  169. // 允许选择的请求消息类型
  170. requestMessageTypes: ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'],
  171. // 遮罩层
  172. loading: true,
  173. // 显示搜索条件
  174. showSearch: true,
  175. // 总条数
  176. total: 0,
  177. // 自动回复列表
  178. list: [],
  179. // 查询参数
  180. queryParams: {
  181. pageNo: 1,
  182. pageSize: 10,
  183. accountId: undefined,
  184. },
  185. // 弹出层标题
  186. title: "",
  187. // 是否显示弹出层
  188. open: false,
  189. // 表单参数
  190. form: {},
  191. // 回复消息
  192. objData: {
  193. type : 'text'
  194. },
  195. // 表单校验
  196. rules: {
  197. requestKeyword: [{ required: true, message: "请求的关键字不能为空", trigger: "blur" }],
  198. requestMatch: [{ required: true, message: "请求的关键字的匹配不能为空", trigger: "blur" }],
  199. },
  200. hackResetWxReplySelect: false, // 重置 WxReplySelect 组件,解决无法清除的问题
  201. // 公众号账号列表
  202. accounts: []
  203. }
  204. },
  205. created() {
  206. getSimpleAccounts().then(response => {
  207. this.accounts = response.data;
  208. // 默认选中第一个
  209. if (this.accounts.length > 0) {
  210. this.queryParams.accountId = this.accounts[0].id;
  211. }
  212. // 加载数据
  213. this.getList();
  214. })
  215. },
  216. methods: {
  217. /** 查询列表 */
  218. getList() {
  219. // 如果没有选中公众号账号,则进行提示。
  220. if (!this.queryParams.accountId) {
  221. this.$message.error('未选中公众号,无法查询自动回复')
  222. return false
  223. }
  224. this.loading = false
  225. // 处理查询参数
  226. let params = {
  227. ...this.queryParams,
  228. type: this.type
  229. }
  230. // 执行查询
  231. getAutoReplyPage(params).then(response => {
  232. this.list = response.data.list
  233. this.total = response.data.total
  234. this.loading = false
  235. })
  236. },
  237. /** 搜索按钮操作 */
  238. handleQuery() {
  239. this.queryParams.pageNo = 1
  240. this.getList()
  241. },
  242. /** 重置按钮操作 */
  243. resetQuery() {
  244. this.resetForm('queryForm')
  245. // 默认选中第一个
  246. if (this.accounts.length > 0) {
  247. this.queryParams.accountId = this.accounts[0].id;
  248. }
  249. this.handleQuery()
  250. },
  251. handleClick(tab, event) {
  252. this.type = tab.name
  253. this.handleQuery()
  254. },
  255. /** 新增按钮操作 */
  256. handleAdd(){
  257. this.reset();
  258. this.resetEditor();
  259. // 打开表单,并设置初始化
  260. this.open = true
  261. this.title = '新增自动回复';
  262. this.objData = {
  263. type : 'text',
  264. accountId: this.queryParams.accountId,
  265. }
  266. },
  267. /** 修改按钮操作 */
  268. handleUpdate(row) {
  269. this.reset();
  270. this.resetEditor();
  271. const id = row.id;
  272. getAutoReply(id).then(response => {
  273. // 设置属性
  274. this.form = {...response.data}
  275. this.$delete(this.form, 'responseMessageType');
  276. this.$delete(this.form, 'responseContent');
  277. this.$delete(this.form, 'responseMediaId');
  278. this.$delete(this.form, 'responseMediaUrl');
  279. this.$delete(this.form, 'responseDescription');
  280. this.$delete(this.form, 'responseArticles');
  281. this.objData = {
  282. type: response.data.responseMessageType,
  283. accountId: this.queryParams.accountId,
  284. content: response.data.responseContent,
  285. mediaId: response.data.responseMediaId,
  286. url: response.data.responseMediaUrl,
  287. title: response.data.responseTitle,
  288. description: response.data.responseDescription,
  289. thumbMediaId: response.data.responseThumbMediaId,
  290. thumbMediaUrl: response.data.responseThumbMediaUrl,
  291. articles: response.data.responseArticles,
  292. musicUrl: response.data.responseMusicUrl,
  293. hqMusicUrl: response.data.responseHqMusicUrl,
  294. }
  295. // 打开表单
  296. this.open = true
  297. this.title = '修改自动回复';
  298. })
  299. },
  300. handleSubmit() {
  301. this.$refs["form"].validate(valid => {
  302. if (!valid) {
  303. return;
  304. }
  305. // 处理回复消息
  306. const form = {...this.form};
  307. form.responseMessageType = this.objData.type;
  308. form.responseContent = this.objData.content;
  309. form.responseMediaId = this.objData.mediaId;
  310. form.responseMediaUrl = this.objData.url;
  311. form.responseTitle = this.objData.title;
  312. form.responseDescription = this.objData.description;
  313. form.responseThumbMediaId = this.objData.thumbMediaId;
  314. form.responseThumbMediaUrl = this.objData.thumbMediaUrl;
  315. form.responseArticles = this.objData.articles;
  316. form.responseMusicUrl = this.objData.musicUrl;
  317. form.responseHqMusicUrl = this.objData.hqMusicUrl;
  318. if (this.form.id !== undefined) {
  319. updateAutoReply(form).then(response => {
  320. this.$modal.msgSuccess("修改成功");
  321. this.open = false;
  322. this.getList();
  323. });
  324. } else {
  325. createAutoReply(form).then(response => {
  326. this.$modal.msgSuccess("新增成功");
  327. this.open = false;
  328. this.getList();
  329. });
  330. }
  331. });
  332. },
  333. // 表单重置
  334. reset() {
  335. this.form = {
  336. id: undefined,
  337. accountId: this.queryParams.accountId,
  338. type: this.type,
  339. requestKeyword: undefined,
  340. requestMatch: this.type === '3' ? 1 : undefined,
  341. requestMessageType: undefined,
  342. };
  343. this.resetForm("form");
  344. },
  345. // 取消按钮
  346. cancel() {
  347. this.open = false;
  348. this.reset();
  349. },
  350. // 表单 Editor 重置
  351. resetEditor() {
  352. this.hackResetWxReplySelect = false // 销毁组件
  353. this.$nextTick(() => {
  354. this.hackResetWxReplySelect = true // 重建组件
  355. })
  356. },
  357. handleDelete: function(row) {
  358. const ids = row.id;
  359. this.$modal.confirm('是否确认删除此数据?').then(function() {
  360. return deleteAutoReply(ids);
  361. }).then(() => {
  362. this.getList();
  363. this.$modal.msgSuccess("删除成功");
  364. }).catch(() => {});
  365. },
  366. }
  367. }
  368. </script>