This commit is contained in:
zyronon
2025-08-17 17:03:50 +08:00
parent b94bd61263
commit a969fce5ac
23 changed files with 511 additions and 148 deletions

View File

@@ -18,8 +18,6 @@
},
"dependencies": {
"@imengyu/vue3-context-menu": "^1.5.1",
"@opentranslate/baidu": "^1.4.2",
"@opentranslate/translator": "^1.4.2",
"axios": "^1.10.0",
"compromise": "^14.14.4",
"copy-to-clipboard": "^3.3.3",
@@ -30,13 +28,14 @@
"mitt": "^3.0.1",
"nanoid": "^5.1.5",
"pinia": "^3.0.3",
"sentence-splitter": "^4.4.1",
"string-comparison": "^1.3.0",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
"vue-virtual-scroller": "2.0.0-beta.8"
"vue-virtual-scroller": "2.0.0-beta.8",
"md5": "^2.2.1"
},
"devDependencies": {
"@types/md5": "^2.1.33",
"@alicloud/pop-core": "^1.8.0",
"@iconify-json/basil": "^1.2.4",
"@iconify-json/bi": "^1.2.6",

112
pnpm-lock.yaml generated
View File

@@ -11,12 +11,6 @@ importers:
'@imengyu/vue3-context-menu':
specifier: ^1.5.1
version: 1.5.2
'@opentranslate/baidu':
specifier: ^1.4.2
version: 1.4.2
'@opentranslate/translator':
specifier: ^1.4.2
version: 1.4.2
axios:
specifier: ^1.10.0
version: 1.11.0
@@ -38,6 +32,9 @@ importers:
libarchive-wasm:
specifier: ^1.2.0
version: 1.2.0
md5:
specifier: ^2.2.1
version: 2.3.0
mitt:
specifier: ^3.0.1
version: 3.0.1
@@ -53,9 +50,6 @@ importers:
string-comparison:
specifier: ^1.3.0
version: 1.3.0
vite-plugin-externals:
specifier: ^0.6.2
version: 0.6.2(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(sass@1.90.0))
vue:
specifier: ^3.5.17
version: 3.5.18(typescript@5.9.2)
@@ -159,6 +153,9 @@ importers:
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
'@types/md5':
specifier: ^2.1.33
version: 2.3.5
'@unocss/postcss':
specifier: ^66.4.0
version: 66.4.2(postcss@8.5.6)
@@ -219,9 +216,9 @@ importers:
vite:
specifier: ^7.0.3
version: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(sass@1.90.0)
vite-plugin-cdn-import:
specifier: file:./plugins/vite-plugin-cdn-import/dist
version: dist@file:plugins/vite-plugin-cdn-import/dist
vite-plugin-externals:
specifier: ^0.6.2
version: 0.6.2(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(sass@1.90.0))
vue-tsc:
specifier: ^3.0.1
version: 3.0.5(typescript@5.9.2)
@@ -668,15 +665,6 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@opentranslate/baidu@1.4.2':
resolution: {integrity: sha512-j8V7P+OCzEIAa+Zh4P6tbgWizVuVfKJOXDvk6M865J4QOE1lUQIN/Hb5SdodePkFinwNbQK2mAb7qF9wO/nQbA==}
'@opentranslate/languages@1.4.2':
resolution: {integrity: sha512-cexyjCYyvFRCUAsuCSpmfO1k4RWxrEHKFJYdCEsb+RqFWrTC8PsXupiWo2172c/8rNOW6BOMxiN3bZ2mETKV7g==}
'@opentranslate/translator@1.4.2':
resolution: {integrity: sha512-AI5hLx5fiBOvhRsrmBwOTBa7ZHV1AsRsbfrowkL2wzLLmt3nBtHN+pqv8xVDEkPHUtOLVGqcmtuO2UQgbp+spQ==}
'@oxc-resolver/binding-darwin-arm64@4.2.0':
resolution: {integrity: sha512-DP+KY4nXRJvL5XayKda0P7NCjcP1zZ5x6RZznMM/bMPCBrjcYNG4XKV9v/EbkSq3Et24mEJFYOM55WmPxtqf0w==}
cpu: [arm64]
@@ -977,9 +965,6 @@ packages:
'@types/node@24.3.0':
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
'@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
@@ -1507,10 +1492,6 @@ packages:
engines: {node: '>= 4.5.0'}
hasBin: true
axios@0.19.2:
resolution: {integrity: sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==}
deprecated: Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410
axios@1.11.0:
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
@@ -1703,9 +1684,6 @@ packages:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
collapse-white-space@1.0.6:
resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==}
collection-map@1.0.0:
resolution: {integrity: sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==}
engines: {node: '>=0.10.0'}
@@ -1955,9 +1933,6 @@ packages:
resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==}
engines: {node: '>= 8.0.0'}
dist@file:plugins/vite-plugin-cdn-import/dist:
resolution: {directory: plugins/vite-plugin-cdn-import/dist, type: directory}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -2180,10 +2155,6 @@ packages:
debug:
optional: true
follow-redirects@1.5.10:
resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==}
engines: {node: '>=4.0'}
for-in@1.0.2:
resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
engines: {node: '>=0.10.0'}
@@ -2207,9 +2178,6 @@ packages:
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
engines: {node: '>=0.10.0'}
franc-min@4.1.1:
resolution: {integrity: sha512-7xpOX5GymdaT6d0qmSNFpyFuEd6tPuEHVZpL+KIh9DocVCVn59c2OnUvLn+NcWGGi7btqV1VZ5VbjSox5KbZKA==}
fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
@@ -2853,9 +2821,6 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
n-gram@1.1.2:
resolution: {integrity: sha512-mBTpWKp0NHdujHmxrskPg2jc108mjyMmVxHN1rZGK/ogTLi9O0debDIXlQPqotNELdNmVGtL4jr7SCig+4OWvQ==}
nan@2.23.0:
resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==}
@@ -3556,13 +3521,6 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
trigram-utils@1.0.3:
resolution: {integrity: sha512-UAhS1Ll21FtClVIzIN0I/SmGnJ+D08BOxX7Dl1penV8raC0ksf2dJkhNI6kU1Mj3uT86Bul12iMvxXquXSYSng==}
trim@0.0.1:
resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==}
deprecated: Use String.prototype.trim() instead
ts-macro@0.1.35:
resolution: {integrity: sha512-cMPJUCH8VsH9s9FANjL1r/SrkV2T6CKBjgWqgP2XGiS+y/zBBwmw0C3C31M4LqrLEjb8djgUMDV18vQ4Dr+/mg==}
@@ -4464,26 +4422,6 @@ snapshots:
'@tybys/wasm-util': 0.10.0
optional: true
'@opentranslate/baidu@1.4.2':
dependencies:
'@opentranslate/translator': 1.4.2
'@types/md5': 2.3.5
'@types/qs': 6.14.0
md5: 2.3.0
qs: 6.14.0
transitivePeerDependencies:
- supports-color
'@opentranslate/languages@1.4.2': {}
'@opentranslate/translator@1.4.2':
dependencies:
'@opentranslate/languages': 1.4.2
axios: 0.19.2
franc-min: 4.1.1
transitivePeerDependencies:
- supports-color
'@oxc-resolver/binding-darwin-arm64@4.2.0':
optional: true
@@ -4683,8 +4621,6 @@ snapshots:
undici-types: 7.10.0
optional: true
'@types/qs@6.14.0': {}
'@types/web-bluetooth@0.0.16':
optional: true
@@ -5428,12 +5364,6 @@ snapshots:
atob@2.1.2: {}
axios@0.19.2:
dependencies:
follow-redirects: 1.5.10
transitivePeerDependencies:
- supports-color
axios@1.11.0:
dependencies:
follow-redirects: 1.15.11
@@ -5679,8 +5609,6 @@ snapshots:
codepage@1.15.0: {}
collapse-white-space@1.0.6: {}
collection-map@1.0.0:
dependencies:
arr-map: 2.0.2
@@ -5912,8 +5840,6 @@ snapshots:
digest-header@1.1.0: {}
dist@file:plugins/vite-plugin-cdn-import/dist: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -6195,12 +6121,6 @@ snapshots:
follow-redirects@1.15.11: {}
follow-redirects@1.5.10:
dependencies:
debug: 3.1.0
transitivePeerDependencies:
- supports-color
for-in@1.0.2: {}
for-own@1.0.0:
@@ -6228,10 +6148,6 @@ snapshots:
dependencies:
map-cache: 0.2.2
franc-min@4.1.1:
dependencies:
trigram-utils: 1.0.3
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
@@ -6904,8 +6820,6 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
n-gram@1.1.2: {}
nan@2.23.0:
optional: true
@@ -7671,14 +7585,6 @@ snapshots:
totalist@3.0.1: {}
trigram-utils@1.0.3:
dependencies:
collapse-white-space: 1.0.6
n-gram: 1.1.2
trim: 0.0.1
trim@0.0.1: {}
ts-macro@0.1.35:
dependencies:
muggle-string: 0.4.1

