feat(token): 添加验证码输入及重新发送功能

- 新增输入验证码模态框组件,支持验证码输入和校验
- 仅在验证码已发送状态显示输入验证码按钮
- 仅在验证码验证失败状态显示重新发送验证码按钮
- 实现验证码表单验证与提交逻辑,提交成功关闭模态框并通知父组件
- 重新发送验证码时显示加载状态,并支持重试与错误提示
- 调整Token列表操作列宽度以适配新按钮
- 优化界面样式,确保模态框滚动条样式主题兼容
This commit is contained in:
danial
2025-12-09 20:33:47 +08:00
parent 44b61956bd
commit 10bf29e084
2 changed files with 250 additions and 2 deletions

View File

@@ -0,0 +1,160 @@
<template>
<a-modal
v-model:visible="modalVisible"
title="输入验证码"
width="500px"
:ok-loading="loading"
@cancel="handleCancel"
@before-ok="handleBeforeOk"
>
<a-form
ref="formRef"
:model="formModel"
:rules="rules"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-form-item field="code" label="验证码" required>
<a-input
v-model="formModel.code"
placeholder="请输入收到的验证码"
:max-length="10"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { Notification, FormInstance } from '@arco-design/web-vue';
import { KamiApiCamelOilV1TokenInfo } from '@/api/generated/index.ts';
import { jdV2TokenClient } from '@/api/index.ts';
import useLoading from '@/hooks/loading';
interface Props {
visible: boolean;
tokenData?: KamiApiCamelOilV1TokenInfo | null;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'success'): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const modalVisible = computed({
get: () => props.visible,
set: value => emit('update:visible', value)
});
const formRef = ref<FormInstance>();
const { loading, setLoading } = useLoading(false);
const generateFormModel = () => {
return {
tokenId: 0,
code: ''
};
};
const formModel = reactive(generateFormModel());
const rules = {
code: [
{ required: true, message: '请输入验证码' },
{ min: 4, max: 10, message: '验证码长度为4-10个字符' }
]
};
// 监听弹窗显示状态和token数据更新表单
watch(
[() => props.visible, () => props.tokenData],
([visible, tokenData]) => {
if (visible && tokenData) {
Object.assign(formModel, {
tokenId: tokenData.id || 0,
code: ''
});
} else if (!visible) {
Object.assign(formModel, generateFormModel());
}
},
{ immediate: true }
);
const handleCancel = () => {
emit('update:visible', false);
};
const handleBeforeOk = (done: (closed: boolean) => void) => {
formRef.value?.validate().then(async (res: any) => {
if (res) {
console.log('Validation failed:', res);
return done(false);
}
try {
setLoading(true);
console.log('Validation passed, submitting verification code...');
await jdV2TokenClient.apiTokenInputVerificationCodePost({
kamiApiCamelOilV1InputVerificationCodeReq: {
tokenId: formModel.tokenId,
code: formModel.code
}
});
Notification.success({
content: '验证码提交成功',
closable: true
});
// 操作成功,触发成功事件并关闭模态框
console.log('Verification code submitted successfully');
emit('success');
emit('update:visible', false);
done(true);
} catch (err) {
console.error('提交验证码失败:', err);
Notification.error({
content: '提交验证码失败,请检查验证码是否正确',
closable: true
});
// 操作失败,阻止模态框关闭
console.log('Verification code submission failed, keeping modal open');
done(false);
} finally {
setLoading(false);
}
});
};
</script>
<style scoped>
/* 使用 Arco Design 的原生样式,确保主题兼容性 */
:deep(.arco-modal-body) {
max-height: 60vh;
overflow-y: auto;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
scrollbar-color: var(--color-border-3) transparent;
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
</style>

View File

@@ -104,6 +104,31 @@
<!-- 操作列模板 -->
<template #operations="{ record }">
<a-space size="small">
<!-- 输入验证码按钮 - 仅在验证码已发送时显示 -->
<a-tooltip v-if="record.status === 5" content="输入验证码">
<a-button
size="small"
type="secondary"
@click="showInputVerificationModal(record)"
>
<template #icon>
<icon-message />
</template>
</a-button>
</a-tooltip>
<!-- 重新发送验证码按钮 - 仅在验证码验证失败时显示 -->
<a-tooltip v-if="record.status === 6" content="重新发送验证码">
<a-button
size="small"
type="secondary"
:loading="resendLoadingIds.has(record.id)"
@click="resendVerificationCode(record)"
>
<template #icon>
<icon-refresh />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="编辑Token">
<a-button
size="small"
@@ -161,6 +186,13 @@
v-model:visible="state.tokenDrawerVisible"
:token-data="state.currentTokenData"
/>
<!-- 输入验证码模态框 -->
<input-verification-modal
v-model:visible="state.inputVerificationModalVisible"
:token-data="state.currentVerificationToken"
@success="handleInputVerificationSuccess"
/>
</div>
</template>
@@ -171,6 +203,7 @@ import { onMounted, reactive, ref } from 'vue';
import { Notification, TableColumnData } from '@arco-design/web-vue';
import AddEditModal from './components/add-edit-modal.vue';
import TokenDrawer from './components/token-drawer.vue';
import InputVerificationModal from './components/input-verification-modal.vue';
import {
ApiTokenListGetPageSizeEnum,
KamiApiCamelOilV1TokenInfo
@@ -243,7 +276,7 @@ const columns: TableColumnData[] = [
dataIndex: 'operations',
slotName: 'operations',
fixed: 'right',
width: 250
width: 380
}
];
@@ -258,12 +291,17 @@ const { loading, setLoading } = useLoading(true);
const renderData = ref<KamiApiCamelOilV1TokenInfo[]>([]);
const formModel = ref(generateFormModel());
// 重新发送验证码的加载状态管理
const resendLoadingIds = ref(new Set<number>());
const state = reactive({
addEditModalVisible: false,
editModalVisible: false,
tokenDrawerVisible: false,
inputVerificationModalVisible: false,
currentTokenData: null as KamiApiCamelOilV1TokenInfo | null,
currentEditData: null as KamiApiCamelOilV1TokenInfo | null
currentEditData: null as KamiApiCamelOilV1TokenInfo | null,
currentVerificationToken: null as KamiApiCamelOilV1TokenInfo | null
});
const fetchData = async (params: any = { current: 1, pageSize: 50 }) => {
@@ -335,6 +373,16 @@ const showTokenDrawer = (record: KamiApiCamelOilV1TokenInfo) => {
);
};
const showInputVerificationModal = (record: KamiApiCamelOilV1TokenInfo) => {
console.log('showInputVerificationModal called with record:', record);
state.currentVerificationToken = record;
state.inputVerificationModalVisible = true;
console.log(
'inputVerificationModalVisible set to true, currentVerificationToken:',
state.currentVerificationToken
);
};
const deleteToken = async (tokenId: number) => {
try {
await jdV2TokenClient.apiTokenDeletePost({
@@ -366,6 +414,10 @@ const handleEditModalSuccess = () => {
fetchData({ ...pagination });
};
const handleInputVerificationSuccess = () => {
fetchData({ ...pagination });
};
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
@@ -407,6 +459,42 @@ const download = () => {
});
};
/**
* 重新发送验证码
* @param record Token记录
*/
const resendVerificationCode = async (record: KamiApiCamelOilV1TokenInfo) => {
if (!record.id) return;
try {
// 添加到加载状态
resendLoadingIds.value.add(record.id);
await jdV2TokenClient.apiTokenResendVerificationCodePost({
kamiApiCamelOilV1ResendVerificationCodeReq: {
tokenId: record.id
}
});
Notification.success({
content: '验证码已重新发送',
closable: true
});
// 刷新列表数据
fetchData({ ...pagination });
} catch (err) {
console.error('重新发送验证码失败:', err);
Notification.error({
content: '重新发送验证码失败,请稍后重试',
closable: true
});
} finally {
// 移除加载状态
resendLoadingIds.value.delete(record.id);
}
};
onMounted(() => {
fetchData();
});