CardHandle.vue 7.9 KB

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