View File

@@ -22,6 +22,7 @@ watch(store.$state, (n: BaseState) => {
})
watch(settingStore.$state, (n) => {
console.log('watch',settingStore.$state)
set(SAVE_SETTING_KEY.key, JSON.stringify({val: n, version: SAVE_SETTING_KEY.version}))
})

View File

@@ -1,13 +1,13 @@
import {Article, Sentence, TranslateEngine} from "@/types/types.ts";
import Baidu from "@opentranslate/baidu";
import {Translator} from "@opentranslate/translator/src/translator.ts";
import Baidu from "@/libs/translate/baidu";
import {Translator} from "@/libs/translate/translator/index.ts";
export function getSentenceAllTranslateText(article: Article) {
return article.sections.map(v => v.map(s => s.translate.trim()).filter(v=>v).join(' \n')).filter(v=>v).join(' \n\n');
return article.sections.map(v => v.map(s => s.translate.trim()).filter(v => v).join(' \n')).filter(v => v).join(' \n\n');
}
export function getSentenceAllText(article: Article) {
return article.sections.map(v => v.map(s => s.text.trim()).filter(v=>v).join(' \n')).filter(v=>v).join(' \n\n');
return article.sections.map(v => v.map(s => s.text.trim()).filter(v => v).join(' \n')).filter(v => v).join(' \n\n');
}
/***
@@ -48,6 +48,8 @@ export async function getNetworkTranslate(
const translate = async (sentence: Sentence) => {
try {
let r = await translator.translate(sentence.text, 'en', 'zh-CN')
console.log(r)
if (r) {
const cb = () => {
sentence.translate = r.trans.paragraphs[0]

7
src/libs/qs.ts Normal file
View File

@@ -0,0 +1,7 @@
export default {
stringify: (params: Record<string, any>): string => {
return Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}
}

View File

@@ -0,0 +1 @@
复制这个库是因为他引入了franc-min这个包太大了50多k我用不到

145
src/libs/translate/baidu.ts Normal file
View File

@@ -0,0 +1,145 @@
import {
Language,
Translator,
TranslateError,
TranslateQueryResult
} from "./translator";
import md5 from "md5";
import qs from "../qs";
const langMap: [Language, string][] = [
["auto", "auto"],
["zh-CN", "zh"],
["en", "en"],
["yue", "yue"],
["wyw", "wyw"],
["ja", "jp"],
["ko", "kor"],
["fr", "fra"],
["es", "spa"],
["th", "th"],
["ar", "ara"],
["ru", "ru"],
["pt", "pt"],
["de", "de"],
["it", "it"],
["el", "el"],
["nl", "nl"],
["pl", "pl"],
["bg", "bul"],
["et", "est"],
["da", "dan"],
["fi", "fin"],
["cs", "cs"],
["ro", "rom"],
["sl", "slo"],
["sv", "swe"],
["hu", "hu"],
["zh-TW", "cht"],
["vi", "vie"]
];
export interface BaiduConfig {
placeholder?: string;
appid: string;
key: string;
}
export class Baidu extends Translator<BaiduConfig> {
readonly name = "baidu";
readonly endpoint = "https://api.fanyi.baidu.com/api/trans/vip/translate";
protected async query(
text: string,
from: Language,
to: Language,
config: BaiduConfig
): Promise<TranslateQueryResult> {
type BaiduTranslateError = {
error_code: "54001" | string;
error_msg: "Invalid Sign" | string;
};
type BaiduTranslateResult = {
from: string;
to: string;
trans_result: Array<{
dst: string;
src: string;
}>;
};
const salt = Date.now();
const {endpoint} = this;
const {appid, key} = config;
const res = await this.request<BaiduTranslateResult | BaiduTranslateError>(
endpoint,
{
params: {
from: Baidu.langMap.get(from),
to: Baidu.langMap.get(to),
q: text,
salt,
appid,
sign: md5(appid + text + salt + key)
}
}
).catch(() => {
throw new TranslateError("NETWORK_ERROR");
});
const {data} = res;
if ((data as BaiduTranslateError).error_code) {
console.error(
new Error("[Baidu service]" + (data as BaiduTranslateError).error_msg)
);
throw new TranslateError("API_SERVER_ERROR");
}
const {
trans_result: transResult,
from: langDetected
} = data as BaiduTranslateResult;
const transParagraphs = transResult.map(({dst}) => dst);
const detectedFrom = Baidu.langMapReverse.get(langDetected) as Language;
return {
text,
from: detectedFrom,
to,
origin: {
paragraphs: transResult.map(({src}) => src),
tts: await this.textToSpeech(text, detectedFrom)
},
trans: {
paragraphs: transParagraphs,
tts: await this.textToSpeech(transParagraphs.join(" "), to)
}
};
}
/** Translator lang to custom lang */
private static readonly langMap = new Map(langMap);
/** Custom lang to translator lang */
private static readonly langMapReverse = new Map(
langMap.map(([translatorLang, lang]) => [lang, translatorLang])
);
getSupportLanguages(): Language[] {
return [...Baidu.langMap.keys()];
}
async textToSpeech(text: string, lang: Language): Promise<string> {
return `https://fanyi.baidu.com/gettts?${qs.stringify({
lan: Baidu.langMap.get(lang !== "auto" ? lang : "zh-CN") || "zh",
text,
spd: 5,
})}`;
}
}
export default Baidu;

