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:
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,90 +44,65 @@ 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>
|
||||||
<!-- 加载中时显示骨架屏 -->
|
<v-expansion-panels v-model="panels" multiple>
|
||||||
<template v-if="loading">
|
<v-expansion-panel
|
||||||
<v-skeleton-loader type="heading" class="mb-4"/>
|
v-for="(group, i) in groups"
|
||||||
|
:key="i"
|
||||||
|
elevation="2"
|
||||||
|
class="my-3"
|
||||||
|
>
|
||||||
|
<v-expansion-panel-title>
|
||||||
|
<div class="d-flex align-center justify-space-between w-100">
|
||||||
|
<span class="text-h6">{{ group.title }}</span>
|
||||||
|
|
||||||
<v-row dense>
|
<v-btn
|
||||||
<v-col
|
size="small"
|
||||||
v-for="i in 8"
|
color="primary"
|
||||||
:key="i"
|
variant="tonal"
|
||||||
cols="12"
|
prepend-icon="mdi-plus"
|
||||||
sm="6"
|
@click.stop="addItem(group)"
|
||||||
md="4"
|
>
|
||||||
lg="3"
|
add
|
||||||
>
|
</v-btn>
|
||||||
<v-skeleton-loader type="card" class="ma-2"/>
|
</div>
|
||||||
</v-col>
|
</v-expansion-panel-title>
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 加载完成后显示实际内容 -->
|
<v-expansion-panel-text>
|
||||||
<template v-else>
|
<v-row dense>
|
||||||
<v-expansion-panels v-model="panels" multiple>
|
<v-col
|
||||||
<v-expansion-panel
|
v-for="(item, j) in group.items"
|
||||||
v-for="(group, i) in groups"
|
:key="j"
|
||||||
:key="i"
|
cols="12"
|
||||||
elevation="2"
|
sm="6"
|
||||||
class="my-3"
|
md="4"
|
||||||
>
|
lg="3"
|
||||||
<v-expansion-panel-title>
|
>
|
||||||
<div class="d-flex align-center justify-space-between w-100">
|
<v-card elevation="3" class="ma-2">
|
||||||
<span class="text-h6">{{ group.title }}</span>
|
<v-card-title class="text-subtitle-1">
|
||||||
|
{{ item.name }}
|
||||||
<v-btn
|
</v-card-title>
|
||||||
size="small"
|
<v-card-text>
|
||||||
color="primary"
|
<div>{{ item.url }}</div>
|
||||||
variant="tonal"
|
<div>usage: {{ item.traffic }}</div>
|
||||||
prepend-icon="mdi-plus"
|
</v-card-text>
|
||||||
@click.stop="addItem(group)"
|
</v-card>
|
||||||
>
|
</v-col>
|
||||||
add
|
</v-row>
|
||||||
</v-btn>
|
</v-expansion-panel-text>
|
||||||
</div>
|
</v-expansion-panel>
|
||||||
</v-expansion-panel-title>
|
</v-expansion-panels>
|
||||||
|
|
||||||
<v-expansion-panel-text>
|
|
||||||
<v-row dense>
|
|
||||||
<v-col
|
|
||||||
v-for="(item, j) in group.items"
|
|
||||||
:key="j"
|
|
||||||
cols="12"
|
|
||||||
sm="6"
|
|
||||||
md="4"
|
|
||||||
lg="3"
|
|
||||||
>
|
|
||||||
<v-card elevation="3" class="ma-2">
|
|
||||||
<v-card-title class="text-subtitle-1">
|
|
||||||
{{ item.name }}
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<div>{{ item.url }}</div>
|
|
||||||
<div>usage: {{ item.traffic }}</div>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-expansion-panel-text>
|
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
</template>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
57
src/entities/group.ts
Normal file
57
src/entities/group.ts
Normal 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
9
src/utils/common.ts
Normal 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
5
src/utils/db.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import Database from "@tauri-apps/plugin-sql";
|
||||||
|
|
||||||
|
export async function getDatabase () {
|
||||||
|
return await Database.load('sqlite:spary.db');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user