swagger 推广后台 优化对战百佳历史接口

This commit is contained in:
2026-09-24 14:55:58 +08:00
parent 4be9ce4e85
commit 43286f10ef
160 changed files with 1710 additions and 1719 deletions
+15 -5
View File
@@ -1,14 +1,10 @@
import request from '@/axios'
import type { RegisterUserType, SmsLoginType, UserLoginType, UserType } from './types'
import type { RegisterUserType, SmsLoginType, UserType } from './types'
interface RoleParams {
roleName: string
}
export const loginApi = (data: UserLoginType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/login', data })
}
export const smsLoginApi = (data: SmsLoginType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/smsLogin', data })
}
@@ -34,6 +30,20 @@ export const getCodeApi = (
})
}
/** 点「获取验证码」时的账号前置校验:无推广后台权限的号在这一步就被拒绝,不用先拖滑块 */
export const precheckPhoneApi = (
phoneNumber: string,
parentInvitationCode?: string
): Promise<IResponse<UserType>> => {
return request.post({
url: '/promo/sms/precheck',
data: {
phoneNumber: phoneNumber,
parentInvitationCode
}
})
}
export const loginOutApi = (): Promise<IResponse> => {
return request.get({ url: '/mock/user/loginOut' })
}
@@ -1,5 +1,5 @@
<script setup lang="tsx">
import { getAdminRoleApi, getCodeApi, smsLoginApi } from '@/api/login'
import { getAdminRoleApi, getCodeApi, precheckPhoneApi, smsLoginApi } from '@/api/login'
import type { SmsLoginType, UserType } from '@/api/login/types'
import { getMyMenus } from '@/api/permission'
import { PATH_URL } from '@/axios/service'
@@ -35,6 +35,7 @@ const loading = ref(false)
const redirect = ref('')
const getCodeTime = ref(60)
const getCodeLoading = ref(false)
const captchaCode = ref('')
let tacInstance: any = null
let codeTimer: ReturnType<typeof setInterval> | undefined
@@ -107,8 +108,17 @@ const schema = reactive<FormSchema[]>([
if (event.key === 'Enter') signIn()
}}
/>
<ElButton class="login-code-btn" disabled={unref(getCodeLoading)} onClick={getCode}>
{unref(getCodeLoading) ? `${unref(getCodeTime)}s` : '获取验证码'}
<ElButton
class="login-code-btn"
disabled={unref(getCodeLoading)}
loading={unref(getCodeLoading) && !unref(captchaCode)}
onClick={getCode}
>
{unref(getCodeLoading)
? unref(captchaCode)
? `${unref(getCodeTime)}s`
: '发送中'
: '获取验证码'}
</ElButton>
</div>
)
@@ -165,6 +175,32 @@ onBeforeUnmount(() => {
if (codeTimer) clearInterval(codeTimer)
})
// 点下按钮就开始倒计时:短信网关往返通常 1~3 秒,等接口返回再倒计时会像卡死
const startCountdown = () => {
if (codeTimer) clearInterval(codeTimer)
captchaCode.value = ''
getCodeTime.value = 60
getCodeLoading.value = true
codeTimer = setInterval(() => {
getCodeTime.value--
if (getCodeTime.value <= 0) {
clearInterval(codeTimer)
codeTimer = undefined
getCodeTime.value = 60
getCodeLoading.value = false
}
}, 1000)
}
// 发送失败(限流/验证码失效/发送异常):解禁按钮,让用户能立刻重试
const resetCountdown = () => {
if (codeTimer) clearInterval(codeTimer)
codeTimer = undefined
getCodeTime.value = 60
getCodeLoading.value = false
captchaCode.value = ''
}
const getCode = async () => {
if (getCodeLoading.value) return
const { username, parentInvitationCode } = await getFormData<SmsLoginType>()
@@ -172,6 +208,10 @@ const getCode = async () => {
ElMessage.error('请输入正确的手机号')
return
}
// 先验账号再弹滑块:不是推广后台用户(与后台账号校验同一份规则)直接拒绝,
// 避免用户白拖一次滑块才被告知没权限;接口失败/被限流时放行走原流程,发送时后端还会再验一次
const phoneCheck = await precheckPhoneApi(username, parentInvitationCode).catch(() => null)
if (!phoneCheck) return
if (typeof window.TAC === 'undefined' || typeof window.CaptchaConfig === 'undefined') {
ElMessage.error('滑块验证码未加载,请刷新页面')
return
@@ -187,19 +227,18 @@ const getCode = async () => {
validSuccess: (_res: unknown, _config: unknown, tac: any) => {
tac.destroyWindow()
tacInstance = null
getCodeApi(username, captchaId, '2', parentInvitationCode).then(() => {
ElMessage.success('验证码已发送')
getCodeLoading.value = true
codeTimer = setInterval(() => {
getCodeTime.value--
if (getCodeTime.value <= 0) {
clearInterval(codeTimer)
codeTimer = undefined
getCodeTime.value = 60
getCodeLoading.value = false
startCountdown()
getCodeApi(username, captchaId, '2', parentInvitationCode)
.then((res: any) => {
// 业务失败(限流/验证码失效)响应拦截器已弹过错误提示,这里只释放倒计时
if (res?.code !== 0 && res?.code !== 200) {
resetCountdown()
return
}
}, 1000)
})
captchaCode.value = captchaId
ElMessage.success('验证码已发送')
})
.catch(() => resetCountdown())
},
validFail: (_res: unknown, _config: unknown, tac: any) => {
ElMessage.error('滑块验证失败,请重试')
@@ -10,7 +10,7 @@ import { RegisterUserType } from '@/api/login/types'
import { useUserStore } from '@/store/modules/user'
import { completeLogin } from '../completeLogin'
import { useRouter } from 'vue-router'
import { registerApi, getCodeApi } from '@/api/login'
import { registerApi, getCodeApi, precheckPhoneApi } from '@/api/login'
import { PATH_URL } from '@/axios/service'
declare global {
@@ -38,12 +38,38 @@ const { required } = useValidator()
const getCodeTime = ref(60)
const getCodeLoading = ref(false)
let tacInstance: any = null
let codeTimer: ReturnType<typeof setInterval> | undefined
// 点下按钮就开始倒计时:短信网关往返通常 1~3 秒,等接口返回再倒计时会像卡死
const startCountdown = () => {
if (codeTimer) clearInterval(codeTimer)
getCodeTime.value = 60
getCodeLoading.value = true
codeTimer = setInterval(() => {
getCodeTime.value--
if (getCodeTime.value <= 0) {
clearInterval(codeTimer)
codeTimer = undefined
getCodeTime.value = 60
getCodeLoading.value = false
}
}, 1000)
}
// 发送失败(限流/验证码失效/发送异常):解禁按钮,让用户能立刻重试
const resetCountdown = () => {
if (codeTimer) clearInterval(codeTimer)
codeTimer = undefined
getCodeTime.value = 60
getCodeLoading.value = false
}
onBeforeUnmount(() => {
if (tacInstance) {
tacInstance.destroyWindow()
tacInstance = null
}
if (codeTimer) clearInterval(codeTimer)
})
const getCode = async () => {
@@ -52,6 +78,12 @@ const getCode = async () => {
ElMessage.error('手机号不能为空')
return
}
// 先验账号再弹滑块,与登录页同一份规则(注册页无账号时靠招商邀请码放行);接口失败/被限流时放行走原流程
const phoneCheck = await precheckPhoneApi(
formData.username,
(formData as any).parentInvitationCode
).catch(() => null)
if (!phoneCheck) return
// 判断TAC是否加载
if (typeof window.TAC === 'undefined' || typeof window.CaptchaConfig === 'undefined') {
@@ -71,23 +103,18 @@ const getCode = async () => {
tac.destroyWindow()
tacInstance = null
const captchaId = window._captchaId
// 滑块验证成功,发送短信验证码
// 滑块验证成功,立刻进入倒计时,再等短信接口返回
startCountdown()
getCodeApi(formData.username, captchaId)
.then(() => {
.then((res: any) => {
// 业务失败(限流/验证码失效)响应拦截器已弹过错误提示,这里只释放倒计时
if (res?.code !== 0 && res?.code !== 200) {
resetCountdown()
return
}
ElMessage.success('获取验证码成功')
getCodeLoading.value = true
const timer = setInterval(() => {
getCodeTime.value--
if (getCodeTime.value <= 0) {
clearInterval(timer)
getCodeTime.value = 60
getCodeLoading.value = false
}
}, 1000)
})
.catch(() => {
ElMessage.error('获取验证码失败')
})
.catch(() => resetCountdown())
},
validFail: (res: any, c: any, tac: any) => {
ElMessage.error('滑块验证失败,请重试')
+5 -2
View File
@@ -255,12 +255,15 @@
<ElTableColumn prop="id" label="ID" width="60" />
<ElTableColumn prop="templateName" label="模板名称" min-width="150" />
<ElTableColumn prop="ornamentCount" label="饰品数" width="80" />
<ElTableColumn prop="createBy" label="创建人" width="100" />
<ElTableColumn prop="createBy" label="创建人" width="100">
<template #default="{ row }">{{ row.createBy || '其他主播' }}</template>
</ElTableColumn>
<ElTableColumn prop="createTime" label="创建时间" width="170" />
<ElTableColumn label="操作" width="140">
<template #default="{ row }">
<ElButton type="primary" link size="small" @click="handleLoadTemplate(row)">加载</ElButton>
<ElButton type="danger" link size="small" @click="handleDeleteTemplate(row.id)">删除</ElButton>
<!-- 后端只允许创建者本人或超管删除;共享模板 createBy 会被置空,这里不给别人显示删除按钮 -->
<ElButton v-if="row.createBy" type="danger" link size="small" @click="handleDeleteTemplate(row.id)">删除</ElButton>
</template>
</ElTableColumn>
</ElTable>