View File

@@ -0,0 +1,2 @@
export * from "./languages";
export * from "./locales";

View File

@@ -0,0 +1,123 @@
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export type Language = (typeof languages)[number];
export const languages = [
"af",
"am",
"ar",
"auto",
"az",
"be",
"bg",
"bn",
"bs",
"ca",
"ceb",
"co",
"cs",
"cy",
"da",
"de",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"fi",
"fil",
"fj",
"fr",
"fy",
"ga",
"gd",
"gl",
"gu",
"ha",
"haw",
"he",
"hi",
"hmn",
"hr",
"ht",
"hu",
"hy",
"id",
"ig",
"is",
"it",
"ja",
"jw",
"ka",
"kk",
"km",
"kn",
"ko",
"ku",
"ky",
"la",
"lb",
"lo",
"lt",
"lv",
"mg",
"mi",
"mk",
"ml",
"mn",
"mr",
"ms",
"mt",
"mww",
"my",
"ne",
"nl",
"no",
"ny",
"otq",
"pa",
"pl",
"ps",
"pt",
"ro",
"ru",
"sd",
"si",
"sk",
"sl",
"sm",
"sn",
"so",
"sq",
"sr",
"sr-Cyrl",
"sr-Latn",
"st",
"su",
"sv",
"sw",
"ta",
"te",
"tg",
"th",
"tlh",
"tlh-Qaak",
"to",
"tr",
"ty",
"ug",
"uk",
"ur",
"uz",
"vi",
"wyw",
"xh",
"yi",
"yo",
"yua",
"yue",
"zh-CN",
"zh-TW",
"zu"
] as const;

