feat: add option to open links in external browser and fix app setting css error

This commit is contained in:
Kuingsmile
2025-07-09 15:30:32 +08:00
parent 83b3caf521
commit 96e995051e
11 changed files with 99 additions and 26 deletions

View File

@@ -61,6 +61,17 @@ pub async fn open_url(url: String) -> Result<bool, String> {
Ok(true)
}
#[tauri::command]
pub async fn open_url_in_browser(url: String, app_handle: AppHandle) -> Result<bool, String> {
use tauri_plugin_opener::OpenerExt;
app_handle
.opener()
.open_url(url, None::<&str>)
.map_err(|e| e.to_string())?;
Ok(true)
}
#[tauri::command]
pub fn select_directory(title: String, app_handle: AppHandle) -> Result<Option<String>, String> {
use tauri_plugin_dialog::DialogExt;

View File

@@ -7,6 +7,7 @@ pub struct AppConfig {
pub auto_update_enabled: Option<bool>,
pub gh_proxy: Option<String>,
pub gh_proxy_api: Option<bool>,
pub open_links_in_browser: Option<bool>,
}
impl AppConfig {
@@ -17,6 +18,7 @@ impl AppConfig {
auto_update_enabled: Some(true),
gh_proxy: None,
gh_proxy_api: Some(false),
open_links_in_browser: Some(false),
}
}
}

View File

