fix: 设备分配代理,虚比例,sim顺序
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m15s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m15s
This commit is contained in:
@@ -5,26 +5,21 @@
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { HOME_PAGE } from '@/router/routesAlias'
|
||||
import AppConfig from '@/config'
|
||||
import { AuthService } from '@/api/modules'
|
||||
import { ApiStatus } from '@/utils/http/status'
|
||||
import { MOCK_ACCOUNTS, mockLogin, mockGetUserInfo, type MockAccount } from '@/mock/auth'
|
||||
import { saveCredentials, getRememberedCredentials } from '@/utils/auth/rememberPassword'
|
||||
import { usernameRules, passwordRules } from '@/utils/auth/loginValidation'
|
||||
import { getRedirectPath } from '@/router/guards/permission'
|
||||
|
||||
// 开发模式:是否使用 Mock 数据
|
||||
const USE_MOCK = false
|
||||
|
||||
/**
|
||||
* 登录表单数据
|
||||
*/
|
||||
interface LoginForm {
|
||||
account: string
|
||||
username: string
|
||||
password: string
|
||||
rememberPassword: boolean
|
||||
@@ -44,7 +39,6 @@ export function useLogin() {
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive<LoginForm>({
|
||||
account: '',
|
||||
username: '',
|
||||
password: '',
|
||||
rememberPassword: true
|
||||
@@ -56,47 +50,19 @@ export function useLogin() {
|
||||
password: passwordRules(t)
|
||||
}))
|
||||
|
||||
// Mock 账号列表
|
||||
const mockAccounts = computed<MockAccount[]>(() => MOCK_ACCOUNTS)
|
||||
|
||||
/**
|
||||
* 初始化表单
|
||||
*/
|
||||
// 初始化表单
|
||||
const initForm = () => {
|
||||
// 尝试加载记住的密码
|
||||
const remembered = getRememberedCredentials()
|
||||
if (remembered) {
|
||||
formData.username = remembered.username
|
||||
formData.password = remembered.password
|
||||
formData.rememberPassword = remembered.rememberPassword
|
||||
|
||||
// 如果使用 Mock,尝试匹配 Mock 账号
|
||||
if (USE_MOCK) {
|
||||
const matchedAccount = MOCK_ACCOUNTS.find((acc) => acc.username === remembered.username)
|
||||
if (matchedAccount) {
|
||||
formData.account = matchedAccount.key
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 开发环境:使用真实 API 默认账号
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
formData.username = 'admin'
|
||||
formData.password = 'Admin@123456'
|
||||
formData.rememberPassword = false
|
||||
}
|
||||
// 生产环境:不填充默认值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Mock 账号
|
||||
*/
|
||||
const setupAccount = (key: string) => {
|
||||
const selectedAccount = MOCK_ACCOUNTS.find((account) => account.key === key)
|
||||
if (selectedAccount) {
|
||||
formData.account = key
|
||||
formData.username = selectedAccount.username
|
||||
formData.password = selectedAccount.password
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +78,7 @@ export function useLogin() {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
|
||||
// 判断使用 Mock 还是真实 API
|
||||
if (USE_MOCK) {
|
||||
await handleMockLogin()
|
||||
} else {
|
||||
await handleRealLogin()
|
||||
}
|
||||
await handleRealLogin()
|
||||
} catch (error: any) {
|
||||
console.error('登录失败:', error)
|
||||
} finally {
|
||||
@@ -127,44 +87,7 @@ export function useLogin() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock 登录(开发测试用)
|
||||
*/
|
||||
const handleMockLogin = async () => {
|
||||
const loginResult = mockLogin(formData.username, formData.password)
|
||||
|
||||
if (!loginResult) {
|
||||
console.log(t('login.error.invalidCredentials'))
|
||||
return
|
||||
}
|
||||
|
||||
// 保存 Token
|
||||
userStore.setToken(loginResult.access_token, loginResult.refresh_token)
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = mockGetUserInfo(loginResult.access_token)
|
||||
|
||||
if (!userInfo) {
|
||||
console.log(t('login.error.getUserInfoFailed'))
|
||||
return
|
||||
}
|
||||
|
||||
// 保存用户信息
|
||||
userStore.setUserInfo(userInfo)
|
||||
userStore.setLoginStatus(true)
|
||||
|
||||
// 保存记住密码
|
||||
saveCredentials(formData.username, formData.password, formData.rememberPassword)
|
||||
|
||||
// 显示登录成功提示
|
||||
showLoginSuccessNotice()
|
||||
|
||||
// 跳转到重定向页面或首页
|
||||
const redirectPath = getRedirectPath(route)
|
||||
await router.push(redirectPath || HOME_PAGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 真实 API 登录
|
||||
* 登录
|
||||
*/
|
||||
const handleRealLogin = async () => {
|
||||
// 调用登录接口
|
||||
@@ -253,8 +176,6 @@ export function useLogin() {
|
||||
formData,
|
||||
rules,
|
||||
loading,
|
||||
mockAccounts,
|
||||
setupAccount,
|
||||
handleLogin
|
||||
}
|
||||
}
|
||||
|
||||
132
src/mock/auth.ts
132
src/mock/auth.ts
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* 登录模块 Mock 数据
|
||||
*/
|
||||
|
||||
import type { LoginData, UserInfo } from '@/types/api'
|
||||
import { UserRole } from '@/types/api'
|
||||
|
||||
/**
|
||||
* Mock 用户账号
|
||||
*/
|
||||
export interface MockAccount {
|
||||
key: string
|
||||
label: string
|
||||
username: string
|
||||
password: string
|
||||
role: UserRole
|
||||
userInfo: UserInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock 账号列表
|
||||
*/
|
||||
export const MOCK_ACCOUNTS: MockAccount[] = [
|
||||
{
|
||||
key: 'super',
|
||||
label: '超级管理员',
|
||||
username: 'super',
|
||||
password: '123456',
|
||||
role: UserRole.SUPER_ADMIN,
|
||||
userInfo: {
|
||||
id: 1,
|
||||
username: 'Super',
|
||||
phone: '13800138000',
|
||||
user_type: 1,
|
||||
user_type_name: '超级管理员'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'admin',
|
||||
label: '平台管理员',
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
role: UserRole.ADMIN,
|
||||
userInfo: {
|
||||
id: 2,
|
||||
username: 'admin',
|
||||
phone: '13800138001',
|
||||
user_type: 2,
|
||||
user_type_name: '平台管理员'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'agent',
|
||||
label: '代理商',
|
||||
username: 'agent',
|
||||
password: '123456',
|
||||
role: UserRole.AGENT,
|
||||
userInfo: {
|
||||
id: 3,
|
||||
username: 'agent',
|
||||
phone: '13800138002',
|
||||
user_type: 3,
|
||||
user_type_name: '代理商'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'enterprise',
|
||||
label: '企业客户',
|
||||
username: 'enterprise',
|
||||
password: '123456',
|
||||
role: UserRole.ENTERPRISE,
|
||||
userInfo: {
|
||||
id: 4,
|
||||
username: 'enterprise',
|
||||
phone: '13800138003',
|
||||
user_type: 4,
|
||||
user_type_name: '企业客户'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* Mock 登录响应
|
||||
*/
|
||||
export const mockLogin = (username: string, password: string): LoginData | null => {
|
||||
const account = MOCK_ACCOUNTS.find(
|
||||
(acc) => acc.username === username && acc.password === password
|
||||
)
|
||||
|
||||
if (!account) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 生成 Mock Token
|
||||
const mockToken = `mock_token_${account.key}_${Date.now()}`
|
||||
const mockRefreshToken = `mock_refresh_${account.key}_${Date.now()}`
|
||||
|
||||
return {
|
||||
access_token: mockToken,
|
||||
refresh_token: mockRefreshToken,
|
||||
expires_in: 7200, // 2小时
|
||||
user: account.userInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock 获取用户信息
|
||||
*/
|
||||
export const mockGetUserInfo = (token: string): UserInfo | null => {
|
||||
// 从 token 中提取用户类型
|
||||
const match = token.match(/mock_token_(\w+)_/)
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
|
||||
const accountKey = match[1]
|
||||
const account = MOCK_ACCOUNTS.find((acc) => acc.key === accountKey)
|
||||
|
||||
return account ? account.userInfo : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mock 账号选项(用于登录页下拉框)
|
||||
*/
|
||||
export const getMockAccountOptions = () => {
|
||||
return MOCK_ACCOUNTS.map((acc) => ({
|
||||
key: acc.key,
|
||||
label: acc.label,
|
||||
username: acc.username,
|
||||
password: acc.password
|
||||
}))
|
||||
}
|
||||
@@ -171,23 +171,6 @@ function matchMenuPath(actualPath: string, menuPath: string): boolean {
|
||||
*/
|
||||
export const isTokenValid = (token: string): boolean => {
|
||||
if (!token) return false
|
||||
|
||||
// Mock Token 格式: mock_token_{key}_{timestamp}
|
||||
if (token.startsWith('mock_token_')) {
|
||||
// Mock Token 永不过期(开发环境)
|
||||
return true
|
||||
}
|
||||
|
||||
// 真实 Token 可以在这里添加 JWT 解析和过期检查
|
||||
// 例如:
|
||||
// try {
|
||||
// const decoded = jwt_decode(token)
|
||||
// const isExpired = decoded.exp * 1000 < Date.now()
|
||||
// return !isExpired
|
||||
// } catch {
|
||||
// return false
|
||||
// }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ export interface LoginData {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
expires_in: number
|
||||
permissions?: string[] | null
|
||||
permissions: string[] | null
|
||||
menus: any[] | null
|
||||
buttons: string[] | null
|
||||
user: UserInfo
|
||||
}
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@
|
||||
getSwitchModeName
|
||||
} = useAssetFormatters(deviceRealtimeRef)
|
||||
|
||||
// 排序后的设备卡列表(当前卡排在最前面)
|
||||
// 排序后的设备卡列表(按卡槽位置排序,当前卡排在最前面)
|
||||
const sortedDeviceCards = computed(() => {
|
||||
if (!props.cardInfo?.cards || props.cardInfo.cards.length === 0) {
|
||||
return []
|
||||
@@ -543,7 +543,7 @@
|
||||
return cards.sort((a, b) => {
|
||||
if (a.is_current && !b.is_current) return -1
|
||||
if (!a.is_current && b.is_current) return 1
|
||||
return 0
|
||||
return (a.slot_position ?? 0) - (b.slot_position ?? 0)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<ElDescriptionsItem label="套餐类型">
|
||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.enable_virtual_data" label="虚流量比例">
|
||||
<ElDescriptionsItem v-if="currentPackage.enable_virtual_data && isSuperAdmin" label="虚流量比例">
|
||||
{{ currentPackage.virtual_ratio || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
|
||||
@@ -134,6 +134,8 @@
|
||||
ElAlert
|
||||
} from 'element-plus'
|
||||
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { PackageInfo } from '../types'
|
||||
|
||||
@@ -146,6 +148,10 @@
|
||||
getProgressColorByPercentage
|
||||
} = useAssetFormatters()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { info: userInfo } = storeToRefs(userStore)
|
||||
const isSuperAdmin = computed(() => userInfo.value.user_type === 1)
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
currentPackage: PackageInfo | null
|
||||
|
||||
@@ -1365,7 +1365,7 @@
|
||||
try {
|
||||
const data = {
|
||||
device_ids: selectedDevices.value.map((d) => d.id),
|
||||
target_shop_id: allocateForm.target_shop_id!,
|
||||
target_shop_id: allocateForm.target_shop_id![0],
|
||||
remark: allocateForm.remark
|
||||
}
|
||||
const res = await DeviceService.allocateDevices(data)
|
||||
|
||||
@@ -9,24 +9,6 @@
|
||||
{{ isDark ? '' : '' }}
|
||||
</i>
|
||||
</div>
|
||||
<ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
|
||||
<!--<div class="btn language-btn">-->
|
||||
<!-- <i class="iconfont-sys icon-language"></i>-->
|
||||
<!--</div>-->
|
||||
<!--<template #dropdown>-->
|
||||
<!-- <ElDropdownMenu>-->
|
||||
<!-- <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">-->
|
||||
<!-- <ElDropdownItem-->
|
||||
<!-- :command="lang.value"-->
|
||||
<!-- :class="{ 'is-selected': locale === lang.value }"-->
|
||||
<!-- >-->
|
||||
<!-- <span class="menu-txt">{{ lang.label }}</span>-->
|
||||
<!-- <i v-if="locale === lang.value" class="iconfont-sys icon-check"></i>-->
|
||||
<!-- </ElDropdownItem>-->
|
||||
<!-- </div>-->
|
||||
<!-- </ElDropdownMenu>-->
|
||||
<!--</template>-->
|
||||
</ElDropdown>
|
||||
</div>
|
||||
<div class="header">
|
||||
<ArtLogo class="icon" />
|
||||
@@ -43,19 +25,6 @@
|
||||
@keyup.enter="handleSubmit"
|
||||
style="margin-top: 25px"
|
||||
>
|
||||
<!-- Mock 账号选择(仅在开发模式且使用 Mock 时显示) -->
|
||||
<ElFormItem prop="account" v-if="false">
|
||||
<ElSelect v-model="formData.account" @change="setupAccount" class="account-select">
|
||||
<ElOption
|
||||
v-for="account in mockAccounts"
|
||||
:key="account.key"
|
||||
:label="account.label"
|
||||
:value="account.key"
|
||||
>
|
||||
<span>{{ account.label }}</span>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="username">
|
||||
<ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
|
||||
</ElFormItem>
|
||||
@@ -74,7 +43,6 @@
|
||||
<ElCheckbox v-model="formData.rememberPassword">{{
|
||||
$t('login.rememberPwd')
|
||||
}}</ElCheckbox>
|
||||
<!--<RouterLink :to="RoutesAlias.ForgetPassword">{{ $t('login.forgetPwd') }}</RouterLink>-->
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px">
|
||||
@@ -97,22 +65,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppConfig from '@/config'
|
||||
import { LanguageEnum, SystemThemeEnum } from '@/enums/appEnum'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { SystemThemeEnum } from '@/enums/appEnum'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useLogin } from '@/composables/useLogin'
|
||||
|
||||
defineOptions({ name: 'Login' })
|
||||
|
||||
const { locale } = useI18n()
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const { isDark, systemThemeType } = storeToRefs(settingStore)
|
||||
|
||||
// 使用登录 Composable
|
||||
const { formRef, formData, rules, loading, mockAccounts, setupAccount, handleLogin } = useLogin()
|
||||
const { formRef, formData, rules, loading, handleLogin } = useLogin()
|
||||
|
||||
const systemName = AppConfig.systemInfo.name
|
||||
|
||||
@@ -121,13 +85,6 @@
|
||||
await handleLogin()
|
||||
}
|
||||
|
||||
// 切换语言
|
||||
const changeLanguage = (lang: LanguageEnum) => {
|
||||
if (locale.value === lang) return
|
||||
locale.value = lang
|
||||
userStore.setLanguage(lang)
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
let { LIGHT, DARK } = SystemThemeEnum
|
||||
|
||||
Reference in New Issue
Block a user