View File

@@ -0,0 +1,3 @@
import { Language } from "./languages";
export type Locale = { [key in Language]: string };

View File

@@ -0,0 +1,3 @@
export * from "../languages";
export * from "./type";
export * from "./translator";

View File

@@ -0,0 +1,100 @@
import {
Languages,
TranslatorEnv,
TranslatorInit,
TranslateResult,
TranslateQueryResult
} from "./type";
import {Language} from "../languages";
import Axios, {AxiosInstance, AxiosRequestConfig, AxiosPromise} from "axios";
export abstract class Translator<Config extends {} = {}> {
axios: AxiosInstance;
protected readonly env: TranslatorEnv;
/**
* 自定义选项
*/
config: Config;
/**
* 翻译源标识符
*/
abstract readonly name: string;
/**
* 可选的axios实例
*/
constructor(init: TranslatorInit<Config> = {}) {
this.env = init.env || "node";
this.axios = init.axios || Axios;
this.config = init.config || ({} as Config);
}
/**
* 获取翻译器所支持的语言列表: 语言标识符数组
*/
abstract getSupportLanguages(): Languages;
/**
* 下游应用调用的接口
*/
async translate(
text: string,
from: Language,
to: Language,
config = {} as Config
): Promise<TranslateResult> {
const queryResult = await this.query(text, from, to, {
...this.config,
...config
});
return {
...queryResult,
engine: this.name
};
}
/**
* 更新 token 的方法
*/
updateToken?(): Promise<void>;
/**
* 翻译源需要实现的方法
*/
protected abstract query(
text: string,
from: Language,
to: Language,
config: Config
): Promise<TranslateQueryResult>;
protected request<R = {}>(
url: string,
config?: AxiosRequestConfig
): AxiosPromise<R> {
return this.axios(url, config);
}
/**
* 如果翻译源提供了单独的检测语言的功能,请实现此接口
*/
async detect(text: string): Promise<Language> {
return
}
/**
* 文本转换为语音
* @returns {Promise<string|null>} 语言文件地址
*/
textToSpeech(
text: string,
lang: Language,
meta?: any // eslint-disable-line @typescript-eslint/no-explicit-any
): Promise<string | null> {
return Promise.resolve(null);
}
}

