remove all software
This commit is contained in:
@@ -8,7 +8,7 @@ use std::path::PathBuf;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tauri::{AppHandle, Manager, State, Emitter};
|
use tauri::{AppHandle, Manager, State, Emitter};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use winget::{Software, list_all_software, list_updates, ensure_winget_dependencies};
|
use winget::{Software, list_installed_software, list_updates, ensure_winget_dependencies};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
@@ -155,8 +155,8 @@ fn get_essentials(app: AppHandle) -> Option<EssentialsRepo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_all_software(app: AppHandle) -> Vec<Software> {
|
async fn get_installed_software(app: AppHandle) -> Vec<Software> {
|
||||||
tokio::task::spawn_blocking(move || list_all_software(&app)).await.unwrap_or_default()
|
tokio::task::spawn_blocking(move || list_installed_software(&app)).await.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -308,7 +308,7 @@ pub fn run() {
|
|||||||
save_settings,
|
save_settings,
|
||||||
sync_essentials,
|
sync_essentials,
|
||||||
get_essentials,
|
get_essentials,
|
||||||
get_all_software,
|
get_installed_software,
|
||||||
get_updates,
|
get_updates,
|
||||||
install_software,
|
install_software,
|
||||||
get_logs_history
|
get_logs_history
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ pub fn ensure_winget_dependencies(handle: &AppHandle) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_all_software(handle: &AppHandle) -> Vec<Software> {
|
pub fn list_installed_software(handle: &AppHandle) -> Vec<Software> {
|
||||||
let log_id = format!("list-all-{}", chrono::Local::now().timestamp_millis());
|
let log_id = format!("list-installed-{}", chrono::Local::now().timestamp_millis());
|
||||||
let script = r#"
|
let script = r#"
|
||||||
$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
$ErrorActionPreference = 'SilentlyContinue'
|
$ErrorActionPreference = 'SilentlyContinue'
|
||||||
@@ -125,7 +125,7 @@ pub fn list_all_software(handle: &AppHandle) -> Vec<Software> {
|
|||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
execute_powershell(handle, &log_id, "Fetch All Software", script)
|
execute_powershell(handle, &log_id, "Fetch Installed Software", script)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_updates(handle: &AppHandle) -> Vec<Software> {
|
pub fn list_updates(handle: &AppHandle) -> Vec<Software> {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/updates" class="nav-item">
|
<router-link to="/updates" class="nav-item">
|
||||||
<span class="nav-icon">
|
<span class="nav-icon">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M21 2v6h-6"></path>
|
<path d="M21 2v6h-6"></path>
|
||||||
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
|
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
|
||||||
<path d="M3 22v-6h6"></path>
|
<path d="M3 22v-6h6"></path>
|
||||||
@@ -23,16 +23,6 @@
|
|||||||
</span>
|
</span>
|
||||||
软件更新
|
软件更新
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/all" class="nav-item">
|
|
||||||
<span class="nav-icon">
|
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 8a2 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"></path>
|
|
||||||
<path d="m3.3 7 8.7 5 8.7-5"></path>
|
|
||||||
<path d="M12 22V12"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
全部软件
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<!-- 底部选项 -->
|
<!-- 底部选项 -->
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ const router = createRouter({
|
|||||||
path: '/updates',
|
path: '/updates',
|
||||||
component: () => import('../views/Updates.vue')
|
component: () => import('../views/Updates.vue')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/all',
|
|
||||||
component: () => import('../views/AllSoftware.vue')
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
component: () => import('../views/Logs.vue')
|
component: () => import('../views/Logs.vue')
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
sortedUpdates: (state) => [...state.updates].sort(sortByName),
|
sortedUpdates: (state) => [...state.updates].sort(sortByName),
|
||||||
sortedAllSoftware: (state) => [...state.allSoftware].sort(sortByName),
|
|
||||||
isBusy: (state) => {
|
isBusy: (state) => {
|
||||||
const allItems = [...state.essentials, ...state.updates, ...state.allSoftware];
|
const allItems = [...state.essentials, ...state.updates, ...state.allSoftware];
|
||||||
return allItems.some(item => item.status === 'pending' || item.status === 'installing');
|
return allItems.some(item => item.status === 'pending' || item.status === 'installing');
|
||||||
@@ -159,16 +158,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchAll() {
|
|
||||||
if (this.isBusy) return;
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
const res = await invoke('get_all_software')
|
|
||||||
this.allSoftware = res as any[]
|
|
||||||
} finally {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async syncDataIfNeeded(force = false) {
|
async syncDataIfNeeded(force = false) {
|
||||||
if (this.isBusy) return;
|
if (this.isBusy) return;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -187,7 +176,7 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
|
|
||||||
// 然后同步本地软件安装/更新状态,不再强制联网下载 JSON
|
// 然后同步本地软件安装/更新状态,不再强制联网下载 JSON
|
||||||
const [all, updates] = await Promise.all([
|
const [all, updates] = await Promise.all([
|
||||||
invoke('get_all_software'),
|
invoke('get_installed_software'),
|
||||||
invoke('get_updates')
|
invoke('get_updates')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
<template>
|
|
||||||
<main class="content">
|
|
||||||
<header class="content-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<h1>全部软件</h1>
|
|
||||||
<p class="count">共 {{ filteredSoftware.length }} 个项目</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-actions">
|
|
||||||
<button
|
|
||||||
@click="store.fetchAll"
|
|
||||||
class="secondary-btn action-btn"
|
|
||||||
:disabled="store.loading || store.isBusy"
|
|
||||||
>
|
|
||||||
<span class="icon" :class="{ 'spinning': store.loading }">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 2v6h-6"></path>
|
|
||||||
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
|
|
||||||
<path d="M3 22v-6h6"></path>
|
|
||||||
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
{{ store.loading ? '正在扫描...' : '重新扫描' }}
|
|
||||||
</button>
|
|
||||||
<div class="search-box">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
v-model="searchQuery"
|
|
||||||
placeholder="搜索已安装的软件..."
|
|
||||||
class="search-input"
|
|
||||||
:disabled="store.isBusy"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div v-if="store.loading && store.sortedAllSoftware.length === 0" class="loading-state">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在读取已安装软件列表...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="software-list">
|
|
||||||
<SoftwareCard
|
|
||||||
v-for="item in filteredSoftware"
|
|
||||||
:key="item.id"
|
|
||||||
:software="item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import SoftwareCard from '../components/SoftwareCard.vue';
|
|
||||||
import { useSoftwareStore } from '../store/software';
|
|
||||||
import { onMounted, ref, computed } from 'vue';
|
|
||||||
|
|
||||||
const store = useSoftwareStore();
|
|
||||||
const searchQuery = ref('');
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (store.allSoftware.length === 0) {
|
|
||||||
store.fetchAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredSoftware = computed(() => {
|
|
||||||
const data = store.sortedAllSoftware;
|
|
||||||
if (!searchQuery.value) return data;
|
|
||||||
const q = searchQuery.value.toLowerCase();
|
|
||||||
return data.filter(s =>
|
|
||||||
s.name.toLowerCase().includes(q) || s.id.toLowerCase().includes(q)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
padding: 40px 60px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-left h1 {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-left .count {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--text-sec);
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background-color: var(--bg-light);
|
|
||||||
color: var(--text-main);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:hover:not(:disabled) {
|
|
||||||
background-color: white;
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.spinning {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
width: 240px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background-color: white;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input:focus {
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input:disabled {
|
|
||||||
background-color: #F2F2F7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.software-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 300px;
|
|
||||||
color: var(--text-sec);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border: 3px solid rgba(0, 122, 255, 0.1);
|
|
||||||
border-top-color: var(--primary-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user