feat(web): add login, auth store and router guard

This commit is contained in:
2026-07-07 15:25:47 +08:00
parent bcef04a8cc
commit e731ee7a2e
9 changed files with 227 additions and 1 deletions

View File

@@ -27,11 +27,27 @@
<el-menu-item index="/inbound-flow">
<span>入站流程</span>
</el-menu-item>
<el-menu-item index="/flow-trace">
<span>流程追踪</span>
</el-menu-item>
<el-menu-item index="/message-test">
<span>消息测试</span>
</el-menu-item>
<el-menu-item v-if="authStore.roleCodes.includes('platform_admin')" index="/tenant-management">
<span>租户管理</span>
</el-menu-item>
<el-menu-item index="/user-management">
<span>用户管理</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header class="header">
<span>{{ $route.meta.title || '消息中台' }}</span>
<div class="header-right">
<span class="user-info">{{ authStore.tenantCode }} / {{ authStore.username }}</span>
<el-button type="primary" text @click="handleLogout">退出登录</el-button>
</div>
</el-header>
<el-main>
<router-view />
@@ -41,8 +57,17 @@
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../stores/auth'
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
function handleLogout() {
authStore.clearAuth()
router.push('/login')
}
</script>
<style scoped>
@@ -67,6 +92,16 @@ const route = useRoute()
border-bottom: 1px solid #e4e7ed;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 500;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.user-info {
color: #606266;
font-size: 14px;
}
</style>

View File

@@ -1,4 +1,5 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './style.css'
@@ -6,6 +7,7 @@ import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(ElementPlus)
app.use(router)
app.mount('#app')

View File

@@ -8,8 +8,19 @@ import Person from '../views/Person.vue'
import Tag from '../views/Tag.vue'
import SendRecord from '../views/SendRecord.vue'
import InboundFlow from '../views/InboundFlow.vue'
import FlowTrace from '../views/FlowTrace.vue'
import MessageTest from '../views/MessageTest.vue'
import TenantManagement from '../views/TenantManagement.vue'
import UserManagement from '../views/UserManagement.vue'
import Login from '../views/Login.vue'
import { useAuthStore } from '../stores/auth'
const routes = [
{
path: '/login',
component: Login,
meta: { public: true },
},
{
path: '/',
component: Layout,
@@ -23,6 +34,10 @@ const routes = [
{ path: 'tag', component: Tag, meta: { title: '标签' } },
{ path: 'send-record', component: SendRecord, meta: { title: '发送记录' } },
{ path: 'inbound-flow', component: InboundFlow, meta: { title: '入站流程' } },
{ path: 'flow-trace', component: FlowTrace, meta: { title: '流程追踪' } },
{ path: 'message-test', component: MessageTest, meta: { title: '消息测试' } },
{ path: 'tenant-management', component: TenantManagement, meta: { title: '租户管理' } },
{ path: 'user-management', component: UserManagement, meta: { title: '用户管理' } },
],
},
]
@@ -32,4 +47,17 @@ const router = createRouter({
routes,
})
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
const isPublic = to.meta?.public === true
if (!isPublic && !authStore.isLoggedIn) {
next('/login')
} else if (to.path === '/login' && authStore.isLoggedIn) {
next('/dashboard')
} else {
next()
}
})
export default router

View File

@@ -0,0 +1,31 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const token = ref(localStorage.getItem('mci-token') || '')
const tenantCode = ref(localStorage.getItem('mci-tenant-code') || '')
const username = ref('')
const roleCodes = ref<string[]>([])
const isLoggedIn = computed(() => !!token.value)
function setAuth(t: string, tc: string, user: string, roles: string[]) {
token.value = t
tenantCode.value = tc
username.value = user
roleCodes.value = roles
localStorage.setItem('mci-token', t)
localStorage.setItem('mci-tenant-code', tc)
}
function clearAuth() {
token.value = ''
tenantCode.value = ''
username.value = ''
roleCodes.value = []
localStorage.removeItem('mci-token')
localStorage.removeItem('mci-tenant-code')
}
return { token, tenantCode, username, roleCodes, isLoggedIn, setAuth, clearAuth }
})

View File

@@ -0,0 +1,5 @@
<template>
<div>
<h2>流程追踪</h2>
</div>
</template>

View File

@@ -0,0 +1,110 @@
<template>
<div class="login-page">
<el-card class="login-card" shadow="always">
<template #header>
<div class="login-title">Sino MCI 管理后台</div>
</template>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
@keyup.enter="handleSubmit"
>
<el-form-item label="租户编码" prop="tenantCode">
<el-input v-model="form.tenantCode" placeholder="请输入租户编码" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
show-password
placeholder="请输入密码"
/>
</el-form-item>
<el-button
type="primary"
class="login-btn"
:loading="loading"
@click="handleSubmit"
>
登录
</el-button>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { login } from '@/api/account'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
tenantCode: '',
username: '',
password: '',
})
const rules: FormRules = {
tenantCode: [{ required: true, message: '请输入租户编码', trigger: 'blur' }],
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
const res: any = await login(form)
const userInfo = res.data?.userInfo || res.data
authStore.setAuth(
res.data?.token || '',
userInfo.tenantCode,
userInfo.username,
userInfo.roleCodes || []
)
ElMessage.success('登录成功')
router.push('/dashboard')
} catch (err: any) {
ElMessage.error(err?.message || '登录失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-page {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
}
.login-card {
width: 400px;
}
.login-title {
text-align: center;
font-size: 18px;
font-weight: bold;
}
.login-btn {
width: 100%;
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<h2>消息测试</h2>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<h2>租户管理</h2>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<h2>用户管理</h2>
</div>
</template>