This commit is contained in:
2025-08-25 13:32:14 +08:00
parent df84619503
commit 80dd42585e
22 changed files with 3214 additions and 54 deletions

15
components.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TrafficLight: typeof import('./src/components/TrafficLight.vue')['default']
}
}

1
deno.lock generated
View File

@@ -1849,6 +1849,7 @@
"npm:unplugin-vue-components@^28.8.0",
"npm:vite@^6.0.3",
"npm:vue-pug-plugin@^2.0.4",
"npm:vue-router@^4.5.1",
"npm:vue-tsc@^2.1.10",
"npm:vue@^3.5.13"
]

View File

@@ -14,6 +14,8 @@
"@arco-plugins/vite-vue": "^1.4.5",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
"@types/lodash": "^4.17.20",
"lodash": "^4.17.21",
"postcss": "^8.5.6",
@@ -23,15 +25,14 @@
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vue": "^3.5.13",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
"vue-pug-plugin": "^2.0.4"
"vue-pug-plugin": "^2.0.4",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "~5.6.2",
"vite": "^6.0.3",
"vue-tsc": "^2.1.10",
"@tauri-apps/cli": "^2"
"vue-tsc": "^2.1.10"
}
}

2919
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

57
src-tauri/Cargo.lock generated
View File

@@ -1003,7 +1003,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading 0.7.4",
"libloading 0.8.8",
]
[[package]]
@@ -1044,6 +1044,7 @@ dependencies = [
"futures",
"minifb",
"ollama-rs",
"rusqlite",
"screenshots",
"serde",
"serde_json",
@@ -1206,6 +1207,18 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -1247,6 +1260,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -1747,6 +1766,18 @@ name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "heck"
@@ -2389,6 +2420,16 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "libwayshot"
version = "0.2.0"
@@ -3753,6 +3794,20 @@ dependencies = [
"web-sys",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags 2.9.1",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.25"

View File

@@ -19,7 +19,7 @@ tauri-build = { version = "2", features = [] }
[dependencies]
ollama-rs = { version = "0.3.2", features = ["stream"] }
tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["macos-private-api"] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -30,6 +30,7 @@ screenshots = "0.8.10"
minifb = "0.28.0"
tesseract = "0.15.2"
uuid = { version = "1.17.0", features = ["v4"] }
rusqlite = "0.37.0"
[target."cfg(target_os = \"macos\")".dependencies]
cocoa = "0.26.1"

View File

@@ -5,6 +5,8 @@
"windows": ["main"],
"permissions": [
"core:default",
"opener:default"
"opener:default",
"core:webview:default",
"core:webview:allow-create-webview-window"
]
}

View File

@@ -0,0 +1,32 @@
use tauri::{WebviewUrl, WebviewWindowBuilder, TitleBarStyle};
#[tauri::command]
pub fn create_window(app: tauri::AppHandle, label: &str, title: &str, url: &str, width: f64, height: f64) {
let win_builder = tauri::WebviewWindowBuilder::new(&app, label, WebviewUrl::App(url.into()))
.title(title)
.inner_size(width, height);
#[cfg(target_os = "macos")]
let win_builder = win_builder.title_bar_style(TitleBarStyle::Transparent);
let window = win_builder.build().unwrap();
// 仅在构建 macOS 时设置背景颜色
#[cfg(target_os = "macos")]
{
use cocoa::appkit::{NSColor, NSWindow};
use cocoa::base::{id, nil};
let ns_window = window.ns_window().unwrap() as id;
unsafe {
let bg_color = NSColor::colorWithRed_green_blue_alpha_(
nil,
255.0 / 255.0,
255.0 / 255.0,
255.0 / 255.0,
1.0,
);
ns_window.setBackgroundColor_(bg_color);
}
}
}

View File

@@ -1,3 +1,4 @@
pub mod translate_steam;
pub mod copy;
pub mod screenshot;
pub mod create_window;

View File

@@ -1,4 +1,4 @@
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder, Emitter};
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
mod commands;
@@ -9,7 +9,7 @@ pub fn run() {
let win_builder =
WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.title("Transparent Titlebar Window")
.inner_size(700.0, 300.0);
.inner_size(700.0, 320.0);
// 仅在 macOS 时设置透明标题栏
#[cfg(target_os = "macos")]
@@ -41,7 +41,8 @@ pub fn run() {
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
commands::translate_steam::translate_steam,
commands::copy::copy
commands::copy::copy,
commands::create_window::create_window
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -10,6 +10,7 @@
"frontendDist": "../dist"
},
"app": {
"macOSPrivateApi": true,
"windows": [],
"security": {
"csp": null

View File

@@ -1,9 +1,8 @@
<script setup lang="ts">
import Home from "./pages/home/index.vue";
</script>
<template lang="pug">
Home
router-view
</template>
<style scoped>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755847223106" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7640" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M192 384h96v64h-96zM192 544h96v64h-96zM192 224h96v64h-96z" fill="#497CAD" p-id="7641"></path><path d="M128 128h353.9l45.9 64h78.8L514.7 64H64v704h223v-64H128V128z m677.1 128H352v704h608V407.9L805.1 256zM896 896H416V320h298v180h182v396z m0-460H778V320h0.9L896 434.8v1.2z" fill="#727272" p-id="7642"></path><path d="M480 412.9h192v64H480zM481 576h354v64H481zM481 734h354v64H481z" fill="#497CAD" p-id="7643"></path></svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755841213736" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5603" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M512 68.266667C266.922667 68.266667 68.266667 266.922667 68.266667 512s198.656 443.733333 443.733333 443.733333 443.733333-198.656 443.733333-443.733333S757.077333 68.266667 512 68.266667z m0 68.266666c207.36 0 375.466667 168.106667 375.466667 375.466667s-168.106667 375.466667-375.466667 375.466667S136.533333 719.36 136.533333 512 304.64 136.533333 512 136.533333z" fill="#444444" p-id="5604"></path><path d="M546.133333 307.2v206.5408l142.148267 126.344533-45.3632 51.029334L477.866667 544.392533V307.2z" fill="#00B386" p-id="5605"></path></svg>

After

Width:  |  Height:  |  Size: 881 B

View File

@@ -1,6 +1,9 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "./style/tailwindcss.css";
import "./scss/index.scss";
createApp(App).mount("#app");
const app = createApp(App);
app.use(router);
app.mount("#app");

View File

@@ -0,0 +1,10 @@
<script lang="ts" setup>
</script>
<template lang="pug">
div.p-3 剪贴板
</template>
<style lang="scss" scoped>
@reference "../../style/tailwindcss.css";
</style>

View File

@@ -0,0 +1,10 @@
<script lang="ts" setup>
</script>
<template lang="pug">
div.p-3 历史
</template>
<style lang="scss" scoped>
@reference "../../style/tailwindcss.css";
</style>

View File

@@ -3,15 +3,40 @@ import { onMounted, onUnmounted, reactive, ref } from "vue";
import { debounce } from "lodash";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { Message } from '@arco-design/web-vue';
import { Message } from "@arco-design/web-vue";
import { type Menu } from "../../types/Menu";
import HISTORY from "../../assets/icons/history.svg";
import CLIPBOARD from "../../assets/icons/clipboard.svg";
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
const menuList = reactive<Menu[]>([
{
id: 1,
name: "翻译历史",
icon: HISTORY,
async onClick() {
await invoke("create_window", { label: "history", title: "翻译历史", url: "/history", width: 500.0, height: 600.0 });
},
},
{
id: 2,
name: "剪贴板",
icon: CLIPBOARD,
onClick() {
new WebviewWindow("clipboard", {
url: "/clipboard",
title: "剪贴板",
});
}
}
]);
const autoCopy = ref(false);
let unlisten: Function;
const loading = ref(false);
const form = reactive<{
in: null | string;
out: null | string;
}>({
}>({
in: null,
out: null,
});
@@ -19,7 +44,7 @@ const form = reactive<{
const copy = () => {
invoke("copy", { content: form.out });
Message.success("复制成功");
}
};
const changeIn = debounce(async () => {
loading.value = true;
@@ -27,7 +52,7 @@ const changeIn = debounce(async () => {
await invoke("translate_steam", { content: form.in });
if (autoCopy.value) copy();
loading.value = false;
}, 500);
}, 1500);
const listen_start = async () => {
unlisten = await listen("translate_steam", (event) => {
@@ -35,6 +60,11 @@ const listen_start = async () => {
if (!form.out) form.out = "";
form.out += event.payload;
});
};
const clear = () => {
form.in = null;
form.out = null;
}
onMounted(() => {
@@ -47,13 +77,14 @@ onUnmounted(() => {
</script>
<template lang="pug">
div.w-full.p-3.h-screen
div.content
div.w-full.p-3.h-screen.flex.flex-col.gap-3
div.content.no-select
div.content-item
div.content-header 中文
div.content-hr
div.content-body
a-textarea(v-model="form.in" @input="changeIn").content-body-textarea
a-button.content-body-copy-btn(@click="clear()") 清空
div.content-item
div.content-header
div 英文
@@ -65,27 +96,56 @@ onUnmounted(() => {
view(v-if="loading") 加载中...
a-textarea(v-model="form.out" v-else).content-body-textarea
a-button.content-body-copy-btn(@click="copy()") 复制
div.tools.no-select
div.tools-item(v-for="v in menuList" ::key="v.id" @click="v.onClick")
img.tools-item-img(:src="v.icon" alt="alt")
</template>
<style lang="scss" scoped>
@reference "../../style/tailwindcss.css";
.tools {
@apply p-2 bg-white rounded-xl overflow-hidden flex gap-3 flex-shrink-0;
&-item {
@apply size-[24px] aspect-square cursor-pointer duration-500 p-[3px] rounded-sm;
&:hover {
background-color: rgba(0, 0, 0, .1);
}
&:active {
background-color: rgba(0, 0, 0, .2);
}
&-img {
@apply size-full object-cover;
}
}
}
.content {
@apply grid grid-cols-2 gap-3 h-full;
&-item {
@apply w-full bg-white rounded-xl overflow-hidden flex flex-col;
}
&-header {
@apply p-3 h-[50px] flex items-center justify-between;
}
&-hr {
@apply h-[1px] bg-[#ddd] w-full;
}
&-body {
@apply p-3 flex flex-grow relative;
&-textarea {
@apply flex-grow min-h-[150px];
}
&-copy-btn {
@apply absolute right-[24px] bottom-[24px];
}

9
src/router/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import { createWebHistory, createRouter, Router } from 'vue-router';
import routes from './routes';
const router: Router = createRouter({
history: createWebHistory(),
routes: routes
});
export default router as any;

25
src/router/routes.ts Normal file
View File

@@ -0,0 +1,25 @@
import { RouteRecordRaw } from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: () => import('../pages/home/index.vue'),
},
{
path: '/history',
name: 'History',
component: () => import('../pages/history/index.vue'),
},
{
path: '/clipboard',
name: 'Clipboard',
component: () => import('../pages/clipboard/index.vue'),
}
];
export default routes;

View File

@@ -7,3 +7,10 @@
body {
background-color: #F9FAFB;
}
.no-select {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}

6
src/types/Menu.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface Menu {
id: number;
name: string;
icon: string;
onClick?: () => void;
}