weixinChannelForm.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <template>
  2. <div>
  3. <el-dialog
  4. v-model="dialogVisible"
  5. :title="title"
  6. @close="close"
  7. append-to-body
  8. destroy-on-close
  9. width="800px"
  10. >
  11. <el-form
  12. ref="formRef"
  13. :model="formData"
  14. :rules="rules"
  15. label-width="120px"
  16. v-loading="formLoading"
  17. >
  18. <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
  19. <el-input
  20. v-model="formData.feeRate"
  21. placeholder="请输入渠道费率"
  22. clearable
  23. :style="{ width: '100%' }"
  24. >
  25. <template #append>%</template>
  26. </el-input>
  27. </el-form-item>
  28. <el-form-item label-width="180px" label="公众号 APPID" prop="config.appId">
  29. <el-input
  30. v-model="formData.config.appId"
  31. placeholder="请输入公众号 APPID"
  32. clearable
  33. :style="{ width: '100%' }"
  34. />
  35. </el-form-item>
  36. <el-form-item label-width="180px" label="商户号" prop="config.mchId">
  37. <el-input v-model="formData.config.mchId" :style="{ width: '100%' }" />
  38. </el-form-item>
  39. <el-form-item label-width="180px" label="渠道状态" prop="status">
  40. <el-radio-group v-model="formData.status">
  41. <el-radio
  42. v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
  43. :key="parseInt(dict.value)"
  44. :label="parseInt(dict.value)"
  45. >
  46. {{ dict.label }}
  47. </el-radio>
  48. </el-radio-group>
  49. </el-form-item>
  50. <el-form-item label-width="180px" label="API 版本" prop="config.apiVersion">
  51. <el-radio-group v-model="formData.config.apiVersion">
  52. <el-radio label="v2">v2</el-radio>
  53. <el-radio label="v3">v3</el-radio>
  54. </el-radio-group>
  55. </el-form-item>
  56. <div v-if="formData.config.apiVersion === 'v2'">
  57. <el-form-item label-width="180px" label="商户密钥" prop="config.mchKey">
  58. <el-input
  59. v-model="formData.config.mchKey"
  60. placeholder="请输入商户密钥"
  61. clearable
  62. :style="{ width: '100%' }"
  63. type="textarea"
  64. :autosize="{ minRows: 8, maxRows: 8 }"
  65. />
  66. </el-form-item>
  67. <el-form-item
  68. label-width="180px"
  69. label="apiclient_cert.p12 证书"
  70. prop="config.keyContent"
  71. >
  72. <el-input
  73. v-model="formData.config.keyContent"
  74. type="textarea"
  75. placeholder="请上传 apiclient_cert.p12 证书"
  76. readonly
  77. :autosize="{ minRows: 8, maxRows: 8 }"
  78. :style="{ width: '100%' }"
  79. />
  80. </el-form-item>
  81. <el-form-item label-width="180px" label="">
  82. <el-upload
  83. :limit="1"
  84. accept=".p12"
  85. action=""
  86. :before-upload="p12FileBeforeUpload"
  87. :http-request="keyContentUpload"
  88. >
  89. <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
  90. </el-upload>
  91. </el-form-item>
  92. </div>
  93. <div v-if="formData.config.apiVersion === 'v3'">
  94. <el-form-item label-width="180px" label="API V3 密钥" prop="config.apiV3Key">
  95. <el-input
  96. v-model="formData.config.apiV3Key"
  97. placeholder="请输入 API V3 密钥"
  98. clearable
  99. :style="{ width: '100%' }"
  100. type="textarea"
  101. :autosize="{ minRows: 8, maxRows: 8 }"
  102. />
  103. </el-form-item>
  104. <el-form-item
  105. label-width="180px"
  106. label="apiclient_key.pem 证书"
  107. prop="config.privateKeyContent"
  108. >
  109. <el-input
  110. v-model="formData.config.privateKeyContent"
  111. type="textarea"
  112. placeholder="请上传 apiclient_key.pem 证书"
  113. readonly
  114. :autosize="{ minRows: 8, maxRows: 8 }"
  115. :style="{ width: '100%' }"
  116. />
  117. </el-form-item>
  118. <el-form-item label-width="180px" label="" prop="privateKeyContentFile">
  119. <el-upload
  120. ref="privateKeyContentFile"
  121. :limit="1"
  122. accept=".pem"
  123. action=""
  124. :before-upload="pemFileBeforeUpload"
  125. :http-request="privateKeyContentUpload"
  126. >
  127. <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
  128. </el-upload>
  129. </el-form-item>
  130. <el-form-item
  131. label-width="180px"
  132. label="apiclient_cert.pem证书"
  133. prop="config.privateCertContent"
  134. >
  135. <el-input
  136. v-model="formData.config.privateCertContent"
  137. type="textarea"
  138. placeholder="请上传apiclient_cert.pem证书"
  139. readonly
  140. :autosize="{ minRows: 8, maxRows: 8 }"
  141. :style="{ width: '100%' }"
  142. />
  143. </el-form-item>
  144. <el-form-item label-width="180px" label="" prop="privateCertContentFile">
  145. <el-upload
  146. ref="privateCertContentFile"
  147. :limit="1"
  148. accept=".pem"
  149. action=""
  150. :before-upload="pemFileBeforeUpload"
  151. :http-request="privateCertContentUpload"
  152. >
  153. <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
  154. </el-upload>
  155. </el-form-item>
  156. </div>
  157. <el-form-item label-width="180px" label="备注" prop="remark">
  158. <el-input v-model="formData.remark" :style="{ width: '100%' }" />
  159. </el-form-item>
  160. </el-form>
  161. <template #footer>
  162. <el-button @click="close">取消</el-button>
  163. <el-button type="primary" @click="submitForm">确定</el-button>
  164. </template>
  165. </el-dialog>
  166. </div>
  167. </template>
  168. <script lang="ts" setup name="WeixinChannelForm">
  169. import { createChannel, getChannel, updateChannel } from '@/api/pay/channel'
  170. import { CommonStatusEnum } from '@/utils/constants'
  171. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  172. const message = useMessage() // 消息弹窗
  173. const dialogVisible = ref(false)
  174. const formLoading = ref(false)
  175. const title = ref('')
  176. const formData = ref<any>({
  177. appId: '',
  178. code: '',
  179. status: undefined,
  180. feeRate: undefined,
  181. remark: '',
  182. config: {
  183. appId: '',
  184. mchId: '',
  185. apiVersion: '',
  186. mchKey: '',
  187. keyContent: '',
  188. privateKeyContent: '',
  189. privateCertContent: '',
  190. apiV3Key: ''
  191. }
  192. })
  193. const formRef = ref()
  194. const emit = defineEmits(['success'])
  195. const rules = {
  196. feeRate: [{ required: true, message: '请输入渠道费率', trigger: 'blur' }],
  197. status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }],
  198. 'config.mchId': [{ required: true, message: '请传入商户号', trigger: 'blur' }],
  199. 'config.appId': [{ required: true, message: '请输入公众号APPID', trigger: 'blur' }],
  200. 'config.apiVersion': [{ required: true, message: 'API版本不能为空', trigger: 'blur' }],
  201. 'config.mchKey': [{ required: true, message: '请输入商户密钥', trigger: 'blur' }],
  202. 'config.keyContent': [
  203. { required: true, message: '请上传 apiclient_cert.p12 证书', trigger: 'blur' }
  204. ],
  205. 'config.privateKeyContent': [
  206. { required: true, message: '请上传 apiclient_key.pem 证书', trigger: 'blur' }
  207. ],
  208. 'config.privateCertContent': [
  209. { required: true, message: '请上传 apiclient_cert.pem证 书', trigger: 'blur' }
  210. ],
  211. 'config.apiV3Key': [{ required: true, message: '请上传 api V3 密钥值', trigger: 'blur' }]
  212. }
  213. const open = async (appId, code) => {
  214. dialogVisible.value = true
  215. formLoading.value = true
  216. reset(appId, code)
  217. try {
  218. const data = await getChannel(appId, code)
  219. if (data && data.id) {
  220. formData.value = data
  221. formData.value.config = JSON.parse(data.config)
  222. }
  223. title.value = !formData.value.id ? '创建支付渠道' : '编辑支付渠道'
  224. } finally {
  225. formLoading.value = false
  226. }
  227. }
  228. const close = () => {
  229. dialogVisible.value = false
  230. reset(undefined, undefined)
  231. }
  232. const submitForm = async () => {
  233. const valid = await formRef.value.validate()
  234. if (!valid) {
  235. return
  236. }
  237. const data: any = { ...formData.value }
  238. data.config = JSON.stringify(formData.value.config)
  239. if (!data.id) {
  240. createChannel(data).then(() => {
  241. message.alertSuccess('新增成功')
  242. emit('success')
  243. close()
  244. })
  245. } else {
  246. updateChannel(data).then(() => {
  247. message.alertSuccess('修改成功')
  248. emit('success')
  249. close()
  250. })
  251. }
  252. }
  253. /** 重置表单 */
  254. const reset = (appId, code) => {
  255. formData.value = {
  256. appId: appId,
  257. code: code,
  258. status: CommonStatusEnum.ENABLE,
  259. feeRate: undefined,
  260. remark: '',
  261. config: {
  262. appId: '',
  263. mchId: '',
  264. apiVersion: '',
  265. mchKey: '',
  266. keyContent: '',
  267. privateKeyContent: '',
  268. privateCertContent: '',
  269. apiV3Key: ''
  270. }
  271. }
  272. formRef.value?.resetFields()
  273. }
  274. /**
  275. * apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem 上传前的校验
  276. */
  277. const fileBeforeUpload = (file, fileAccept) => {
  278. let format = '.' + file.name.split('.')[1]
  279. if (format !== fileAccept) {
  280. debugger
  281. message.error('请上传指定格式"' + fileAccept + '"文件')
  282. return false
  283. }
  284. let isRightSize = file.size / 1024 / 1024 < 2
  285. if (!isRightSize) {
  286. message.error('文件大小超过 2MB')
  287. }
  288. return isRightSize
  289. }
  290. const p12FileBeforeUpload = (file) => {
  291. fileBeforeUpload(file, '.p12')
  292. }
  293. const pemFileBeforeUpload = (file) => {
  294. fileBeforeUpload(file, '.pem')
  295. }
  296. /**
  297. * 读取 apiclient_key.pem 到 privateKeyContent 字段
  298. */
  299. const privateKeyContentUpload = (event) => {
  300. const readFile = new FileReader()
  301. readFile.onload = (e: any) => {
  302. formData.value.config.privateKeyContent = e.target.result
  303. }
  304. readFile.readAsText(event.file)
  305. }
  306. /**
  307. * 读取 apiclient_cert.pem 到 privateCertContent 字段
  308. */
  309. const privateCertContentUpload = (event) => {
  310. const readFile = new FileReader()
  311. readFile.onload = (e: any) => {
  312. formData.value.config.privateCertContent = e.target.result
  313. }
  314. readFile.readAsText(event.file)
  315. }
  316. /**
  317. * 读取 apiclient_cert.p12 到 keyContent 字段
  318. */
  319. const keyContentUpload = (event) => {
  320. const readFile = new FileReader()
  321. readFile.onload = (e: any) => {
  322. formData.value.config.keyContent = e.target.result.split(',')[1]
  323. }
  324. readFile.readAsDataURL(event.file) // 读成 base64
  325. }
  326. defineExpose({ open })
  327. </script>