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 { exposeWindowContext } from "./window/window-context";
|
||||
import { exposeRecordingContext } from "./recording/recording-context";
|
||||
|
||||
export default function exposeContexts() {
|
||||
exposeWindowContext();
|
||||
exposeThemeContext();
|
||||
exposeRecordingContext();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { addThemeEventListeners } from "./theme/theme-listeners";
|
||||
import { addWindowEventListeners } from "./window/window-listeners";
|
||||
|
||||
export default function registerListeners(mainWindow: BrowserWindow) {
|
||||
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_TOGGLE_CHANNEL,
|
||||
} from "./theme-channels";
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
|
||||
export function exposeThemeContext() {
|
||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("themeMode", {
|
||||
current: () => ipcRenderer.invoke(THEME_MODE_CURRENT_CHANNEL),
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import {
|
||||
WIN_MINIMIZE_CHANNEL,
|
||||
WIN_MAXIMIZE_CHANNEL,
|
||||
WIN_CLOSE_CHANNEL,
|
||||
WIN_CLOSE_CHANNEL
|
||||
} from "./window-channels";
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
|
||||
export function exposeWindowContext() {
|
||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("electronWindow", {
|
||||
minimize: () => ipcRenderer.invoke(WIN_MINIMIZE_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 {
|
||||
WIN_CLOSE_CHANNEL,
|
||||
WIN_MAXIMIZE_CHANNEL,
|
||||
WIN_MINIMIZE_CHANNEL,
|
||||
WIN_MINIMIZE_CHANNEL
|
||||
} from "./window-channels";
|
||||
|
||||
export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
||||
ipcMain.handle(WIN_MINIMIZE_CHANNEL, () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle(WIN_MAXIMIZE_CHANNEL, () => {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
@@ -16,6 +17,7 @@ export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(WIN_CLOSE_CHANNEL, () => {
|
||||
mainWindow.close();
|
||||
});
|
||||
|
||||
@@ -37,13 +37,8 @@ export async function setTheme(newTheme: ThemeMode) {
|
||||
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() {
|
||||
const { local } = await getCurrentTheme();
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
|
||||
sans-serif;
|
||||
margin: auto;
|
||||
max-width: 38rem;
|
||||
padding: 2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import path from "node:path";
|
||||
// app.quit();
|
||||
// }
|
||||
|
||||
const inDevelopment = process.env.NODE_ENV === "development";
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
@@ -18,10 +19,14 @@ const createWindow = () => {
|
||||
maximizable: false, // 禁止最大化
|
||||
minimizable: true, // 允许最小化
|
||||
webPreferences: {
|
||||
devTools: inDevelopment,
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 IPC 事件监听器(包括录制功能)
|
||||
registerListeners(mainWindow);
|
||||
|
||||
// and load the index.html of the app.
|
||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||
@@ -32,7 +37,7 @@ const createWindow = () => {
|
||||
}
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools();
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
// 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";
|
||||
|
||||
const routes = [
|
||||
// {
|
||||
// path: "/",
|
||||
// name: "Login",
|
||||
// component: () => import("@/views/login/index.vue"),
|
||||
// },
|
||||
{
|
||||
path: "/",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
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
|
||||
// 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).
|
||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
|
||||
declare const MAIN_WINDOW_VITE_NAME: string;
|
||||
|
||||
// Preload types
|
||||
interface ThemeModeContext {
|
||||
@@ -19,7 +17,14 @@ interface ElectronWindow {
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface RecordingContext {
|
||||
runRecordingScript: () => Promise<unknown>;
|
||||
onRecordingSave: (callback: (payload: unknown) => void) => void;
|
||||
onRecordingEdit: (callback: (payload: unknown) => void) => void;
|
||||
}
|
||||
|
||||
declare interface Window {
|
||||
themeMode: ThemeModeContext;
|
||||
electronWindow: ElectronWindow;
|
||||
recording: RecordingContext;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,16 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<h2 class="text-2xl font-bold mb-4">欢迎使用智念科技 AI</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow-md">
|
||||
<h3 class="text-lg font-semibold mb-4">计数器示例</h3>
|
||||
<div class="flex items-center space-x-4 mb-4">
|
||||
<button
|
||||
@click="counterStore.decrement"
|
||||
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>
|
||||
<div class="bg-white p-6">
|
||||
<button
|
||||
@click="counterStore.reset"
|
||||
class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded"
|
||||
class="bg-blue-300 hover:bg-blue-500 text-white px-4 py-2 rounded mr-2"
|
||||
>
|
||||
重置
|
||||
打开百度
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCounterStore } from "@/stores/counter";
|
||||
|
||||
const counterStore = useCounterStore();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user