✨ Add self-updating service
This commit is contained in:
@@ -471,6 +471,84 @@ export class GeneralConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GiteaConfig Gitea配置
|
||||
*/
|
||||
export class GiteaConfig {
|
||||
/**
|
||||
* Gitea服务器URL
|
||||
*/
|
||||
"baseURL": string;
|
||||
|
||||
/**
|
||||
* 仓库所有者
|
||||
*/
|
||||
"owner": string;
|
||||
|
||||
/**
|
||||
* 仓库名称
|
||||
*/
|
||||
"repo": string;
|
||||
|
||||
/** Creates a new GiteaConfig instance. */
|
||||
constructor($$source: Partial<GiteaConfig> = {}) {
|
||||
if (!("baseURL" in $$source)) {
|
||||
this["baseURL"] = "";
|
||||
}
|
||||
if (!("owner" in $$source)) {
|
||||
this["owner"] = "";
|
||||
}
|
||||
if (!("repo" in $$source)) {
|
||||
this["repo"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GiteaConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): GiteaConfig {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new GiteaConfig($$parsedSource as Partial<GiteaConfig>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GithubConfig GitHub配置
|
||||
*/
|
||||
export class GithubConfig {
|
||||
/**
|
||||
* 仓库所有者
|
||||
*/
|
||||
"owner": string;
|
||||
|
||||
/**
|
||||
* 仓库名称
|
||||
*/
|
||||
"repo": string;
|
||||
|
||||
/** Creates a new GithubConfig instance. */
|
||||
constructor($$source: Partial<GithubConfig> = {}) {
|
||||
if (!("owner" in $$source)) {
|
||||
this["owner"] = "";
|
||||
}
|
||||
if (!("repo" in $$source)) {
|
||||
this["repo"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GithubConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): GithubConfig {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new GithubConfig($$parsedSource as Partial<GithubConfig>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HotkeyCombo 热键组合定义
|
||||
*/
|
||||
@@ -1023,6 +1101,26 @@ export enum TabType {
|
||||
TabTypeTab = "tab",
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateSourceType 更新源类型
|
||||
*/
|
||||
export enum UpdateSourceType {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* UpdateSourceGithub GitHub更新源
|
||||
*/
|
||||
UpdateSourceGithub = "github",
|
||||
|
||||
/**
|
||||
* UpdateSourceGitea Gitea更新源
|
||||
*/
|
||||
UpdateSourceGitea = "gitea",
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdatesConfig 更新设置配置
|
||||
*/
|
||||
@@ -1037,6 +1135,36 @@ export class UpdatesConfig {
|
||||
*/
|
||||
"autoUpdate": boolean;
|
||||
|
||||
/**
|
||||
* 主要更新源
|
||||
*/
|
||||
"primarySource": UpdateSourceType;
|
||||
|
||||
/**
|
||||
* 备用更新源
|
||||
*/
|
||||
"backupSource": UpdateSourceType;
|
||||
|
||||
/**
|
||||
* 更新前是否备份
|
||||
*/
|
||||
"backupBeforeUpdate": boolean;
|
||||
|
||||
/**
|
||||
* 更新超时时间(秒)
|
||||
*/
|
||||
"updateTimeout": number;
|
||||
|
||||
/**
|
||||
* GitHub配置
|
||||
*/
|
||||
"github": GithubConfig;
|
||||
|
||||
/**
|
||||
* Gitea配置
|
||||
*/
|
||||
"gitea": GiteaConfig;
|
||||
|
||||
/** Creates a new UpdatesConfig instance. */
|
||||
constructor($$source: Partial<UpdatesConfig> = {}) {
|
||||
if (!("version" in $$source)) {
|
||||
@@ -1045,6 +1173,24 @@ export class UpdatesConfig {
|
||||
if (!("autoUpdate" in $$source)) {
|
||||
this["autoUpdate"] = false;
|
||||
}
|
||||
if (!("primarySource" in $$source)) {
|
||||
this["primarySource"] = ("" as UpdateSourceType);
|
||||
}
|
||||
if (!("backupSource" in $$source)) {
|
||||
this["backupSource"] = ("" as UpdateSourceType);
|
||||
}
|
||||
if (!("backupBeforeUpdate" in $$source)) {
|
||||
this["backupBeforeUpdate"] = false;
|
||||
}
|
||||
if (!("updateTimeout" in $$source)) {
|
||||
this["updateTimeout"] = 0;
|
||||
}
|
||||
if (!("github" in $$source)) {
|
||||
this["github"] = (new GithubConfig());
|
||||
}
|
||||
if (!("gitea" in $$source)) {
|
||||
this["gitea"] = (new GiteaConfig());
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
@@ -1053,7 +1199,15 @@ export class UpdatesConfig {
|
||||
* Creates a new UpdatesConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): UpdatesConfig {
|
||||
const $$createField6_0 = $$createType11;
|
||||
const $$createField7_0 = $$createType12;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("github" in $$parsedSource) {
|
||||
$$parsedSource["github"] = $$createField6_0($$parsedSource["github"]);
|
||||
}
|
||||
if ("gitea" in $$parsedSource) {
|
||||
$$parsedSource["gitea"] = $$createField7_0($$parsedSource["gitea"]);
|
||||
}
|
||||
return new UpdatesConfig($$parsedSource as Partial<UpdatesConfig>);
|
||||
}
|
||||
}
|
||||
@@ -1075,3 +1229,5 @@ const $$createType7 = HotkeyCombo.createFrom;
|
||||
const $$createType8 = KeyBinding.createFrom;
|
||||
const $$createType9 = $Create.Array($$createType8);
|
||||
const $$createType10 = KeyBindingMetadata.createFrom;
|
||||
const $$createType11 = GithubConfig.createFrom;
|
||||
const $$createType12 = GiteaConfig.createFrom;
|
||||
|
@@ -8,10 +8,10 @@ import * as ExtensionService from "./extensionservice.js";
|
||||
import * as HotkeyService from "./hotkeyservice.js";
|
||||
import * as KeyBindingService from "./keybindingservice.js";
|
||||
import * as MigrationService from "./migrationservice.js";
|
||||
import * as SelfUpdateService from "./selfupdateservice.js";
|
||||
import * as StartupService from "./startupservice.js";
|
||||
import * as SystemService from "./systemservice.js";
|
||||
import * as TrayService from "./trayservice.js";
|
||||
import * as UpdateService from "./updateservice.js";
|
||||
export {
|
||||
ConfigService,
|
||||
DialogService,
|
||||
@@ -20,10 +20,10 @@ export {
|
||||
HotkeyService,
|
||||
KeyBindingService,
|
||||
MigrationService,
|
||||
SelfUpdateService,
|
||||
StartupService,
|
||||
SystemService,
|
||||
TrayService,
|
||||
UpdateService
|
||||
TrayService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
|
@@ -116,9 +116,9 @@ export enum MigrationStatus {
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateCheckResult 更新检查结果
|
||||
* SelfUpdateResult 自我更新结果
|
||||
*/
|
||||
export class UpdateCheckResult {
|
||||
export class SelfUpdateResult {
|
||||
/**
|
||||
* 是否有更新
|
||||
*/
|
||||
@@ -127,57 +127,73 @@ export class UpdateCheckResult {
|
||||
/**
|
||||
* 当前版本
|
||||
*/
|
||||
"currentVer": string;
|
||||
"currentVersion": string;
|
||||
|
||||
/**
|
||||
* 最新版本
|
||||
*/
|
||||
"latestVer": string;
|
||||
"latestVersion": string;
|
||||
|
||||
/**
|
||||
* 是否已应用更新
|
||||
*/
|
||||
"updateApplied": boolean;
|
||||
|
||||
/**
|
||||
* 下载链接
|
||||
*/
|
||||
"assetURL": string;
|
||||
|
||||
/**
|
||||
* 发布说明
|
||||
*/
|
||||
"releaseNotes": string;
|
||||
|
||||
/**
|
||||
* 发布页面URL
|
||||
*/
|
||||
"releaseURL": string;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
"error": string;
|
||||
|
||||
/** Creates a new UpdateCheckResult instance. */
|
||||
constructor($$source: Partial<UpdateCheckResult> = {}) {
|
||||
/**
|
||||
* 更新源(github/gitea)
|
||||
*/
|
||||
"source": string;
|
||||
|
||||
/** Creates a new SelfUpdateResult instance. */
|
||||
constructor($$source: Partial<SelfUpdateResult> = {}) {
|
||||
if (!("hasUpdate" in $$source)) {
|
||||
this["hasUpdate"] = false;
|
||||
}
|
||||
if (!("currentVer" in $$source)) {
|
||||
this["currentVer"] = "";
|
||||
if (!("currentVersion" in $$source)) {
|
||||
this["currentVersion"] = "";
|
||||
}
|
||||
if (!("latestVer" in $$source)) {
|
||||
this["latestVer"] = "";
|
||||
if (!("latestVersion" in $$source)) {
|
||||
this["latestVersion"] = "";
|
||||
}
|
||||
if (!("updateApplied" in $$source)) {
|
||||
this["updateApplied"] = false;
|
||||
}
|
||||
if (!("assetURL" in $$source)) {
|
||||
this["assetURL"] = "";
|
||||
}
|
||||
if (!("releaseNotes" in $$source)) {
|
||||
this["releaseNotes"] = "";
|
||||
}
|
||||
if (!("releaseURL" in $$source)) {
|
||||
this["releaseURL"] = "";
|
||||
}
|
||||
if (!("error" in $$source)) {
|
||||
this["error"] = "";
|
||||
}
|
||||
if (!("source" in $$source)) {
|
||||
this["source"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new UpdateCheckResult instance from a string or object.
|
||||
* Creates a new SelfUpdateResult instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): UpdateCheckResult {
|
||||
static createFrom($$source: any = {}): SelfUpdateResult {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new UpdateCheckResult($$parsedSource as Partial<UpdateCheckResult>);
|
||||
return new SelfUpdateResult($$parsedSource as Partial<SelfUpdateResult>);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,51 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* SelfUpdateService 自我更新服务
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* ApplyUpdate 应用更新
|
||||
*/
|
||||
export function ApplyUpdate(): Promise<$models.SelfUpdateResult | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2009328394) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckForUpdates 检查更新
|
||||
*/
|
||||
export function CheckForUpdates(): Promise<$models.SelfUpdateResult | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(438757208) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* RestartApplication 重启应用程序
|
||||
*/
|
||||
export function RestartApplication(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3341481538) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.SelfUpdateResult.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
@@ -1,30 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* UpdateService 更新服务
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* CheckForUpdates 检查更新
|
||||
*/
|
||||
export function CheckForUpdates(): Promise<$models.UpdateCheckResult> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3024322786) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType0($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.UpdateCheckResult.createFrom;
|
55
frontend/package-lock.json
generated
55
frontend/package-lock.json
generated
@@ -49,6 +49,7 @@
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"prettier": "^3.5.3",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.6",
|
||||
@@ -58,6 +59,7 @@
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@lezer/generator": "^1.7.3",
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@wailsio/runtime": "latest",
|
||||
"eslint": "^9.29.0",
|
||||
@@ -2099,6 +2101,13 @@
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/remarkable": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/@types/remarkable/-/remarkable-2.0.8.tgz",
|
||||
"integrity": "sha512-eKXqPZfpQl1kOADjdKchHrp2gwn9qMnGXhH/AtZe0UrklzhGJkawJo/Y/D0AlWcdWoWamFNIum8+/nkAISQVGg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
@@ -2685,6 +2694,15 @@
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/autolinker": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmmirror.com/autolinker/-/autolinker-3.16.2.tgz",
|
||||
"integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -4337,6 +4355,31 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/remarkable": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/remarkable/-/remarkable-2.0.1.tgz",
|
||||
"integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.10",
|
||||
"autolinker": "^3.11.0"
|
||||
},
|
||||
"bin": {
|
||||
"remarkable": "bin/remarkable.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remarkable/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -4510,6 +4553,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.9.0.tgz",
|
||||
@@ -4655,6 +4704,12 @@
|
||||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
|
||||
|
@@ -53,6 +53,7 @@
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"prettier": "^3.5.3",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.6",
|
||||
@@ -62,6 +63,7 @@
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@lezer/generator": "^1.7.3",
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@wailsio/runtime": "latest",
|
||||
"eslint": "^9.29.0",
|
||||
|
@@ -1,18 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, nextTick, computed, watch } from 'vue';
|
||||
import { SystemService } from '@/../bindings/voidraft/internal/services';
|
||||
import type { MemoryStats } from '@/../bindings/voidraft/internal/services';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useThemeStore } from '@/stores/themeStore';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const themeStore = useThemeStore();
|
||||
const memoryStats = ref<MemoryStats | null>(null);
|
||||
const formattedMemory = ref('');
|
||||
const isLoading = ref(true);
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// 存储历史数据点 (最近60个数据点,每3秒一个点,总共3分钟历史)
|
||||
// 存储历史数据点 (最近60个数据点)
|
||||
const historyData = ref<number[]>([]);
|
||||
const maxDataPoints = 60;
|
||||
|
||||
// 动态最大内存值(MB),初始为200MB,会根据实际使用动态调整
|
||||
const maxMemoryMB = ref(200);
|
||||
|
||||
// 使用themeStore获取当前主题
|
||||
const isDarkTheme = computed(() => {
|
||||
const theme = themeStore.currentTheme;
|
||||
if (theme === SystemThemeType.SystemThemeAuto) {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === SystemThemeType.SystemThemeDark;
|
||||
});
|
||||
|
||||
// 监听主题变化,重新绘制图表
|
||||
watch(() => themeStore.currentTheme, () => {
|
||||
nextTick(() => drawChart());
|
||||
});
|
||||
|
||||
// 静默错误处理包装器
|
||||
const withSilentErrorHandling = async <T>(
|
||||
operation: () => Promise<T>,
|
||||
@@ -47,8 +69,14 @@ const fetchMemoryStats = async () => {
|
||||
formattedMemory.value = `${heapMB.toFixed(0)}M`;
|
||||
}
|
||||
|
||||
// 添加新数据点到历史记录
|
||||
const memoryUsagePercent = Math.min((stats.heapInUse / (100 * 1024 * 1024)) * 100, 100);
|
||||
// 自动调整最大内存值,确保图表能够显示更大范围
|
||||
if (heapMB > maxMemoryMB.value * 0.8) {
|
||||
// 如果内存使用超过当前最大值的80%,则将最大值调整为当前使用值的2倍
|
||||
maxMemoryMB.value = Math.ceil(heapMB * 2);
|
||||
}
|
||||
|
||||
// 添加新数据点到历史记录 - 使用动态最大值计算百分比
|
||||
const memoryUsagePercent = Math.min((heapMB / maxMemoryMB.value) * 100, 100);
|
||||
historyData.value.push(memoryUsagePercent);
|
||||
|
||||
// 保持最大数据点数量
|
||||
@@ -62,7 +90,7 @@ const fetchMemoryStats = async () => {
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
// 绘制实时曲线图
|
||||
// 绘制实时曲线图 - 简化版
|
||||
const drawChart = () => {
|
||||
if (!canvasRef.value || historyData.value.length === 0) return;
|
||||
|
||||
@@ -82,11 +110,16 @@ const drawChart = () => {
|
||||
// 清除画布
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 绘制背景网格 - 朦胧的网格,从上到下逐渐清晰
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
const y = (height / 6) * i;
|
||||
const opacity = 0.01 + (i / 6) * 0.03; // 从上到下逐渐清晰
|
||||
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
|
||||
// 根据主题选择合适的颜色 - 更柔和的颜色
|
||||
const gridColor = isDarkTheme.value ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.07)';
|
||||
const lineColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.6)' : 'rgba(37, 99, 235, 0.6)';
|
||||
const fillColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.05)' : 'rgba(37, 99, 235, 0.05)';
|
||||
const pointColor = isDarkTheme.value ? 'rgba(74, 158, 255, 0.8)' : 'rgba(37, 99, 235, 0.8)';
|
||||
|
||||
// 绘制背景网格 - 更加柔和
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const y = (height / 4) * i;
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y);
|
||||
@@ -95,9 +128,9 @@ const drawChart = () => {
|
||||
}
|
||||
|
||||
// 垂直网格线
|
||||
for (let i = 0; i <= 8; i++) {
|
||||
const x = (width / 8) * i;
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.02)';
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
const x = (width / 6) * i;
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, 0);
|
||||
@@ -112,13 +145,7 @@ const drawChart = () => {
|
||||
const stepX = width / (maxDataPoints - 1);
|
||||
const startX = width - (dataLength - 1) * stepX;
|
||||
|
||||
// 绘制填充区域 - 从上朦胧到下清晰的渐变
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
gradient.addColorStop(0, 'rgba(74, 158, 255, 0.1)'); // 顶部很淡
|
||||
gradient.addColorStop(0.3, 'rgba(74, 158, 255, 0.15)');
|
||||
gradient.addColorStop(0.7, 'rgba(74, 158, 255, 0.25)');
|
||||
gradient.addColorStop(1, 'rgba(74, 158, 255, 0.4)'); // 底部较浓
|
||||
|
||||
// 绘制填充区域 - 更柔和的填充
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, height);
|
||||
|
||||
@@ -126,17 +153,23 @@ const drawChart = () => {
|
||||
const firstY = height - (historyData.value[0] / 100) * height;
|
||||
ctx.lineTo(startX, firstY);
|
||||
|
||||
// 使用二次贝塞尔曲线平滑曲线
|
||||
// 绘制数据点路径 - 使用曲线连接点,确保连续性
|
||||
for (let i = 1; i < dataLength; i++) {
|
||||
const x = startX + i * stepX;
|
||||
const y = height - (historyData.value[i] / 100) * height;
|
||||
|
||||
// 使用贝塞尔曲线平滑连接
|
||||
if (i < dataLength - 1) {
|
||||
const nextX = startX + (i + 1) * stepX;
|
||||
const nextY = height - (historyData.value[i + 1] / 100) * height;
|
||||
const controlX = x + stepX / 2;
|
||||
const controlY = y;
|
||||
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
|
||||
const cpX1 = x - stepX / 4;
|
||||
const cpY1 = y;
|
||||
const cpX2 = x + stepX / 4;
|
||||
const cpY2 = nextY;
|
||||
|
||||
// 使用三次贝塞尔曲线平滑连接点
|
||||
ctx.bezierCurveTo(cpX1, cpY1, cpX2, cpY2, nextX, nextY);
|
||||
i++; // 跳过下一个点,因为已经在曲线中处理了
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
@@ -146,68 +179,55 @@ const drawChart = () => {
|
||||
const lastX = startX + (dataLength - 1) * stepX;
|
||||
ctx.lineTo(lastX, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillStyle = fillColor;
|
||||
ctx.fill();
|
||||
|
||||
// 绘制主曲线 - 从上到下逐渐清晰
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
// 绘制主曲线 - 平滑连续的曲线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, firstY);
|
||||
|
||||
// 分段绘制曲线,每段有不同的透明度
|
||||
const segments = 10;
|
||||
for (let seg = 0; seg < segments; seg++) {
|
||||
const segmentStart = seg / segments;
|
||||
const segmentEnd = (seg + 1) / segments;
|
||||
const opacity = 0.3 + (seg / segments) * 0.7; // 从上0.3到下1.0
|
||||
// 重新绘制曲线路径,但这次只绘制线条
|
||||
for (let i = 1; i < dataLength; i++) {
|
||||
const x = startX + i * stepX;
|
||||
const y = height - (historyData.value[i] / 100) * height;
|
||||
|
||||
ctx.strokeStyle = `rgba(74, 158, 255, ${opacity})`;
|
||||
ctx.lineWidth = 1.5 + (seg / segments) * 0.8; // 线条也从细到粗
|
||||
|
||||
ctx.beginPath();
|
||||
let segmentStarted = false;
|
||||
|
||||
for (let i = 0; i < dataLength; i++) {
|
||||
const x = startX + i * stepX;
|
||||
const y = height - (historyData.value[i] / 100) * height;
|
||||
const yPercent = 1 - (y / height);
|
||||
// 使用贝塞尔曲线平滑连接
|
||||
if (i < dataLength - 1) {
|
||||
const nextX = startX + (i + 1) * stepX;
|
||||
const nextY = height - (historyData.value[i + 1] / 100) * height;
|
||||
const cpX1 = x - stepX / 4;
|
||||
const cpY1 = y;
|
||||
const cpX2 = x + stepX / 4;
|
||||
const cpY2 = nextY;
|
||||
|
||||
if (yPercent >= segmentStart && yPercent <= segmentEnd) {
|
||||
if (!segmentStarted) {
|
||||
ctx.moveTo(x, y);
|
||||
segmentStarted = true;
|
||||
} else {
|
||||
if (i < dataLength - 1) {
|
||||
const nextX = startX + (i + 1) * stepX;
|
||||
const nextY = height - (historyData.value[i + 1] / 100) * height;
|
||||
const controlX = x + stepX / 2;
|
||||
const controlY = y;
|
||||
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segmentStarted) {
|
||||
ctx.stroke();
|
||||
// 使用三次贝塞尔曲线平滑连接点
|
||||
ctx.bezierCurveTo(cpX1, cpY1, cpX2, cpY2, nextX, nextY);
|
||||
i++; // 跳过下一个点,因为已经在曲线中处理了
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制当前值的高亮点 - 根据位置调整透明度
|
||||
ctx.strokeStyle = lineColor;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制当前值的高亮点
|
||||
const lastY = height - (historyData.value[dataLength - 1] / 100) * height;
|
||||
const pointOpacity = 0.4 + (1 - lastY / height) * 0.6;
|
||||
|
||||
// 外圈
|
||||
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity * 0.3})`;
|
||||
ctx.fillStyle = pointColor;
|
||||
ctx.globalAlpha = 0.4;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
|
||||
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 内圈
|
||||
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity})`;
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 2, 0, Math.PI * 2);
|
||||
ctx.arc(lastX, lastY, 1.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
@@ -228,31 +248,59 @@ const handleResize = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 仅监听系统主题变化
|
||||
const setupSystemThemeListener = () => {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleSystemThemeChange = () => {
|
||||
// 仅当设置为auto时才响应系统主题变化
|
||||
if (themeStore.currentTheme === SystemThemeType.SystemThemeAuto) {
|
||||
nextTick(() => drawChart());
|
||||
}
|
||||
};
|
||||
|
||||
// 添加监听器
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handleSystemThemeChange);
|
||||
}
|
||||
|
||||
// 返回清理函数
|
||||
return () => {
|
||||
if (mediaQuery.removeEventListener) {
|
||||
mediaQuery.removeEventListener('change', handleSystemThemeChange);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchMemoryStats();
|
||||
// 每3秒更新一次内存信息
|
||||
// 每1秒更新一次内存信息
|
||||
intervalId = setInterval(fetchMemoryStats, 3000);
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
|
||||
// 设置系统主题监听器(仅用于auto模式)
|
||||
const cleanupThemeListener = setupSystemThemeListener();
|
||||
|
||||
// 在卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
cleanupThemeListener();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="memory-monitor" @click="triggerGC" :title="`内存: ${formattedMemory} | 点击清理内存`">
|
||||
<div class="memory-monitor" @click="triggerGC" :title="`${t('monitor.memory')}: ${formattedMemory} | ${t('monitor.clickToClean')}`">
|
||||
<div class="monitor-info">
|
||||
<div class="memory-label">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||
</svg>
|
||||
<span>内存</span>
|
||||
<span>{{ t('monitor.memory') }}</span>
|
||||
</div>
|
||||
<div class="memory-value" v-if="!isLoading">{{ formattedMemory }}</div>
|
||||
<div class="memory-loading" v-else>--</div>
|
||||
@@ -279,11 +327,11 @@ onUnmounted(() => {
|
||||
&:hover {
|
||||
.monitor-info {
|
||||
.memory-label {
|
||||
color: #4a9eff;
|
||||
color: var(--selection-text);
|
||||
}
|
||||
|
||||
.memory-value {
|
||||
color: #ffffff;
|
||||
color: var(--toolbar-text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +349,7 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #a0a0a0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
@@ -318,7 +366,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.memory-value, .memory-loading {
|
||||
color: #e0e0e0;
|
||||
color: var(--toolbar-text-secondary);
|
||||
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
|
@@ -95,7 +95,7 @@ const selectDoc = async (doc: Document) => {
|
||||
closeMenu();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换文档失败:', error);
|
||||
console.error('Failed to switch documents:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ const createDoc = async (title: string) => {
|
||||
await selectDoc(newDoc);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建文档失败:', error);
|
||||
console.error('Failed to create document:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {onMounted, onUnmounted, ref, watch} from 'vue';
|
||||
import {onMounted, onUnmounted, ref, watch, computed} from 'vue';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useEditorStore} from '@/stores/editorStore';
|
||||
import {useUpdateStore} from '@/stores/updateStore';
|
||||
import {useDocumentStore} from '@/stores/documentStore';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import {useRouter} from 'vue-router';
|
||||
import BlockLanguageSelector from './BlockLanguageSelector.vue';
|
||||
@@ -16,7 +15,6 @@ import {formatBlockContent} from '@/views/editor/extensions/codeblock/formatCode
|
||||
const editorStore = useEditorStore();
|
||||
const configStore = useConfigStore();
|
||||
const updateStore = useUpdateStore();
|
||||
const documentStore = useDocumentStore();
|
||||
const {t} = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -154,6 +152,25 @@ watch(isLoaded, async (loaded) => {
|
||||
await setWindowAlwaysOnTop(true);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUpdateButtonClick = async () => {
|
||||
if (updateStore.hasUpdate && !updateStore.isUpdating && !updateStore.updateSuccess) {
|
||||
// 开始下载更新
|
||||
await updateStore.applyUpdate();
|
||||
} else if (updateStore.updateSuccess) {
|
||||
// 更新成功后,点击重启
|
||||
await updateStore.restartApplication();
|
||||
}
|
||||
};
|
||||
|
||||
// 更新按钮标题计算属性
|
||||
const updateButtonTitle = computed(() => {
|
||||
if (updateStore.isChecking) return t('settings.checking');
|
||||
if (updateStore.isUpdating) return t('settings.updating');
|
||||
if (updateStore.updateSuccess) return t('settings.updateSuccessRestartRequired');
|
||||
if (updateStore.hasUpdate) return `${t('settings.newVersionAvailable')}: ${updateStore.updateResult?.latestVersion || ''}`;
|
||||
return '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -203,14 +220,41 @@ watch(isLoaded, async (loaded) => {
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 更新提示图标 -->
|
||||
<!-- 更新按钮 - 根据状态显示不同图标 -->
|
||||
<div
|
||||
v-if="updateStore.hasUpdate"
|
||||
v-if="updateStore.hasUpdate || updateStore.isChecking || updateStore.isUpdating || updateStore.updateSuccess"
|
||||
class="update-button"
|
||||
:title="`发现新版本 ${updateStore.updateResult?.latestVer || ''}`"
|
||||
@click="updateStore.openReleaseURL"
|
||||
:class="{
|
||||
'checking': updateStore.isChecking,
|
||||
'updating': updateStore.isUpdating,
|
||||
'success': updateStore.updateSuccess,
|
||||
'available': updateStore.hasUpdate && !updateStore.isUpdating && !updateStore.updateSuccess
|
||||
}"
|
||||
:title="updateButtonTitle"
|
||||
@click="handleUpdateButtonClick"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
<!-- 检查更新中 -->
|
||||
<svg v-if="updateStore.isChecking" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rotating">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||
</svg>
|
||||
|
||||
<!-- 下载更新中 -->
|
||||
<svg v-else-if="updateStore.isUpdating" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rotating">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
|
||||
<path d="M12 2a10 10 0 1 0 10 10"></path>
|
||||
</svg>
|
||||
|
||||
<!-- 更新成功,等待重启 -->
|
||||
<svg v-else-if="updateStore.updateSuccess" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="pulsing">
|
||||
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path>
|
||||
<line x1="12" y1="2" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
|
||||
<!-- 有更新可用 -->
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="7.5,10.5 12,15 16.5,10.5"/>
|
||||
@@ -282,8 +326,7 @@ watch(isLoaded, async (loaded) => {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
|
||||
/* 更新提示按钮样式 */
|
||||
/* 更新按钮样式 */
|
||||
.update-button {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -293,17 +336,58 @@ watch(isLoaded, async (loaded) => {
|
||||
height: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(76, 175, 80, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(76, 175, 80, 0.2);
|
||||
transform: scale(1.05);
|
||||
/* 有更新可用状态 */
|
||||
&.available {
|
||||
background-color: rgba(76, 175, 80, 0.1);
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
svg {
|
||||
stroke: #4caf50;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(76, 175, 80, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* 检查更新中状态 */
|
||||
&.checking {
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
|
||||
svg {
|
||||
stroke: #ffc107;
|
||||
}
|
||||
}
|
||||
|
||||
/* 更新下载中状态 */
|
||||
&.updating {
|
||||
background-color: rgba(33, 150, 243, 0.1);
|
||||
|
||||
svg {
|
||||
stroke: #2196f3;
|
||||
}
|
||||
}
|
||||
|
||||
/* 更新成功状态 */
|
||||
&.success {
|
||||
background-color: rgba(156, 39, 176, 0.1);
|
||||
|
||||
svg {
|
||||
stroke: #9c27b0;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: #4caf50;
|
||||
/* 旋转动画 */
|
||||
.rotating {
|
||||
animation: rotate 1.5s linear infinite;
|
||||
}
|
||||
|
||||
/* 脉冲动画 */
|
||||
.pulsing {
|
||||
animation: pulse-strong 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
@@ -314,6 +398,26 @@ watch(isLoaded, async (loaded) => {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-strong {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 窗口置顶图标按钮样式 */
|
||||
|
@@ -156,15 +156,15 @@ export default {
|
||||
saveOptions: 'Save Options',
|
||||
autoSaveDelay: 'Auto Save Delay (ms)',
|
||||
updateSettings: 'Update Settings',
|
||||
autoCheckUpdates: 'Auto Check Updates',
|
||||
autoCheckUpdatesDescription: 'Automatically check for updates on startup',
|
||||
manualCheck: 'Manual Check',
|
||||
autoCheckUpdates: 'Automatically Check Updates',
|
||||
autoCheckUpdatesDescription: 'Check for updates when application starts',
|
||||
manualCheck: 'Manual Update',
|
||||
currentVersion: 'Current Version',
|
||||
checkForUpdates: 'Check for Updates',
|
||||
checking: 'Checking...',
|
||||
checkFailed: 'Check Failed',
|
||||
newVersionAvailable: 'New Version Available',
|
||||
upToDate: 'You are using the latest version',
|
||||
upToDate: 'Up to Date',
|
||||
viewUpdate: 'View Update',
|
||||
releaseNotes: 'Release Notes',
|
||||
networkError: 'Network connection error, please check your network settings',
|
||||
@@ -177,7 +177,12 @@ export default {
|
||||
configuration: 'Configuration',
|
||||
resetToDefault: 'Reset to Default Configuration',
|
||||
// Keep necessary extension interface translations, configuration items display in English directly
|
||||
}
|
||||
},
|
||||
updateNow: 'Update Now',
|
||||
updating: 'Updating...',
|
||||
updateSuccess: 'Update Success',
|
||||
updateSuccessRestartRequired: 'Update has been successfully applied. Please restart the application.',
|
||||
restartNow: 'Restart Now',
|
||||
},
|
||||
extensions: {
|
||||
rainbowBrackets: {
|
||||
@@ -216,5 +221,9 @@ export default {
|
||||
name: 'Checkbox',
|
||||
description: 'Render [x] and [ ] as interactive checkboxes'
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
memory: 'Memory',
|
||||
clickToClean: 'Click to clean memory'
|
||||
}
|
||||
};
|
@@ -158,17 +158,23 @@ export default {
|
||||
autoSaveDelay: '自动保存延迟(毫秒)',
|
||||
updateSettings: '更新设置',
|
||||
autoCheckUpdates: '自动检查更新',
|
||||
autoCheckUpdatesDescription: '启动应用时自动检查更新',
|
||||
manualCheck: '手动检查',
|
||||
autoCheckUpdatesDescription: '应用启动时自动检查更新',
|
||||
manualCheck: '手动更新',
|
||||
currentVersion: '当前版本',
|
||||
checkForUpdates: '检查更新',
|
||||
checking: '检查中...',
|
||||
checking: '正在检查...',
|
||||
checkFailed: '检查失败',
|
||||
newVersionAvailable: '发现新版本',
|
||||
upToDate: '您正在使用最新版本',
|
||||
upToDate: '已是最新版本',
|
||||
viewUpdate: '查看更新',
|
||||
releaseNotes: '更新内容',
|
||||
releaseNotes: '更新日志',
|
||||
networkError: '网络连接错误,请检查网络设置',
|
||||
updateNow: '立即更新',
|
||||
updating: '正在更新...',
|
||||
updateSuccess: '更新成功',
|
||||
updateSuccessRestartRequired: '更新已成功应用,请重启应用以生效',
|
||||
updateSuccessNoRestart: '更新已完成,无需重启',
|
||||
restartNow: '立即重启',
|
||||
extensionsPage: {
|
||||
loading: '加载中',
|
||||
categoryEditing: '编辑增强',
|
||||
@@ -216,5 +222,9 @@ export default {
|
||||
name: '选择框',
|
||||
description: '将 [x] 和 [ ] 渲染为可交互的选择框'
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
memory: '内存',
|
||||
clickToClean: '点击清理内存'
|
||||
}
|
||||
};
|
@@ -10,6 +10,7 @@ import {
|
||||
SystemThemeType,
|
||||
TabType,
|
||||
UpdatesConfig,
|
||||
UpdateSourceType,
|
||||
} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {ConfigUtils} from '@/utils/configUtils';
|
||||
@@ -77,7 +78,13 @@ const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
||||
|
||||
const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
||||
version: 'updates.version',
|
||||
autoUpdate: 'updates.autoUpdate'
|
||||
autoUpdate: 'updates.autoUpdate',
|
||||
primarySource: 'updates.primarySource',
|
||||
backupSource: 'updates.backupSource',
|
||||
backupBeforeUpdate: 'updates.backupBeforeUpdate',
|
||||
updateTimeout: 'updates.updateTimeout',
|
||||
github: 'updates.github',
|
||||
gitea: 'updates.gitea'
|
||||
} as const;
|
||||
|
||||
// 配置限制
|
||||
@@ -155,6 +162,19 @@ const DEFAULT_CONFIG: AppConfig = {
|
||||
updates: {
|
||||
version: "1.0.0",
|
||||
autoUpdate: true,
|
||||
primarySource: UpdateSourceType.UpdateSourceGithub,
|
||||
backupSource: UpdateSourceType.UpdateSourceGitea,
|
||||
backupBeforeUpdate: true,
|
||||
updateTimeout: 30,
|
||||
github: {
|
||||
owner: "landaiqing",
|
||||
repo: "voidraft",
|
||||
},
|
||||
gitea: {
|
||||
baseURL: "https://git.landaiqing.cn",
|
||||
owner: "landaiqing",
|
||||
repo: "voidraft",
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
version: '1.0.0',
|
||||
|
@@ -1,43 +1,90 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {computed, ref} from 'vue'
|
||||
import {CheckForUpdates} from '@/../bindings/voidraft/internal/services/updateservice'
|
||||
import {UpdateCheckResult} from '@/../bindings/voidraft/internal/services/models'
|
||||
import {CheckForUpdates, ApplyUpdate, RestartApplication} from '@/../bindings/voidraft/internal/services/selfupdateservice'
|
||||
import {SelfUpdateResult} from '@/../bindings/voidraft/internal/services/models'
|
||||
import {useConfigStore} from './configStore'
|
||||
import * as runtime from "@wailsio/runtime"
|
||||
|
||||
export const useUpdateStore = defineStore('update', () => {
|
||||
// 状态
|
||||
const isChecking = ref(false)
|
||||
const updateResult = ref<UpdateCheckResult | null>(null)
|
||||
const isUpdating = ref(false)
|
||||
const updateResult = ref<SelfUpdateResult | null>(null)
|
||||
const hasCheckedOnStartup = ref(false)
|
||||
const updateSuccess = ref(false)
|
||||
const errorMessage = ref('')
|
||||
|
||||
// 计算属性
|
||||
const hasUpdate = computed(() => updateResult.value?.hasUpdate || false)
|
||||
const errorMessage = computed(() => updateResult.value?.error || '')
|
||||
|
||||
// 检查更新
|
||||
const checkForUpdates = async (): Promise<boolean> => {
|
||||
if (isChecking.value) return false
|
||||
|
||||
// 重置错误信息
|
||||
errorMessage.value = ''
|
||||
isChecking.value = true
|
||||
try {
|
||||
const result = await CheckForUpdates()
|
||||
updateResult.value = result
|
||||
return !result.error
|
||||
if (result) {
|
||||
updateResult.value = result
|
||||
if (result.error) {
|
||||
errorMessage.value = result.error
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
updateResult.value = new UpdateCheckResult({
|
||||
hasUpdate: false,
|
||||
currentVer: '1.0.0',
|
||||
latestVer: '',
|
||||
releaseNotes: '',
|
||||
releaseURL: '',
|
||||
error: 'Network error'
|
||||
})
|
||||
errorMessage.value = error instanceof Error ? error.message : 'Network error'
|
||||
return false
|
||||
} finally {
|
||||
isChecking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 应用更新
|
||||
const applyUpdate = async (): Promise<boolean> => {
|
||||
if (isUpdating.value) return false
|
||||
|
||||
// 重置错误信息
|
||||
errorMessage.value = ''
|
||||
isUpdating.value = true
|
||||
try {
|
||||
const result = await ApplyUpdate()
|
||||
if (result) {
|
||||
updateResult.value = result
|
||||
|
||||
if (result.error) {
|
||||
errorMessage.value = result.error
|
||||
return false
|
||||
}
|
||||
|
||||
if (result.updateApplied) {
|
||||
updateSuccess.value = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : 'Update failed'
|
||||
return false
|
||||
} finally {
|
||||
isUpdating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重启应用
|
||||
const restartApplication = async (): Promise<boolean> => {
|
||||
try {
|
||||
await RestartApplication()
|
||||
return true
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : 'Restart failed'
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 启动时检查更新
|
||||
const checkOnStartup = async () => {
|
||||
if (hasCheckedOnStartup.value) return
|
||||
@@ -50,9 +97,9 @@ export const useUpdateStore = defineStore('update', () => {
|
||||
}
|
||||
|
||||
// 打开发布页面
|
||||
const openReleaseURL = () => {
|
||||
if (updateResult.value?.releaseURL) {
|
||||
window.open(updateResult.value.releaseURL, '_blank')
|
||||
const openReleaseURL = async () => {
|
||||
if (updateResult.value?.assetURL) {
|
||||
await runtime.Browser.OpenURL(updateResult.value.assetURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,20 +107,27 @@ export const useUpdateStore = defineStore('update', () => {
|
||||
const reset = () => {
|
||||
updateResult.value = null
|
||||
isChecking.value = false
|
||||
isUpdating.value = false
|
||||
updateSuccess.value = false
|
||||
errorMessage.value = ''
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isChecking,
|
||||
isUpdating,
|
||||
updateResult,
|
||||
hasCheckedOnStartup,
|
||||
updateSuccess,
|
||||
errorMessage,
|
||||
|
||||
// 计算属性
|
||||
hasUpdate,
|
||||
errorMessage,
|
||||
|
||||
// 方法
|
||||
checkForUpdates,
|
||||
applyUpdate,
|
||||
restartApplication,
|
||||
checkOnStartup,
|
||||
openReleaseURL,
|
||||
reset
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
ViewUpdate,
|
||||
} from '@codemirror/view';
|
||||
import { Extension, Range } from '@codemirror/state';
|
||||
|
||||
import * as runtime from "@wailsio/runtime"
|
||||
const pathStr = `<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"><path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z"></path><path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z"></path></svg>`;
|
||||
const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)\b/gi;
|
||||
|
||||
@@ -201,7 +201,8 @@ export const hyperLinkClickHandler = EditorView.domEventHandlers({
|
||||
if (target.classList.contains('cm-hyper-link-text')) {
|
||||
const url = target.getAttribute('data-url');
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
// window.open(url, '_blank', 'noopener,noreferrer');
|
||||
runtime.Browser.OpenURL(url).then()
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
@@ -1,16 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed, onMounted} from 'vue';
|
||||
import {computed, onMounted, ref} from 'vue';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useUpdateStore} from '@/stores/updateStore';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import { Remarkable } from 'remarkable';
|
||||
|
||||
const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const updateStore = useUpdateStore();
|
||||
|
||||
// 初始化Remarkable实例并配置
|
||||
const md = new Remarkable({
|
||||
html: true, // 允许HTML
|
||||
xhtmlOut: false, // 不使用'/'闭合单标签
|
||||
breaks: true, // 将'\n'转换为<br>
|
||||
typographer: true // 启用排版增强
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const autoCheckUpdates = computed({
|
||||
get: () => configStore.config.updates.autoUpdate,
|
||||
@@ -19,30 +28,30 @@ const autoCheckUpdates = computed({
|
||||
}
|
||||
});
|
||||
|
||||
// 格式化发布说明
|
||||
const formatReleaseNotes = (notes: string) => {
|
||||
if (!notes) return [];
|
||||
|
||||
// 简单的Markdown列表解析
|
||||
return notes
|
||||
.split('\n')
|
||||
.filter(line => line.trim().startsWith('-') || line.trim().startsWith('*'))
|
||||
.map(line => line.replace(/^[\s\-\*]+/, '').trim())
|
||||
.filter(line => line.length > 0);
|
||||
// 使用Remarkable解析Markdown
|
||||
const parseMarkdown = (markdown: string) => {
|
||||
if (!markdown) return '';
|
||||
return md.render(markdown);
|
||||
};
|
||||
|
||||
// 处理查看更新
|
||||
const viewUpdate = () => {
|
||||
updateStore.openReleaseURL();
|
||||
};
|
||||
|
||||
// 获取错误信息的国际化文本
|
||||
const getErrorMessage = (error: string) => {
|
||||
if (error.includes('Network') || error.includes('network')) {
|
||||
return t('settings.networkError');
|
||||
// 处理更新按钮点击
|
||||
const handleUpdateButtonClick = async () => {
|
||||
if (updateStore.updateSuccess) {
|
||||
// 如果更新成功,点击按钮重启应用
|
||||
await updateStore.restartApplication();
|
||||
} else if (updateStore.hasUpdate) {
|
||||
// 如果有更新,点击按钮应用更新
|
||||
await updateStore.applyUpdate();
|
||||
} else {
|
||||
// 否则检查更新
|
||||
await updateStore.checkForUpdates();
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
// 当前版本号
|
||||
const currentVersion = computed(() => {
|
||||
return updateStore.updateResult?.currentVersion || configStore.config.updates.version;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -60,15 +69,26 @@ const getErrorMessage = (error: string) => {
|
||||
<!-- 手动检查更新 -->
|
||||
<SettingSection :title="t('settings.manualCheck')">
|
||||
<SettingItem
|
||||
:title="`${t('settings.currentVersion')}: ${updateStore.updateResult?.currentVer || configStore.config.updates.version}`"
|
||||
:title="`${t('settings.currentVersion')}: ${currentVersion}`"
|
||||
>
|
||||
<button
|
||||
class="check-button"
|
||||
@click="updateStore.checkForUpdates"
|
||||
:disabled="updateStore.isChecking"
|
||||
:class="{
|
||||
'update-available-button': updateStore.hasUpdate && !updateStore.updateSuccess,
|
||||
'update-success-button': updateStore.updateSuccess
|
||||
}"
|
||||
@click="handleUpdateButtonClick"
|
||||
:disabled="updateStore.isChecking || updateStore.isUpdating"
|
||||
>
|
||||
<span v-if="updateStore.isChecking" class="loading-spinner"></span>
|
||||
{{ updateStore.isChecking ? t('settings.checking') : t('settings.checkForUpdates') }}
|
||||
<span v-if="updateStore.isChecking || updateStore.isUpdating" class="loading-spinner"></span>
|
||||
{{ updateStore.isChecking
|
||||
? t('settings.checking')
|
||||
: (updateStore.isUpdating
|
||||
? t('settings.updating')
|
||||
: (updateStore.updateSuccess
|
||||
? t('settings.restartNow')
|
||||
: (updateStore.hasUpdate ? t('settings.updateNow') : t('settings.checkForUpdates'))))
|
||||
}}
|
||||
</button>
|
||||
</SettingItem>
|
||||
|
||||
@@ -78,42 +98,40 @@ const getErrorMessage = (error: string) => {
|
||||
<div v-if="updateStore.errorMessage" class="result-item error-result">
|
||||
<div class="result-text">
|
||||
<span class="result-icon">⚠️</span>
|
||||
<span class="result-message">{{ getErrorMessage(updateStore.errorMessage) }}</span>
|
||||
<div class="result-message">{{ updateStore.errorMessage }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更新成功 -->
|
||||
<div v-else-if="updateStore.updateSuccess" class="result-item update-success">
|
||||
<div class="result-text">
|
||||
<span class="result-icon">✅</span>
|
||||
<span class="result-message">
|
||||
{{ t('settings.updateSuccessRestartRequired') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 有新版本 -->
|
||||
<div v-else-if="updateStore.hasUpdate" class="result-item update-result">
|
||||
<div class="result-header">
|
||||
<div class="result-text">
|
||||
<span class="result-icon">🎉</span>
|
||||
<span class="result-message">
|
||||
{{ t('settings.newVersionAvailable') }}: {{ updateStore.updateResult?.latestVer }}
|
||||
</span>
|
||||
</div>
|
||||
<button class="view-button" @click="viewUpdate">
|
||||
{{ t('settings.viewUpdate') }}
|
||||
</button>
|
||||
<div class="result-text">
|
||||
<span class="result-icon">🎉</span>
|
||||
<span class="result-message">
|
||||
{{ t('settings.newVersionAvailable') }}: {{ updateStore.updateResult?.latestVersion }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="updateStore.updateResult?.releaseNotes" class="release-notes">
|
||||
<div class="notes-title">{{ t('settings.releaseNotes') }}:</div>
|
||||
<ul class="notes-list" v-if="formatReleaseNotes(updateStore.updateResult.releaseNotes).length > 0">
|
||||
<li v-for="(note, index) in formatReleaseNotes(updateStore.updateResult.releaseNotes)" :key="index">
|
||||
{{ note }}
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="notes-text">
|
||||
{{ updateStore.updateResult.releaseNotes }}
|
||||
</div>
|
||||
<div class="markdown-content" v-html="parseMarkdown(updateStore.updateResult.releaseNotes)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已是最新版本 -->
|
||||
<div v-else-if="updateStore.updateResult && !updateStore.hasUpdate && !updateStore.errorMessage"
|
||||
class="result-item success-result">
|
||||
class="result-item latest-version">
|
||||
<div class="result-text">
|
||||
<span class="result-icon">✅</span>
|
||||
<span class="result-icon">✓</span>
|
||||
<span class="result-message">{{ t('settings.upToDate') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,6 +143,7 @@ const getErrorMessage = (error: string) => {
|
||||
<style scoped lang="scss">
|
||||
.settings-page {
|
||||
max-width: 800px;
|
||||
width: 100%; // 确保在小屏幕上也能占满可用空间
|
||||
}
|
||||
|
||||
.check-button {
|
||||
@@ -139,6 +158,8 @@ const getErrorMessage = (error: string) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 120px;
|
||||
justify-content: center;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--settings-hover);
|
||||
@@ -164,6 +185,28 @@ const getErrorMessage = (error: string) => {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
&.update-available-button {
|
||||
background-color: #2196f3;
|
||||
border-color: #2196f3;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #1976d2;
|
||||
border-color: #1976d2;
|
||||
}
|
||||
}
|
||||
|
||||
&.update-success-button {
|
||||
background-color: #4caf50;
|
||||
border-color: #4caf50;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #43a047;
|
||||
border-color: #43a047;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
@@ -172,122 +215,157 @@ const getErrorMessage = (error: string) => {
|
||||
}
|
||||
|
||||
.check-results {
|
||||
padding: 0 16px;
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
|
||||
// 为错误消息添加特殊样式
|
||||
.error-result {
|
||||
padding: 12px;
|
||||
background-color: rgba(255, 82, 82, 0.05);
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid var(--error-text, #ff5252);
|
||||
margin-bottom: 8px;
|
||||
|
||||
.result-message {
|
||||
color: var(--error-text, #ff5252);
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
padding-right: 8px; // 添加右侧内边距,防止文本贴近容器边缘
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 12px 0;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.result-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
line-height: 1.5; // 增加行高,提高可读性
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.view-button {
|
||||
padding: 4px 12px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
color: var(--settings-text);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--settings-hover);
|
||||
border-color: var(--settings-border);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.release-notes {
|
||||
margin-top: 8px;
|
||||
padding-left: 24px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--settings-border, rgba(0,0,0,0.1));
|
||||
|
||||
.notes-title {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
color: var(--settings-text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.notes-list {
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
|
||||
li {
|
||||
font-size: 12px;
|
||||
color: var(--settings-text-secondary);
|
||||
line-height: 1.4;
|
||||
margin-bottom: 3px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notes-text {
|
||||
.markdown-content {
|
||||
font-size: 12px;
|
||||
color: var(--settings-text-secondary);
|
||||
color: var(--settings-text);
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
|
||||
/* Markdown内容样式 */
|
||||
:deep(p) {
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
:deep(ul), :deep(ol) {
|
||||
margin: 6px 0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
||||
margin: 10px 0 6px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-result {
|
||||
.result-message {
|
||||
color: #f44336;
|
||||
}
|
||||
background-color: rgba(244, 67, 54, 0.03);
|
||||
|
||||
.result-icon {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
color: var(--error-text, #ff5252);
|
||||
}
|
||||
}
|
||||
|
||||
.update-result {
|
||||
background-color: rgba(33, 150, 243, 0.03);
|
||||
|
||||
.result-icon {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
color: #2196f3;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.update-success {
|
||||
background-color: rgba(76, 175, 80, 0.03);
|
||||
|
||||
.result-icon {
|
||||
color: #2196f3;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
color: var(--settings-text);
|
||||
}
|
||||
}
|
||||
|
||||
.success-result {
|
||||
.result-message {
|
||||
color: #4caf50;
|
||||
}
|
||||
.latest-version {
|
||||
background-color: transparent;
|
||||
border-left: 3px solid #9e9e9e;
|
||||
padding-left: 10px;
|
||||
|
||||
.result-icon {
|
||||
color: #4caf50;
|
||||
color: #9e9e9e;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
color: var(--settings-text-secondary, #757575);
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式布局调整
|
||||
@media (max-width: 600px) {
|
||||
.result-item {
|
||||
padding: 10px;
|
||||
|
||||
.result-text {
|
||||
font-size: 12px; // 小屏幕上稍微减小字体
|
||||
}
|
||||
}
|
||||
|
||||
.check-button {
|
||||
min-width: 100px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user