From 86805d882e547b8204f4dcb1491bd4d74fbc4c3c Mon Sep 17 00:00:00 2001 From: Allen Date: Tue, 29 Apr 2025 18:36:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=99=BB=E5=BD=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 16 ++++++ .gitignore | 92 +++++++++++++++++++++++++++++++ forge.config.ts | 54 +++++++++++++++++++ forge.env.d.ts | 1 + index.html | 52 ++++++++++++++++++ login.html | 26 +++++++++ package.json | 43 +++++++++++++++ src/index.css | 117 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 100 ++++++++++++++++++++++++++++++++++ src/login.css | 63 ++++++++++++++++++++++ src/login.ts | 86 +++++++++++++++++++++++++++++ src/main.ts | 79 +++++++++++++++++++++++++++ src/preload.ts | 37 +++++++++++++ src/types/electron.d.ts | 9 ++++ tsconfig.json | 16 ++++++ vite.main.config.ts | 4 ++ vite.preload.config.ts | 4 ++ vite.renderer.config.ts | 13 +++++ 18 files changed, 812 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 forge.config.ts create mode 100644 forge.env.d.ts create mode 100644 index.html create mode 100644 login.html create mode 100644 package.json create mode 100644 src/index.css create mode 100644 src/index.ts create mode 100644 src/login.css create mode 100644 src/login.ts create mode 100644 src/main.ts create mode 100644 src/preload.ts create mode 100644 src/types/electron.d.ts create mode 100644 tsconfig.json create mode 100644 vite.main.config.ts create mode 100644 vite.preload.config.ts create mode 100644 vite.renderer.config.ts 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' + } + } + } +});