feat: 更新文件
This commit is contained in:
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Add files here to ignore them from prettier formatting
|
||||||
|
/dist
|
||||||
|
/coverage
|
||||||
|
/.vite
|
||||||
|
README.md
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
28
src/automation/baidu-automation.js
Normal file
28
src/automation/baidu-automation.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const { chromium } = require("playwright");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const context = await browser.newContext();
|
||||||
|
const page = await context.newPage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 打开百度
|
||||||
|
await page.goto("https://www.baidu.com");
|
||||||
|
|
||||||
|
// 输入搜索关键词并回车
|
||||||
|
await page.fill("textarea#chat-textarea", "Playwright 自动化");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
|
||||||
|
// 等待搜索结果加载
|
||||||
|
await page.waitForSelector("#content_left");
|
||||||
|
|
||||||
|
console.log("百度搜索执行成功");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("自动化执行失败:", e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
} finally {
|
||||||
|
// 保持几秒以方便观察,再关闭
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
})();
|
||||||
247
src/automation/recording-automation.js
Normal file
247
src/automation/recording-automation.js
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
const { chromium } = require('playwright');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 录制配置
|
||||||
|
const recordingConfig = {
|
||||||
|
outputFile: 'recorded-steps.json',
|
||||||
|
headless: false, // 显示浏览器窗口
|
||||||
|
slowMo: 100, // 减慢操作速度,便于观察
|
||||||
|
viewport: { width: 1280, height: 720 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储录制的步骤
|
||||||
|
let recordedSteps = [];
|
||||||
|
let recordingStartTime = Date.now();
|
||||||
|
let isRecording = true;
|
||||||
|
|
||||||
|
// 生成时间戳
|
||||||
|
function getTimestamp() {
|
||||||
|
return Date.now() - recordingStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录步骤
|
||||||
|
function recordStep(step) {
|
||||||
|
const stepWithTimestamp = {
|
||||||
|
...step,
|
||||||
|
timestamp: getTimestamp()
|
||||||
|
};
|
||||||
|
recordedSteps.push(stepWithTimestamp);
|
||||||
|
console.log(`📹 录制步骤: ${step.type} - ${step.description}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存录制的步骤到文件
|
||||||
|
function saveRecordedSteps() {
|
||||||
|
const outputPath = path.join(__dirname, recordingConfig.outputFile);
|
||||||
|
const data = {
|
||||||
|
steps: recordedSteps,
|
||||||
|
metadata: {
|
||||||
|
totalSteps: recordedSteps.length,
|
||||||
|
duration: getTimestamp(),
|
||||||
|
recordedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
||||||
|
console.log(`\n✅ 录制完成!共录制 ${recordedSteps.length} 个步骤`);
|
||||||
|
console.log(`📁 录制文件已保存到: ${outputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听页面事件
|
||||||
|
async function setupPageListeners(page) {
|
||||||
|
// 监听点击事件
|
||||||
|
await page.exposeFunction('__recordClick', (selector, coordinates) => {
|
||||||
|
if (!isRecording) return;
|
||||||
|
|
||||||
|
recordStep({
|
||||||
|
type: 'click',
|
||||||
|
description: `点击元素: ${selector || '未知元素'}`,
|
||||||
|
selector: selector || '',
|
||||||
|
coordinates: coordinates || {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听输入事件
|
||||||
|
await page.exposeFunction('__recordInput', (selector, text) => {
|
||||||
|
if (!isRecording) return;
|
||||||
|
|
||||||
|
recordStep({
|
||||||
|
type: 'type',
|
||||||
|
description: `输入文本: "${text || ''}"`,
|
||||||
|
selector: selector || '',
|
||||||
|
text: text || ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注入脚本监听用户操作
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
// 监听点击事件
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
const selector = generateSelector(target);
|
||||||
|
const coordinates = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
|
if (window.__recordClick) {
|
||||||
|
window.__recordClick(selector, coordinates);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// 监听输入事件
|
||||||
|
document.addEventListener('input', (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
const selector = generateSelector(target);
|
||||||
|
const text = target.value || target.textContent || '';
|
||||||
|
|
||||||
|
if (window.__recordInput) {
|
||||||
|
window.__recordInput(selector, text);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// 生成元素选择器
|
||||||
|
function generateSelector(element) {
|
||||||
|
if (!element) return '';
|
||||||
|
|
||||||
|
// 优先使用 ID
|
||||||
|
if (element.id) {
|
||||||
|
return `#${element.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 class 和标签名
|
||||||
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
if (element.className) {
|
||||||
|
const classes = element.className.split(' ').filter(c => c.trim());
|
||||||
|
if (classes.length > 0) {
|
||||||
|
return `${tagName}.${classes.join('.')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用属性
|
||||||
|
const attributes = ['name', 'placeholder', 'type'];
|
||||||
|
for (const attr of attributes) {
|
||||||
|
if (element.getAttribute(attr)) {
|
||||||
|
return `${tagName}[${attr}="${element.getAttribute(attr)}"]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用路径
|
||||||
|
return getElementPath(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElementPath(element) {
|
||||||
|
const path = [];
|
||||||
|
while (element && element.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
let selector = element.nodeName.toLowerCase();
|
||||||
|
if (element.id) {
|
||||||
|
selector += `#${element.id}`;
|
||||||
|
path.unshift(selector);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let sibling = element;
|
||||||
|
let nth = 1;
|
||||||
|
while (sibling = sibling.previousElementSibling) {
|
||||||
|
if (sibling.nodeName.toLowerCase() === selector) nth++;
|
||||||
|
}
|
||||||
|
if (nth !== 1) selector += `:nth-of-type(${nth})`;
|
||||||
|
}
|
||||||
|
path.unshift(selector);
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
return path.join(' > ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主录制函数
|
||||||
|
async function startRecording() {
|
||||||
|
console.log('🎬 开始自动化录制...');
|
||||||
|
console.log('请在浏览器中进行操作,系统会自动记录您的操作步骤');
|
||||||
|
console.log('按 Ctrl+C 停止录制并保存结果\n');
|
||||||
|
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 启动浏览器
|
||||||
|
browser = await chromium.launch({
|
||||||
|
headless: recordingConfig.headless,
|
||||||
|
slowMo: recordingConfig.slowMo
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建新页面
|
||||||
|
page = await browser.newPage({
|
||||||
|
viewport: recordingConfig.viewport
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置页面监听器
|
||||||
|
await setupPageListeners(page);
|
||||||
|
|
||||||
|
// 监听页面导航
|
||||||
|
page.on('framenavigated', async (frame) => {
|
||||||
|
if (frame === page.mainFrame() && isRecording) {
|
||||||
|
recordStep({
|
||||||
|
type: 'navigate',
|
||||||
|
description: `导航到: ${frame.url()}`,
|
||||||
|
url: frame.url()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重新设置监听器
|
||||||
|
await setupPageListeners(page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导航到百度首页作为起始页面
|
||||||
|
console.log('🌐 正在打开百度首页...');
|
||||||
|
await page.goto('https://www.baidu.com');
|
||||||
|
|
||||||
|
recordStep({
|
||||||
|
type: 'navigate',
|
||||||
|
description: '打开百度首页',
|
||||||
|
url: 'https://www.baidu.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ 录制已启动!请在浏览器中进行操作...');
|
||||||
|
console.log('💡 您可以:');
|
||||||
|
console.log(' - 点击页面元素');
|
||||||
|
console.log(' - 在输入框中输入文本');
|
||||||
|
console.log(' - 导航到其他页面');
|
||||||
|
console.log(' - 按回车键或其他键盘操作');
|
||||||
|
console.log('');
|
||||||
|
console.log('⚠️ 按 Ctrl+C 停止录制并保存结果');
|
||||||
|
|
||||||
|
// 保持浏览器打开,直到用户手动停止
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
console.log('\n🛑 用户停止录制');
|
||||||
|
isRecording = false;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 录制过程中发生错误:', error);
|
||||||
|
} finally {
|
||||||
|
if (browser) {
|
||||||
|
// 保存录制的步骤
|
||||||
|
saveRecordedSteps();
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理程序终止
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
console.log('\n🛑 用户停止录制');
|
||||||
|
saveRecordedSteps();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
console.log('\n🛑 程序终止');
|
||||||
|
saveRecordedSteps();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动录制
|
||||||
|
(async () => {
|
||||||
|
await startRecording();
|
||||||
|
})();
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { exposeThemeContext } from "./theme/theme-context";
|
import { exposeThemeContext } from "./theme/theme-context";
|
||||||
import { exposeWindowContext } from "./window/window-context";
|
import { exposeWindowContext } from "./window/window-context";
|
||||||
|
import { exposeRecordingContext } from "./recording/recording-context";
|
||||||
|
|
||||||
export default function exposeContexts() {
|
export default function exposeContexts() {
|
||||||
exposeWindowContext();
|
exposeWindowContext();
|
||||||
exposeThemeContext();
|
exposeThemeContext();
|
||||||
|
exposeRecordingContext();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
import { addThemeEventListeners } from "./theme/theme-listeners";
|
|
||||||
import { addWindowEventListeners } from "./window/window-listeners";
|
import { addWindowEventListeners } from "./window/window-listeners";
|
||||||
|
|
||||||
export default function registerListeners(mainWindow: BrowserWindow) {
|
export default function registerListeners(mainWindow: BrowserWindow) {
|
||||||
addWindowEventListeners(mainWindow);
|
addWindowEventListeners(mainWindow);
|
||||||
addThemeEventListeners();
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/helpers/ipc/recording/recording-channels.ts
Normal file
5
src/helpers/ipc/recording/recording-channels.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// 自动化录制相关的 IPC 通道
|
||||||
|
|
||||||
|
export const RUN_RECORDING_SCRIPT_CHANNEL = "run-recording-script";
|
||||||
|
export const RUN_RECORDING_SCRIPT_SAVE_CHANNEL = "run-recording-script-save";
|
||||||
|
export const RUN_RECORDING_SCRIPT_EDIT_CHANNEL = "run-recording-script-edit";
|
||||||
35
src/helpers/ipc/recording/recording-context.ts
Normal file
35
src/helpers/ipc/recording/recording-context.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
RUN_RECORDING_SCRIPT_CHANNEL,
|
||||||
|
RUN_RECORDING_SCRIPT_SAVE_CHANNEL,
|
||||||
|
RUN_RECORDING_SCRIPT_EDIT_CHANNEL,
|
||||||
|
} from "./recording-channels";
|
||||||
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
export function exposeRecordingContext() {
|
||||||
|
contextBridge.exposeInMainWorld("recording", {
|
||||||
|
// 运行录制脚本
|
||||||
|
runRecordingScript: () => ipcRenderer.invoke(RUN_RECORDING_SCRIPT_CHANNEL),
|
||||||
|
|
||||||
|
// 监听录制脚本输出(保存)
|
||||||
|
onRecordingSave: (callback: (payload: unknown) => void) => {
|
||||||
|
ipcRenderer.on(RUN_RECORDING_SCRIPT_SAVE_CHANNEL, (_event, payload) => {
|
||||||
|
try {
|
||||||
|
callback(payload);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("onRecordingSave callback error:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 监听录制脚本输出(编辑)
|
||||||
|
onRecordingEdit: (callback: (payload: unknown) => void) => {
|
||||||
|
ipcRenderer.on(RUN_RECORDING_SCRIPT_EDIT_CHANNEL, (_event, payload) => {
|
||||||
|
try {
|
||||||
|
callback(payload);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("onRecordingEdit callback error:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
81
src/helpers/ipc/recording/recording-listeners.ts
Normal file
81
src/helpers/ipc/recording/recording-listeners.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { BrowserWindow, ipcMain, app } from "electron";
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
import path from "path";
|
||||||
|
import { RUN_RECORDING_SCRIPT_CHANNEL } from "./recording-channels";
|
||||||
|
|
||||||
|
export function addRecordingEventListeners(mainWindow: BrowserWindow) {
|
||||||
|
// 监听运行录制脚本的请求
|
||||||
|
ipcMain.handle(RUN_RECORDING_SCRIPT_CHANNEL, async () => {
|
||||||
|
try {
|
||||||
|
console.log('🎬 主进程:开始执行录制脚本');
|
||||||
|
|
||||||
|
const basePath = app.isPackaged ? process.resourcesPath : app.getAppPath();
|
||||||
|
const scriptPath = app.isPackaged
|
||||||
|
? path.join(basePath, 'automation', 'recording-automation.js')
|
||||||
|
: path.join(basePath, 'src', 'automation', 'recording-automation.js');
|
||||||
|
|
||||||
|
// 启动录制脚本
|
||||||
|
const recordingProcess = spawn('node', [scriptPath], {
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
detached: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
// 监听标准输出
|
||||||
|
recordingProcess.stdout.on('data', (data) => {
|
||||||
|
const message = data.toString();
|
||||||
|
stdout += message;
|
||||||
|
console.log('录制脚本输出:', message);
|
||||||
|
|
||||||
|
// 发送输出到渲染进程
|
||||||
|
mainWindow.webContents.send(RECORDING_CHANNELS.RECORDING_SCRIPT_RESULT, {
|
||||||
|
type: 'stdout',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听错误输出
|
||||||
|
recordingProcess.stderr.on('data', (data) => {
|
||||||
|
const message = data.toString();
|
||||||
|
stderr += message;
|
||||||
|
console.error('录制脚本错误:', message);
|
||||||
|
|
||||||
|
// 发送错误到渲染进程
|
||||||
|
mainWindow.webContents.send(RECORDING_CHANNELS.RECORDING_SCRIPT_ERROR, {
|
||||||
|
type: 'stderr',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听进程退出
|
||||||
|
recordingProcess.on('close', (code) => {
|
||||||
|
console.log(`录制脚本退出,退出码: ${code}`);
|
||||||
|
|
||||||
|
mainWindow.webContents.send(RECORDING_CHANNELS.RECORDING_SCRIPT_RESULT, {
|
||||||
|
type: 'exit',
|
||||||
|
code: code,
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听进程错误
|
||||||
|
recordingProcess.on('error', (error) => {
|
||||||
|
console.error('录制脚本启动失败:', error);
|
||||||
|
|
||||||
|
mainWindow.webContents.send(RECORDING_CHANNELS.RECORDING_SCRIPT_ERROR, {
|
||||||
|
type: 'error',
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: '录制脚本已启动' };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('执行录制脚本失败:', error);
|
||||||
|
return { success: false, error: (error as Error).message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
THEME_MODE_SYSTEM_CHANNEL,
|
THEME_MODE_SYSTEM_CHANNEL,
|
||||||
THEME_MODE_TOGGLE_CHANNEL,
|
THEME_MODE_TOGGLE_CHANNEL,
|
||||||
} from "./theme-channels";
|
} from "./theme-channels";
|
||||||
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
export function exposeThemeContext() {
|
export function exposeThemeContext() {
|
||||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("themeMode", {
|
contextBridge.exposeInMainWorld("themeMode", {
|
||||||
current: () => ipcRenderer.invoke(THEME_MODE_CURRENT_CHANNEL),
|
current: () => ipcRenderer.invoke(THEME_MODE_CURRENT_CHANNEL),
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
WIN_MINIMIZE_CHANNEL,
|
WIN_MINIMIZE_CHANNEL,
|
||||||
WIN_MAXIMIZE_CHANNEL,
|
WIN_MAXIMIZE_CHANNEL,
|
||||||
WIN_CLOSE_CHANNEL,
|
WIN_CLOSE_CHANNEL
|
||||||
} from "./window-channels";
|
} from "./window-channels";
|
||||||
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
export function exposeWindowContext() {
|
export function exposeWindowContext() {
|
||||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electronWindow", {
|
contextBridge.exposeInMainWorld("electronWindow", {
|
||||||
minimize: () => ipcRenderer.invoke(WIN_MINIMIZE_CHANNEL),
|
minimize: () => ipcRenderer.invoke(WIN_MINIMIZE_CHANNEL),
|
||||||
maximize: () => ipcRenderer.invoke(WIN_MAXIMIZE_CHANNEL),
|
maximize: () => ipcRenderer.invoke(WIN_MAXIMIZE_CHANNEL),
|
||||||
close: () => ipcRenderer.invoke(WIN_CLOSE_CHANNEL),
|
close: () => ipcRenderer.invoke(WIN_CLOSE_CHANNEL)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import { BrowserWindow, ipcMain } from "electron";
|
|||||||
import {
|
import {
|
||||||
WIN_CLOSE_CHANNEL,
|
WIN_CLOSE_CHANNEL,
|
||||||
WIN_MAXIMIZE_CHANNEL,
|
WIN_MAXIMIZE_CHANNEL,
|
||||||
WIN_MINIMIZE_CHANNEL,
|
WIN_MINIMIZE_CHANNEL
|
||||||
} from "./window-channels";
|
} from "./window-channels";
|
||||||
|
|
||||||
export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
||||||
ipcMain.handle(WIN_MINIMIZE_CHANNEL, () => {
|
ipcMain.handle(WIN_MINIMIZE_CHANNEL, () => {
|
||||||
mainWindow.minimize();
|
mainWindow.minimize();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(WIN_MAXIMIZE_CHANNEL, () => {
|
ipcMain.handle(WIN_MAXIMIZE_CHANNEL, () => {
|
||||||
if (mainWindow.isMaximized()) {
|
if (mainWindow.isMaximized()) {
|
||||||
mainWindow.unmaximize();
|
mainWindow.unmaximize();
|
||||||
@@ -16,6 +17,7 @@ export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
|||||||
mainWindow.maximize();
|
mainWindow.maximize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(WIN_CLOSE_CHANNEL, () => {
|
ipcMain.handle(WIN_CLOSE_CHANNEL, () => {
|
||||||
mainWindow.close();
|
mainWindow.close();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,13 +37,8 @@ export async function setTheme(newTheme: ThemeMode) {
|
|||||||
localStorage.setItem(THEME_KEY, newTheme);
|
localStorage.setItem(THEME_KEY, newTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toggleTheme() {
|
|
||||||
const isDarkMode = await window.themeMode.toggle();
|
|
||||||
const newTheme = isDarkMode ? "dark" : "light";
|
|
||||||
|
|
||||||
updateDocumentTheme(isDarkMode);
|
|
||||||
localStorage.setItem(THEME_KEY, newTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function syncThemeWithLocal() {
|
export async function syncThemeWithLocal() {
|
||||||
const { local } = await getCurrentTheme();
|
const { local } = await getCurrentTheme();
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family:
|
margin: 0;
|
||||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
|
padding: 0;
|
||||||
sans-serif;
|
|
||||||
margin: auto;
|
|
||||||
max-width: 38rem;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import path from "node:path";
|
|||||||
// app.quit();
|
// app.quit();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const inDevelopment = process.env.NODE_ENV === "development";
|
||||||
const createWindow = () => {
|
const createWindow = () => {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
@@ -18,10 +19,14 @@ const createWindow = () => {
|
|||||||
maximizable: false, // 禁止最大化
|
maximizable: false, // 禁止最大化
|
||||||
minimizable: true, // 允许最小化
|
minimizable: true, // 允许最小化
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
devTools: inDevelopment,
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 注册 IPC 事件监听器(包括录制功能)
|
||||||
|
registerListeners(mainWindow);
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||||
@@ -32,7 +37,7 @@ const createWindow = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
// mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
};
|
};
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import exposeContexts from "./helpers/ipc/context-exposer";
|
||||||
|
|
||||||
|
exposeContexts();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
// {
|
||||||
|
// path: "/",
|
||||||
|
// name: "Login",
|
||||||
|
// component: () => import("@/views/login/index.vue"),
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "Login",
|
|
||||||
component: () => import("@/views/login/index.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/home",
|
|
||||||
name: "Home",
|
name: "Home",
|
||||||
component: () => import("@/views/home/index.vue"),
|
component: () => import("@/views/home/index.vue"),
|
||||||
},
|
},
|
||||||
|
|||||||
9
src/types.d.ts
vendored
9
src/types.d.ts
vendored
@@ -1,8 +1,6 @@
|
|||||||
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
|
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
|
||||||
// plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
|
// plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
|
||||||
// whether you're running in development or production).
|
// whether you're running in development or production).
|
||||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
|
|
||||||
declare const MAIN_WINDOW_VITE_NAME: string;
|
|
||||||
|
|
||||||
// Preload types
|
// Preload types
|
||||||
interface ThemeModeContext {
|
interface ThemeModeContext {
|
||||||
@@ -19,7 +17,14 @@ interface ElectronWindow {
|
|||||||
close: () => Promise<void>;
|
close: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RecordingContext {
|
||||||
|
runRecordingScript: () => Promise<unknown>;
|
||||||
|
onRecordingSave: (callback: (payload: unknown) => void) => void;
|
||||||
|
onRecordingEdit: (callback: (payload: unknown) => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
declare interface Window {
|
declare interface Window {
|
||||||
themeMode: ThemeModeContext;
|
themeMode: ThemeModeContext;
|
||||||
electronWindow: ElectronWindow;
|
electronWindow: ElectronWindow;
|
||||||
|
recording: RecordingContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<h2 class="text-2xl font-bold mb-4">欢迎使用智念科技 AI</h2>
|
<h2 class="text-2xl font-bold mb-4">欢迎使用智念科技 AI</h2>
|
||||||
<div class="bg-white p-6 rounded-lg shadow-md">
|
<div class="bg-white p-6">
|
||||||
<h3 class="text-lg font-semibold mb-4">计数器示例</h3>
|
|
||||||
<div class="flex items-center space-x-4 mb-4">
|
|
||||||
<button
|
<button
|
||||||
@click="counterStore.decrement"
|
class="bg-blue-300 hover:bg-blue-500 text-white px-4 py-2 rounded mr-2"
|
||||||
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded"
|
|
||||||
>
|
>
|
||||||
-
|
打开百度
|
||||||
</button>
|
|
||||||
<span class="text-xl font-bold">{{ counterStore.count }}</span>
|
|
||||||
<button
|
|
||||||
@click="counterStore.increment"
|
|
||||||
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<p>双倍值: {{ counterStore.doubleCount }}</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="counterStore.reset"
|
|
||||||
class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded"
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCounterStore } from "@/stores/counter";
|
|
||||||
|
|
||||||
const counterStore = useCounterStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.home {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user