feat: 打包加固调整
This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true
|
"node": true,
|
||||||
|
"no-console": "off"
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
|||||||
@@ -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` 能提交;失败提示文案显示;成功跳转到首页
|
|
||||||
|
|
||||||
* 移动端/桌面端居中显示良好
|
|
||||||
|
|
||||||
## 后续可选
|
|
||||||
|
|
||||||
* 接入真实认证 API(axios),保存 token(Pinia/LocalStorage),路由守卫控制访问
|
|
||||||
|
|
||||||
* 增加“记住我”、忘记密码、国际化
|
|
||||||
|
|
||||||
请确认以上方案,确认后我将直接补全 `index.vue` 表单与交互,并注册 `/login` 路由。
|
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
const fs = require('fs')
|
const { execSync } = require('child_process')
|
||||||
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
function cleanDirectory(dirPath) {
|
function cleanOutRoot() {
|
||||||
const resolvedPath = path.resolve(dirPath)
|
const outDir = path.resolve(process.cwd(), 'out')
|
||||||
|
|
||||||
const parentDir = path.basename(path.dirname(resolvedPath))
|
if (!fs.pathExistsSync(outDir)) {
|
||||||
if (parentDir !== 'out') {
|
|
||||||
console.error(`Error: Directory "${resolvedPath}" is not inside a "out" folder`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const distPath = path.join(resolvedPath, 'dist')
|
|
||||||
|
|
||||||
if (fs.existsSync(distPath)) {
|
fs.removeSync(outDir)
|
||||||
fs.rmSync(distPath, { recursive: true, force: true })
|
|
||||||
console.log(`deleted: ${distPath}`)
|
if (fs.pathExistsSync(outDir)) {
|
||||||
|
execSync(`cmd /c rd /s /q "${outDir}"`, { stdio: 'ignore' })
|
||||||
|
|
||||||
|
process.exitCode = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanDirectory(process.cwd())
|
cleanOutRoot()
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(process.cwd(), 'dist/main/build.jsc'))) {
|
const entryPath = path.join(process.cwd(), '.vite/build/main.js')
|
||||||
throw new Error('字节码文件未找到,请先执行编译')
|
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 content = [
|
||||||
const {app} = require('electron');
|
"require('bytenode')",
|
||||||
require('bytenode');
|
"require('./main.jsc')"
|
||||||
require('./index.jsc);
|
].join('\n')
|
||||||
`
|
|
||||||
|
|
||||||
const outputPath = path.join(process.cwd(), 'dist/main/build.js')
|
const outputPath = entryPath
|
||||||
|
|
||||||
// 确保目录存在
|
|
||||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
||||||
|
fs.writeFileSync(outputPath, content)
|
||||||
// 写入文件
|
|
||||||
fs.writeFileSync(outputPath, content.trim())
|
|
||||||
console.log(`生产环境入口文件已生成: ${outputPath}`)
|
console.log(`生产环境入口文件已生成: ${outputPath}`)
|
||||||
@@ -6,6 +6,8 @@ import { MakerRpm } from '@electron-forge/maker-rpm';
|
|||||||
import { VitePlugin } from '@electron-forge/plugin-vite';
|
import { VitePlugin } from '@electron-forge/plugin-vite';
|
||||||
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
||||||
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
packagerConfig: {
|
packagerConfig: {
|
||||||
@@ -54,6 +56,66 @@ const config: ForgeConfig = {
|
|||||||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
[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;
|
export default config;
|
||||||
|
|||||||
@@ -14,19 +14,6 @@ export default function electronBytecode(options?: ElectronBytecodeOptions): Plu
|
|||||||
apply: 'build',
|
apply: 'build',
|
||||||
async closeBundle() {
|
async closeBundle() {
|
||||||
if (process.env.NODE_ENV !== 'production') return
|
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')
|
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 outputPath = entryPath.replace(/\.js$/, '.jsc')
|
||||||
const backupPath = `${entryPath}.bak`
|
const backupPath = `${entryPath}.bak`
|
||||||
|
|
||||||
fs.copyFileSync(entryPath, backupPath)
|
fs.copyFileSync(entryPath, backupPath)
|
||||||
|
|
||||||
await bytenode.compileFile({ filename: entryPath, output: outputPath })
|
await bytenode.compileFile({ filename: entryPath, output: outputPath })
|
||||||
|
const stub = [
|
||||||
if (!options?.keepSource) {
|
"require('bytenode')",
|
||||||
fs.removeSync(entryPath)
|
`require('./${path.basename(outputPath)}')`
|
||||||
}
|
].join("\n")
|
||||||
|
fs.writeFileSync(entryPath, stub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
import electronBytecode from "./src/plugins/bytenode/vite-plugin-electron-encrypt";
|
||||||
|
|
||||||
// https://vitejs.dev/config
|
// https://vitejs.dev/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
plugins: [electronBytecode({ entry: ".vite/build/main.js", keepSource: false })],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": resolve(__dirname, "./src/electron"),
|
"@": resolve(__dirname, "./src/electron"),
|
||||||
|
|||||||
Reference in New Issue
Block a user