View File

@@ -0,0 +1,45 @@
import {Language} from "../languages";
import {AxiosInstance} from "axios";
export type Languages = Array<Language>;
export type TranslatorEnv = "node" | "ext";
export interface TranslatorInit<Config extends {}> {
env?: TranslatorEnv;
axios?: AxiosInstance;
config?: Config;
}
export type TranslateErrorType =
| "NETWORK_ERROR"
| "NETWORK_TIMEOUT"
| "API_SERVER_ERROR"
| "UNSUPPORTED_LANG"
| "UNKNOWN";
export class TranslateError extends Error {
constructor(message: TranslateErrorType) {
super(message);
}
}
/** 统一的查询结果的数据结构 */
export interface TranslateResult {
engine: string;
text: string;
from: Language;
to: Language;
/** 原文 */
origin: {
paragraphs: string[];
tts?: string;
};
/** 译文 */
trans: {
paragraphs: string[];
tts?: string;
};
}
export type TranslateQueryResult = Omit<TranslateResult, "engine">;

View File

@@ -6,17 +6,17 @@ import EditAbleText from "@/pages/pc/components/EditAbleText.vue";
import {getNetworkTranslate, getSentenceAllText, getSentenceAllTranslateText} from "@/hooks/translate.ts";
import {genArticleSectionData, splitCNArticle2, splitEnArticle2, usePlaySentenceAudio} from "@/hooks/article.ts";
import {_nextTick, _parseLRC, cloneDeep, last} from "@/utils";
import {watch} from "vue";
import {defineAsyncComponent, watch} from "vue";
import Empty from "@/components/Empty.vue";
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
import * as Comparison from "string-comparison"
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {getDefaultArticle} from "@/types/func.ts";
import copy from "copy-to-clipboard";
import {Option, Select} from "@/pages/pc/components/base/select";
import Tooltip from "@/pages/pc/components/base/Tooltip.vue";
import InputNumber from "@/pages/pc/components/base/InputNumber.vue";
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
interface IProps {
article?: Article,
@@ -38,8 +38,8 @@ let progress = $ref(0)
let failCount = $ref(0)
let textareaRef = $ref<HTMLTextAreaElement>()
const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
{value: 'youdao', label: '有道'},
{value: 'baidu', label: '百度'},
]
let editArticle = $ref<Article>(getDefaultArticle())
@@ -494,11 +494,11 @@ function setStartTime(val: Sentence, i: number, j: number) {
<span v-if="editSentence.audioPosition?.[1] !== -1"> - {{ editSentence.audioPosition?.[1] }}s</span>
<span v-else> - 结束</span>
</div>
<BaseIcon2
<BaseIcon
title="试听"
@click="playSentenceAudio(editSentence,sentenceAudioRef,editArticle)">
<IconHugeiconsPlay/>
</BaseIcon2>
</BaseIcon>
</div>
</div>
<div class="flex flex-col gap-2">
@@ -507,18 +507,18 @@ function setStartTime(val: Sentence, i: number, j: number) {
<div class="flex justify-between flex-1">
<div class="flex items-center gap-2">
<InputNumber v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1"/>
<BaseIcon2
<BaseIcon
@click="jumpAudio(editSentence.audioPosition[0])"
title="跳转"
>
<IconIcSharpMyLocation/>
</BaseIcon2>
<BaseIcon2
</BaseIcon>
<BaseIcon
@click="setPreEndTimeToCurrentStartTime"
title="使用前一句的结束时间"
>
<IconTwemojiEndArrow/>
</BaseIcon2>
</BaseIcon>
</div>
<BaseButton @click="recordStart">记录</BaseButton>
</div>

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import {Article} from "@/types/types.ts";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {useDisableEventListener} from "@/hooks/event.ts";
import EditArticle from "@/pages/pc/article/components/EditArticle.vue";
import {getDefaultArticle} from "@/types/func.ts";
import {defineAsyncComponent} from "vue";
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
interface IProps {
article?: Article

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {onMounted, watch} from "vue";
import {defineAsyncComponent, onMounted, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
let settingStore = useSettingStore()
let show = $ref(false)

View File

@@ -158,9 +158,9 @@ async function cancel() {
>
<Tooltip title="关闭">
<IconIonCloseOutline @click="close"
v-if="showClose"
class="close hvr-grow cursor-pointer"
width="24" color="#929596"/>
v-if="showClose"
class="close hvr-grow cursor-pointer"
width="24" color="#929596"/>
</Tooltip>
<div class="modal-header" v-if="header">
<div class="title">{{ props.title }}</div>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {useBaseStore} from "@/stores/base.ts";
import BaseButton from "@/components/BaseButton.vue";
import {ShortcutKey, Statistics} from "@/types/types.ts";
@@ -8,7 +7,8 @@ import {useSettingStore} from "@/stores/setting.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import {watch} from "vue";
import {defineAsyncComponent, watch} from "vue";
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
dayjs.extend(isBetween);

View File

@@ -2,11 +2,10 @@
import {useBaseStore} from "@/stores/base.ts";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {_getAccomplishDate, _getAccomplishDays, _getDictDataByUrl, useNav} from "@/utils";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {DictResource} from "@/types/types.ts";
import {onMounted, watch} from "vue";
import {defineAsyncComponent, onMounted, watch} from "vue";
import {getCurrentStudyWord} from "@/hooks/dict.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import Book from "@/pages/pc/components/Book.vue";
@@ -18,6 +17,9 @@ import {getDefaultDict} from "@/types/func.ts";
import Slider from "@/pages/pc/components/base/Slider.vue";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
const store = useBaseStore()
const router = useRouter()
const {nav} = useNav()
@@ -136,8 +138,8 @@ const progressTextRight = $computed(() => {
<span @click="goDictDetail(store.sdict)"
class="text-lg font-bold cursor-pointer">{{ store.sdict.name || '请选择词典开始学习' }}</span>
<BaseIcon title="切换词典"
class="ml-4"
@click="router.push('/dict-list')"
class="ml-4"
@click="router.push('/dict-list')"
>
<IconGgArrowsExchange v-if="store.sdict.name"/>

View File

@@ -1,6 +1,5 @@
import * as VueRouter from 'vue-router'
import {RouteRecordRaw} from 'vue-router'
import Test from "@/pages/test/test.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import WordHomePage from "@/pages/pc/word/WordHomePage.vue";
import PC from "@/pages/pc/index.vue";
@@ -10,8 +9,8 @@ import DictDetail from "@/pages/pc/word/DictDetail.vue";
import StudyWord from "@/pages/pc/word/StudyWord.vue";
import BookDetail from "@/pages/pc/article/BookDetail.vue";
import DictList from "@/pages/pc/word/DictList.vue";
import Setting from "@/pages/pc/Setting.vue";
import BookList from "@/pages/pc/article/BookList.vue";
import Setting from "@/pages/pc/Setting.vue";
export const routes: RouteRecordRaw[] = [
{
@@ -27,15 +26,15 @@ export const routes: RouteRecordRaw[] = [
{path: 'article', component: ArticleHomePage},
{path: 'study-article', component: StudyArticle},
{path: 'edit-article', component: () => import("@/pages/pc/article/EditArticlePage.vue")},
{path: 'batch-edit-article', component: () => import("@/pages/pc/article/BatchEditArticlePage.vue")},
{path: 'book-detail', component: BookDetail},
{path: 'book-list', component: BookList},
{path: 'edit-article', component: () => import("@/pages/pc/article/EditArticlePage.vue")},
{path: 'batch-edit-article', component: () => import("@/pages/pc/article/BatchEditArticlePage.vue")},
{path: 'setting', component: Setting},
]
},
{path: '/test', component: Test},
{path: '/test', component: () => import("@/pages/test/test.vue")},
{path: '/:pathMatch(.*)*', redirect: '/word'},
]

View File

@@ -2,6 +2,7 @@ import {defineStore} from "pinia"
import {checkAndUpgradeSaveSetting, cloneDeep} from "@/utils";
import {DefaultShortcutKeyMap} from "@/types/types.ts";
import {SAVE_SETTING_KEY} from "@/utils/const.ts";
import {get} from "idb-keyval";
export interface SettingState {
showToolbar: boolean,
@@ -97,8 +98,11 @@ export const useSettingStore = defineStore('setting', {
this.$patch(obj)
},
init() {
return new Promise(resolve => {
return new Promise(async resolve => {
let configStr = localStorage.getItem(SAVE_SETTING_KEY.key)
if (!configStr) {
configStr = await get(SAVE_SETTING_KEY.key)
}
let data = checkAndUpgradeSaveSetting(configStr)
this.setState(data)
this.load = true

View File

@@ -11,6 +11,7 @@ import {nextTick} from "vue";
import {dictionaryResources, enArticle} from "@/assets/dictionary.ts";
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts";
import {set} from "idb-keyval";
export function no() {
Toast.warning('未现实')
@@ -50,7 +51,8 @@ export function checkAndUpgradeSaveDict(val: any) {
return defaultState
} else {
if (version === 3) {
localStorage.setItem('type-word-dict-v3',JSON.stringify(state))
localStorage.setItem('type-word-dict-v3', JSON.stringify(state))
set('type-word-dict-v3', JSON.stringify(state))
let studyDictId = ''
if (state.current.index >= 0) {
@@ -114,15 +116,15 @@ export function checkAndUpgradeSaveDict(val: any) {
delete v.id
delete v.name
if (currentType === 'collect') {
if (v.words.length){
if (currentDictId === studyDictId) defaultState.word.studyIndex = 0
checkRiskKey(defaultState.word.bookList[0], cloneDeep(v))
defaultState.word.bookList[0].length = v.words.length
if (v.words.length) {
if (currentDictId === studyDictId) defaultState.word.studyIndex = 0
checkRiskKey(defaultState.word.bookList[0], cloneDeep(v))
defaultState.word.bookList[0].length = v.words.length
}
if (v.articles.length){
if (currentDictId === studyDictId) defaultState.article.studyIndex = 0
checkRiskKey(defaultState.article.bookList[0], cloneDeep(v))
defaultState.article.bookList[0].length = v.articles.length
if (v.articles.length) {
if (currentDictId === studyDictId) defaultState.article.studyIndex = 0
checkRiskKey(defaultState.article.bookList[0], cloneDeep(v))
defaultState.article.bookList[0].length = v.articles.length
}
}
if (currentType === 'simple' || currentType === 'skip') {

View File

@@ -79,7 +79,25 @@ export default defineConfig(() => {
],
build: {
rollupOptions: {
external: isBuild ? ['axios'] : [],// 使用全局的 axios。因为百度翻译库内部用了0.19版本的axios会被打包到代码里面
// 因为已经把包复制过来了里面的axios实例用的项目的所以这行代码可以不要了
// external: isBuild ? ['axios'] : [],// 使用全局的 axios。因为百度翻译库内部用了0.19版本的axios会被打包到代码里面
output: {
manualChunks(id) {
if (id.includes('dialog')) {
return 'dialog'
}
if (id.includes('utils')
|| id.includes('hooks')
// || id.includes('types')
// || id.includes('libs')
) {
return 'utils'
}
if (id.includes('node_modules/@iconify') || id.includes('~icons')) {
return 'icons';
}
}
}
}
},
define: {