index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <template>
  2. <doc-alert title="自动回复" url="https://doc.iocoder.cn/mp/auto-reply/" />
  3. <!-- 搜索工作栏 -->
  4. <ContentWrap>
  5. <WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
  6. </ContentWrap>
  7. <!-- tab 切换 -->
  8. <ContentWrap>
  9. <el-tabs v-model="type" @tab-change="handleTabChange">
  10. <!-- 操作工具栏 -->
  11. <el-row :gutter="10" class="mb8">
  12. <el-col :span="1.5">
  13. <el-button
  14. type="primary"
  15. plain
  16. @click="handleAdd"
  17. v-hasPermi="['mp:auto-reply:create']"
  18. v-if="type !== '1' || list.length <= 0"
  19. >
  20. <Icon icon="ep:plus" />新增
  21. </el-button>
  22. </el-col>
  23. </el-row>
  24. <!-- tab 项 -->
  25. <el-tab-pane name="1">
  26. <template #label>
  27. <span><Icon icon="ep:star-off" /> 关注时回复</span>
  28. </template>
  29. </el-tab-pane>
  30. <el-tab-pane name="2">
  31. <template #label>
  32. <span><Icon icon="ep:chat-line-round" /> 消息回复</span>
  33. </template>
  34. </el-tab-pane>
  35. <el-tab-pane name="3">
  36. <template #label>
  37. <span><Icon icon="ep:news" /> 关键词回复</span>
  38. </template>
  39. </el-tab-pane>
  40. </el-tabs>
  41. <!-- 列表 -->
  42. <el-table v-loading="loading" :data="list">
  43. <el-table-column
  44. label="请求消息类型"
  45. align="center"
  46. prop="requestMessageType"
  47. v-if="type === '2'"
  48. />
  49. <el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
  50. <el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
  51. <template #default="scope">
  52. <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
  53. </template>
  54. </el-table-column>
  55. <el-table-column label="回复消息类型" align="center">
  56. <template #default="scope">
  57. <dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType" />
  58. </template>
  59. </el-table-column>
  60. <el-table-column label="回复内容" align="center">
  61. <template #default="scope">
  62. <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
  63. <div v-else-if="scope.row.responseMessageType === 'voice'">
  64. <WxVoicePlayer :url="scope.row.responseMediaUrl" />
  65. </div>
  66. <div v-else-if="scope.row.responseMessageType === 'image'">
  67. <a target="_blank" :href="scope.row.responseMediaUrl">
  68. <img :src="scope.row.responseMediaUrl" style="width: 100px" />
  69. </a>
  70. </div>
  71. <div
  72. v-else-if="
  73. scope.row.responseMessageType === 'video' ||
  74. scope.row.responseMessageType === 'shortvideo'
  75. "
  76. >
  77. <WxVideoPlayer :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
  78. </div>
  79. <div v-else-if="scope.row.responseMessageType === 'news'">
  80. <WxNews :articles="scope.row.responseArticles" />
  81. </div>
  82. <div v-else-if="scope.row.responseMessageType === 'music'">
  83. <WxMusic
  84. :title="scope.row.responseTitle"
  85. :description="scope.row.responseDescription"
  86. :thumb-media-url="scope.row.responseThumbMediaUrl"
  87. :music-url="scope.row.responseMusicUrl"
  88. :hq-music-url="scope.row.responseHqMusicUrl"
  89. />
  90. </div>
  91. </template>
  92. </el-table-column>
  93. <el-table-column
  94. label="创建时间"
  95. align="center"
  96. prop="createTime"
  97. :formatter="dateFormatter"
  98. width="180"
  99. />
  100. <el-table-column label="操作" align="center">
  101. <template #default="scope">
  102. <el-button
  103. type="primary"
  104. link
  105. @click="handleUpdate(scope.row)"
  106. v-hasPermi="['mp:auto-reply:update']"
  107. >
  108. 修改
  109. </el-button>
  110. <el-button
  111. type="danger"
  112. link
  113. @click="handleDelete(scope.row)"
  114. v-hasPermi="['mp:auto-reply:delete']"
  115. >
  116. 删除
  117. </el-button>
  118. </template>
  119. </el-table-column>
  120. </el-table>
  121. <!-- 添加或修改自动回复的对话框 -->
  122. <el-dialog :title="title" v-model="open" width="800px" append-to-body>
  123. <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
  124. <el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
  125. <el-select v-model="form.requestMessageType" placeholder="请选择">
  126. <template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
  127. <el-option
  128. v-if="requestMessageTypes.includes(dict.value)"
  129. :label="dict.label"
  130. :value="dict.value"
  131. />
  132. </template>
  133. </el-select>
  134. </el-form-item>
  135. <el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
  136. <el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable>
  137. <el-option
  138. v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
  139. :key="dict.value"
  140. :label="dict.label"
  141. :value="dict.value"
  142. />
  143. </el-select>
  144. </el-form-item>
  145. <el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
  146. <el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
  147. </el-form-item>
  148. <el-form-item label="回复消息">
  149. <WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
  150. </el-form-item>
  151. </el-form>
  152. <template #footer>
  153. <el-button @click="cancel">取 消</el-button>
  154. <el-button type="primary" @click="handleSubmit">确 定</el-button>
  155. </template>
  156. </el-dialog>
  157. </ContentWrap>
  158. </template>
  159. <script setup name="MpAutoReply">
  160. import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
  161. import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
  162. import WxMusic from '@/views/mp/components/wx-music/main.vue'
  163. import WxNews from '@/views/mp/components/wx-news/main.vue'
  164. import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
  165. import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
  166. import * as MpAutoReplyApi from '@/api/mp/autoReply'
  167. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  168. import { dateFormatter } from '@/utils/formatTime'
  169. import { ContentWrap } from '@/components/ContentWrap'
  170. const message = useMessage()
  171. // const queryFormRef = ref()
  172. const formRef = ref()
  173. // tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
  174. const type = ref('3')
  175. // 允许选择的请求消息类型
  176. const requestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
  177. // 遮罩层
  178. const loading = ref(true)
  179. // 显示搜索条件
  180. // const showSearch = ref(true)
  181. // 总条数
  182. const total = ref(0)
  183. // 自动回复列表
  184. const list = ref([])
  185. // 查询参数
  186. const queryParams = reactive({
  187. pageNo: 1,
  188. pageSize: 10,
  189. accountId: undefined
  190. })
  191. // 弹出层标题
  192. const title = ref('')
  193. // 是否显示弹出层
  194. const open = ref(false)
  195. // 表单参数
  196. const form = ref({})
  197. // 回复消息
  198. const objData = ref({
  199. type: 'text'
  200. })
  201. // 表单校验
  202. const rules = {
  203. requestKeyword: [{ required: true, message: '请求的关键字不能为空', trigger: 'blur' }],
  204. requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
  205. }
  206. // 重置 WxReplySelect 组件,解决无法清除的问题
  207. const hackResetWxReplySelect = ref(false)
  208. const accountChanged = (accountId) => {
  209. queryParams.accountId = accountId
  210. getList()
  211. }
  212. /** 查询列表 */
  213. const getList = async () => {
  214. loading.value = false
  215. try {
  216. const data = await MpAutoReplyApi.getAutoReplyPage({
  217. ...queryParams,
  218. type: type.value
  219. })
  220. list.value = data.list
  221. total.value = data.total
  222. } finally {
  223. loading.value = false
  224. }
  225. }
  226. /** 搜索按钮操作 */
  227. const handleQuery = () => {
  228. queryParams.pageNo = 1
  229. getList()
  230. }
  231. const handleTabChange = (tabName) => {
  232. type.value = tabName
  233. handleQuery()
  234. }
  235. /** 新增按钮操作 */
  236. const handleAdd = () => {
  237. reset()
  238. resetEditor()
  239. // 打开表单,并设置初始化
  240. open.value = true
  241. title.value = '新增自动回复'
  242. objData.value = {
  243. type: 'text',
  244. accountId: queryParams.accountId
  245. }
  246. }
  247. /** 修改按钮操作 */
  248. const handleUpdate = (row) => {
  249. reset()
  250. resetEditor()
  251. console.log(row)
  252. MpAutoReplyApi.getAutoReply(row.id).then((data) => {
  253. // 设置属性
  254. form.value = { ...data }
  255. delete form.value['responseMessageType']
  256. delete form.value['responseContent']
  257. delete form.value['responseMediaId']
  258. delete form.value['responseMediaUrl']
  259. delete form.value['responseDescription']
  260. delete form.value['responseArticles']
  261. objData.value = {
  262. type: data.responseMessageType,
  263. accountId: queryParams.accountId,
  264. content: data.responseContent,
  265. mediaId: data.responseMediaId,
  266. url: data.responseMediaUrl,
  267. title: data.responseTitle,
  268. description: data.responseDescription,
  269. thumbMediaId: data.responseThumbMediaId,
  270. thumbMediaUrl: data.responseThumbMediaUrl,
  271. articles: data.responseArticles,
  272. musicUrl: data.responseMusicUrl,
  273. hqMusicUrl: data.responseHqMusicUrl
  274. }
  275. // 打开表单
  276. open.value = true
  277. title.value = '修改自动回复'
  278. })
  279. }
  280. const handleSubmit = () => {
  281. formRef.value?.validate((valid) => {
  282. if (!valid) {
  283. return
  284. }
  285. // 处理回复消息
  286. const form = { ...form.value }
  287. form.responseMessageType = objData.value.type
  288. form.responseContent = objData.value.content
  289. form.responseMediaId = objData.value.mediaId
  290. form.responseMediaUrl = objData.value.url
  291. form.responseTitle = objData.value.title
  292. form.responseDescription = objData.value.description
  293. form.responseThumbMediaId = objData.value.thumbMediaId
  294. form.responseThumbMediaUrl = objData.value.thumbMediaUrl
  295. form.responseArticles = objData.value.articles
  296. form.responseMusicUrl = objData.value.musicUrl
  297. form.responseHqMusicUrl = objData.value.hqMusicUrl
  298. if (form.value.id !== undefined) {
  299. MpAutoReplyApi.updateAutoReply(form).then(() => {
  300. message.success('修改成功')
  301. open.value = false
  302. getList()
  303. })
  304. } else {
  305. MpAutoReplyApi.createAutoReply(form).then(() => {
  306. message.success('新增成功')
  307. open.value = false
  308. getList()
  309. })
  310. }
  311. })
  312. }
  313. // 表单重置
  314. const reset = () => {
  315. form.value = {
  316. id: undefined,
  317. accountId: queryParams.accountId,
  318. type: type.value,
  319. requestKeyword: undefined,
  320. requestMatch: type.value === '3' ? 1 : undefined,
  321. requestMessageType: undefined
  322. }
  323. formRef.value?.resetFields()
  324. }
  325. // 取消按钮
  326. const cancel = () => {
  327. open.value = false
  328. reset()
  329. }
  330. // 表单 Editor 重置
  331. const resetEditor = () => {
  332. hackResetWxReplySelect.value = false // 销毁组件
  333. nextTick(() => {
  334. hackResetWxReplySelect.value = true // 重建组件
  335. })
  336. }
  337. const handleDelete = async (row) => {
  338. await message.confirm('是否确认删除此数据?')
  339. await MpAutoReplyApi.deleteAutoReply(row.id)
  340. await getList()
  341. message.success('删除成功')
  342. }
  343. </script>