feat: 登录联调
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
export type LoginRole = 'super_admin' | 'doctor'
|
||||
|
||||
export interface LoginPayload {
|
||||
account: string
|
||||
password: string
|
||||
role: LoginRole
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
token: string
|
||||
raw: unknown
|
||||
}
|
||||
|
||||
function getTokenFromResponse(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
const directToken = record.token || record.access_token || record.accessToken
|
||||
if (typeof directToken === 'string') {
|
||||
return directToken
|
||||
}
|
||||
|
||||
const nested = record.data
|
||||
if (nested && typeof nested === 'object') {
|
||||
return getTokenFromResponse(nested)
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function getMessageFromResponse(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
const message = record.message || record.msg || record.detail
|
||||
if (typeof message === 'string') {
|
||||
return message
|
||||
}
|
||||
|
||||
return getMessageFromResponse(record.data)
|
||||
}
|
||||
|
||||
function parseResponseText(text: string): unknown {
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(payload: LoginPayload): Promise<LoginResult> {
|
||||
const response = await fetch('/server/api/user/auth/login/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
const text = await response.text()
|
||||
const data = parseResponseText(text)
|
||||
|
||||
if (!response.ok) {
|
||||
const message = getMessageFromResponse(data) || '登录失败,请检查账号、密码和角色'
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return {
|
||||
token: getTokenFromResponse(data),
|
||||
raw: data
|
||||
}
|
||||
}
|
||||
@@ -178,38 +178,12 @@ p {
|
||||
}
|
||||
}
|
||||
|
||||
.login-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 28px 0 18px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
content: "";
|
||||
background: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
.login-methods {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
.login-role-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-shell {
|
||||
|
||||
+29
-6
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { LoginRole } from '@/api/auth'
|
||||
import type { RoleKey, UserProfile } from '@/types'
|
||||
import { roleOptions } from '@/mock/dashboard'
|
||||
|
||||
@@ -10,34 +11,55 @@ const roleProfiles: Record<RoleKey, Pick<UserProfile, 'name' | 'avatarText'>> =
|
||||
teacher: { name: '陈医生', avatarText: '陈' }
|
||||
}
|
||||
|
||||
const loginRoleMap: Record<LoginRole, RoleKey> = {
|
||||
super_admin: 'super-admin',
|
||||
doctor: 'teacher'
|
||||
}
|
||||
|
||||
function getStoredRole(): RoleKey {
|
||||
const storedRole = localStorage.getItem('mediai-role')
|
||||
return storedRole && storedRole in roleProfiles ? (storedRole as RoleKey) : 'super-admin'
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
const token = ref(localStorage.getItem('mediai-token') || '')
|
||||
const collapsed = ref(false)
|
||||
const darkMode = ref(false)
|
||||
const storedRole = getStoredRole()
|
||||
const user = ref<UserProfile>({
|
||||
name: '张管理员',
|
||||
avatarText: '张',
|
||||
role: 'super-admin'
|
||||
name: roleProfiles[storedRole].name,
|
||||
avatarText: roleProfiles[storedRole].avatarText,
|
||||
role: storedRole
|
||||
})
|
||||
|
||||
const roleLabel = computed(() => roleOptions.find(item => item.value === user.value.role)?.label || '管理员')
|
||||
const isLoggedIn = computed(() => Boolean(token.value))
|
||||
|
||||
function login(username: string) {
|
||||
token.value = `mock-token-${username}`
|
||||
function login(username: string, role: LoginRole, authToken: string) {
|
||||
const appRole = loginRoleMap[role]
|
||||
token.value = authToken || `session-${username}`
|
||||
localStorage.setItem('mediai-token', token.value)
|
||||
localStorage.setItem('mediai-role', appRole)
|
||||
user.value.role = appRole
|
||||
user.value.name = username === 'admin' ? '张管理员' : username
|
||||
user.value.avatarText = user.value.name.slice(0, 1).toUpperCase()
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = ''
|
||||
localStorage.removeItem('mediai-token')
|
||||
localStorage.removeItem('mediai-role')
|
||||
}
|
||||
|
||||
function switchRole(role: RoleKey) {
|
||||
user.value.role = role
|
||||
user.value.name = roleProfiles[role].name
|
||||
user.value.avatarText = roleProfiles[role].avatarText
|
||||
localStorage.setItem('mediai-role', role)
|
||||
}
|
||||
|
||||
function getRoleByLoginRole(role: LoginRole) {
|
||||
return loginRoleMap[role]
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -49,6 +71,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
isLoggedIn,
|
||||
login,
|
||||
logout,
|
||||
switchRole
|
||||
switchRole,
|
||||
getRoleByLoginRole
|
||||
}
|
||||
})
|
||||
|
||||
+37
-25
@@ -26,24 +26,19 @@
|
||||
</div>
|
||||
|
||||
<el-form ref="formRef" :model="form" :rules="rules" size="large" @keyup.enter="handleLogin">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="form.username" :prefix-icon="User" placeholder="请输入账号" />
|
||||
<el-form-item prop="account">
|
||||
<el-input v-model="form.account" :prefix-icon="User" placeholder="请输入账号" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" :prefix-icon="Lock" placeholder="请输入密码" show-password type="password" />
|
||||
</el-form-item>
|
||||
<div class="login-options">
|
||||
<el-checkbox v-model="form.remember">记住我</el-checkbox>
|
||||
<el-button link type="primary">忘记密码?</el-button>
|
||||
</div>
|
||||
<el-form-item prop="role">
|
||||
<el-select v-model="form.role" class="login-role-select" :prefix-icon="UserFilled" placeholder="请选择角色">
|
||||
<el-option v-for="item in loginRoleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-button :loading="loading" class="login-submit" type="primary" @click="handleLogin">登录</el-button>
|
||||
</el-form>
|
||||
|
||||
<div class="login-divider"><span>其他登录方式</span></div>
|
||||
<div class="login-methods">
|
||||
<el-button :icon="Iphone">扫码登录</el-button>
|
||||
<el-button :icon="Key">SSO登录</el-button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -51,8 +46,11 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Collection, DataAnalysis, FirstAidKit, Iphone, Key, Lock, OfficeBuilding, User } from '@element-plus/icons-vue'
|
||||
import { Collection, DataAnalysis, FirstAidKit, Lock, OfficeBuilding, User, UserFilled } from '@element-plus/icons-vue'
|
||||
import { login, type LoginRole } from '@/api/auth'
|
||||
import { getFirstPage, getPagePath } from '@/mock/navigation'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -62,16 +60,22 @@ const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
remember: true
|
||||
account: 'admin',
|
||||
password: '',
|
||||
role: 'super_admin' as LoginRole
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
||||
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
role: [{ required: true, message: '请选择角色', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const loginRoleOptions: Array<{ label: string; value: LoginRole }> = [
|
||||
{ label: '超级管理员', value: 'super_admin' },
|
||||
{ label: '医生', value: 'doctor' }
|
||||
]
|
||||
|
||||
const features = [
|
||||
{ title: '智能病例训练', desc: 'AI驱动的沉浸式病例问诊训练', icon: FirstAidKit },
|
||||
{ title: '数据驱动决策', desc: '全方位数据分析与能力评估', icon: DataAnalysis },
|
||||
@@ -80,13 +84,21 @@ const features = [
|
||||
]
|
||||
|
||||
async function handleLogin() {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
window.setTimeout(() => {
|
||||
appStore.login(form.username)
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
const result = await login({
|
||||
account: form.account,
|
||||
password: form.password,
|
||||
role: form.role
|
||||
})
|
||||
appStore.login(form.account, form.role, result.token)
|
||||
loading.value = false
|
||||
router.push((route.query.redirect as string) || '/')
|
||||
}, 420)
|
||||
const defaultPath = getPagePath(getFirstPage(appStore.getRoleByLoginRole(form.role)))
|
||||
router.push((route.query.redirect as string) || defaultPath)
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
ElMessage.error(error instanceof Error ? error.message : '登录失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+7
-1
@@ -10,6 +10,12 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/server': {
|
||||
target: 'http://8.160.178.88/',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user