feat(node): Implements node group selection functionality

- Introduces the Group entity and groupRepository
- Adds a new database utility function, getDatabase, to centrally retrieve database instances
- Removes the loading state and skeleton screen logic from nodeList to simplify the page structure
- Updates dependencies and adds the zod library for data validation
This commit is contained in:
2025-10-15 12:53:51 +08:00
parent edb026e6ed
commit 8988766d09
7 changed files with 462 additions and 395 deletions

View File

@@ -17,7 +17,8 @@
"@tauri-apps/plugin-sql": "~2", "@tauri-apps/plugin-sql": "~2",
"vue": "^3.5.21", "vue": "^3.5.21",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"vuetify": "^3.10.1" "vuetify": "^3.10.1",
"zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",

View File

@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, onMounted, computed} from 'vue' import {computed, onMounted, ref} from 'vue'
import {invoke} from '@tauri-apps/api/core' import {invoke} from '@tauri-apps/api/core'
import Database from '@tauri-apps/plugin-sql' import Database from '@tauri-apps/plugin-sql'
import {Group, groupRepository} from "@/entities/group.ts";
defineProps<{ defineProps<{
groupId: string groupId: string
@@ -10,6 +11,13 @@ defineProps<{
const db = ref<any>(null) const db = ref<any>(null)
const db_ready = ref(false) const db_ready = ref(false)
const allGroups = ref<Group[]>([])
const loadGroups = async () => {
allGroups.value = await groupRepository.findAll()
}
onMounted(async () => { onMounted(async () => {
db.value = await Database.load('sqlite:spary.db') db.value = await Database.load('sqlite:spary.db')
db_ready.value = true db_ready.value = true
@@ -64,6 +72,8 @@ async function add_node(nodeAlias: string, nodeArguments: string | null) {
isAdding.value = false isAdding.value = false
} }
} }
loadGroups()
</script> </script>
<template> <template>
@@ -74,7 +84,13 @@ async function add_node(nodeAlias: string, nodeArguments: string | null) {
v-model="nodeAlias" v-model="nodeAlias"
label="Node alias" label="Node alias"
></v-text-field> ></v-text-field>
<v-select></v-select> <v-select
label="Group"
:items="allGroups"
item-title="name"
item-value="id"
variant="solo"
></v-select>
<v-textarea <v-textarea
v-model="nodeArguments" v-model="nodeArguments"

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, onMounted} from 'vue' import {ref} from 'vue'
import Database from "@tauri-apps/plugin-sql"; import Database from "@tauri-apps/plugin-sql";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
const router = useRouter()
const router = useRouter()
interface GroupItem { interface GroupItem {
name: string name: string
@@ -19,10 +19,9 @@ interface Group {
const db = ref<any>(null) const db = ref<any>(null)
const panels = ref([]) const panels = ref([])
const loading = ref(true) // 是否在加载中
const groups = ref<Group[]>([]) const groups = ref<Group[]>([])
onMounted(async () => { const loadData = async () => {
db.value = await Database.load('sqlite:spary.db') db.value = await Database.load('sqlite:spary.db')
const groupsInDb = await db.value.select( const groupsInDb = await db.value.select(
@@ -45,42 +44,18 @@ onMounted(async () => {
id: group.id id: group.id
}) })
} }
}
// 模拟异步加载(例如从 API 获取)
await new Promise(resolve => setTimeout(resolve, 100))
loading.value = false
})
function addItem(group: Group) { function addItem(group: Group) {
console.log(group.id) console.log(group.id)
router.push(`/addNode/${group.id}`) router.push(`/addNode/${group.id}`)
} }
loadData()
</script> </script>
<template> <template>
<v-container> <v-container>
<!-- 加载中时显示骨架屏 -->
<template v-if="loading">
<v-skeleton-loader type="heading" class="mb-4"/>
<v-row dense>
<v-col
v-for="i in 8"
:key="i"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-skeleton-loader type="card" class="ma-2"/>
</v-col>
</v-row>
</template>
<!-- 加载完成后显示实际内容 -->
<template v-else>
<v-expansion-panels v-model="panels" multiple> <v-expansion-panels v-model="panels" multiple>
<v-expansion-panel <v-expansion-panel
v-for="(group, i) in groups" v-for="(group, i) in groups"
@@ -128,7 +103,6 @@ function addItem(group: Group) {
</v-expansion-panel-text> </v-expansion-panel-text>
</v-expansion-panel> </v-expansion-panel>
</v-expansion-panels> </v-expansion-panels>
</template>
</v-container> </v-container>
</template> </template>

57
src/entities/group.ts Normal file
View File

@@ -0,0 +1,57 @@
import {z} from "zod";
import {getDatabase} from "@/utils/db.ts";
import {DBDefaultDateTime} from "@/utils/common.ts";
export const GroupSchema = z.object({
id: z.number(),
name: z.string(),
url: z.string().nullable().optional(),
arguments: z
.union([z.string(), z.record(z.any(), z.any())]) // 兼容 SQLite JSON 字段可能返回 string 或 object
.transform(val => {
if (typeof val === "string") {
try {
return JSON.parse(val);
} catch {
return {};
}
}
return val ?? {};
}),
created_at: DBDefaultDateTime,
updated_at: DBDefaultDateTime,
});
export type Group = z.infer<typeof GroupSchema>;
export class GroupRepository {
async findAll(): Promise<Group[]> {
const db = await getDatabase();
const rows = await db.select(`SELECT *
FROM "group"
ORDER BY id DESC`);
console.log(
rows
)
return GroupSchema.array().parse(rows);
}
async insert(group: Group): Promise<void> {
const db = await getDatabase();
await db.execute(`INSERT INTO "group" (name, url, arguments)
VALUES (?, ?, ?)`, [
group.name,
group.url,
JSON.stringify(group.arguments),
]);
}
async findByName(name: string): Promise<Group[]> {
const db = await getDatabase();
const rows = await db.select(`SELECT *
FROM "group"
WHERE name = ?`, [name]);
return GroupSchema.array().parse(rows);
}
}
export const groupRepository = new GroupRepository();

9
src/utils/common.ts Normal file
View File

@@ -0,0 +1,9 @@
// 自定义日期时间格式 yyyy-MM-dd HH:mm:ss
import {z} from "zod";
export const DBDefaultDateTime = z
.string()
.refine(
val => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(val),
{ message: "Invalid datetime format, expected yyyy-MM-dd HH:mm:ss" }
);

5
src/utils/db.ts Normal file
View File

@@ -0,0 +1,5 @@
import Database from "@tauri-apps/plugin-sql";
export async function getDatabase () {
return await Database.load('sqlite:spary.db');
}

635
yarn.lock

File diff suppressed because it is too large Load Diff