index.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <template>
  2. <div class="login-container">
  3. <!-- 顶部可拖动标题栏 -->
  4. <div class="drag-region">
  5. <div class="window-controls">
  6. <button class="window-btn minimize" @click="handleMinimize" title="最小化">
  7. <svg viewBox="0 0 12 12"><rect y="5" width="12" height="2" fill="currentColor"/></svg>
  8. </button>
  9. <button class="window-btn maximize" @click="handleMaximize" :title="isMaximized ? '还原' : '最大化'">
  10. <svg v-if="isMaximized" viewBox="0 0 12 12">
  11. <rect x="1.5" y="3" width="7" height="7" stroke="currentColor" stroke-width="1.5" fill="none"/>
  12. <path d="M3.5 3V1.5H11V9H9.5" stroke="currentColor" stroke-width="1.5" fill="none"/>
  13. </svg>
  14. <svg v-else viewBox="0 0 12 12">
  15. <rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="1.5" fill="none"/>
  16. </svg>
  17. </button>
  18. <button class="window-btn close" @click="handleClose" title="关闭">
  19. <svg viewBox="0 0 12 12">
  20. <path d="M1 1L11 11M1 11L11 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
  21. </svg>
  22. </button>
  23. </div>
  24. </div>
  25. <!-- 背景装饰 -->
  26. <div class="bg-decoration">
  27. <div class="circle circle-1"></div>
  28. <div class="circle circle-2"></div>
  29. <div class="circle circle-3"></div>
  30. </div>
  31. <div class="login-card">
  32. <div class="login-header">
  33. <div class="logo">
  34. <el-icon><VideoPlay /></el-icon>
  35. </div>
  36. <h1>多平台媒体管理系统</h1>
  37. <p>登录您的账号以继续</p>
  38. </div>
  39. <el-form
  40. ref="formRef"
  41. :model="form"
  42. :rules="rules"
  43. class="login-form"
  44. @submit.prevent="handleLogin"
  45. >
  46. <el-form-item prop="username">
  47. <el-input
  48. v-model="form.username"
  49. placeholder="用户名或邮箱"
  50. size="large"
  51. :prefix-icon="User"
  52. />
  53. </el-form-item>
  54. <el-form-item prop="password">
  55. <el-input
  56. v-model="form.password"
  57. type="password"
  58. placeholder="密码"
  59. size="large"
  60. :prefix-icon="Lock"
  61. show-password
  62. />
  63. </el-form-item>
  64. <el-form-item class="remember-row">
  65. <el-checkbox v-model="form.rememberMe">记住登录状态</el-checkbox>
  66. </el-form-item>
  67. <el-form-item>
  68. <el-button
  69. type="primary"
  70. size="large"
  71. :loading="loading"
  72. class="login-btn"
  73. native-type="submit"
  74. >
  75. 登录
  76. </el-button>
  77. </el-form-item>
  78. </el-form>
  79. <div class="login-footer">
  80. <span>还没有账号?</span>
  81. <el-button type="primary" link @click="$router.push('/register')">
  82. 立即注册
  83. </el-button>
  84. </div>
  85. <div class="server-info">
  86. <el-icon><Link /></el-icon>
  87. <span>{{ serverStore.currentServer?.name || '未配置服务器' }}</span>
  88. <el-button type="primary" link size="small" @click="$router.push('/server-config')">
  89. 配置
  90. </el-button>
  91. </div>
  92. </div>
  93. </div>
  94. </template>
  95. <script setup lang="ts">
  96. import { ref, reactive, onMounted } from 'vue';
  97. import { useRouter } from 'vue-router';
  98. import { User, Lock, Link, VideoPlay } from '@element-plus/icons-vue';
  99. import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
  100. import { useAuthStore } from '@/stores/auth';
  101. import { useServerStore } from '@/stores/server';
  102. const router = useRouter();
  103. const authStore = useAuthStore();
  104. const serverStore = useServerStore();
  105. const formRef = ref<FormInstance>();
  106. const loading = ref(false);
  107. const isMaximized = ref(false);
  108. // 窗口控制
  109. function handleMinimize() {
  110. window.electronAPI?.minimizeWindow?.();
  111. }
  112. function handleMaximize() {
  113. window.electronAPI?.maximizeWindow?.();
  114. }
  115. function handleClose() {
  116. window.electronAPI?.closeWindow?.();
  117. }
  118. // 监听窗口最大化状态
  119. onMounted(async () => {
  120. if (window.electronAPI?.isMaximized) {
  121. isMaximized.value = await window.electronAPI.isMaximized();
  122. }
  123. window.electronAPI?.onMaximizedChange?.((maximized: boolean) => {
  124. isMaximized.value = maximized;
  125. });
  126. });
  127. const form = reactive({
  128. username: '',
  129. password: '',
  130. rememberMe: true,
  131. });
  132. const rules: FormRules = {
  133. username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  134. password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
  135. };
  136. async function handleLogin() {
  137. if (!formRef.value) return;
  138. const valid = await formRef.value.validate().catch(() => false);
  139. if (!valid) return;
  140. loading.value = true;
  141. try {
  142. await authStore.login({
  143. username: form.username,
  144. password: form.password,
  145. rememberMe: form.rememberMe,
  146. });
  147. ElMessage.success('登录成功');
  148. router.push('/');
  149. } catch (error: any) {
  150. // 错误已在拦截器中处理
  151. } finally {
  152. loading.value = false;
  153. }
  154. }
  155. </script>
  156. <style lang="scss" scoped>
  157. @use '@/styles/variables.scss' as *;
  158. .login-container {
  159. min-height: 100vh;
  160. display: flex;
  161. align-items: center;
  162. justify-content: center;
  163. background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
  164. position: relative;
  165. overflow: hidden;
  166. }
  167. // 顶部可拖动区域
  168. .drag-region {
  169. position: fixed;
  170. top: 0;
  171. left: 0;
  172. right: 0;
  173. height: 32px;
  174. -webkit-app-region: drag;
  175. z-index: 999;
  176. }
  177. // 窗口控制按钮
  178. .window-controls {
  179. position: absolute;
  180. top: 0;
  181. right: 0;
  182. display: flex;
  183. -webkit-app-region: no-drag;
  184. .window-btn {
  185. width: 46px;
  186. height: 32px;
  187. border: none;
  188. background: transparent;
  189. display: flex;
  190. align-items: center;
  191. justify-content: center;
  192. cursor: pointer;
  193. transition: background 0.15s;
  194. color: $text-secondary;
  195. svg {
  196. width: 12px;
  197. height: 12px;
  198. }
  199. &:hover {
  200. background: rgba(0, 0, 0, 0.06);
  201. }
  202. &.close:hover {
  203. background: #e81123;
  204. color: #fff;
  205. }
  206. }
  207. }
  208. // 背景装饰
  209. .bg-decoration {
  210. position: absolute;
  211. inset: 0;
  212. overflow: hidden;
  213. pointer-events: none;
  214. .circle {
  215. position: absolute;
  216. border-radius: 50%;
  217. opacity: 0.5;
  218. }
  219. .circle-1 {
  220. width: 400px;
  221. height: 400px;
  222. background: linear-gradient(135deg, rgba(79, 140, 255, 0.2), rgba(79, 140, 255, 0.05));
  223. top: -100px;
  224. right: -100px;
  225. }
  226. .circle-2 {
  227. width: 300px;
  228. height: 300px;
  229. background: linear-gradient(135deg, rgba(250, 112, 154, 0.15), rgba(254, 225, 64, 0.1));
  230. bottom: -80px;
  231. left: -80px;
  232. }
  233. .circle-3 {
  234. width: 200px;
  235. height: 200px;
  236. background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.1));
  237. top: 50%;
  238. left: 10%;
  239. transform: translateY(-50%);
  240. }
  241. }
  242. .login-card {
  243. width: 420px;
  244. padding: 48px 40px;
  245. background: #fff;
  246. border-radius: $radius-xl;
  247. box-shadow: $shadow-lg;
  248. position: relative;
  249. z-index: 1;
  250. border: 1px solid rgba(255, 255, 255, 0.8);
  251. }
  252. .login-header {
  253. text-align: center;
  254. margin-bottom: 36px;
  255. .logo {
  256. width: 64px;
  257. height: 64px;
  258. margin: 0 auto 20px;
  259. border-radius: $radius-lg;
  260. background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  261. display: flex;
  262. align-items: center;
  263. justify-content: center;
  264. box-shadow: 0 8px 24px rgba(79, 172, 254, 0.3);
  265. .el-icon {
  266. font-size: 32px;
  267. color: #fff;
  268. }
  269. }
  270. h1 {
  271. margin: 0 0 12px;
  272. font-size: 24px;
  273. font-weight: 700;
  274. color: $text-primary;
  275. }
  276. p {
  277. margin: 0;
  278. color: $text-secondary;
  279. font-size: 14px;
  280. }
  281. }
  282. .login-form {
  283. :deep(.el-input__wrapper) {
  284. border-radius: $radius-base;
  285. box-shadow: 0 0 0 1px $border-light inset;
  286. transition: all 0.2s;
  287. &:hover {
  288. box-shadow: 0 0 0 1px $primary-color inset;
  289. }
  290. &.is-focus {
  291. box-shadow: 0 0 0 1px $primary-color inset, 0 0 0 3px rgba($primary-color, 0.1);
  292. }
  293. }
  294. :deep(.el-input__inner) {
  295. height: 44px;
  296. font-size: 15px;
  297. }
  298. :deep(.el-input__prefix) {
  299. color: $text-secondary;
  300. }
  301. .remember-row {
  302. margin-bottom: 24px;
  303. :deep(.el-checkbox__label) {
  304. color: $text-secondary;
  305. }
  306. }
  307. .login-btn {
  308. width: 100%;
  309. height: 48px;
  310. font-size: 16px;
  311. font-weight: 600;
  312. border-radius: $radius-base;
  313. background: linear-gradient(135deg, $primary-color 0%, #3a7bd5 100%);
  314. border: none;
  315. box-shadow: 0 4px 16px rgba($primary-color, 0.3);
  316. transition: all 0.3s;
  317. &:hover {
  318. transform: translateY(-1px);
  319. box-shadow: 0 6px 20px rgba($primary-color, 0.4);
  320. }
  321. &:active {
  322. transform: translateY(0);
  323. }
  324. }
  325. }
  326. .login-footer {
  327. text-align: center;
  328. color: $text-secondary;
  329. font-size: 14px;
  330. margin-top: 8px;
  331. .el-button {
  332. font-weight: 500;
  333. }
  334. }
  335. .server-info {
  336. margin-top: 28px;
  337. padding-top: 24px;
  338. border-top: 1px solid $border-light;
  339. display: flex;
  340. align-items: center;
  341. justify-content: center;
  342. gap: 8px;
  343. color: $text-secondary;
  344. font-size: 13px;
  345. .el-icon {
  346. color: $primary-color;
  347. }
  348. }
  349. </style>