CardHandle.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <template>
  2. <u-modal v-model:show="showDialog" class="card-handle-dialog" :title="t('card.vaildate.v42')"
  3. :show-confirm-button="false" :show-cancel-button="false" :close-on-click-overlay="false" @closed="handleClose">
  4. <view class="card-handle-dialog-content">
  5. <u-form ref="formRef" :model="form" class="payment-form">
  6. <view class="code-input-label">{{ t("newSignup.item9") }}</view>
  7. <view class="code-input-wrapper">
  8. <view class="code-input">
  9. <cwg-input v-model:value="form.emailCode" fkey="emailCode" type="text" :required="true"
  10. :placeholder="t('newSignup.item10')" @change="handleChange" />
  11. </view>
  12. <view class="get-code-btn">
  13. <view class="cwg-button ok-button">
  14. <u-button type="primary" block @click="handleGetCode">{{
  15. getCodeString
  16. }}</u-button>
  17. </view>
  18. </view>
  19. </view>
  20. <cwg-input v-if="cvv" v-model:value="cvv" fkey="cvv" type="text" label="CVV" :readonly="true"
  21. :disabled="true" />
  22. </u-form>
  23. <view class="dialog-footer">
  24. <view v-if="!cvv" class="cwg-button ok-button">
  25. <u-button type="primary" block @click="handleConfirm">{{
  26. t("card.vaildate.v42")
  27. }}</u-button>
  28. </view>
  29. <view v-else class="cwg-button ok-button">
  30. <u-button type="primary" block @click="cardCopy">{{
  31. t("card.vaildate.v43")
  32. }}</u-button>
  33. </view>
  34. <view class="cwg-button no-button">
  35. <u-button type="default" block @click="handleClose">{{
  36. t("card.Btn.Cancel")
  37. }}</u-button>
  38. </view>
  39. </view>
  40. </view>
  41. </u-modal>
  42. </template>
  43. <script setup lang="ts">
  44. import { ref, onMounted, watch, onBeforeUnmount } from "vue";
  45. import { showToast } from "@/utils/toast";
  46. import { useI18n } from "vue-i18n";
  47. import { ucardApi } from "@/api/ucard";
  48. import errorIcon from "/static/images/error.png";
  49. import copyIcon from "/static/images/success.png";
  50. const { t } = useI18n();
  51. const props = defineProps<{
  52. dialogInfoTradingAdd: boolean;
  53. formList: Record<string, any>;
  54. }>();
  55. const emit = defineEmits(["closeAdd"]);
  56. const showDialog = ref(false);
  57. const formRef = ref();
  58. const form = ref<{
  59. emailCode: string;
  60. cardNo?: string;
  61. password?: string;
  62. country?: string;
  63. email?: string;
  64. }>({
  65. emailCode: "",
  66. });
  67. const cvv = ref("");
  68. const timer = ref(59);
  69. const getCodeString = ref("");
  70. const interval = ref<NodeJS.Timeout | null>(null);
  71. const rules = {
  72. emailCode: [
  73. {
  74. required: true,
  75. message: t("vaildate.code.empty"),
  76. },
  77. ],
  78. };
  79. // 监听 props 变化
  80. watch(
  81. () => props.dialogInfoTradingAdd,
  82. (newVal) => {
  83. showDialog.value = newVal;
  84. if (newVal) {
  85. initForm();
  86. initTimer();
  87. } else {
  88. handleClose();
  89. }
  90. },
  91. { immediate: true }
  92. );
  93. // 初始化表单
  94. function initForm() {
  95. if (props.formList) {
  96. form.value = {
  97. ...JSON.parse(JSON.stringify(props.formList)),
  98. emailCode: "",
  99. };
  100. }
  101. cvv.value = "";
  102. timer.value = 59;
  103. getCodeString.value = t("newSignup.item11");
  104. if (interval.value) {
  105. clearInterval(interval.value);
  106. interval.value = null;
  107. }
  108. }
  109. // 初始化定时器
  110. function initTimer() {
  111. const savedTimer = Number(localStorage.getItem("cvvTimer")) || 59;
  112. if (savedTimer === 59) {
  113. getCodeString.value = t("newSignup.item11");
  114. } else {
  115. timer.value = savedTimer;
  116. startTimer();
  117. }
  118. }
  119. // 开始倒计时
  120. function startTimer() {
  121. if (interval.value) {
  122. return;
  123. }
  124. getCodeString.value = `${t("signup.form.waitCode1")}${timer.value}${t(
  125. "signup.form.waitCode2"
  126. )}`;
  127. interval.value = setInterval(() => {
  128. timer.value--;
  129. localStorage.setItem("cvvTimer", timer.value.toString());
  130. if (timer.value > 0) {
  131. getCodeString.value = `${t("signup.form.waitCode1")}${timer.value}${t(
  132. "signup.form.waitCode2"
  133. )}`;
  134. } else {
  135. getCodeString.value = t("newSignup.item11");
  136. if (interval.value) {
  137. clearInterval(interval.value);
  138. interval.value = null;
  139. }
  140. timer.value = 59;
  141. localStorage.setItem("cvvTimer", "59");
  142. }
  143. }, 1000);
  144. }
  145. // 发送邮箱验证码
  146. async function sendEmailCode() {
  147. try {
  148. if (!form.value.country) {
  149. showToast(t("vaildate.country.empty"));
  150. return false;
  151. }
  152. if (!form.value.email) {
  153. showToast(t("vaildate.email.empty"));
  154. return false;
  155. }
  156. const res = await ucardApi.sendEmailCode({
  157. ...form.value,
  158. });
  159. if (res.code === 200) {
  160. showToast(t("Msg.CodeSuccess"));
  161. startTimer();
  162. return true;
  163. } else {
  164. showToast(t("Msg.CodeFail"));
  165. return false;
  166. }
  167. } catch (error: any) {
  168. console.log(error, 12);
  169. showToast(t("Msg.CodeFail"));
  170. return false;
  171. }
  172. }
  173. // 获取验证码按钮点击
  174. async function handleGetCode() {
  175. if (timer.value > 0 && timer.value < 59) {
  176. return;
  177. }
  178. cvv.value = "";
  179. await sendEmailCode();
  180. }
  181. // 表单字段变化
  182. function handleChange(value: any) {
  183. if (value.key === "emailCode") {
  184. form.value.emailCode = value.value;
  185. }
  186. }
  187. // 确认获取 CVV
  188. async function handleConfirm() {
  189. try {
  190. await formRef.value?.validate();
  191. if (!form.value.emailCode) {
  192. // showToast(t('vaildate.code.empty'))
  193. return;
  194. }
  195. const res = await ucardApi.getCvvCode({
  196. ...form.value,
  197. } as any);
  198. if (res.code === 200) {
  199. cvv.value = res.data;
  200. } else {
  201. showToast(res.msg);
  202. cvv.value = "";
  203. }
  204. } catch (error: any) {
  205. if (Array.isArray(error) && error.length > 0) {
  206. showToast({
  207. message: error[0].message,
  208. icon: errorIcon,
  209. className: "custom-toast",
  210. });
  211. } else {
  212. showToast({
  213. message: error?.message,
  214. icon: errorIcon,
  215. className: "custom-toast",
  216. });
  217. }
  218. }
  219. }
  220. // 复制 CVV
  221. function cardCopy() {
  222. let title = t("common.copy2");
  223. uni.setClipboardData({
  224. data: cvv.value,
  225. success: () => {
  226. uni.showToast({
  227. title,
  228. });
  229. },
  230. });
  231. }
  232. // 降级复制方案
  233. function fallbackCopy() {
  234. const textarea = document.createElement("textarea");
  235. textarea.value = cvv.value;
  236. textarea.setAttribute("readonly", "");
  237. textarea.style.position = "absolute";
  238. textarea.style.left = "-9999px";
  239. document.body.appendChild(textarea);
  240. textarea.select();
  241. try {
  242. document.execCommand("copy");
  243. showToast({
  244. message: t("card.Msg.m8"),
  245. icon: copyIcon,
  246. className: "custom-toast",
  247. });
  248. } catch (_err) {
  249. showToast({
  250. message: t("card.Msg.m9"),
  251. icon: errorIcon,
  252. className: "custom-toast",
  253. });
  254. }
  255. document.body.removeChild(textarea);
  256. }
  257. // 关闭对话框
  258. function handleClose() {
  259. showDialog.value = false;
  260. emit("closeAdd", false);
  261. if (interval.value) {
  262. clearInterval(interval.value);
  263. interval.value = null;
  264. }
  265. }
  266. // 组件卸载时清理
  267. onBeforeUnmount(() => {
  268. if (interval.value) {
  269. clearInterval(interval.value);
  270. interval.value = null;
  271. }
  272. });
  273. </script>
  274. <style scoped lang="scss">
  275. @import "@/uni.scss";
  276. .card-handle-dialog {
  277. width: 100%;
  278. :deep(.u-popup__content) {
  279. width: 90% !important;
  280. }
  281. :deep(.u-modal) {
  282. width: 100% !important;
  283. }
  284. .card-handle-dialog-content {
  285. padding: px2rpx(44) px2rpx(2);
  286. .no-button {
  287. width: 100%;
  288. margin: px2rpx(12) 0;
  289. .u-button {
  290. background-color: #ffbdc8 !important;
  291. }
  292. }
  293. .ok-button {
  294. margin: px2rpx(4) 0;
  295. /* background-color: #EA002A; */
  296. }
  297. }
  298. .code-input-label {
  299. font-size: var(--font-size-16);
  300. line-height: px2rpx(44);
  301. letter-spacing: px2rpx(1);
  302. color: #474747;
  303. }
  304. .code-input-wrapper {
  305. position: relative;
  306. display: flex;
  307. align-items: center;
  308. }
  309. .code-input {
  310. flex: 1;
  311. }
  312. .get-code-btn {
  313. min-width: px2rpx(100);
  314. margin-bottom: px2rpx(12);
  315. margin-left: px2rpx(10);
  316. .cwg-button .u-button {
  317. border-radius: px2rpx(8);
  318. }
  319. }
  320. }
  321. </style>