BatchPayAuthorize.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <el-card>
  3. <template #header>
  4. <div class="card-header">
  5. <span>制单授权(批量付款到户有密)</span>
  6. </div>
  7. </template>
  8. <el-form label-width="160px">
  9. <el-form-item>
  10. <el-button
  11. v-if="!isAuthed"
  12. v-hasPerm="['module_payment:account:authorize']"
  13. type="primary"
  14. :loading="applying"
  15. @click="handleApply"
  16. >
  17. {{ existingAuthing ? "重新生成授权链接" : "生成授权链接" }}
  18. </el-button>
  19. <el-button
  20. v-if="outBizNo"
  21. v-hasPerm="['module_payment:account:authorize']"
  22. size="small"
  23. style="margin-left: 12px"
  24. :loading="querying"
  25. @click="handleQuery"
  26. >
  27. 刷新状态
  28. </el-button>
  29. </el-form-item>
  30. <el-form-item v-if="link" label="授权链接(PC 浏览器打开)">
  31. <div style="display: flex; align-items: flex-start; gap: 12px">
  32. <el-input :model-value="link" readonly style="width: 560px">
  33. <template #append>
  34. <el-button @click="handleCopyLink">复制</el-button>
  35. </template>
  36. </el-input>
  37. <div class="qrcode-wrapper">
  38. <canvas ref="qrcodeCanvas" class="qrcode-canvas"></canvas>
  39. <div class="form-item-tip">手机支付宝扫码打开</div>
  40. </div>
  41. </div>
  42. </el-form-item>
  43. <el-form-item v-if="outBizNo" label="授权单号 / 状态">
  44. <span>{{ outBizNo }} / {{ statusText }}</span>
  45. </el-form-item>
  46. </el-form>
  47. </el-card>
  48. </template>
  49. <script setup lang="ts">
  50. import { computed, nextTick, onMounted, ref } from "vue";
  51. import BatchPayAPI from "@/api/module_payment/batch";
  52. import { useEnterpriseStore } from "@/store";
  53. import { ElMessage, ElMessageBox } from "element-plus";
  54. import QRCode from "qrcode";
  55. const enterpriseStore = useEnterpriseStore();
  56. const enterpriseId = computed(() => enterpriseStore.getCurrentEnterprise?.enterprise_id);
  57. const link = ref("");
  58. const outBizNo = ref("");
  59. const agreementNo = ref("");
  60. const status = ref("");
  61. /** 最新记录已 AUTHED(永久生效授权)→ 不再提供生成入口,仅展示状态 */
  62. const isAuthed = ref(false);
  63. const applying = ref(false);
  64. const querying = ref(false);
  65. const qrcodeCanvas = ref<HTMLCanvasElement>();
  66. const AUTH_STATUS_TEXT: Record<string, string> = {
  67. AUTHING: "授权中",
  68. AUTHED: "已授权",
  69. // 存量兼容: 通知归一前落库的 NORMAL(支付宝生效状态)视同已授权
  70. NORMAL: "已授权",
  71. UNBIND: "已解绑",
  72. };
  73. const statusText = computed(() => {
  74. const s = AUTH_STATUS_TEXT[status.value] || status.value || "-";
  75. return agreementNo.value ? `${s}(协议号 ${agreementNo.value})` : s;
  76. });
  77. const existingAuthing = computed(() => status.value === "AUTHING");
  78. /** 进入页面时拉取该企业最新授权记录:AUTHING → 回溯展示链接,AUTHED → 展示已授权,UNBIND/无记录 → 初始空态 */
  79. async function loadLatest() {
  80. if (!enterpriseId.value) return;
  81. try {
  82. const res = await BatchPayAPI.authorizeList({
  83. enterprise_id: enterpriseId.value,
  84. page_no: 1,
  85. page_size: 1,
  86. });
  87. const rows = (res.data.data?.items ?? res.data.data?.list) || [];
  88. const latest = rows[0];
  89. if (!latest) return;
  90. if (latest.status === "AUTHING") {
  91. link.value = latest.authorize_link || "";
  92. outBizNo.value = latest.out_biz_no;
  93. status.value = "AUTHING";
  94. agreementNo.value = "";
  95. if (link.value) nextTick(() => drawQRCode());
  96. } else if (latest.status === "AUTHED" || latest.status === "NORMAL") {
  97. // NORMAL 为通知归一前落库的支付宝生效状态,视同已授权
  98. outBizNo.value = latest.out_biz_no;
  99. status.value = latest.status;
  100. agreementNo.value = latest.agreement_no || "";
  101. isAuthed.value = true;
  102. }
  103. // UNBIND → 保持初始空态(可正常生成新链接)
  104. } catch (err) {
  105. console.error("加载授权记录失败:", err);
  106. }
  107. }
  108. onMounted(loadLatest);
  109. async function handleApply() {
  110. if (!enterpriseId.value) {
  111. ElMessage.warning("请选择企业");
  112. return;
  113. }
  114. // 已有未完成授权申请:确认后作废旧链接重新生成(后端 /authorize/rebind)
  115. if (existingAuthing.value) {
  116. try {
  117. await ElMessageBox.confirm(
  118. "当前存在未完成的授权申请,重新生成将作废旧授权链接。是否继续?",
  119. "重新生成授权链接",
  120. { type: "warning", confirmButtonText: "重新生成", cancelButtonText: "取消" }
  121. );
  122. } catch {
  123. return;
  124. }
  125. }
  126. applying.value = true;
  127. try {
  128. const res = existingAuthing.value
  129. ? await BatchPayAPI.authorizeRebind(enterpriseId.value)
  130. : await BatchPayAPI.authorizeApply(enterpriseId.value);
  131. link.value = res.data.data.authorize_link;
  132. outBizNo.value = res.data.data.out_biz_no;
  133. status.value = res.data.data.status;
  134. agreementNo.value = "";
  135. isAuthed.value = false;
  136. nextTick(() => drawQRCode());
  137. ElMessage.success("授权链接已生成,请尽快完成授权");
  138. } finally {
  139. applying.value = false;
  140. }
  141. }
  142. /** 授权链接二维码(复用 InviteDialog 的 qrcode 库用法) */
  143. async function drawQRCode() {
  144. if (!qrcodeCanvas.value || !link.value) return;
  145. try {
  146. await QRCode.toCanvas(qrcodeCanvas.value, link.value, { width: 160, margin: 1 });
  147. } catch (err) {
  148. console.error("授权链接二维码生成失败:", err);
  149. }
  150. }
  151. async function handleQuery() {
  152. if (!enterpriseId.value || !outBizNo.value) return;
  153. querying.value = true;
  154. try {
  155. const res = await BatchPayAPI.queryAuthorize(enterpriseId.value, outBizNo.value);
  156. status.value = res.data.data.status;
  157. agreementNo.value = res.data.data.agreement_no || "";
  158. if (status.value === "AUTHED" || status.value === "NORMAL") isAuthed.value = true;
  159. } finally {
  160. querying.value = false;
  161. }
  162. }
  163. async function handleCopyLink() {
  164. try {
  165. await navigator.clipboard.writeText(link.value);
  166. ElMessage.success("授权链接已复制");
  167. } catch {
  168. ElMessage.warning("复制失败,请手动复制");
  169. }
  170. }
  171. </script>
  172. <style lang="scss" scoped>
  173. .card-header {
  174. display: flex;
  175. justify-content: space-between;
  176. align-items: center;
  177. }
  178. .qrcode-wrapper {
  179. display: flex;
  180. flex-direction: column;
  181. align-items: center;
  182. gap: 4px;
  183. }
  184. .qrcode-canvas {
  185. width: 160px;
  186. height: 160px;
  187. border: 1px solid #e4e7ed;
  188. border-radius: 4px;
  189. }
  190. .form-item-tip {
  191. font-size: 12px;
  192. color: #909399;
  193. }
  194. </style>