commit 86805d882e547b8204f4dcb1491bd4d74fbc4c3c Author: Allen Date: Tue Apr 29 18:36:29 2025 +0800 完成登录功能 diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2d7aa60 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/electron", + "plugin:import/typescript" + ], + "parser": "@typescript-eslint/parser" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8296128 --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock +.DS_Store + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Webpack +.webpack/ + +# Vite +.vite/ + +# Electron-Forge +out/ diff --git a/forge.config.ts b/forge.config.ts new file mode 100644 index 0000000..42181d2 --- /dev/null +++ b/forge.config.ts @@ -0,0 +1,54 @@ +import type { ForgeConfig } from '@electron-forge/shared-types'; +import { MakerSquirrel } from '@electron-forge/maker-squirrel'; +import { MakerZIP } from '@electron-forge/maker-zip'; +import { MakerDeb } from '@electron-forge/maker-deb'; +import { MakerRpm } from '@electron-forge/maker-rpm'; +import { VitePlugin } from '@electron-forge/plugin-vite'; +import { FusesPlugin } from '@electron-forge/plugin-fuses'; +import { FuseV1Options, FuseVersion } from '@electron/fuses'; + +const config: ForgeConfig = { + packagerConfig: { + asar: true, + }, + rebuildConfig: {}, + makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], + plugins: [ + new VitePlugin({ + // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. + // If you are familiar with Vite configuration, it will look really familiar. + build: [ + { + // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. + entry: 'src/main.ts', + config: 'vite.main.config.ts', + target: 'main', + }, + { + entry: 'src/preload.ts', + config: 'vite.preload.config.ts', + target: 'preload', + }, + ], + renderer: [ + { + name: 'main_window', + config: 'vite.renderer.config.ts', + }, + ], + }), + // Fuses are used to enable/disable various Electron functionality + // at package time, before code signing the application + new FusesPlugin({ + version: FuseVersion.V1, + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + }), + ], +}; + +export default config; diff --git a/forge.env.d.ts b/forge.env.d.ts new file mode 100644 index 0000000..9700e0a --- /dev/null +++ b/forge.env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..94e8cdf --- /dev/null +++ b/index.html @@ -0,0 +1,52 @@ + + + + + + + 中国电信桌面应用 + + + +
+
+
+

中国电信桌面应用

