feat:登录的功能完成
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
VITE_API_BASE_URL=http://192.168.1.187:8080
|
VITE_API_BASE_URL=http://192.168.0.206:8080
|
||||||
# 后台「系统管理 -> 客户端管理」中 APP 端的 clientId
|
# APP 端 OAuth 客户端 ID(与 Web 端区分)
|
||||||
VITE_AUTH_CLIENT_ID=
|
VITE_AUTH_CLIENT_ID=428a8310cd442757ae699df5d894f051
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import type { LoginResult, PasswordLoginParams, RegisterParams, RegisterResult, SendSmsCodeParams } from '@/types/auth'
|
import type { LoginResult, PasswordLoginParams, RegisterParams, RegisterResult, ResetPasswordParams, SendSmsCodeParams, SmsLoginParams } from '@/types/auth'
|
||||||
import { AUTH_CLIENT_ID } from '@/constants/config'
|
import { AUTH_CLIENT_ID } from '@/constants/config'
|
||||||
import { post } from '@/utils/request'
|
import { post } from '@/utils/request'
|
||||||
|
|
||||||
@@ -6,6 +6,16 @@ const AUTH_API = {
|
|||||||
register: '/app/auth/register',
|
register: '/app/auth/register',
|
||||||
sendSmsCode: '/app/auth/sms/code',
|
sendSmsCode: '/app/auth/sms/code',
|
||||||
loginByPassword: '/app/auth/login/password',
|
loginByPassword: '/app/auth/login/password',
|
||||||
|
loginBySms: '/app/auth/login/sms',
|
||||||
|
resetPassword: '/app/auth/password/reset',
|
||||||
|
logout: '/app/auth/logout',
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLIENT_ID_MISSING_MESSAGE = '未配置认证客户端 ID,请联系后端获取 clientId 并写入 .env.development'
|
||||||
|
|
||||||
|
function rejectMissingClientId() {
|
||||||
|
uni.showToast({ title: CLIENT_ID_MISSING_MESSAGE, icon: 'none', duration: 3000 })
|
||||||
|
return Promise.reject(new Error(CLIENT_ID_MISSING_MESSAGE))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSmsCode(data: SendSmsCodeParams) {
|
export function sendSmsCode(data: SendSmsCodeParams) {
|
||||||
@@ -20,11 +30,8 @@ export function loginByPassword(
|
|||||||
data: PasswordLoginParams,
|
data: PasswordLoginParams,
|
||||||
options?: { showErrorToast?: boolean },
|
options?: { showErrorToast?: boolean },
|
||||||
) {
|
) {
|
||||||
if (!AUTH_CLIENT_ID) {
|
if (!AUTH_CLIENT_ID)
|
||||||
const message = '未配置认证客户端 ID,请联系后端获取 clientId 并写入 .env.development'
|
return rejectMissingClientId()
|
||||||
uni.showToast({ title: message, icon: 'none', duration: 3000 })
|
|
||||||
return Promise.reject(new Error(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
return post<LoginResult>(
|
return post<LoginResult>(
|
||||||
AUTH_API.loginByPassword,
|
AUTH_API.loginByPassword,
|
||||||
@@ -36,3 +43,29 @@ export function loginByPassword(
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loginBySms(
|
||||||
|
data: SmsLoginParams,
|
||||||
|
options?: { showErrorToast?: boolean },
|
||||||
|
) {
|
||||||
|
if (!AUTH_CLIENT_ID)
|
||||||
|
return rejectMissingClientId()
|
||||||
|
|
||||||
|
return post<LoginResult>(
|
||||||
|
AUTH_API.loginBySms,
|
||||||
|
{
|
||||||
|
clientId: AUTH_CLIENT_ID,
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
smsCode: data.smsCode,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return post<void>(AUTH_API.logout, {}, { showErrorToast: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetPassword(data: ResetPasswordParams) {
|
||||||
|
return post<void>(AUTH_API.resetPassword, data)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ function getTabIcon(tab: typeof tabs[number]) {
|
|||||||
return tab.key === props.active ? tab.activeIcon : tab.icon
|
return tab.key === props.active ? tab.activeIcon : tab.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { ensureLoggedIn } from '@/utils/auth-guard'
|
||||||
|
|
||||||
function handleSwitch(path: string, key: TabKey) {
|
function handleSwitch(path: string, key: TabKey) {
|
||||||
if (key === props.active)
|
if (key === props.active)
|
||||||
return
|
return
|
||||||
|
if (key === 'home' && !ensureLoggedIn())
|
||||||
|
return
|
||||||
uni.redirectTo({ url: path })
|
uni.redirectTo({ url: path })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** 后端服务地址 */
|
/** 后端服务地址 */
|
||||||
export const API_HOST = '192.168.1.187'
|
export const API_HOST = '192.168.0.206'
|
||||||
|
|
||||||
export const API_PORT = 8080
|
export const API_PORT = 8080
|
||||||
|
|
||||||
@@ -8,5 +8,6 @@ export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || `http://${API_H
|
|||||||
/** 接口请求超时(毫秒) */
|
/** 接口请求超时(毫秒) */
|
||||||
export const API_TIMEOUT = 15000
|
export const API_TIMEOUT = 15000
|
||||||
|
|
||||||
/** APP 端 OAuth 客户端 ID(sys_client 表,需与后端「客户端管理」一致) */
|
/** APP 端 OAuth 客户端 ID(与 Web 端区分,可用 VITE_AUTH_CLIENT_ID 覆盖) */
|
||||||
export const AUTH_CLIENT_ID = import.meta.env.VITE_AUTH_CLIENT_ID || ''
|
export const AUTH_CLIENT_ID =
|
||||||
|
import.meta.env.VITE_AUTH_CLIENT_ID || '428a8310cd442757ae699df5d894f051'
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { resetPassword } from '@/api/auth'
|
||||||
|
import PasswordStrength from '@/pages/Login/user-register/components/PasswordStrength.vue'
|
||||||
|
import { getPasswordStrength } from '@/utils/password-strength'
|
||||||
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||||
|
import { validatePhone } from '@/utils/sms-code'
|
||||||
import CodeInput from './components/CodeInput.vue'
|
import CodeInput from './components/CodeInput.vue'
|
||||||
import ForgotInput from './components/ForgotInput.vue'
|
import ForgotInput from './components/ForgotInput.vue'
|
||||||
import PasswordField from './components/PasswordField.vue'
|
import PasswordField from './components/PasswordField.vue'
|
||||||
@@ -10,12 +15,16 @@ const code = ref('')
|
|||||||
const newPassword = ref('')
|
const newPassword = ref('')
|
||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
const agreed = ref(false)
|
const agreed = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
function toggleAgreed() {
|
function toggleAgreed() {
|
||||||
agreed.value = !agreed.value
|
agreed.value = !agreed.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
async function handleSubmit() {
|
||||||
|
if (submitting.value)
|
||||||
|
return
|
||||||
|
|
||||||
if (!agreed.value) {
|
if (!agreed.value) {
|
||||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -24,15 +33,40 @@ function handleSubmit() {
|
|||||||
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!validatePhone(phone.value))
|
||||||
|
return
|
||||||
if (newPassword.value !== confirmPassword.value) {
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
uni.showToast({ title: '两次密码输入不一致', icon: 'none' })
|
uni.showToast({ title: '两次密码输入不一致', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (getPasswordStrength(newPassword.value).isTooWeak) {
|
||||||
|
uni.showToast({ title: '密码等级过低,请重新输入', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
uni.showLoading({ title: '提交中...', mask: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
await resetPassword({
|
||||||
|
phoneNumber: phone.value.trim(),
|
||||||
|
smsCode: code.value.trim(),
|
||||||
|
newPassword: newPassword.value,
|
||||||
|
confirmPassword: confirmPassword.value,
|
||||||
|
})
|
||||||
navigateToResultPage('resetPassword')
|
navigateToResultPage('resetPassword')
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 错误提示由 request 统一处理
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
submitting.value = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
uni.navigateBack()
|
navigateBackWithFallback('/pages/Login/user-login/index')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserAgreement() {
|
function handleUserAgreement() {
|
||||||
@@ -75,6 +109,7 @@ function handleUserAgreement() {
|
|||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
placeholder="请输入新密码"
|
placeholder="请输入新密码"
|
||||||
/>
|
/>
|
||||||
|
<PasswordStrength :password="newPassword" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="forgot-page__field">
|
<view class="forgot-page__field">
|
||||||
@@ -126,9 +161,10 @@ function handleUserAgreement() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="forgot-page__submit"
|
class="forgot-page__submit"
|
||||||
|
:disabled="submitting"
|
||||||
@tap="handleSubmit"
|
@tap="handleSubmit"
|
||||||
>
|
>
|
||||||
确认修改
|
{{ submitting ? '提交中...' : '确认修改' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { loginByPassword } from '@/api/auth'
|
import { loginByPassword } from '@/api/auth'
|
||||||
import { setAccessToken } from '@/utils/auth-storage'
|
import { setAccessToken, setLoginPhoneNumber } from '@/utils/auth-storage'
|
||||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||||
import { validatePhone } from '@/utils/sms-code'
|
import { validatePhone } from '@/utils/sms-code'
|
||||||
import LoginInput from './components/LoginInput.vue'
|
import LoginInput from './components/LoginInput.vue'
|
||||||
@@ -46,6 +46,8 @@ async function handleLogin() {
|
|||||||
if (result.access_token)
|
if (result.access_token)
|
||||||
setAccessToken(result.access_token)
|
setAccessToken(result.access_token)
|
||||||
|
|
||||||
|
setLoginPhoneNumber(phone.value.trim())
|
||||||
|
|
||||||
uni.redirectTo({ url: '/pages/home-index/index' })
|
uni.redirectTo({ url: '/pages/home-index/index' })
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -87,21 +89,10 @@ function handleUserAgreement() {
|
|||||||
url: '/pages/Login/user-agreement/index',
|
url: '/pages/Login/user-agreement/index',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGuestMode() {
|
|
||||||
uni.redirectTo({ url: '/pages/home-index/index' })
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="login-page">
|
<view class="login-page">
|
||||||
<text
|
|
||||||
class="login-page__guest"
|
|
||||||
@tap="handleGuestMode"
|
|
||||||
>
|
|
||||||
游客模式
|
|
||||||
</text>
|
|
||||||
|
|
||||||
<view class="login-page__header">
|
<view class="login-page__header">
|
||||||
<text class="login-page__title">
|
<text class="login-page__title">
|
||||||
欢迎回来
|
欢迎回来
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { register } from '@/api/auth'
|
import { register } from '@/api/auth'
|
||||||
import { getPasswordStrength } from '@/utils/password-strength'
|
import { getPasswordStrength } from '@/utils/password-strength'
|
||||||
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||||
import CodeInput from './components/CodeInput.vue'
|
import CodeInput from './components/CodeInput.vue'
|
||||||
import PasswordField from './components/PasswordField.vue'
|
import PasswordField from './components/PasswordField.vue'
|
||||||
@@ -62,7 +63,7 @@ async function handleRegister() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
uni.navigateBack()
|
navigateBackWithFallback('/pages/Login/user-login/index')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserAgreement() {
|
function handleUserAgreement() {
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { loginBySms } from '@/api/auth'
|
||||||
|
import { setAccessToken, setLoginPhoneNumber } from '@/utils/auth-storage'
|
||||||
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
|
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||||
|
import { validatePhone } from '@/utils/sms-code'
|
||||||
import CodeInput from './components/CodeInput.vue'
|
import CodeInput from './components/CodeInput.vue'
|
||||||
import LoginInput from './components/LoginInput.vue'
|
import LoginInput from './components/LoginInput.vue'
|
||||||
|
|
||||||
const phone = ref('')
|
const phone = ref('')
|
||||||
const code = ref('')
|
const code = ref('')
|
||||||
const agreed = ref(false)
|
const agreed = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
function toggleAgreed() {
|
function toggleAgreed() {
|
||||||
agreed.value = !agreed.value
|
agreed.value = !agreed.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin() {
|
function shouldNavigateToRegister(msg: string) {
|
||||||
|
return /未注册|不存在|请先注册|创建账号/.test(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
if (submitting.value)
|
||||||
|
return
|
||||||
|
|
||||||
if (!agreed.value) {
|
if (!agreed.value) {
|
||||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -20,13 +33,43 @@ function handleLogin() {
|
|||||||
uni.showToast({ title: '请输入手机号和验证码', icon: 'none' })
|
uni.showToast({ title: '请输入手机号和验证码', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!validatePhone(phone.value))
|
||||||
|
return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
uni.showLoading({ title: '登录中...', mask: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await loginBySms({
|
||||||
|
phoneNumber: phone.value.trim(),
|
||||||
|
smsCode: code.value.trim(),
|
||||||
|
}, { showErrorToast: false })
|
||||||
|
|
||||||
|
if (result.access_token)
|
||||||
|
setAccessToken(result.access_token)
|
||||||
|
|
||||||
|
setLoginPhoneNumber(phone.value.trim())
|
||||||
|
|
||||||
// TODO: 接入登录接口
|
|
||||||
uni.redirectTo({ url: '/pages/home-index/index' })
|
uni.redirectTo({ url: '/pages/home-index/index' })
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : '登录失败'
|
||||||
|
if (message.includes('未配置认证客户端'))
|
||||||
|
return
|
||||||
|
if (shouldNavigateToRegister(message)) {
|
||||||
|
navigateToResultPage('loginFailed')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.showToast({ title: message, icon: 'none' })
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
submitting.value = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePasswordLogin() {
|
function handlePasswordLogin() {
|
||||||
uni.navigateBack()
|
navigateBackWithFallback('/pages/Login/user-login/index')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRegister() {
|
function handleRegister() {
|
||||||
@@ -111,9 +154,10 @@ function handleUserAgreement() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="sms-login-page__submit"
|
class="sms-login-page__submit"
|
||||||
|
:disabled="submitting"
|
||||||
@tap="handleLogin"
|
@tap="handleLogin"
|
||||||
>
|
>
|
||||||
登录
|
{{ submitting ? '登录中...' : '登录' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import { getBusinessDetail } from '@/constants/business-detail'
|
import { getBusinessDetail } from '@/constants/business-detail'
|
||||||
import type { BusinessDetailContent } from '@/types/business-detail'
|
import type { BusinessDetailContent } from '@/types/business-detail'
|
||||||
|
import { ensureLoggedIn } from '@/utils/auth-guard'
|
||||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
import heroBg from '@/static/icons/background.png'
|
import heroBg from '@/static/icons/background.png'
|
||||||
|
|
||||||
@@ -18,6 +19,10 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
ensureLoggedIn()
|
||||||
|
})
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
navigateBackWithFallback('/pages/home-index/index')
|
navigateBackWithFallback('/pages/home-index/index')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import AppTabBar from '@/components/AppTabBar.vue'
|
import AppTabBar from '@/components/AppTabBar.vue'
|
||||||
import { useGreeting } from '@/hooks/use-greeting'
|
import { useGreeting } from '@/hooks/use-greeting'
|
||||||
import { useQuickEntries } from '@/hooks/use-quick-entries'
|
import { useQuickEntries } from '@/hooks/use-quick-entries'
|
||||||
import type { ProjectItem, QuickEntryItem, ServiceItem } from '@/types/home'
|
import type { ProjectItem, QuickEntryItem, ServiceItem } from '@/types/home'
|
||||||
|
import { ensureLoggedIn } from '@/utils/auth-guard'
|
||||||
import { navigateQuickEntry } from '@/utils/quick-entry-navigate'
|
import { navigateQuickEntry } from '@/utils/quick-entry-navigate'
|
||||||
import headerBg from '@/static/icons/1.png'
|
import headerBg from '@/static/icons/1.png'
|
||||||
import softwareIllus from '@/static/icons/software.png'
|
import softwareIllus from '@/static/icons/software.png'
|
||||||
@@ -25,6 +27,10 @@ const {
|
|||||||
addToPinned,
|
addToPinned,
|
||||||
} = useQuickEntries()
|
} = useQuickEntries()
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
ensureLoggedIn()
|
||||||
|
})
|
||||||
|
|
||||||
const services: ServiceItem[] = [
|
const services: ServiceItem[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import { BUSINESS_TYPE_DETAIL_ID, BUSINESS_TYPE_OPTIONS } from '@/constants/business-types'
|
import { BUSINESS_TYPE_DETAIL_ID, BUSINESS_TYPE_OPTIONS } from '@/constants/business-types'
|
||||||
|
import { ensureLoggedIn } from '@/utils/auth-guard'
|
||||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||||
import deleteIcon from '@/static/icons/X.png'
|
import deleteIcon from '@/static/icons/X.png'
|
||||||
@@ -41,6 +42,10 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
ensureLoggedIn()
|
||||||
|
})
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
closeBusinessTypeDropdown()
|
closeBusinessTypeDropdown()
|
||||||
navigateBackWithFallback(`/pages/home-index/business-detail/index?id=${detailId.value}`)
|
navigateBackWithFallback(`/pages/home-index/business-detail/index?id=${detailId.value}`)
|
||||||
|
|||||||
@@ -94,13 +94,31 @@
|
|||||||
color: $color-text-placeholder;
|
color: $color-text-placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-security__delete-btn {
|
.account-security__logout-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
margin-top: 120rpx;
|
margin-top: 120rpx;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
|
color: $color-primary;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2rpx solid $color-primary;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-security__logout-btn--disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-security__delete-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 88rpx;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background: $color-primary;
|
background: $color-primary;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
import { ACCOUNT_SECURITY_ITEMS } from '@/constants/account-security'
|
import { ACCOUNT_SECURITY_ITEMS } from '@/constants/account-security'
|
||||||
import type { AccountSecurityItem } from '@/types/account-security'
|
import type { AccountSecurityItem } from '@/types/account-security'
|
||||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||||
|
import { performLogout } from '@/utils/logout'
|
||||||
|
|
||||||
const securityItems = ACCOUNT_SECURITY_ITEMS
|
const securityItems = ACCOUNT_SECURITY_ITEMS
|
||||||
|
const loggingOut = ref(false)
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
navigateBackWithFallback('/pages/personal-center/index')
|
navigateBackWithFallback('/pages/personal-center/index')
|
||||||
@@ -40,6 +43,32 @@ function handleDeleteAccount() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
if (loggingOut.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '退出登录',
|
||||||
|
content: '确认退出当前账号吗?',
|
||||||
|
confirmText: '退出',
|
||||||
|
confirmColor: '#2563EB',
|
||||||
|
success: async (res) => {
|
||||||
|
if (!res.confirm)
|
||||||
|
return
|
||||||
|
|
||||||
|
loggingOut.value = true
|
||||||
|
uni.showLoading({ title: '退出中...', mask: true })
|
||||||
|
try {
|
||||||
|
await performLogout()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loggingOut.value = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -88,6 +117,14 @@ function handleDeleteAccount() {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="account-security__logout-btn"
|
||||||
|
:class="{ 'account-security__logout-btn--disabled': loggingOut }"
|
||||||
|
@tap="handleLogout"
|
||||||
|
>
|
||||||
|
退出登录
|
||||||
|
</view>
|
||||||
|
|
||||||
<view
|
<view
|
||||||
class="account-security__delete-btn"
|
class="account-security__delete-btn"
|
||||||
@tap="handleDeleteAccount"
|
@tap="handleDeleteAccount"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { ref } from 'vue'
|
||||||
import AppTabBar from '@/components/AppTabBar.vue'
|
import AppTabBar from '@/components/AppTabBar.vue'
|
||||||
import {
|
import {
|
||||||
PERSONAL_CENTER_MENU,
|
PERSONAL_CENTER_MENU,
|
||||||
@@ -6,9 +8,19 @@ import {
|
|||||||
} from '@/constants/personal-center'
|
} from '@/constants/personal-center'
|
||||||
import type { PersonalCenterMenuItem } from '@/types/personal-center'
|
import type { PersonalCenterMenuItem } from '@/types/personal-center'
|
||||||
import editIcon from '@/static/icon/edit.svg'
|
import editIcon from '@/static/icon/edit.svg'
|
||||||
|
import { getLoginPhoneNumber } from '@/utils/auth-storage'
|
||||||
|
|
||||||
const profile = PERSONAL_CENTER_PROFILE
|
const profile = PERSONAL_CENTER_PROFILE
|
||||||
const menuList = PERSONAL_CENTER_MENU
|
const menuList = PERSONAL_CENTER_MENU
|
||||||
|
const displayPhone = ref('')
|
||||||
|
|
||||||
|
function refreshProfilePhone() {
|
||||||
|
displayPhone.value = getLoginPhoneNumber() || '未登录'
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
refreshProfilePhone()
|
||||||
|
})
|
||||||
|
|
||||||
function handleEditNickname() {
|
function handleEditNickname() {
|
||||||
uni.showToast({ title: '编辑昵称开发中', icon: 'none' })
|
uni.showToast({ title: '编辑昵称开发中', icon: 'none' })
|
||||||
@@ -47,7 +59,7 @@ function handleMenuTap(item: PersonalCenterMenuItem) {
|
|||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<text class="personal-center__phone">
|
<text class="personal-center__phone">
|
||||||
{{ profile.phone }}
|
{{ displayPhone }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -22,8 +22,20 @@ export interface PasswordLoginParams {
|
|||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SmsLoginParams {
|
||||||
|
phoneNumber: string
|
||||||
|
smsCode: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
access_token: string
|
access_token: string
|
||||||
expire_in?: number
|
expire_in?: number
|
||||||
client_id?: string
|
client_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResetPasswordParams {
|
||||||
|
phoneNumber: string
|
||||||
|
smsCode: string
|
||||||
|
newPassword: string
|
||||||
|
confirmPassword: string
|
||||||
|
}
|
||||||
|
|||||||
19
src/utils/auth-guard.ts
Normal file
19
src/utils/auth-guard.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { getAccessToken } from '@/utils/auth-storage'
|
||||||
|
|
||||||
|
export const LOGIN_PAGE = '/pages/Login/user-login/index'
|
||||||
|
|
||||||
|
export function isLoggedIn() {
|
||||||
|
return Boolean(getAccessToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 未登录时跳转登录页,返回是否已登录 */
|
||||||
|
export function ensureLoggedIn(showToast = true) {
|
||||||
|
if (isLoggedIn())
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (showToast)
|
||||||
|
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||||
|
|
||||||
|
uni.reLaunch({ url: LOGIN_PAGE })
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const ACCESS_TOKEN_KEY = 'access_token'
|
const ACCESS_TOKEN_KEY = 'access_token'
|
||||||
|
const LOGIN_PHONE_KEY = 'login_phone_number'
|
||||||
|
|
||||||
export function setAccessToken(token: string) {
|
export function setAccessToken(token: string) {
|
||||||
uni.setStorageSync(ACCESS_TOKEN_KEY, token)
|
uni.setStorageSync(ACCESS_TOKEN_KEY, token)
|
||||||
@@ -11,3 +12,20 @@ export function getAccessToken() {
|
|||||||
export function clearAccessToken() {
|
export function clearAccessToken() {
|
||||||
uni.removeStorageSync(ACCESS_TOKEN_KEY)
|
uni.removeStorageSync(ACCESS_TOKEN_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setLoginPhoneNumber(phoneNumber: string) {
|
||||||
|
uni.setStorageSync(LOGIN_PHONE_KEY, phoneNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoginPhoneNumber() {
|
||||||
|
return uni.getStorageSync(LOGIN_PHONE_KEY) as string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearLoginPhoneNumber() {
|
||||||
|
uni.removeStorageSync(LOGIN_PHONE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAuthSession() {
|
||||||
|
clearAccessToken()
|
||||||
|
clearLoginPhoneNumber()
|
||||||
|
}
|
||||||
|
|||||||
17
src/utils/logout.ts
Normal file
17
src/utils/logout.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { logout as logoutApi } from '@/api/auth'
|
||||||
|
import { LOGIN_PAGE } from '@/utils/auth-guard'
|
||||||
|
import { clearAuthSession } from '@/utils/auth-storage'
|
||||||
|
|
||||||
|
/** 调用退出接口并清除本地登录态,跳转登录页 */
|
||||||
|
export async function performLogout() {
|
||||||
|
try {
|
||||||
|
await logoutApi()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 网络或服务异常时仍清除本地会话,避免无法退出
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
clearAuthSession()
|
||||||
|
uni.reLaunch({ url: LOGIN_PAGE })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://192.168.1.187:8080',
|
target: 'http://192.168.0.206:8080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user