@@ -81,10 +99,33 @@ import { List, Clock, Tickets, - Document + Document, + User, + SwitchButton } from '@element-plus/icons-vue' +import { useUserStore } from '@/stores/modules/user' +import { ElMessageBox } from 'element-plus' const route = useRoute() +const router = useRouter() +const userStore = useUserStore() + +// 处理下拉菜单命令 +const handleCommand = async (command: string) => { + if (command === 'logout') { + try { + await ElMessageBox.confirm('确定要退出登录吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + await userStore.logout() + router.push('/login') + } catch { + // 用户取消 + } + } +} // 展开状态 const expandedMenus = ref(['/data-factory/process']) @@ -313,6 +354,9 @@ const breadcrumbList = computed(() => { padding: 16px 24px; border-bottom: 1px solid #e8e8e8; flex-shrink: 0; + display: flex; + justify-content: space-between; + align-items: center; :deep(.el-breadcrumb) { font-size: 14px; @@ -339,6 +383,36 @@ const breadcrumbList = computed(() => { } } +.header-actions { + .user-dropdown { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + color: #666; + font-size: 14px; + padding: 6px 12px; + border-radius: 6px; + transition: all 0.2s; + + &:hover { + background: #f5f5f5; + color: #409eff; + } + + .username { + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .arrow { + font-size: 12px; + } + } +} + .main-content { flex: 1; overflow: auto; diff --git a/src/router/index.ts b/src/router/index.ts index 9d0cc2a..91aa8d6 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -15,6 +15,15 @@ const routes: RouteRecordRaw[] = [ requiresAuth: false } }, + { + path: '/register', + name: 'Register', + component: () => import('@/views/register/index.vue'), + meta: { + title: '注册', + requiresAuth: false + } + }, ...dataRoutes ] @@ -24,7 +33,7 @@ const router = createRouter({ }) // 白名单路由(不需要登录) -const whiteList = ['/login'] +const whiteList = ['/login', '/register'] // 路由守卫 router.beforeEach((to, _from, next) => { diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts index 82a49a3..5c8a90b 100644 --- a/src/stores/modules/user.ts +++ b/src/stores/modules/user.ts @@ -1,118 +1,131 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { login as loginApi, logout as logoutApi, getUserInfo, exchangeDifyToken, LoginParams, UserInfo } from '@/api/auth' -import { removeDifyToken } from '@/micro-app/agents/request/service' +import {defineStore} from 'pinia' +import {ref, computed} from 'vue' +import {getUserInfo, exchangeDifyToken, UserInfo} from '@/api/auth' +import { + login as loginApi, + logout as logoutApi, + register as registerApi, + LoginParams, + RegisterParams +} from '@/api/user-auth' +import {removeDifyToken} from '@/micro-app/agents/request/service' export const useUserStore = defineStore('user', () => { - const token = ref(localStorage.getItem('Access-Token') || '') - const refreshToken = ref(localStorage.getItem('Refresh-Token') || '') - const userInfo = ref({ - userId: '', - userAccount: '', - userFullName: '', - userImageUrl: '', - deptId: '', - deptName: '', - orgId: '', - orgName: '', - roles: [] - }) + const token = ref(localStorage.getItem('Access-Token') || '') + const refreshToken = ref(localStorage.getItem('Refresh-Token') || '') + const userInfo = ref({ + userId: '', + userAccount: '', + userFullName: '', + userImageUrl: '', + deptId: '', + deptName: '', + orgId: '', + orgName: '', + roles: [] + }) - // 是否已登录 - const isLoggedIn = computed(() => !!token.value) + // 是否已登录 + const isLoggedIn = computed(() => !!token.value) - // 设置 token - const setToken = (accessToken: string, refToken?: string) => { - token.value = accessToken - localStorage.setItem('Access-Token', accessToken) - if (refToken) { - refreshToken.value = refToken - localStorage.setItem('Refresh-Token', refToken) + // 设置 token + const setToken = (accessToken: string, refToken?: string) => { + token.value = accessToken + localStorage.setItem('Access-Token', accessToken) + if (refToken) { + refreshToken.value = refToken + localStorage.setItem('Refresh-Token', refToken) + } } - } - // 设置用户信息 - const setUserInfo = (info: UserInfo) => { - userInfo.value = info - } + // 设置用户信息 + const setUserInfo = (info: UserInfo) => { + userInfo.value = info + } + + // 登录 + const login = async (params: LoginParams) => { + try { + const res = await loginApi(params) + setToken(res.access_token) - // 登录 - const login = async (params: LoginParams) => { - try { - const res = await loginApi(params) - setToken(res.access_token, res.refresh_token) + // 获取用户信息 + // await fetchUserInfo() - // 获取用户信息 - await fetchUserInfo() + // 交换 dify token (用于 agents 模块) + try { + const difyTokenRes = await exchangeDifyToken() + localStorage.setItem('dify_access_token', difyTokenRes.access_token) + localStorage.setItem('dify_refresh_token', difyTokenRes.refresh_token) + } catch (e) { + console.warn('交换 dify token 失败', e) + } - // 交换 dify token (用于 agents 模块) - try { - const difyTokenRes = await exchangeDifyToken() - localStorage.setItem('dify_access_token', difyTokenRes.access_token) - localStorage.setItem('dify_refresh_token', difyTokenRes.refresh_token) - } catch (e) { - console.warn('交换 dify token 失败', e) - } + return res + } catch (error) { + throw error + } + } - return res - } catch (error) { - throw error + // 注册 + const register = async (params: RegisterParams) => { + await registerApi(params) } - } - // 获取用户信息 - const fetchUserInfo = async () => { - try { - const info = await getUserInfo() - setUserInfo(info) - return info - } catch (error) { - throw error + // 获取用户信息 + const fetchUserInfo = async () => { + try { + const info = await getUserInfo() + setUserInfo(info) + return info + } catch (error) { + throw error + } } - } - // 登出 - const logout = async () => { - try { - await logoutApi() - } catch (e) { - console.warn('登出接口调用失败', e) - } finally { - clearAuth() + // 登出 + const logout = async () => { + try { + await logoutApi() + } catch (e) { + console.warn('登出接口调用失败', e) + } finally { + clearAuth() + } } - } - // 清除认证信息 - const clearAuth = () => { - token.value = '' - refreshToken.value = '' - userInfo.value = { - userId: '', - userAccount: '', - userFullName: '', - userImageUrl: '', - deptId: '', - deptName: '', - orgId: '', - orgName: '', - roles: [] + // 清除认证信息 + const clearAuth = () => { + token.value = '' + refreshToken.value = '' + userInfo.value = { + userId: '', + userAccount: '', + userFullName: '', + userImageUrl: '', + deptId: '', + deptName: '', + orgId: '', + orgName: '', + roles: [] + } + localStorage.removeItem('Access-Token') + localStorage.removeItem('Refresh-Token') + // 清除 dify token + removeDifyToken() } - localStorage.removeItem('Access-Token') - localStorage.removeItem('Refresh-Token') - // 清除 dify token - removeDifyToken() - } - return { - token, - refreshToken, - userInfo, - isLoggedIn, - setToken, - setUserInfo, - login, - logout, - fetchUserInfo, - clearAuth - } + return { + token, + refreshToken, + userInfo, + isLoggedIn, + setToken, + setUserInfo, + login, + register, + logout, + fetchUserInfo, + clearAuth + } }) diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 7ad554b..01c1971 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -12,9 +12,9 @@ class="login-form" size="large" > - + + + + @@ -65,12 +70,12 @@ const loading = ref(false) const rememberPassword = ref(true) const loginForm = reactive({ - account: '', + username: '', password: '' }) const loginRules: FormRules = { - account: [ + username: [ {required: true, message: '请输入用户名', trigger: 'blur'}, {min: 1, max: 50, message: '用户名长度不超过50字符', trigger: 'blur'} ], @@ -84,8 +89,8 @@ onMounted(() => { const savedCredentials = localStorage.getItem('login-credentials') if (savedCredentials) { try { - const {account, password} = JSON.parse(atob(savedCredentials)) - loginForm.account = account || '' + const {username, password} = JSON.parse(atob(savedCredentials)) + loginForm.username = username || '' loginForm.password = password || '' } catch (e) { console.error('Failed to parse saved credentials') @@ -101,19 +106,16 @@ const handleLogin = async () => { loading.value = true try { - // await userStore.login({ - // account: loginForm.account, - // password: loginForm.password - // }) - - // 临时设置 token - localStorage.setItem('Access-Token', 'eyJraWQiOiJmMGQ4YjkyZC0yZjIzLTQzNmUtOGIzMS1hZWQzOGEwZmY5MDYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbnVzZXJkZXB0aWQiLCJhdWQiOiJvYXV0aDJzZXJ2ZXIiLCJuYmYiOjE3NTYwODM3MjUsImNsaWVudElkIjoibGxtYWlwIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiaXNzIjoiaHR0cDpcL1wvYXV0aC1zZXJ2ZXI6ODA4NiIsImV4cCI6MTc1NjA4NzMyNSwibG9jYWxlIjoiemgtY24iLCJpYXQiOjE3NTYwODM3MjUsInVzZXJkZXB0SWQiOiJhZG1pbnVzZXJkZXB0aWQifQ.IGB4FZ1uYErluF0q7Owutw7GGqxVGCknSQNcOlgo1tmU-lLF0YVQ3eONX-iNQo4C4Um-q7FVkk5UZtuz190HJRIE7RenaFzYa4UjAGrGfgN7ZjjlXTHGy2T2Z_tKzrQlsF4i56SegXhKZGfHXGmBDTc9i3bhu9Frs-pWCahpP2Ehg7tP9q4pnzePr0wqIkMhgdFsq2sLBSwYzWwXHVVexgbySLbWCJlI0c-IdH0dYBHowMT_tVfUqqpg5IQysUokLspRjM_mxlIahMNRkiwvx8X4LjASGJMzWj0VsQJf-IBJKi13O-_f9gaR8Sq4eAar6i-e7X9BFoV9lRXNXp_2DA') + await userStore.login({ + username: loginForm.username, + password: loginForm.password + }) // 记住密码 if (rememberPassword.value) { localStorage.setItem( 'login-credentials', - btoa(JSON.stringify({account: loginForm.account, password: loginForm.password})) + btoa(JSON.stringify({username: loginForm.username, password: loginForm.password})) ) } else { localStorage.removeItem('login-credentials') @@ -136,7 +138,7 @@ const handleLogin = async () => { .login-container { width: 100vw; height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%); display: flex; align-items: center; justify-content: center; @@ -166,7 +168,7 @@ const handleLogin = async () => { color: #333; margin: 0; padding-bottom: 10px; - border-bottom: 3px solid #667eea; + border-bottom: 3px solid #1e3a5f; display: inline-block; } @@ -182,4 +184,20 @@ const handleLogin = async () => { font-size: 16px; border-radius: 8px; } + +.register-link { + width: 100%; + text-align: center; + color: #666; + + a { + color: #1e3a5f; + text-decoration: none; + font-weight: 500; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/views/register/index.vue b/src/views/register/index.vue new file mode 100644 index 0000000..b7e90cd --- /dev/null +++ b/src/views/register/index.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/vite.config.ts b/vite.config.ts index d9f106e..a2a720e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -58,6 +58,12 @@ export default defineConfig({ changeOrigin: true, rewrite: path => path.replace(/^\/api\/databuilder/, '') }, + // 数据工厂专用鉴权接口 + '/api/databuilder/v1/': { + target: 'http://192.168.8.131:8084', + changeOrigin: true, + rewrite: path => path.replace(/^\/api\/databuilder\/v1/, '') + }, // console API (dify agents 服务) '/api/console': { target: 'http://192.168.8.122:12800/console',