feat: 迁移商务登录品牌和三步找回密码

This commit is contained in:
Yvan
2026-11-03 19:07:30 +08:00
parent 317c253ba9
commit 8048dfb514
39 changed files with 2190 additions and 283 deletions
+7
View File
@@ -0,0 +1,7 @@
import service from '@/utils/request'
const request = (action, data) => service({ url: `/sw/auth/forgot-password/${action}`, method: 'post', data, donNotShowLoading: true })
export const forgotPasswordCheck = (username) => request('check', { username })
export const forgotPasswordSendCode = (username) => request('send-code', { username })
export const forgotPasswordVerifyCode = (username, smsCode) => request('verify-code', { username, smsCode })
export const forgotPasswordReset = (data) => request('reset', data)
Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

+1 -1
View File
@@ -2,7 +2,7 @@
<div
class="kra-bottom-info flex flex-col md:flex-row gap-2 items-center text-sm text-slate-700 dark:text-slate-500 justify-center py-2"
>
<div class="text-center font-bold text-active">Kra</div>
<div class="text-center font-bold text-active">{{ $KRA.appName }}</div>
<slot />
</div>
</template>
+5 -8
View File
@@ -17,8 +17,8 @@ const props = defineProps({
}
})
const darkLogoPath = '/logo.svg'
const lightLogoPath = '/logo.svg'
const darkLogoPath = '/business-logo.png'
const lightLogoPath = '/business-logo.png'
const themeStore = useThemeStore();
const { isDark } = storeToRefs(themeStore);
@@ -60,10 +60,10 @@ watchEffect(async (onCleanup) => {
// 到这里就包没有提供两种 logo 了
showTextPlaceholder.value = true;
console.error(
'错误: 在公共目录中找不到 logo.svg。'
'错误: 在公共目录中找不到 business-logo.png。'
);
console.warn(
'解决方案: 请在 public 目录中放置 logo.svg。'
'解决方案: 请在 public 目录中放置 business-logo.png。'
);
});
@@ -81,15 +81,12 @@ function getSize() {
<img v-if="!showTextPlaceholder && logoSrc" :src="logoSrc" :alt="$KRA.appName" class="object-contain"
:style="{
...getSize()
}" :class="{
'filter invert-[90%] hue-rotate-180 brightness-110':
effectiveDark && logoSrc === '/logo.svg',
}" />
<div v-else-if="showTextPlaceholder"
class="rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-200 font-bold text-xs"
:style="{
...getSize()
}">
KRA
商务
</div>
</template>
+1 -1
View File
@@ -4,7 +4,7 @@
const greenText = (text) => `\x1b[32m${text}\x1b[0m`
export const config = {
appName: 'Kra',
appName: '遨游龙翔游戏平台',
showViteLogo: true,
keepAliveTabs: false,
logs: []
File diff suppressed because it is too large Load Diff
+143
View File
@@ -0,0 +1,143 @@
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { captcha } from '@/api/user'
import { forgotPasswordCheck, forgotPasswordSendCode, forgotPasswordVerifyCode, forgotPasswordReset } from '@/api/businessPassword'
export function useBusinessLogin() {
const title = '遨游龙翔游戏平台'
const currentYear = new Date().getFullYear()
const featureList = [
{ text: '实时渠道充值与注册趋势看板', icon: 'trend' },
{ text: '多级商务层级与游戏 SDK 绑定管理', icon: 'user' },
{ text: '智能预警规则与风险监控', icon: 'shield' }
]
const userStore = useUserStore()
const accountKey = 'biz_login_account'
let saved = window.localStorage.getItem(accountKey) || ''
try { const parsed = JSON.parse(saved); if (typeof parsed === 'string') saved = parsed; else if (parsed && typeof parsed === 'object') saved = parsed.username || '' } catch { /* 兼容旧版直接保存账号字符串 */ }
const loginForm = ref({ username: saved, password: '', code: '', rememberMe: Boolean(saved) })
const loginRef = ref()
const loading = ref(false)
const passwordReadonly = ref(true)
const captchaEnabled = ref(false)
const captchaId = ref('')
const captchaLength = ref(6)
const codeUrl = ref('')
const loginRules = {
username: [{ required: true, message: '请输入商务账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入登录密码', trigger: 'blur' }],
code: [{ validator: (_rule, value, callback) => {
if (captchaEnabled.value && !new RegExp(`^\\d{${captchaLength.value}}$`).test(value || '')) callback(new Error('请输入正确的验证码'))
else callback()
}, trigger: 'blur' }]
}
const getCode = async () => {
const res = await captcha()
if (res.code !== 0) return
captchaEnabled.value = Boolean(res.data.openCaptcha)
captchaId.value = res.data.captchaId
captchaLength.value = Number(res.data.captchaLength) || 6
codeUrl.value = res.data.picPath
loginForm.value.code = ''
}
const valid = async (form) => Boolean(await form.value?.validate().catch(() => false))
const handleLogin = async () => {
if (loading.value || !await valid(loginRef)) return
loading.value = true
try {
const success = await userStore.LoginIn({ username: loginForm.value.username.trim(), password: loginForm.value.password, captcha: loginForm.value.code, captchaId: captchaId.value })
if (success) {
if (loginForm.value.rememberMe) window.localStorage.setItem(accountKey, loginForm.value.username.trim())
else window.localStorage.removeItem(accountKey)
} else await getCode()
} finally { loading.value = false }
}
const forgotPasswordVisible = ref(false)
const forgotPasswordStep = ref('account')
const forgotAccountRef = ref()
const forgotSmsRef = ref()
const forgotResetRef = ref()
const forgotPasswordForm = ref({ username: '', smsCode: '', newPassword: '', confirmPassword: '' })
const forgotAccountError = ref('')
const forgotMaskedPhone = ref('')
const forgotResetToken = ref('')
const forgotSubmitting = ref(false)
const smsCountdown = ref(0)
let smsTimer
const forgotAccountRules = { username: [{ required: true, message: '请输入您注册的商务账号', trigger: 'blur' }] }
const forgotSmsRules = { smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }, { pattern: /^\d{6}$/, message: '请输入 6 位数字验证码', trigger: 'blur' }] }
const forgotResetRules = {
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }, { min: 6, max: 15, message: '密码长度为 6-15 位字符', trigger: 'blur' }],
confirmPassword: [{ required: true, message: '请再次输入新密码', trigger: 'blur' }, { validator: (_rule, value, callback) => {
if (value !== forgotPasswordForm.value.newPassword) callback(new Error('两次输入的密码不一致'))
else callback()
}, trigger: 'blur' }]
}
const clearSmsTimer = () => { clearInterval(smsTimer); smsTimer = undefined; smsCountdown.value = 0 }
const handleForgotPasswordClosed = () => {
clearSmsTimer()
forgotPasswordStep.value = 'account'
forgotPasswordForm.value = { username: '', smsCode: '', newPassword: '', confirmPassword: '' }
forgotResetToken.value = ''
forgotMaskedPhone.value = ''
forgotAccountError.value = ''
forgotSubmitting.value = false
}
const handleOpenForgotPassword = () => {
handleForgotPasswordClosed()
forgotPasswordForm.value.username = loginForm.value.username
forgotPasswordVisible.value = true
}
const requestSendCode = async () => {
const res = await forgotPasswordSendCode(forgotPasswordForm.value.username.trim())
if (res.code !== 0) return false
forgotMaskedPhone.value = res.data.maskedPhone
clearSmsTimer()
const deadline = Date.now() + 60000
smsCountdown.value = 60
smsTimer = setInterval(() => { smsCountdown.value = Math.max(0, Math.ceil((deadline - Date.now()) / 1000)); if (!smsCountdown.value) clearSmsTimer() }, 1000)
ElMessage.success(`验证码已发送至 ${forgotMaskedPhone.value}`)
return true
}
const handleForgotAccountNext = async () => {
if (forgotSubmitting.value || !await valid(forgotAccountRef)) return
forgotSubmitting.value = true
try {
const res = await forgotPasswordCheck(forgotPasswordForm.value.username.trim())
if (res.code !== 0) { forgotAccountError.value = res.msg; return }
forgotMaskedPhone.value = res.data.maskedPhone
if (await requestSendCode()) forgotPasswordStep.value = 'sms'
} finally { forgotSubmitting.value = false }
}
const handleSendSmsCode = async () => {
if (forgotSubmitting.value || smsCountdown.value > 0) return
forgotSubmitting.value = true
try { await requestSendCode() } finally { forgotSubmitting.value = false }
}
const handleForgotSmsNext = async () => {
if (forgotSubmitting.value || !await valid(forgotSmsRef)) return
forgotSubmitting.value = true
try {
const res = await forgotPasswordVerifyCode(forgotPasswordForm.value.username.trim(), forgotPasswordForm.value.smsCode)
if (res.code === 0) { forgotResetToken.value = res.data.resetToken; forgotPasswordStep.value = 'reset' }
} finally { forgotSubmitting.value = false }
}
const handleForgotResetSubmit = async () => {
if (forgotSubmitting.value || !await valid(forgotResetRef)) return
forgotSubmitting.value = true
try {
const res = await forgotPasswordReset({ resetToken: forgotResetToken.value, newPassword: forgotPasswordForm.value.newPassword, confirmPassword: forgotPasswordForm.value.confirmPassword })
if (res.code === 0) { ElMessage.success('密码重置成功,请使用新密码登录'); loginForm.value.username = forgotPasswordForm.value.username; loginForm.value.password = ''; forgotPasswordVisible.value = false }
} finally { forgotSubmitting.value = false }
}
onMounted(getCode)
onBeforeUnmount(clearSmsTimer)
return {
title, currentYear, featureList, loginRef, loginForm, loginRules, loading, passwordReadonly, captchaEnabled, codeUrl, getCode, handleLogin,
forgotPasswordVisible, forgotPasswordStep, forgotAccountRef, forgotSmsRef, forgotResetRef, forgotPasswordForm, forgotAccountError, forgotMaskedPhone,
forgotSubmitting, smsCountdown, forgotAccountRules, forgotSmsRules, forgotResetRules, handleOpenForgotPassword, handleForgotPasswordClosed,
handleForgotAccountNext, handleSendSmsCode, handleForgotSmsNext, handleForgotResetSubmit
}
}