增加手机号短信验证查询逻辑

This commit is contained in:
2026-06-09 16:05:37 +08:00
parent ce4ed6213b
commit f1d7491288
14 changed files with 1942 additions and 136 deletions

View File

@@ -25,7 +25,8 @@
<el-input v-model.trim="form.course_name" />
</el-form-item>
<el-form-item label="阶段名称">
<el-input v-model.trim="form.stage_name" />
<el-input v-model.trim="form.stage_name" placeholder="如:全部、初级、中级" />
<div class="form-tip">证书上将显示为{输入内容}课程的专业学习留空则默认显示初级课程的专业学习</div>
</el-form-item>
<el-form-item label="发证单位">
<el-input v-model.trim="form.issuer_name" />
@@ -213,4 +214,11 @@ onMounted(async () => {
.toolbar .el-input {
max-width: 340px;
}
.form-tip {
font-size: 12px;
color: #94a3b8;
line-height: 1.6;
margin-top: 4px;
}
</style>

View File

@@ -13,13 +13,10 @@
<el-form-item v-if="queryMode === 'certificate'" label="证书编号">
<el-input v-model.trim="form.certificateNo" placeholder="例如 PX2026-DBY-K7M9Q2R-83" />
</el-form-item>
<el-form-item v-else label="手机号">
<el-input v-model.trim="form.phone" placeholder="请输入证书对应手机号" />
</el-form-item>
<el-form-item label="姓名">
<el-form-item v-if="queryMode === 'certificate'" label="姓名">
<el-input v-model.trim="form.name" placeholder="请输入证书对应姓名" />
</el-form-item>
<el-form-item label="图形验证码">
<el-form-item v-if="queryMode === 'certificate'" label="图形验证码">
<div class="captcha-row">
<el-input v-model.trim="form.captcha" placeholder="请输入验证码" />
<button class="captcha-button" type="button" @click="loadCaptcha">
@@ -28,6 +25,31 @@
</button>
</div>
</el-form-item>
<el-form-item v-if="queryMode === 'sms'" label="手机号">
<el-input v-model.trim="form.phone" placeholder="请输入证书对应手机号" />
</el-form-item>
<el-form-item v-if="queryMode === 'sms'" label="图形验证码">
<div class="captcha-row">
<el-input v-model.trim="form.smsCaptcha" placeholder="请输入验证码" />
<button class="captcha-button" type="button" @click="loadSmsCaptcha">
<img v-if="smsCaptchaImage" :src="smsCaptchaImage" alt="验证码" />
<span v-else>刷新</span>
</button>
</div>
</el-form-item>
<el-form-item v-if="queryMode === 'sms'" label="短信验证码">
<div class="sms-row">
<el-input v-model.trim="form.smsCode" placeholder="请输入短信验证码" />
<el-button
type="primary"
:disabled="smsCooldown > 0 || !form.phone"
@click="sendSmsCode"
:loading="sendingSms"
>
{{ smsCooldown > 0 ? `${smsCooldown}秒后重试` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
<el-button type="primary" class="submit" :loading="loading" @click="submitQuery">查询</el-button>
</el-form>
@@ -111,7 +133,7 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from "vue";
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
import { http, type Captcha, type PublicCertificate } from "../api";
@@ -121,13 +143,15 @@ interface SearchResponse {
const queryOptions = [
{ label: "证书编号查询", value: "certificate" },
{ label: "手机号查询", value: "phone" },
{ label: "短信验证码查询", value: "sms" },
];
const queryMode = ref<"certificate" | "phone">("certificate");
const form = reactive({ certificateNo: "", phone: "", name: "", captcha: "" });
const queryMode = ref<"certificate" | "sms">("certificate");
const form = reactive({ certificateNo: "", phone: "", name: "", captcha: "", smsCaptcha: "", smsCode: "" });
const captchaId = ref("");
const captchaImage = ref("");
const smsCaptchaId = ref("");
const smsCaptchaImage = ref("");
const loading = ref(false);
const message = ref("");
const messageType = ref<"success" | "warning" | "error">("warning");
@@ -135,6 +159,10 @@ const results = ref<PublicCertificate[]>([]);
const previewVisible = ref(false);
const current = ref<PublicCertificate | null>(null);
const previewPdfUrl = ref("");
const smsId = ref("");
const sendingSms = ref(false);
const smsCooldown = ref(0);
let smsTimer: number | null = null;
function statusText(status: string) {
return status === "valid" ? "有效" : "已作废";
@@ -147,6 +175,9 @@ function courseText(item: PublicCertificate) {
watch(queryMode, () => {
message.value = "";
results.value = [];
if (queryMode.value === "sms") {
loadSmsCaptcha();
}
});
async function loadCaptcha() {
@@ -156,18 +187,86 @@ async function loadCaptcha() {
form.captcha = "";
}
async function loadSmsCaptcha() {
const { data } = await http.get<Captcha>("/public/captcha");
smsCaptchaId.value = data.captcha_id;
smsCaptchaImage.value = data.image;
form.smsCaptcha = "";
}
async function sendSmsCode() {
if (!form.phone) {
message.value = "请先输入手机号";
messageType.value = "warning";
return;
}
sendingSms.value = true;
message.value = "";
try {
const { data } = await http.post<{ sms_id: string; message: string }>("/public/sms/send", {
phone: form.phone,
captcha_id: smsCaptchaId.value,
captcha_code: form.smsCaptcha,
});
smsId.value = data.sms_id;
message.value = data.message;
messageType.value = "success";
// 开始倒计时
smsCooldown.value = 60;
if (smsTimer) clearInterval(smsTimer);
smsTimer = window.setInterval(() => {
smsCooldown.value--;
if (smsCooldown.value <= 0) {
if (smsTimer) clearInterval(smsTimer);
smsTimer = null;
}
}, 1000);
} catch (error: any) {
message.value = error?.response?.data?.detail || "发送失败,请稍后再试";
messageType.value = "error";
await loadSmsCaptcha();
} finally {
sendingSms.value = false;
}
}
async function submitQuery() {
loading.value = true;
message.value = "";
results.value = [];
try {
const { data } = await http.post<SearchResponse>("/public/certificates/search", {
certificate_no: queryMode.value === "certificate" ? form.certificateNo : null,
phone: queryMode.value === "phone" ? form.phone : null,
name: form.name,
captcha_id: captchaId.value,
captcha_code: form.captcha,
});
let data: SearchResponse;
if (queryMode.value === "certificate") {
// 证书编号查询
const response = await http.post<SearchResponse>("/public/certificates/search", {
certificate_no: form.certificateNo,
phone: null,
name: form.name,
captcha_id: captchaId.value,
captcha_code: form.captcha,
});
data = response.data;
} else {
// 短信验证码查询
if (!smsId.value) {
message.value = "请先获取短信验证码";
messageType.value = "warning";
return;
}
const response = await http.post<SearchResponse>("/public/certificates/search-by-sms", {
phone: form.phone,
sms_id: smsId.value,
sms_code: form.smsCode,
});
data = response.data;
}
results.value = data.items;
const hasVoided = data.items.some((item) => item.status !== "valid");
message.value = hasVoided ? "已查询到证书,其中包含作废证书,请核对状态。" : "已查询到匹配证书。";
@@ -175,7 +274,9 @@ async function submitQuery() {
} catch (error: any) {
message.value = error?.response?.data?.detail || "查询失败,请稍后再试";
messageType.value = "error";
await loadCaptcha();
if (queryMode.value === "certificate") {
await loadCaptcha();
}
} finally {
loading.value = false;
}
@@ -208,6 +309,14 @@ function downloadPdf(item: PublicCertificate) {
}
onMounted(loadCaptcha);
// 清理定时器
onUnmounted(() => {
if (smsTimer) {
clearInterval(smsTimer);
smsTimer = null;
}
});
</script>
<style scoped>
@@ -258,6 +367,13 @@ h1 {
width: 100%;
}
.sms-row {
display: grid;
grid-template-columns: 1fr 120px;
gap: 10px;
width: 100%;
}
.captcha-button {
height: 34px;
padding: 0;