@@ -19,8 +19,8 @@ use cmd::http_api::{
use cmd::logs::{clear_logs, get_admin_password, get_logs};
use cmd::openlist_core::{create_openlist_core_process, get_openlist_core_status};
use cmd::os_operate::{
get_available_versions, list_files, open_file, open_folder, open_url, select_directory,
update_tool_version,
get_available_versions, list_files, open_file, open_folder, open_url, open_url_in_browser,
select_directory, update_tool_version,
};
use cmd::rclone_core::{
create_and_start_rclone_backend, create_rclone_backend_process, get_rclone_backend_status,
@@ -139,6 +139,7 @@ pub fn run() {
open_file,
open_folder,
open_url,
open_url_in_browser,
save_settings,
save_settings_with_update_port,
load_settings,

View File

@@ -60,7 +60,8 @@ export class TauriAPI {
list: (path: string): Promise<FileItem[]> => call('list_files', { path }),
open: (path: string): Promise<boolean> => call('open_file', { path }),
folder: (path: string): Promise<boolean> => call('open_folder', { path }),
url: (path: string): Promise<boolean> => call('open_url', { path })
url: (path: string): Promise<boolean> => call('open_url', { path }),
urlInBrowser: (url: string): Promise<boolean> => call('open_url_in_browser', { url })
}
// --- Settings management ---

View File

@@ -27,7 +27,13 @@ const navigationItems = computed(() => [
const openLink = async (url: string) => {
try {
await TauriAPI.files.url(url)
const openInBrowser = appStore.settings.app.open_links_in_browser ?? false
if (openInBrowser) {
await TauriAPI.files.urlInBrowser(url)
} else {
await TauriAPI.files.url(url)
}
} catch (error) {
console.error('Failed to open link:', error)
window.open(url, '_blank')

View File

@@ -79,8 +79,10 @@ import { useTranslation } from '../../composables/useI18n'
import { ExternalLink, Github, BookOpen, Cloud, Code, Terminal, HelpCircle, MessageCircle } from 'lucide-vue-next'
import Card from '../ui/Card.vue'
import { TauriAPI } from '../../api/tauri'
import { useAppStore } from '../../stores/app'
const { t } = useTranslation()
const appStore = useAppStore()
const openOpenListDocs = () => {
openLink('https://docs.oplist.org/')
@@ -100,7 +102,13 @@ const openRcloneGitHub = () => {
const openLink = async (url: string) => {
try {
await TauriAPI.files.url(url)
const openInBrowser = appStore.settings.app.open_links_in_browser ?? false
if (openInBrowser) {
await TauriAPI.files.urlInBrowser(url)
} else {
await TauriAPI.files.url(url)
}
} catch (error) {
console.error('Failed to open link:', error)
window.open(url, '_blank')

View File

@@ -183,6 +183,14 @@
"description": "Also use proxy for api.github.com URLs "
}
},
"links": {
"title": "Link Handling",
"subtitle": "Configure how links are opened",
"openInBrowser": {
"title": "Open links in external browser",
"description": "Use system default browser instead of built-in window"
}
},
"tutorial": {
"title": "Tutorial",
"subtitle": "Learn how to use OpenList Desktop",

View File

@@ -183,6 +183,14 @@
"description": "同时为 api.github.com 地址使用代理"
}
},
"links": {
"title": "链接处理",
"subtitle": "配置链接的打开方式",
"openInBrowser": {
"title": "在外部浏览器中打开链接",
"description": "使用系统默认浏览器而不是内置窗口"
}
},
"tutorial": {
"title": "教程",
"subtitle": "学习如何使用 OpenList 桌面版",

View File

@@ -49,6 +49,7 @@ interface AppConfig {
auto_update_enabled?: boolean
gh_proxy?: string
gh_proxy_api?: boolean
open_links_in_browser?: boolean
}
interface MergedSettings {

View File

@@ -72,6 +72,7 @@ onMounted(async () => {
if (appSettings.auto_update_enabled === undefined) appSettings.auto_update_enabled = true
if (!appSettings.gh_proxy) appSettings.gh_proxy = ''
if (appSettings.gh_proxy_api === undefined) appSettings.gh_proxy_api = false
if (appSettings.open_links_in_browser === undefined) appSettings.open_links_in_browser = false
originalOpenlistPort = openlistCoreSettings.port || 5244
})
@@ -378,6 +379,22 @@ const handleReset = async () => {
</div>
</div>
<div class="settings-section">
<h2>{{ t('settings.app.links.title') }}</h2>
<p>{{ t('settings.app.links.subtitle') }}</p>
<div class="form-group">
<label class="switch-label">
<input v-model="appSettings.open_links_in_browser" type="checkbox" class="switch-input" />
<span class="switch-slider"></span>
<div class="switch-content">
<span class="switch-title">{{ t('settings.app.links.openInBrowser.title') }}</span>
<span class="switch-description">{{ t('settings.app.links.openInBrowser.description') }}</span>
</div>
</label>
</div>
</div>
<div class="settings-section">
<h2>{{ t('settings.app.tutorial.title') }}</h2>
<p>{{ t('settings.app.tutorial.subtitle') }}</p>

View File

@@ -1,6 +1,6 @@
.settings-container {
padding: 2rem;
height: 100vh;
min-height: 100vh;
background: var(--color-background-secondary);
color: var(--color-text-primary);
overflow-y: auto;
@@ -229,6 +229,7 @@
border-radius: 12px;
overflow: hidden;
box-shadow: var(--shadow-sm);
min-height: fit-content;
}
:root.dark .settings-content,
@@ -239,6 +240,7 @@
.tab-content {
padding: 2rem;
min-height: fit-content;
}
.settings-section {
@@ -390,7 +392,7 @@
/* Switch */
.switch-label {
display: flex;
align-items: flex-start;
align-items: center;
gap: 0.75rem;
cursor: pointer;
padding: 1rem;
@@ -398,22 +400,24 @@
border-radius: 8px;
background: var(--color-background-tertiary);
transition: all 0.2s ease;
min-height: auto;
}
.switch-label:hover {
background: rgb(243 244 246);
border-color: rgb(209 213 219);
background: var(--color-background-secondary);
border-color: var(--color-border);
}
@media (prefers-color-scheme: dark) {
.switch-label {
background: rgb(55 65 81);
border-color: rgb(75 85 99);
}
:root.dark .switch-label,
:root.auto.dark .switch-label {
background: var(--color-background-tertiary);
border-color: var(--color-border);
}
.switch-label:hover {
background: rgb(75 85 99);
}
:root.dark .switch-label:hover,
:root.auto.dark .switch-label:hover {
background: var(--color-background-secondary);
border-color: var(--color-border);
}
.switch-input {
@@ -426,11 +430,10 @@
position: relative;
width: 44px;
height: 24px;
background: rgb(209 213 219);
background: var(--color-border);
border-radius: 12px;
transition: all 0.2s ease;
flex-shrink: 0;
margin-top: 2px;
}
.switch-slider::before {
@@ -447,7 +450,7 @@
}
.switch-input:checked + .switch-slider {
background: rgb(59 130 246);
background: var(--color-accent);
}
.switch-input:checked + .switch-slider::before {
@@ -458,23 +461,30 @@
display: flex;
flex-direction: column;
gap: 0.125rem;
flex: 1;
}
.switch-title {
font-weight: 500;
color: rgb(55 65 81);
color: var(--color-text-primary);
font-size: 0.875rem;
line-height: 1.25;
}
.switch-description {
font-size: 0.75rem;
color: rgb(107 114 128);
color: var(--color-text-secondary);
line-height: 1.25;
}
@media (prefers-color-scheme: dark) {
.switch-title {
color: rgb(209 213 219);
}
:root.dark .switch-title,
:root.auto.dark .switch-title {
color: var(--color-text-primary);
}
:root.dark .switch-description,
:root.auto.dark .switch-description {
color: var(--color-text-secondary);
}
/* Flags */