MsgTemplateForm.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <template>
  2. <Dialog :title="dialogTitle" v-model="dialogVisible">
  3. <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" v-loading="formLoading">
  4. <!-- 其他表单项保持不变 -->
  5. <el-form-item label="appId" prop="appId">
  6. <el-input v-model="formData.appId" placeholder="请输入appId"/>
  7. </el-form-item>
  8. <el-form-item label="公众号模板ID" prop="templateId">
  9. <el-input v-model="formData.templateId" placeholder="请输入公众号模板ID"/>
  10. </el-form-item>
  11. <el-form-item label="模版名称" prop="name">
  12. <el-input v-model="formData.name" placeholder="请输入模版名称"/>
  13. </el-form-item>
  14. <el-form-item label="标题" prop="title">
  15. <el-input v-model="formData.title" placeholder="请输入标题"/>
  16. </el-form-item>
  17. <el-form-item label="链接" prop="url">
  18. <el-input v-model="formData.url" placeholder="请输入链接"/>
  19. </el-form-item>
  20. <el-form-item label="小程序appId" prop="miniProgramAppId">
  21. <el-input v-model="formData.miniProgramAppId" placeholder="请输入小程序appId"/>
  22. </el-form-item>
  23. <el-form-item label="小程序页面路径" prop="miniProgramPagePath">
  24. <el-input v-model="formData.miniProgramPagePath" placeholder="请输入小程序页面路径"/>
  25. </el-form-item>
  26. <el-row :gutter="10">
  27. <el-col :span="12">
  28. <el-form-item label="是否有效" prop="status">
  29. <el-radio-group v-model="formData.status">
  30. <el-radio
  31. v-for="dict in getIntDictOptions(DICT_TYPE.IS_VALID)"
  32. :key="dict.value"
  33. :label="dict.value"
  34. >
  35. {{ dict.label }}
  36. </el-radio>
  37. </el-radio-group>
  38. </el-form-item>
  39. </el-col>
  40. <!-- <el-col :span="12">
  41. <el-form-item label="公众号是否已移除" prop="isRemoved">
  42. <el-radio-group v-model="formData.isRemoved">
  43. <el-radio
  44. v-for="dict in getIntDictOptions(DICT_TYPE.IS_DELETE)"
  45. :key="dict.value"
  46. :label="dict.value"
  47. >
  48. {{ dict.label }}
  49. </el-radio>
  50. </el-radio-group>
  51. </el-form-item>
  52. </el-col>-->
  53. </el-row>
  54. <el-form-item label="模板内容" prop="content">
  55. <el-input
  56. readonly
  57. type="textarea"
  58. v-model="formData.content"
  59. :rows="3"
  60. autosize
  61. placeholder="请输入模板内容,使用{{变量名.DATA}}格式定义变量"
  62. />
  63. </el-form-item>
  64. <!-- 修改点:消息变量部分 -->
  65. <el-form-item label="消息变量" v-if="templateVariables.length > 0">
  66. <div class="variable-editor">
  67. <div v-for="(item, index) in templateVariables" :key="item.name" class="variable-item">
  68. <el-row :gutter="10">
  69. <el-col :span="6">
  70. <div class="variable-name">{{ item.name }}</div>
  71. </el-col>
  72. <el-col :span="14"> <!-- 修改点:增加输入框宽度 -->
  73. <el-form-item
  74. :prop="`templateVariables.${index}.value`"
  75. >
  76. <el-input v-model="item.value" placeholder="填充内容" clearable/>
  77. </el-form-item>
  78. </el-col>
  79. <el-col :span="4">
  80. <el-form-item>
  81. <el-color-picker v-model="item.color" :predefine="predefineColors" size="small"/>
  82. </el-form-item>
  83. </el-col>
  84. </el-row>
  85. </div>
  86. </div>
  87. </el-form-item>
  88. </el-form>
  89. <template #footer>
  90. <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
  91. <el-button @click="dialogVisible = false">取 消</el-button>
  92. </template>
  93. </Dialog>
  94. </template>
  95. <script setup lang="ts">
  96. import {ref, reactive, watch, nextTick} from 'vue'
  97. import {MsgTemplateApi, type MsgTemplateVO} from '@/api/mp/template'
  98. import {getIntDictOptions, DICT_TYPE} from '@/utils/dict'
  99. import type {FormInstance, FormRules} from 'element-plus'
  100. interface TemplateVariable {
  101. name: string;
  102. value: string;
  103. color: string;
  104. }
  105. // 消息模板表单组件
  106. defineOptions({name: 'MsgTemplateForm'})
  107. const {t} = useI18n() // 国际化
  108. const message = useMessage() // 消息弹窗
  109. const dialogVisible = ref(false) // 弹窗是否展示
  110. const dialogTitle = ref('') // 弹窗标题
  111. const formLoading = ref(false) // 表单加载状态
  112. const formType = ref('') // 表单类型:create/update
  113. const list = ref<any[]>([])
  114. // 表单数据保持不变
  115. const formData = reactive<MsgTemplateVO>({
  116. id: undefined,
  117. appId: '',
  118. templateId: '',
  119. name: '',
  120. title: '',
  121. content: '',
  122. data: '',
  123. url: '',
  124. miniProgramAppId: '',
  125. miniProgramPagePath: '',
  126. status: 0,
  127. isRemoved: 0
  128. })
  129. // 表单验证规则保持不变
  130. const formRules = reactive<FormRules<MsgTemplateVO>>({
  131. appId: [{required: true, message: 'appId不能为空', trigger: 'blur'}],
  132. templateId: [{required: true, message: '公众号模板ID不能为空', trigger: 'blur'}],
  133. name: [{required: true, message: '模板名称不能为空', trigger: 'blur'}],
  134. title: [{required: true, message: '标题不能为空', trigger: 'blur'}],
  135. status: [{required: true, message: '请选择是否有效', trigger: 'change'}]
  136. })
  137. const templateVariables = ref<TemplateVariable[]>([]) // 模板变量
  138. const predefineColors = ref([
  139. '#000000', // 黑色
  140. '#173177', // 深蓝色
  141. '#ff0000', // 红色
  142. '#00b050', // 绿色
  143. '#ff9900', // 橙色
  144. '#800080', // 紫色
  145. ]) // 预定义颜色
  146. const formRef = ref<FormInstance>() // 表单引用
  147. // 以下是新增的模板变量相关代码
  148. // 提取模板中的变量名
  149. const extractTemplateVariables = (content: string): string[] => {
  150. if (!content) return []
  151. const regex = /\{\{(\w+)\.DATA\}\}/g
  152. const matches = content.match(regex) || []
  153. const uniqueNames = [...new Set(matches.map(match =>
  154. match.replace('{{', '').replace('.DATA}}', '')
  155. ))]
  156. return uniqueNames
  157. }
  158. // 当模板内容变化时更新变量列表
  159. watch(() => formData.content, (newContent) => {
  160. const variableNames = extractTemplateVariables(newContent)
  161. // 保留现有变量值
  162. const currentVariables = [...templateVariables.value]
  163. // 创建新的变量列表
  164. const newVariables = variableNames.map(name => {
  165. const existing = currentVariables.find(v => v.name === name)
  166. return existing || {name, value: '', color: '#000000'}
  167. })
  168. templateVariables.value = newVariables
  169. })
  170. /** 打开弹窗 */
  171. const open = async (type: string, id?: number) => {
  172. dialogVisible.value = true
  173. dialogTitle.value = t('action.' + type)
  174. formType.value = type
  175. resetForm()
  176. if (id) {
  177. formLoading.value = true
  178. try {
  179. const res = await MsgTemplateApi.getMsgTemplate(id)
  180. Object.assign(formData, res)
  181. // 解析模板变量
  182. if (res.data) {
  183. try {
  184. const parsedData = JSON.parse(res.data)
  185. if (Array.isArray(parsedData)) {
  186. templateVariables.value = parsedData
  187. }
  188. } catch (e) {
  189. // 如果解析失败,从内容中提取变量
  190. const variableNames = extractTemplateVariables(res.content || '')
  191. templateVariables.value = variableNames.map(name => ({
  192. name,
  193. value: '',
  194. color: '#000000'
  195. }))
  196. }
  197. } else {
  198. // 没有数据字段,从内容中提取变量
  199. const variableNames = extractTemplateVariables(res.content || '')
  200. templateVariables.value = variableNames.map(name => ({
  201. name,
  202. value: '',
  203. color: '#000000'
  204. }))
  205. }
  206. } finally {
  207. formLoading.value = false
  208. }
  209. }
  210. }
  211. defineExpose({open}) // 暴露open方法
  212. /** 提交表单 */
  213. const emit = defineEmits(['success'])
  214. const submitForm = async () => {
  215. if (!formRef.value) return
  216. // 使用 Element Plus 表单验证统一处理所有校验
  217. try {
  218. await formRef.value.validate()
  219. // 验证模板变量是否都填写了
  220. const isAllFilled = templateVariables.value.every(item => item.value.trim() !== '')
  221. if (!isAllFilled) {
  222. message.error('请填写所有消息变量的内容')
  223. return
  224. }
  225. // 准备数据 - 修改点:将模板变量转为JSON字符串
  226. const submitData = {
  227. ...formData,
  228. data: JSON.stringify(templateVariables.value)
  229. }
  230. formLoading.value = true
  231. try {
  232. if (formType.value === 'create') {
  233. await MsgTemplateApi.createMsgTemplate(submitData)
  234. message.success(t('common.createSuccess'))
  235. } else {
  236. await MsgTemplateApi.updateMsgTemplate(submitData)
  237. message.success(t('common.updateSuccess'))
  238. }
  239. dialogVisible.value = false
  240. emit('success')
  241. } catch (error) {
  242. message.error('操作失败,请重试')
  243. } finally {
  244. formLoading.value = false
  245. }
  246. } catch (error) {
  247. // 表单验证失败
  248. message.error('请完善表单信息')
  249. }
  250. }
  251. /** 重置表单 */
  252. const resetForm = () => {
  253. formRef.value?.resetFields()
  254. Object.assign(formData, {
  255. id: undefined,
  256. appId: '',
  257. templateId: '',
  258. name: '',
  259. title: '',
  260. content: '',
  261. data: '',
  262. url: '',
  263. miniProgramAppId: '',
  264. miniProgramPagePath: '',
  265. status: 0,
  266. isRemoved: 0
  267. })
  268. // 新增:重置模板变量
  269. templateVariables.value = []
  270. }
  271. </script>
  272. <style scoped>
  273. /* 新增的样式 */
  274. .variable-editor {
  275. width: 100%;
  276. border: 1px solid #EBEEF5;
  277. border-radius: 4px;
  278. padding: 12px;
  279. background-color: var(--el-bg-color);
  280. }
  281. .variable-item {
  282. padding: 8px 0;
  283. }
  284. .variable-item + .variable-item {
  285. border-top: 1px dashed #EBEEF5;
  286. padding-top: 12px;
  287. }
  288. .variable-name {
  289. line-height: 32px;
  290. font-weight: bold;
  291. color: var(--el-text-color);
  292. overflow: hidden;
  293. text-overflow: ellipsis;
  294. white-space: nowrap;
  295. }
  296. :deep(.el-color-picker) {
  297. vertical-align: middle;
  298. margin-left: 8px;
  299. }
  300. /* 增加输入框宽度 */
  301. .variable-editor :deep(.el-input) {
  302. width: 100%;
  303. }
  304. </style>