Merge pull request '初始化前端窗口组件框架' (#11) from feature-nxdev into main
Reviewed-on: #11
This commit is contained in:
commit
eb9733b4b0
6
backend/IM_API/Interface/Services/IAuthService.cs
Normal file
6
backend/IM_API/Interface/Services/IAuthService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
public interface IAuthService
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,17 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>You did it!</h1>
|
||||
<p>
|
||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
<div id="app">
|
||||
<WindowLayout>
|
||||
|
||||
<RouterView></RouterView>
|
||||
</WindowLayout>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import WindowLayout from './components/Layout.vue'
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
262
frontend/web/src/components/Layout.vue
Normal file
262
frontend/web/src/components/Layout.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="window-container">
|
||||
<div class="window">
|
||||
<!-- Windows 风格标题栏 -->
|
||||
<div class="window-header">
|
||||
<div class="window-title-area">
|
||||
<div class="window-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 1.5H1.5V6.5H6.5V1.5Z" fill="currentColor"/>
|
||||
<path d="M14.5 1.5H9.5V6.5H14.5V1.5Z" fill="currentColor"/>
|
||||
<path d="M6.5 9.5H1.5V14.5H6.5V9.5Z" fill="currentColor"/>
|
||||
<path d="M14.5 9.5H9.5V14.5H14.5V9.5Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="window-title">我的窗口</div>
|
||||
</div>
|
||||
<div class="window-controls">
|
||||
<button class="control-btn minimize" @click="minimize" title="最小化">
|
||||
<span class="control-icon"></span>
|
||||
</button>
|
||||
<button class="control-btn maximize" @click="maximize" title="最大化">
|
||||
<span class="control-icon"></span>
|
||||
</button>
|
||||
<button class="control-btn close" @click="close" title="关闭">
|
||||
<span class="control-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 窗口内容 -->
|
||||
<div class="window-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
function minimize() {
|
||||
console.log('最小化')
|
||||
}
|
||||
|
||||
function maximize() {
|
||||
console.log('最大化/还原')
|
||||
}
|
||||
|
||||
function close() {
|
||||
console.log('关闭窗口')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 外层容器,居中 - 保持不变 */
|
||||
.window-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f0f0f0;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 窗口主体 - 保持不变 */
|
||||
.window {
|
||||
width: 800px;
|
||||
height: 550px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 窗口顶部栏 - 优化按钮样式 */
|
||||
.window-header {
|
||||
height: 42px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
|
||||
color: #fff;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: 600;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 添加顶栏的微光效果 */
|
||||
.window-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.4) 20%,
|
||||
rgba(255, 255, 255, 0.7) 50%,
|
||||
rgba(255, 255, 255, 0.4) 80%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
/* 标题区域 */
|
||||
.window-title-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 窗口图标 */
|
||||
.window-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 窗口标题 */
|
||||
.window-title {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.3px;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 标题栏按钮容器 - 增加间距 */
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 控制按钮通用样式 - 优化尺寸和交互 */
|
||||
.control-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.control-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0);
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.control-btn:hover::before {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 控制按钮图标 - 使用CSS绘制精致图标 */
|
||||
.control-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 最小化按钮图标 */
|
||||
.minimize .control-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.minimize .control-icon::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
border-radius: 1px;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
/* 最大化按钮图标 */
|
||||
.maximize .control-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.maximize .control-icon::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1.5px solid currentColor;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 关闭按钮图标 */
|
||||
.close .control-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close .control-icon::before,
|
||||
.close .control-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 1.5px;
|
||||
background: currentColor;
|
||||
border-radius: 1px;
|
||||
top: 4px;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.close .control-icon::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.close .control-icon::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
/* 关闭按钮特殊样式 */
|
||||
.close:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.close:hover::before {
|
||||
background: rgba(232, 17, 35, 0.9);
|
||||
}
|
||||
|
||||
/* 窗口内容 - 保持不变 */
|
||||
.window-content {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
background-color: #fdfdfd;
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,10 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [{ path: '/auth/login', component: () => import('@/views/auth/Login.vue') }]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [],
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
386
frontend/web/src/views/auth/Login.vue
Normal file
386
frontend/web/src/views/auth/Login.vue
Normal file
@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<div class="login-content">
|
||||
<div class="login-header">
|
||||
<h2 class="login-title">欢迎登录</h2>
|
||||
<p class="login-subtitle">请输入您的账号信息</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="login-form">
|
||||
<div class="input-group">
|
||||
<div class="input-container">
|
||||
<input id="username" type="text" v-model="username" placeholder=" " required>
|
||||
<label for="username" class="floating-label">用户名</label>
|
||||
<div class="input-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 21V19C20 16.7909 18.2091 15 16 15H8C5.79086 15 4 16.7909 4 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-container">
|
||||
<input id="password" type="password" v-model="password" placeholder=" " required>
|
||||
<label for="password" class="floating-label">密码</label>
|
||||
<div class="input-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-row">
|
||||
<label class="remember-me">
|
||||
<input type="checkbox">
|
||||
<span class="checkmark"></span>
|
||||
记住我
|
||||
</label>
|
||||
<a href="#" class="forgot-link">忘记密码?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="login-btn">
|
||||
<span>登录</span>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<p v-if="errorMsg" class="error">{{ errorMsg }}</p>
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>还没有账号? <a href="#" class="register-link">立即注册</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const errorMsg = ref('')
|
||||
|
||||
const handleLogin = () => {
|
||||
if (!username.value || !password.value) {
|
||||
errorMsg.value = '用户名或密码不能为空'
|
||||
return
|
||||
}
|
||||
|
||||
if (username.value === 'admin' && password.value === '123456') {
|
||||
alert('登录成功!')
|
||||
errorMsg.value = ''
|
||||
// TODO: 跳转到IM主界面
|
||||
} else {
|
||||
errorMsg.value = '用户名或密码错误'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-content {
|
||||
padding: 40px 30px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 添加背景装饰元素 */
|
||||
.login-content::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -20%;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(37, 99, 235, 0.1) 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.login-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -30%;
|
||||
left: -10%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(37, 99, 235, 0.08) 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
max-width: 320px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 输入组 */
|
||||
.input-group {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.input-container input {
|
||||
width: 100%;
|
||||
padding: 16px 16px 16px 48px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.input-container input:focus {
|
||||
border-color: #3b82f6;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 2px 6px rgba(59, 130, 246, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.input-container input:focus + .floating-label,
|
||||
.input-container input:not(:placeholder-shown) + .floating-label {
|
||||
top: -8px;
|
||||
left: 48px;
|
||||
font-size: 12px;
|
||||
color: #3b82f6;
|
||||
background: linear-gradient(180deg, #f8fafc 50%, #ffffff 50%);
|
||||
padding: 0 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.floating-label {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 48px;
|
||||
font-size: 15px;
|
||||
color: #64748b;
|
||||
pointer-events: none;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #94a3b8;
|
||||
transition: color 0.3s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.input-container input:focus ~ .input-icon {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* 选项行 */
|
||||
.options-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 10px 0 5px;
|
||||
}
|
||||
|
||||
.remember-me {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.remember-me input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #cbd5e1;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.remember-me:hover .checkmark {
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
|
||||
.remember-me input:checked + .checkmark {
|
||||
background-color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.remember-me input:checked + .checkmark::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.forgot-link {
|
||||
font-size: 14px;
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.forgot-link:hover {
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 4px 6px rgba(59, 130, 246, 0.25);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
||||
box-shadow: 0 6px 12px rgba(59, 130, 246, 0.35);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.login-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
color: #ef4444;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
background-color: #fef2f2;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #ef4444;
|
||||
box-shadow: 0 2px 4px rgba(239, 68, 68, 0.1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 登录页脚 */
|
||||
.login-footer {
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: color 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.register-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: #3b82f6;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.register-link:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.register-link:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user