+
+
+ + +
+
+ +
+ + +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/login.html b/login.html new file mode 100644 index 0000000..8127acc --- /dev/null +++ b/login.html @@ -0,0 +1,26 @@ + + + + + + 登录 + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d011d15 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "china-telecom-app", + "productName": "china-telecom-app", + "version": "1.0.0", + "description": "My Electron application description", + "main": ".vite/build/main.js", + "scripts": { + "start": "electron-forge start", + "package": "electron-forge package", + "make": "electron-forge make", + "publish": "electron-forge publish", + "lint": "eslint --ext .ts,.tsx ." + }, + "keywords": [], + "author": { + "name": "Allen", + "email": "caizz520@gmail.com" + }, + "license": "MIT", + "devDependencies": { + "@electron-forge/cli": "^7.8.0", + "@electron-forge/maker-deb": "^7.8.0", + "@electron-forge/maker-rpm": "^7.8.0", + "@electron-forge/maker-squirrel": "^7.8.0", + "@electron-forge/maker-zip": "^7.8.0", + "@electron-forge/plugin-auto-unpack-natives": "^7.8.0", + "@electron-forge/plugin-fuses": "^7.8.0", + "@electron-forge/plugin-vite": "^7.8.0", + "@electron/fuses": "^1.8.0", + "@types/electron-squirrel-startup": "^1.0.2", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "electron": "36.0.0", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.31.0", + "ts-node": "^10.9.2", + "typescript": "~4.5.4", + "vite": "^5.4.18" + }, + "dependencies": { + "electron-squirrel-startup": "^1.0.1" + } +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..77f969e --- /dev/null +++ b/src/index.css @@ -0,0 +1,117 @@ +/* 全局样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Microsoft YaHei', sans-serif; + background-color: #f0f2f5; + color: #333; +} + +/* 应用容器 */ +.app-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* 头部样式 */ +.app-header { + background-color: #1890ff; + color: white; + padding: 0 20px; + height: 60px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.header-left h1 { + font-size: 20px; + font-weight: 500; +} + +.header-right { + display: flex; + align-items: center; + gap: 20px; +} + +#userInfo { + font-size: 14px; +} + +.logout-btn { + background-color: transparent; + border: 1px solid white; + color: white; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s; +} + +.logout-btn:hover { + background-color: white; + color: #1890ff; +} + +/* 主内容区域 */ +.app-main { + display: flex; + flex: 1; + overflow: hidden; +} + +/* 侧边栏样式 */ +.sidebar { + width: 200px; + background-color: white; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); + padding: 20px 0; +} + +.menu-list { + list-style: none; +} + +.menu-item { + padding: 12px 20px; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + transition: all 0.3s; +} + +.menu-item:hover { + background-color: #e6f7ff; +} + +.menu-item.active { + background-color: #e6f7ff; + color: #1890ff; + border-right: 3px solid #1890ff; +} + +.menu-item .icon { + font-size: 18px; +} + +/* 内容区域 */ +.content-area { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.page-content { + background-color: white; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..367b4bb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,100 @@ +console.log('👋 This message is being logged by "index.ts", included via Vite'); + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and import them here. + +// 检查登录状态 +function checkLoginStatus() { + const auth = document.cookie.split('; ').find(row => row.startsWith('EACToken=') || row.startsWith('MssSsoToken=')); + if (!auth) { + window.location.href = 'login.html'; + return; + } + + // 显示用户信息 + const userInfo = document.getElementById('userInfo'); + if (userInfo) { + userInfo.textContent = '欢迎使用'; + } +} + +// 处理退出登录 +function handleLogout() { + document.cookie = 'expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + window.location.href = 'login.html'; +} + +// 处理菜单切换 +function handleMenuClick(event: Event) { + const target = event.target as HTMLElement; + const menuItem = target.closest('.menu-item'); + + if (menuItem) { + // 移除所有菜单项的active类 + document.querySelectorAll('.menu-item').forEach(item => { + item.classList.remove('active'); + }); + + // 添加active类到当前点击的菜单项 + menuItem.classList.add('active'); + + // 获取页面标识 + const page = menuItem.getAttribute('data-page'); + if (page) { + loadPageContent(page); + } + } +} + +// 加载页面内容 +async function loadPageContent(page: string) { + const contentArea = document.getElementById('pageContent'); + if (!contentArea) return; + + try { + // 这里可以根据不同的页面加载不同的内容 + switch (page) { + case 'dashboard': + contentArea.innerHTML = '

仪表盘

欢迎使用中国电信桌面应用

'; + break; + case 'tasks': + contentArea.innerHTML = '

任务管理

任务管理功能开发中...

'; + break; + case 'reports': + contentArea.innerHTML = '

报表统计

报表统计功能开发中...

'; + break; + case 'settings': + contentArea.innerHTML = '

系统设置

系统设置功能开发中...

'; + break; + default: + contentArea.innerHTML = '

页面不存在

'; + } + } catch (error) { + console.error('加载页面内容失败:', error); + contentArea.innerHTML = '

加载失败

请稍后重试

'; + } +} + +// 初始化 +function initialize() { + // 检查登录状态 + checkLoginStatus(); + + // 绑定退出登录事件 + const logoutBtn = document.getElementById('btnLogout'); + if (logoutBtn) { + logoutBtn.addEventListener('click', handleLogout); + } + + // 绑定菜单点击事件 + const menuList = document.querySelector('.menu-list'); + if (menuList) { + menuList.addEventListener('click', handleMenuClick); + } + + // 加载默认页面(仪表盘) + loadPageContent('dashboard'); +} + +// 当DOM加载完成后初始化 +document.addEventListener('DOMContentLoaded', initialize); diff --git a/src/login.css b/src/login.css new file mode 100644 index 0000000..045e89a --- /dev/null +++ b/src/login.css @@ -0,0 +1,63 @@ +body { + font-family: 'Microsoft YaHei', sans-serif; + background-color: #f5f5f5; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; +} + +.login-container { + background-color: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + width: 320px; +} + +.login-title { + text-align: center; + margin-bottom: 30px; + color: #333; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + color: #666; +} + +.form-group input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; +} + +.login-btn { + width: 100%; + padding: 12px; + background-color: #1890ff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +.login-btn:hover { + background-color: #40a9ff; +} + +.error-message { + color: #ff4d4f; + margin-top: 10px; + text-align: center; + display: none; +} \ No newline at end of file diff --git a/src/login.ts b/src/login.ts new file mode 100644 index 0000000..6613491 --- /dev/null +++ b/src/login.ts @@ -0,0 +1,86 @@ +// 登录请求参数 +interface LoginRequest { + Account: string; + Password: string; +} + +// 登录响应数据 +interface LoginResponse { + IsNeedSendSMS: boolean; + Token: string; + Cookies: Array<{ Key: string, Value: string }>; +} + +// API 响应类型 +interface ApiResponse { + status: number; + code: number; + msg: string; + data: T; +} + + +// 设置Cookie的示例 +const setCookies = async (cookieString: string) => { + try { + await window.electronAPI.setCookie(cookieString); + console.log('Cookies设置成功'); + } catch (error) { + console.error('设置Cookies失败:', error); + } +}; + +// 使用示例 +// setCookies('key1=value1; key2=value2; key3=value3'); + +// 登录处理函数 +async function handleLogin(account: string, password: string): Promise { + const errorMessage = document.getElementById('errorMessage') as HTMLDivElement; + + if (!account || !password) { + errorMessage.style.display = 'block'; + errorMessage.textContent = '请输入账号和密码'; + return; + } + + try { + const response = await fetch('http://1.12.73.211:8848/EIAC_Desktop_Api/api/Auth/Login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + body: JSON.stringify({ + Account: account, + Password: password + } as LoginRequest) + }); + + const rspBody = await response.json() as ApiResponse; + + if (rspBody.status !== 0) { + errorMessage.style.display = 'block'; + errorMessage.textContent = rspBody.msg || '登录失败'; + } else { + // 登录成功,将data.Cookies拼接为字符串,存储cookie + const cookies = rspBody.data.Cookies.map(cookie => `${cookie.Key.trim()}=${cookie.Value.trim()}`).join('; '); + await setCookies(cookies); + // 跳转到主页面 + window.location.href = 'index.html'; + } + } catch (error) { + errorMessage.style.display = 'block'; + errorMessage.textContent = '网络错误,请稍后重试'; + } +} + +// 页面加载完成后绑定事件 +document.addEventListener('DOMContentLoaded', () => { + const loginButton = document.getElementById('btnLogin'); + if (loginButton) { + loginButton.addEventListener('click', () => { + const account = (document.getElementById('txtAccount') as HTMLInputElement).value; + const password = (document.getElementById('txtPassword') as HTMLInputElement).value; + handleLogin(account, password); + }); + } +}); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..8c0fa7a --- /dev/null +++ b/src/main.ts @@ -0,0 +1,79 @@ +import { app, BrowserWindow, ipcMain } from 'electron'; +import path from 'node:path'; +import started from 'electron-squirrel-startup'; + +// Handle creating/removing shortcuts on Windows when installing/uninstalling. +if (started) { + app.quit(); +} + +// 处理设置Cookie的IPC请求 +ipcMain.handle('set-cookie', async (event, cookie) => { + try { + await event.sender.session.cookies.set(cookie); + return true; + } catch (error) { + console.error('设置Cookie失败:', error); + return false; + } +}); + +const createWindow = () => { + // Create the browser window. + const win = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + contextIsolation: true, // 启用上下文隔离。 + nodeIntegration: false, // 禁用 Node.js 集成,提高安全性。 + preload: path.join(__dirname, 'preload.js'), // 预加载脚本 + webviewTag: true, // 启用webview标签 + webSecurity: false, + allowRunningInsecureContent: true, + sandbox: false + }, + }); + + // 设置session + win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => { + callback(true); + }); + + win.setMenuBarVisibility(false); // 隐藏菜单栏 + win.setAutoHideMenuBar(true); // 自动隐藏菜单栏 + win.setMenu(null); // 隐藏菜单栏 + + // and load the index.html of the app. + if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { + win.loadURL(`${MAIN_WINDOW_VITE_DEV_SERVER_URL}/login.html`); + } else { + win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/login.html`)); + } + + // Open the DevTools. + win.webContents.openDevTools(); +}; + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow); + +// 在非 macOS 平台上,关闭所有窗口时退出应用 +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +// 在 macOS 平台上,点击 dock 图标时重新创建窗口 +app.on('activate', () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); \ No newline at end of file diff --git a/src/preload.ts b/src/preload.ts new file mode 100644 index 0000000..ba39303 --- /dev/null +++ b/src/preload.ts @@ -0,0 +1,37 @@ +// See the Electron documentation for details on how to use preload scripts: +// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts + +import { contextBridge, ipcRenderer } from 'electron'; + +contextBridge.exposeInMainWorld('electronAPI', { + setCookie: async (cookieString: string) => { + const cookies = cookieString.split(';').map(cookie => { + const [name, value] = cookie.trim().split('='); + return { + name: name.trim(), + value: value.trim(), + path: '/', + secure: false, + httpOnly: false, + expirationDate: Math.floor(Date.now() / 1000) + 86400 // 24小时后过期 + }; + }); + + // 为每个域名设置Cookie + const domains = ['localhost', '1.12.73.211']; + const ports = ['', ':8848']; + + for (const cookie of cookies) { + for (const domain of domains) { + for (const port of ports) { + const url = `http://${domain}${port}`; + await ipcRenderer.invoke('set-cookie', { + ...cookie, + url, + domain + }); + } + } + } + } +}); diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts new file mode 100644 index 0000000..2f6382a --- /dev/null +++ b/src/types/electron.d.ts @@ -0,0 +1,9 @@ +export interface ElectronAPI { + setCookie: (cookieString: string) => Promise; +} + +declare global { + interface Window { + electronAPI: ElectronAPI; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..04afc51 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "commonjs", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "noImplicitAny": true, + "sourceMap": true, + "baseUrl": ".", + "outDir": "dist", + "moduleResolution": "node", + "resolveJsonModule": true, + "removeComments": true + } +} diff --git a/vite.main.config.ts b/vite.main.config.ts new file mode 100644 index 0000000..690be5b --- /dev/null +++ b/vite.main.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config +export default defineConfig({}); diff --git a/vite.preload.config.ts b/vite.preload.config.ts new file mode 100644 index 0000000..690be5b --- /dev/null +++ b/vite.preload.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config +export default defineConfig({}); diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts new file mode 100644 index 0000000..959f6c6 --- /dev/null +++ b/vite.renderer.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config +export default defineConfig({ + build: { + rollupOptions: { + input: { + login: 'login.html', + index: 'index.html' + } + } + } +});