index.vue 11 KB

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