Ver Fonte

feat(auth): adapt mobile login to RBAC backend with endpoint selection

- Add MpRoleType and extend LoginResult with role/permission/endpoint data
- Store roleInfo (menus, permissions, endpoints) in auth store
- Re-enable role selector in login page using mp-sales/mp-dispatch/mp-construction
- Map backend roleKey to frontend sales/dispatch/construction role
zhangzhicheng há 2 semanas atrás
pai
commit
ed150edbe0
3 ficheiros alterados com 101 adições e 34 exclusões
  1. 23 1
      src/api/auth.ts
  2. 20 19
      src/pages/login/login.vue
  3. 58 14
      src/stores/auth.ts

+ 23 - 1
src/api/auth.ts

@@ -1,9 +1,23 @@
 import { post } from './request'
 
+export type MpRoleType = 'mp-sales' | 'mp-dispatch' | 'mp-construction'
+
 export interface LoginParams {
   username: string
   password: string
-  roleType?: string
+  roleType?: MpRoleType
+}
+
+export interface LoginMenu {
+  menuId: number
+  menuName: string
+  parentId?: number
+  orderNum?: number
+  path?: string
+  component?: string
+  icon?: string
+  menuType?: string
+  children?: LoginMenu[]
 }
 
 export interface LoginResult {
@@ -12,6 +26,10 @@ export interface LoginResult {
   userName: string
   nickName: string
   roleType: string
+  roleId?: number
+  roleName?: string
+  roleKey?: string
+  homePath?: string
   avatar: string
   phonenumber?: string
   phone?: string
@@ -22,6 +40,10 @@ export interface LoginResult {
   hireDate?: string
   team?: string
   department?: string
+  needChangePassword?: boolean
+  menus?: LoginMenu[]
+  permissions?: string[]
+  endpoints?: string[]
 }
 
 export function login(params: LoginParams) {

+ 20 - 19
src/pages/login/login.vue

@@ -11,19 +11,19 @@
     </view>
 
     <!-- 角色选择 -->
-<!--    <view class="w-full mb-4">-->
-<!--      <view class="flex bg-surface rounded-xl p-1 border border-gray-200">-->
-<!--        <view-->
-<!--          v-for="item in roleOptions"-->
-<!--          :key="item.value"-->
-<!--          class="flex-1 py-2 text-center text-sm font-medium rounded-lg transition-all"-->
-<!--          :class="selectedRole === item.value ? 'bg-primary text-white shadow-card' : 'text-gray-500'"-->
-<!--          @click="selectedRole = item.value"-->
-<!--        >-->
-<!--          {{ item.label }}-->
-<!--        </view>-->
-<!--      </view>-->
-<!--    </view>-->
+    <view class="w-full mb-4">
+      <view class="flex bg-surface rounded-xl p-1 border border-gray-200">
+        <view
+          v-for="item in roleOptions"
+          :key="item.value"
+          class="flex-1 py-2 text-center text-sm font-medium rounded-lg transition-all"
+          :class="selectedRole === item.value ? 'bg-primary text-white shadow-card' : 'text-gray-500'"
+          @click="selectedRole = item.value"
+        >
+          {{ item.label }}
+        </view>
+      </view>
+    </view>
 
     <!-- 登录表单 -->
     <view class="w-full bg-white rounded-2xl p-6 shadow-card border border-gray-200">
@@ -81,21 +81,22 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
 import { useAuthStore } from '../../stores/auth'
+import type { MpRoleType } from '../../api/auth'
 
 const authStore = useAuthStore()
 
 interface RoleOption {
   label: string
-  value: 'sales' | 'dispatch' | 'construction'
+  value: MpRoleType
 }
 
 const roleOptions: RoleOption[] = [
-  { label: '销售端', value: 'sales' },
-  { label: '调度端', value: 'dispatch' },
-  { label: '施工端', value: 'construction' },
+  { label: '销售端', value: 'mp-sales' },
+  { label: '调度端', value: 'mp-dispatch' },
+  { label: '施工端', value: 'mp-construction' },
 ]
 
-const selectedRole = ref<RoleOption['value']>('sales')
+const selectedRole = ref<MpRoleType>('mp-sales')
 
 const form = ref({
   username: '',
@@ -117,7 +118,7 @@ async function handleLogin() {
 
   loading.value = true
   try {
-    await authStore.doLogin(form.value.username, form.value.password)
+    await authStore.doLogin(form.value.username, form.value.password, selectedRole.value)
     uni.switchTab({ url: '/pages/home/home' })
   } catch (error: any) {
     // 请求封装已统一弹出 toast,这里避免重复提示

+ 58 - 14
src/stores/auth.ts

@@ -1,34 +1,49 @@
 import { defineStore } from 'pinia'
 import { ref, computed } from 'vue'
-import type { User } from '../types'
-import { login, logout } from '../api/auth'
+import type { User, UserRole } from '../types'
+import { login, logout, type LoginMenu } from '../api/auth'
 
 export const useAuthStore = defineStore('auth', () => {
   // State
   const token = ref<string>('')
   const user = ref<User | null>(null)
+  const roleInfo = ref<{
+    roleId?: number
+    roleName?: string
+    roleKey?: string
+    homePath?: string
+    menus?: LoginMenu[]
+    permissions?: string[]
+    endpoints?: string[]
+  } | null>(null)
 
   // Getters
   const isLoggedIn = computed(() => !!token.value)
   const userName = computed(() => user.value?.name || '')
   const userRole = computed(() => user.value?.role || '')
   const userInfo = computed(() => user.value)
+  const homePath = computed(() => roleInfo.value?.homePath || '')
+  const userEndpoints = computed(() => roleInfo.value?.endpoints || [])
 
-  // Actions
-  async function doLogin(username: string, password: string, selectedRole?: 'sales' | 'dispatch' | 'construction') {
-    const res = await login({ username, password })
-
-    // 后端 roleType: front/dispatch/construction
-    // 前端 role: sales/dispatch/construction
-    const roleMap: Record<string, 'sales' | 'dispatch' | 'construction'> = {
+  // 后端角色标识映射到前端业务端
+  const backendRoleToFrontend = (roleKey?: string): UserRole => {
+    const map: Record<string, UserRole> = {
       front: 'sales',
       dispatch: 'dispatch',
       construction: 'construction',
     }
+    return map[roleKey || ''] || 'sales'
+  }
+
+  // Actions
+  async function doLogin(
+    username: string,
+    password: string,
+    selectedRole?: 'mp-sales' | 'mp-dispatch' | 'mp-construction'
+  ) {
+    const res = await login({ username, password, roleType: selectedRole })
 
-    // 优先使用后端返回的角色
-    const backendRole = roleMap[res.roleType]
-    const finalRole = backendRole || selectedRole || 'sales'
+    const finalRole = backendRoleToFrontend(res.roleKey)
 
     const userData: User = {
       id: String(res.userId),
@@ -45,14 +60,26 @@ export const useAuthStore = defineStore('auth', () => {
       status: 'active',
     }
 
+    const roleData = {
+      roleId: res.roleId,
+      roleName: res.roleName,
+      roleKey: res.roleKey,
+      homePath: res.homePath,
+      menus: res.menus,
+      permissions: res.permissions,
+      endpoints: res.endpoints,
+    }
+
     user.value = userData
+    roleInfo.value = roleData
     token.value = res.token
 
     // 保存到本地存储
     uni.setStorageSync('token', res.token)
     uni.setStorageSync('user', JSON.stringify(userData))
+    uni.setStorageSync('roleInfo', JSON.stringify(roleData))
 
-    return userData.role
+    return finalRole
   }
 
   async function doLogout() {
@@ -63,8 +90,10 @@ export const useAuthStore = defineStore('auth', () => {
     }
     token.value = ''
     user.value = null
+    roleInfo.value = null
     uni.removeStorageSync('token')
     uni.removeStorageSync('user')
+    uni.removeStorageSync('roleInfo')
   }
 
   // 兼容旧代码的别名
@@ -73,19 +102,34 @@ export const useAuthStore = defineStore('auth', () => {
   function checkLogin() {
     const savedToken = uni.getStorageSync('token')
     const savedUser = uni.getStorageSync('user')
+    const savedRoleInfo = uni.getStorageSync('roleInfo')
     if (savedToken && savedUser) {
       token.value = savedToken
-      user.value = JSON.parse(savedUser)
+      try {
+        user.value = JSON.parse(savedUser)
+      } catch {
+        user.value = null
+      }
+    }
+    if (savedRoleInfo) {
+      try {
+        roleInfo.value = JSON.parse(savedRoleInfo)
+      } catch {
+        roleInfo.value = null
+      }
     }
   }
 
   return {
     token,
     user,
+    roleInfo,
     isLoggedIn,
     userName,
     userRole,
     userInfo,
+    homePath,
+    userEndpoints,
     doLogin,
     doLogout,
     logout,