feat: 打包加固调整

This commit is contained in:
duanshuwen
2025-11-23 18:21:28 +08:00
parent 0b1e7a6679
commit 7eb2a13efe
7 changed files with 99 additions and 129 deletions

View File

@@ -2,7 +2,8 @@
"env": {
"browser": true,
"es6": true,
"node": true
"node": true,
"no-console": "off"
},
"extends": [
"eslint:recommended",

View File

@@ -1,85 +0,0 @@
## 页面需求
* 输入:账号、密码;按钮:登录
* 交互:输入校验(必填、长度/格式)、回车提交、禁用按钮(加载中/校验失败)、错误提示、明文/密文切换
* 布局:简约卡片居中、品牌标题、副文、响应式(桌面)、浅色渐变背景
## 现状与集成点
* 已存在页面骨架:`src/views/login/index.vue`(中心卡片+标题,待补表单)
* 路由:登录路由目前注释(`src/router/index.ts`),默认首页为 `/``BrowserLayout`(标签页界面)
## 实现步骤
1. 表单与校验
*`src/views/login/index.vue` 中添加账号/密码输入框与登录按钮
* 使用 `ref`/`reactive` 管理表单;规则:
* 账号:必填、长度 4-32、允许邮箱/手机号/用户名(简单格式校验)
* 密码:必填、长度 ≥ 6
* 按钮禁用条件:`!valid || loading`
* 支持 `Enter` 键触发提交
* 密码框添加“显示/隐藏”切换
1. 反馈与跳转
* 登录过程显示 `loading`(按钮文案切换为“登录中…”)
* 失败显示错误提示(红色文本);成功暂时跳转到 `/`(浏览器标签页界面)
* 留出接口占位:`login(account, password)`(后续接入真实 API
1. 样式
* Tailwind 构建简约风格:卡片圆角阴影、输入框边框、聚焦效果、按钮主色(蓝色)
* 响应式:移动端输入宽度 100%,桌面端 `max-w-md` 保持卡片紧凑
1. 路由接入
*`src/router/index.ts` 增加登录路由:`/login``views/login/index.vue`
* 暂不改默认首页;如需登录守卫,可后续将 `/login` 作为默认或在未认证时重定向到 `/login`
## 代码改动概览(不执行,仅说明)
* `src/views/login/index.vue`
* 模板:两个输入框(账号、密码)、显示/隐藏密码按钮、登录按钮、错误提示区域
* 逻辑:`form``errors``validate()``onSubmit()``loading``togglePassword()`;成功后:`router.push('/')`
* 样式Tailwind 类名添加于容器/卡片/输入/按钮
* `src/router/index.ts`
* 添加:
```ts
{ path: '/login', name: 'Login', component: () => import('@/views/login/index.vue') }
```
## 验证
* 输入为空时按钮禁用;填入合法后启用
* 按 `Enter` 能提交;失败提示文案显示;成功跳转到首页
* 移动端/桌面端居中显示良好
## 后续可选
* 接入真实认证 APIaxios保存 tokenPinia/LocalStorage路由守卫控制访问
* 增加“记住我”、忘记密码、国际化
请确认以上方案,确认后我将直接补全 `index.vue` 表单与交互,并注册 `/login` 路由。

View File

@@ -1,21 +1,23 @@
const fs = require('fs')
const { execSync } = require('child_process')
const fs = require('fs-extra')
const path = require('path')
function cleanDirectory(dirPath) {
const resolvedPath = path.resolve(dirPath)
function cleanOutRoot() {
const outDir = path.resolve(process.cwd(), 'out')
const parentDir = path.basename(path.dirname(resolvedPath))
if (parentDir !== 'out') {
console.error(`Error: Directory "${resolvedPath}" is not inside a "out" folder`)
if (!fs.pathExistsSync(outDir)) {
return
}
const distPath = path.join(resolvedPath, 'dist')
if (fs.existsSync(distPath)) {
fs.rmSync(distPath, { recursive: true, force: true })
console.log(`deleted: ${distPath}`)
fs.removeSync(outDir)
if (fs.pathExistsSync(outDir)) {
execSync(`cmd /c rd /s /q "${outDir}"`, { stdio: 'ignore' })
process.exitCode = 1
}
}
cleanDirectory(process.cwd())
cleanOutRoot()

View File

@@ -1,21 +1,23 @@
const fs = require('fs')
const path = require('path')
if (!fs.existsSync(path.join(process.cwd(), 'dist/main/build.jsc'))) {
throw new Error('字节码文件未找到,请先执行编译')
const entryPath = path.join(process.cwd(), '.vite/build/main.js')
const jscPath = path.join(process.cwd(), '.vite/build/main.jsc')
if (!fs.existsSync(entryPath)) {
throw new Error('主进程入口未找到,请先构建主进程')
}
if (!fs.existsSync(jscPath)) {
const bytenode = require('bytenode')
bytenode.compileFile({ filename: entryPath, output: jscPath })
}
const content = `
const {app} = require('electron');
require('bytenode');
require('./index.jsc);
`
const content = [
"require('bytenode')",
"require('./main.jsc')"
].join('\n')
const outputPath = path.join(process.cwd(), 'dist/main/build.js')
const outputPath = entryPath
// 确保目录存在
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
// 写入文件
fs.writeFileSync(outputPath, content.trim())
fs.writeFileSync(outputPath, content)
console.log(`生产环境入口文件已生成: ${outputPath}`)

View File

@@ -6,6 +6,8 @@ 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';
import * as fs from 'fs-extra';
import * as path from 'path';
const config: ForgeConfig = {
packagerConfig: {
@@ -54,6 +56,66 @@ const config: ForgeConfig = {
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
hooks: {
async prePackage() {
const outDir = path.resolve(process.cwd(), 'out');
fs.rmSync(outDir, { recursive: true, force: true });
},
async preMake() {
const outDir = path.resolve(process.cwd(), 'out');
fs.rmSync(outDir, { recursive: true, force: true });
},
async postPackage(_forgeConfig, options) {
const electronVersion = require('electron/package.json').version;
const nodeVersion = process.version;
const versionData = {
node: nodeVersion,
electron: electronVersion,
platform: process.platform,
arch: process.arch,
buildTime: new Date().toISOString(),
};
const outDir = path.resolve(process.cwd(), 'out');
fs.ensureDirSync(outDir);
const outputs = (options && (options as any).outputPaths) || [];
if (Array.isArray(outputs) && outputs.length > 0) {
for (const p of outputs) {
try {
const versionFile = path.join(p, 'version');
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
} catch {}
}
}
},
async postMake(_forgeConfig, outputs) {
const electronVersion = require('electron/package.json').version;
const nodeVersion = process.version;
const versionData = {
node: nodeVersion,
electron: electronVersion,
platform: process.platform,
arch: process.arch,
buildTime: new Date().toISOString(),
};
const outDir = path.resolve(process.cwd(), 'out');
fs.ensureDirSync(outDir);
if (Array.isArray(outputs) && outputs.length > 0) {
for (const p of outputs) {
try {
const versionFile = path.join(p as any, 'version');
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
} catch {}
}
}
},
},
};
export default config;

View File

@@ -14,19 +14,6 @@ export default function electronBytecode(options?: ElectronBytecodeOptions): Plu
apply: 'build',
async closeBundle() {
if (process.env.NODE_ENV !== 'production') return
const electronVersion = require('electron/package.json').version
const nodeVersion = process.version
const versionData = {
node: nodeVersion,
electron: electronVersion,
platform: process.platform,
arch: process.arch,
buildTime: new Date().toISOString()
}
const versionPath = path.resolve(process.cwd(), 'dist', 'version.json')
fs.ensureDirSync(path.dirname(versionPath))
fs.writeJsonSync(versionPath, versionData, { spaces: 2 })
const entryPath = path.resolve(process.cwd(), options?.entry || '.vite/build/main.js')
@@ -34,14 +21,13 @@ export default function electronBytecode(options?: ElectronBytecodeOptions): Plu
const outputPath = entryPath.replace(/\.js$/, '.jsc')
const backupPath = `${entryPath}.bak`
fs.copyFileSync(entryPath, backupPath)
await bytenode.compileFile({ filename: entryPath, output: outputPath })
if (!options?.keepSource) {
fs.removeSync(entryPath)
}
const stub = [
"require('bytenode')",
`require('./${path.basename(outputPath)}')`
].join("\n")
fs.writeFileSync(entryPath, stub)
}
}
}

View File

@@ -1,8 +1,10 @@
import { defineConfig } from "vite";
import { resolve } from "path";
import electronBytecode from "./src/plugins/bytenode/vite-plugin-electron-encrypt";
// https://vitejs.dev/config
export default defineConfig({
plugins: [electronBytecode({ entry: ".vite/build/main.js", keepSource: false })],
resolve: {
alias: {
"@": resolve(__dirname, "./src/electron"),