AlipayChannelForm.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <div>
  3. <Dialog v-model="dialogVisible" :title="dialogTitle" @closed="close" width="830px">
  4. <el-form
  5. ref="formRef"
  6. :model="formData"
  7. :rules="formRules"
  8. label-width="100px"
  9. v-loading="formLoading"
  10. >
  11. <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
  12. <el-input v-model="formData.feeRate" placeholder="请输入渠道费率" clearable>
  13. <template #append>%</template>
  14. </el-input>
  15. </el-form-item>
  16. <el-form-item label-width="180px" label="开放平台 APPID" prop="config.appId">
  17. <el-input v-model="formData.config.appId" placeholder="请输入开放平台 APPID" clearable />
  18. </el-form-item>
  19. <el-form-item label-width="180px" label="渠道状态" prop="status">
  20. <el-radio-group v-model="formData.status">
  21. <el-radio
  22. v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
  23. :key="parseInt(dict.value)"
  24. :label="parseInt(dict.value)"
  25. >
  26. {{ dict.label }}
  27. </el-radio>
  28. </el-radio-group>
  29. </el-form-item>
  30. <el-form-item label-width="180px" label="网关地址" prop="config.serverUrl">
  31. <el-radio-group v-model="formData.config.serverUrl">
  32. <el-radio label="https://openapi.alipay.com/gateway.do">线上环境</el-radio>
  33. <el-radio label="https://openapi-sandbox.dl.alipaydev.com/gateway.do">
  34. 沙箱环境
  35. </el-radio>
  36. </el-radio-group>
  37. </el-form-item>
  38. <el-form-item label-width="180px" label="算法类型" prop="config.signType">
  39. <el-radio-group v-model="formData.config.signType">
  40. <el-radio key="RSA2" label="RSA2">RSA2</el-radio>
  41. </el-radio-group>
  42. </el-form-item>
  43. <el-form-item label-width="180px" label="公钥类型" prop="config.mode">
  44. <el-radio-group v-model="formData.config.mode">
  45. <el-radio key="公钥模式" :label="1">公钥模式</el-radio>
  46. <el-radio key="证书模式" :label="2">证书模式</el-radio>
  47. </el-radio-group>
  48. </el-form-item>
  49. <div v-if="formData.config.mode === 1">
  50. <el-form-item label-width="180px" label="应用私钥" prop="config.privateKey">
  51. <el-input
  52. type="textarea"
  53. :autosize="{ minRows: 8, maxRows: 8 }"
  54. v-model="formData.config.privateKey"
  55. placeholder="请输入应用私钥"
  56. clearable
  57. :style="{ width: '100%' }"
  58. />
  59. </el-form-item>
  60. <el-form-item label-width="180px" label="支付宝公钥" prop="config.alipayPublicKey">
  61. <el-input
  62. type="textarea"
  63. :autosize="{ minRows: 8, maxRows: 8 }"
  64. v-model="formData.config.alipayPublicKey"
  65. placeholder="请输入支付宝公钥"
  66. clearable
  67. :style="{ width: '100%' }"
  68. />
  69. </el-form-item>
  70. </div>
  71. <div v-if="formData.config.mode === 2">
  72. <el-form-item label-width="180px" label="应用私钥" prop="config.privateKey">
  73. <el-input
  74. type="textarea"
  75. :autosize="{ minRows: 8, maxRows: 8 }"
  76. v-model="formData.config.privateKey"
  77. placeholder="请输入应用私钥"
  78. clearable
  79. :style="{ width: '100%' }"
  80. />
  81. </el-form-item>
  82. <el-form-item label-width="180px" label="商户公钥应用证书" prop="config.appCertContent">
  83. <el-input
  84. v-model="formData.config.appCertContent"
  85. type="textarea"
  86. placeholder="请上传商户公钥应用证书"
  87. readonly
  88. :autosize="{ minRows: 8, maxRows: 8 }"
  89. :style="{ width: '100%' }"
  90. />
  91. </el-form-item>
  92. <el-form-item label-width="180px" label="">
  93. <el-upload
  94. action=""
  95. ref="privateKeyContentFile"
  96. :limit="1"
  97. :accept="fileAccept"
  98. :http-request="appCertUpload"
  99. :before-upload="fileBeforeUpload"
  100. >
  101. <el-button type="primary">
  102. <Icon icon="ep:upload" class="mr-5px" /> 点击上传
  103. </el-button>
  104. </el-upload>
  105. </el-form-item>
  106. <el-form-item
  107. label-width="180px"
  108. label="支付宝公钥证书"
  109. prop="config.alipayPublicCertContent"
  110. >
  111. <el-input
  112. v-model="formData.config.alipayPublicCertContent"
  113. type="textarea"
  114. placeholder="请上传支付宝公钥证书"
  115. readonly
  116. :autosize="{ minRows: 8, maxRows: 8 }"
  117. :style="{ width: '100%' }"
  118. />
  119. </el-form-item>
  120. <el-form-item label-width="180px" label="">
  121. <el-upload
  122. ref="privateCertContentFile"
  123. action=""
  124. :limit="1"
  125. :accept="fileAccept"
  126. :before-upload="fileBeforeUpload"
  127. :http-request="alipayPublicCertUpload"
  128. >
  129. <el-button type="primary">
  130. <Icon icon="ep:upload" class="mr-5px" /> 点击上传
  131. </el-button>
  132. </el-upload>
  133. </el-form-item>
  134. <el-form-item label-width="180px" label="根证书" prop="config.rootCertContent">
  135. <el-input
  136. v-model="formData.config.rootCertContent"
  137. type="textarea"
  138. placeholder="请上传根证书"
  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="">
  145. <el-upload
  146. ref="privateCertContentFile"
  147. :limit="1"
  148. :accept="fileAccept"
  149. action=""
  150. :before-upload="fileBeforeUpload"
  151. :http-request="rootCertUpload"
  152. >
  153. <el-button type="primary">
  154. <Icon icon="ep:upload" class="mr-5px" /> 点击上传
  155. </el-button>
  156. </el-upload>
  157. </el-form-item>
  158. </div>
  159. <el-form-item label-width="180px" label="接口内容加密方式" prop="config.encryptType">
  160. <el-radio-group v-model="formData.config.encryptType">
  161. <el-radio key="NONE" label="">无加密</el-radio>
  162. <el-radio key="AES" label="AES">AES</el-radio>
  163. </el-radio-group>
  164. </el-form-item>
  165. <div v-if="formData.config.encryptType === 'AES'">
  166. <el-form-item label-width="180px" label="接口内容加密密钥" prop="config.encryptKey">
  167. <el-input
  168. v-model="formData.config.encryptKey"
  169. placeholder="请输入接口内容加密密钥"
  170. clearable
  171. />
  172. </el-form-item>
  173. </div>
  174. <el-form-item label-width="180px" label="备注" prop="remark">
  175. <el-input v-model="formData.remark" :style="{ width: '100%' }" />
  176. </el-form-item>
  177. </el-form>
  178. <template #footer>
  179. <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
  180. <el-button @click="dialogVisible = false">取 消</el-button>
  181. </template>
  182. </Dialog>
  183. </div>
  184. </template>
  185. <script lang="ts" setup>
  186. import { CommonStatusEnum } from '@/utils/constants'
  187. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  188. import * as ChannelApi from '@/api/pay/channel'
  189. defineOptions({ name: 'AlipayChannelForm' })
  190. const { t } = useI18n() // 国际化
  191. const message = useMessage() // 消息弹窗
  192. const dialogVisible = ref(false) // 弹窗的是否展示
  193. const dialogTitle = ref('') // 弹窗的标题
  194. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  195. const formData = ref<any>({
  196. appId: '',
  197. code: '',
  198. status: undefined,
  199. feeRate: undefined,
  200. remark: '',
  201. config: {
  202. appId: '',
  203. serverUrl: null,
  204. signType: '',
  205. mode: null,
  206. privateKey: '',
  207. alipayPublicKey: '',
  208. appCertContent: '',
  209. alipayPublicCertContent: '',
  210. rootCertContent: '',
  211. encryptType: '',
  212. encryptKey: ''
  213. }
  214. })
  215. const formRules = {
  216. feeRate: [{ required: true, message: '请输入渠道费率', trigger: 'blur' }],
  217. status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }],
  218. 'config.appId': [{ required: true, message: '请输入开放平台上创建的应用的 ID', trigger: 'blur' }],
  219. 'config.serverUrl': [{ required: true, message: '请传入网关地址', trigger: 'blur' }],
  220. 'config.signType': [{ required: true, message: '请传入签名算法类型', trigger: 'blur' }],
  221. 'config.mode': [{ required: true, message: '公钥类型不能为空', trigger: 'blur' }],
  222. 'config.privateKey': [{ required: true, message: '请输入商户私钥', trigger: 'blur' }],
  223. 'config.alipayPublicKey': [
  224. { required: true, message: '请输入支付宝公钥字符串', trigger: 'blur' }
  225. ],
  226. 'config.appCertContent': [{ required: true, message: '请上传商户公钥应用证书', trigger: 'blur' }],
  227. 'config.alipayPublicCertContent': [
  228. { required: true, message: '请上传支付宝公钥证书', trigger: 'blur' }
  229. ],
  230. 'config.rootCertContent': [{ required: true, message: '请上传指定根证书', trigger: 'blur' }],
  231. 'config.encryptKey': [{ required: true, message: '请输入接口内容加密密钥', trigger: 'blur' }]
  232. }
  233. const fileAccept = '.crt'
  234. const formRef = ref() // 表单 Ref
  235. /** 打开弹窗 */
  236. const open = async (appId, code) => {
  237. dialogVisible.value = true
  238. formLoading.value = true
  239. resetForm(appId, code)
  240. // 加载数据
  241. try {
  242. const data = await ChannelApi.getChannel(appId, code)
  243. if (data && data.id) {
  244. formData.value = data
  245. formData.value.config = JSON.parse(data.config)
  246. }
  247. dialogTitle.value = !formData.value.id ? '创建支付渠道' : '编辑支付渠道'
  248. } finally {
  249. formLoading.value = false
  250. }
  251. }
  252. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  253. /** 提交表单 */
  254. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  255. const submitForm = async () => {
  256. // 校验表单
  257. if (!formRef) return
  258. const valid = await formRef.value.validate()
  259. if (!valid) return
  260. // 提交请求
  261. formLoading.value = true
  262. try {
  263. const data = { ...formData.value } as unknown as ChannelApi.ChannelVO
  264. data.config = JSON.stringify(formData.value.config)
  265. if (!data.id) {
  266. await ChannelApi.createChannel(data)
  267. message.success(t('common.createSuccess'))
  268. } else {
  269. await ChannelApi.updateChannel(data)
  270. message.success(t('common.updateSuccess'))
  271. }
  272. dialogVisible.value = false
  273. // 发送操作成功的事件
  274. emit('success')
  275. } finally {
  276. formLoading.value = false
  277. }
  278. }
  279. /** 重置表单 */
  280. const resetForm = (appId, code) => {
  281. formData.value = {
  282. appId: appId,
  283. code: code,
  284. status: CommonStatusEnum.ENABLE,
  285. remark: '',
  286. feeRate: null,
  287. config: {
  288. appId: '',
  289. serverUrl: null,
  290. signType: 'RSA2',
  291. mode: null,
  292. privateKey: '',
  293. alipayPublicKey: '',
  294. appCertContent: '',
  295. alipayPublicCertContent: '',
  296. rootCertContent: '',
  297. encryptType: '',
  298. encryptKey: ''
  299. }
  300. }
  301. formRef.value?.resetFields()
  302. }
  303. const fileBeforeUpload = (file) => {
  304. let format = '.' + file.name.split('.')[1]
  305. if (format !== fileAccept) {
  306. message.error(`请上传指定格式"${fileAccept}"文件`)
  307. return false
  308. }
  309. let isRightSize = file.size / 1024 / 1024 < 2
  310. if (!isRightSize) {
  311. message.error('文件大小超过 2MB')
  312. }
  313. return isRightSize
  314. }
  315. const appCertUpload = (event) => {
  316. const readFile = new FileReader()
  317. readFile.onload = (e: any) => {
  318. formData.value.config.appCertContent = e.target.result
  319. }
  320. readFile.readAsText(event.file)
  321. }
  322. const alipayPublicCertUpload = (event) => {
  323. const readFile = new FileReader()
  324. readFile.onload = (e: any) => {
  325. formData.value.config.alipayPublicCertContent = e.target.result
  326. }
  327. readFile.readAsText(event.file)
  328. }
  329. const rootCertUpload = (event) => {
  330. const readFile = new FileReader()
  331. readFile.onload = (e: any) => {
  332. formData.value.config.rootCertContent = e.target.result
  333. }
  334. readFile.readAsText(event.file)
  335. }
  336. </script>