refactor(group): Refactor grouping to add logic and optimize validation rules.
- Remove direct database operations and use groupRepository instead. - Use notifyStore to unify notification messages.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<notification-provider />
|
||||
<v-main>
|
||||
<v-card class="fill-height">
|
||||
<v-layout class="fill-height">
|
||||
@@ -53,6 +54,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import notificationProvider from './components/notify/notificationProvider.vue'
|
||||
|
||||
const drawer = ref(true)
|
||||
const router = useRouter()
|
||||
|
||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
AddNode: typeof import('./components/nodeEdit/addNode.vue')['default']
|
||||
NodeList: typeof import('./components/nodeEdit/nodeList.vue')['default']
|
||||
NodesFloatButton: typeof import('./components/nodeEdit/nodesFloatButton.vue')['default']
|
||||
NotificationProvider: typeof import('./components/notify/notificationProvider.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Spary: typeof import('./components/index/spary.vue')['default']
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted, computed} from 'vue'
|
||||
import {ref, computed} from 'vue'
|
||||
import {invoke} from '@tauri-apps/api/core'
|
||||
import Database from '@tauri-apps/plugin-sql'
|
||||
|
||||
const db = ref<any>(null)
|
||||
const db_ready = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
db.value = await Database.load('sqlite:spary.db')
|
||||
db_ready.value = true
|
||||
})
|
||||
import {groupRepository} from "@/entities/group.ts";
|
||||
import {notify} from "@/components/notify/notifyStore.ts";
|
||||
|
||||
const groupName = ref('')
|
||||
const groupNameRule = [
|
||||
(value: string): boolean | string => {
|
||||
if (value?.length >= 3 && value?.length <= 15) return true
|
||||
return 'Group name must be between 3 and 15 characters.'
|
||||
if (value?.length >= 1 && value?.length <= 15) return true
|
||||
return 'Group name must be between 1 and 15 characters.'
|
||||
},
|
||||
]
|
||||
|
||||
@@ -38,43 +31,31 @@ const groupArguments = ref<string | null>(null)
|
||||
const isAdding = ref(false)
|
||||
|
||||
const isAddDisabled = computed(() => {
|
||||
return !groupName.value || groupName.value.length < 3 || groupName.value.length > 15 || isAdding.value || !db_ready.value
|
||||
return !groupName.value || groupName.value.length < 3 || groupName.value.length > 15 || isAdding.value
|
||||
})
|
||||
|
||||
async function addGroup() {
|
||||
if (!isAddDisabled.value) {
|
||||
add_group(groupName.value, groupSubscribeUrl.value, groupArguments.value)
|
||||
await add_group(groupName.value, groupSubscribeUrl.value, groupArguments.value)
|
||||
|
||||
const check_repeat_one = await db.value.select(
|
||||
"SELECT * FROM `group` WHERE name = ?",
|
||||
[groupName.value]
|
||||
)
|
||||
const check_repeat_one = await groupRepository.findByName(groupName.value)
|
||||
console.log(check_repeat_one)
|
||||
if (check_repeat_one.length > 0) {
|
||||
alert("Group already exists.")
|
||||
notify("Group already exists.", {
|
||||
color: "error"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let add_result
|
||||
const params = [groupName.value];
|
||||
let sqlStmt = "INSERT INTO `group` (name";
|
||||
|
||||
if (groupSubscribeUrl.value) {
|
||||
sqlStmt += ", url";
|
||||
params.push(groupSubscribeUrl.value);
|
||||
}
|
||||
|
||||
if (groupArguments.value) {
|
||||
sqlStmt += ", arguments";
|
||||
params.push(groupArguments.value);
|
||||
}
|
||||
|
||||
sqlStmt += ") VALUES (" + params.map(() => "?").join(", ") + ")";
|
||||
add_result = await db.value.execute(sqlStmt, params);
|
||||
|
||||
if (add_result.rowsAffected > 0) {
|
||||
alert("Group added.")
|
||||
await groupRepository.insert(
|
||||
{
|
||||
created_at: null, id: null, updated_at: null,
|
||||
name: groupName.value,
|
||||
url: groupSubscribeUrl.value,
|
||||
arguments: groupArguments.value
|
||||
}
|
||||
)
|
||||
notify("Group added.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +67,7 @@ async function add_group(groupName: string, groupSubscribeUrl: string | null, gr
|
||||
isAdding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,90 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import {invoke} from '@tauri-apps/api/core'
|
||||
import Database from '@tauri-apps/plugin-sql'
|
||||
import {ref, onMounted, computed} from 'vue'
|
||||
import {Group, groupRepository} from "@/entities/group.ts";
|
||||
import {nodeRepository} from "@/entities/node.ts";
|
||||
import {notify} from "@/components/notify/notifyStore.ts";
|
||||
|
||||
defineProps<{
|
||||
groupId: string
|
||||
}>()
|
||||
|
||||
const db = ref<any>(null)
|
||||
const db_ready = ref(false)
|
||||
const props = defineProps<{ groupId: string }>()
|
||||
|
||||
const allGroups = ref<Group[]>([])
|
||||
|
||||
const selectedGroupId = ref<number | null>(null)
|
||||
|
||||
const loadGroups = async () => {
|
||||
allGroups.value = await groupRepository.findAll()
|
||||
const group = allGroups.value.find(g => g.id === Number(props.groupId))
|
||||
selectedGroupId.value = group ? group.id : null
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
db.value = await Database.load('sqlite:spary.db')
|
||||
db_ready.value = true
|
||||
})
|
||||
onMounted(loadGroups)
|
||||
|
||||
const nodeAlias = ref('')
|
||||
|
||||
const nodeArguments = ref<string | null>(null)
|
||||
const isAdding = ref(false)
|
||||
|
||||
const isAddDisabled = computed(() => {
|
||||
return !nodeAlias.value || nodeAlias.value.length < 3 || nodeAlias.value.length > 15 || isAdding.value || !db_ready.value
|
||||
return !nodeAlias.value || nodeAlias.value.length < 3 || nodeAlias.value.length > 15 || isAdding.value
|
||||
})
|
||||
|
||||
async function addNode() {
|
||||
if (!isAddDisabled.value) {
|
||||
await add_node(nodeAlias.value, nodeArguments.value)
|
||||
|
||||
const check_repeat_one = await db.value.select(
|
||||
"SELECT * FROM `node` WHERE alias = ?",
|
||||
[nodeAlias.value]
|
||||
)
|
||||
console.log(check_repeat_one)
|
||||
if (check_repeat_one.length > 0) {
|
||||
alert("Node already exists.")
|
||||
return
|
||||
}
|
||||
|
||||
let add_result
|
||||
const params = [nodeAlias.value];
|
||||
let sqlStmt = "INSERT INTO `node` (alias";
|
||||
|
||||
if (nodeArguments.value) {
|
||||
sqlStmt += ", arguments";
|
||||
params.push(nodeArguments.value);
|
||||
}
|
||||
|
||||
sqlStmt += ") VALUES (" + params.map(() => "?").join(", ") + ")";
|
||||
add_result = await db.value.execute(sqlStmt, params);
|
||||
|
||||
if (add_result.rowsAffected > 0) {
|
||||
alert("Node added.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function add_node(nodeAlias: string, nodeArguments: string | null) {
|
||||
if (!isAddDisabled.value && selectedGroupId.value !== null) {
|
||||
isAdding.value = true
|
||||
try {
|
||||
await invoke("add_node", {nodeAlias, nodeArguments})
|
||||
} finally {
|
||||
await nodeRepository.insert({
|
||||
created_at: null,
|
||||
id: null,
|
||||
updated_at: null,
|
||||
alias: nodeAlias.value,
|
||||
arguments: nodeArguments.value,
|
||||
group_id: selectedGroupId.value
|
||||
})
|
||||
notify("Node added successfully")
|
||||
isAdding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
loadGroups()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<v-sheet class="mx-auto" width="80vw">
|
||||
<v-form fast-fail @submit.prevent>
|
||||
<v-text-field
|
||||
v-model="nodeAlias"
|
||||
label="Node alias"
|
||||
></v-text-field>
|
||||
<v-select
|
||||
v-model="selectedGroupId"
|
||||
label="Group"
|
||||
:items="allGroups"
|
||||
item-title="name"
|
||||
@@ -92,6 +54,11 @@ loadGroups()
|
||||
variant="solo"
|
||||
></v-select>
|
||||
|
||||
<v-text-field
|
||||
v-model="nodeAlias"
|
||||
label="Node alias"
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="nodeArguments"
|
||||
label="Arguments"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import Database from "@tauri-apps/plugin-sql";
|
||||
import {useRouter} from "vue-router";
|
||||
import {groupRepository} from "@/entities/group.ts";
|
||||
import {nodeRepository} from "@/entities/node.ts";
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -17,19 +18,13 @@ interface Group {
|
||||
id: number
|
||||
}
|
||||
|
||||
const db = ref<any>(null)
|
||||
const panels = ref([])
|
||||
const groups = ref<Group[]>([])
|
||||
|
||||
const loadData = async () => {
|
||||
db.value = await Database.load('sqlite:spary.db')
|
||||
|
||||
const groupsInDb = await db.value.select(
|
||||
'SELECT * FROM `group`'
|
||||
)
|
||||
const nodesInDb = await db.value.select(
|
||||
'SELECT * FROM `node`'
|
||||
)
|
||||
const groupsInDb = await groupRepository.findAll()
|
||||
const nodesInDb = await nodeRepository.findAll()
|
||||
|
||||
for (let i = 0; i < groupsInDb.length; i++) {
|
||||
const group: any = groupsInDb[i]
|
||||
|
||||
37
src/components/notify/notificationProvider.vue
Normal file
37
src/components/notify/notificationProvider.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-snackbar
|
||||
v-for="(item, index) in notifyQueue"
|
||||
:key="item.id"
|
||||
v-model="visible[item.id]"
|
||||
:timeout="item.timeout"
|
||||
:color="item.color"
|
||||
:variant="item.variant"
|
||||
top
|
||||
right
|
||||
:style="{
|
||||
zIndex: 9999,
|
||||
marginTop: `${index * 70 + 16}px` // 每条通知向下偏移
|
||||
}"
|
||||
>
|
||||
{{ item.message }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch } from 'vue'
|
||||
import { notifyQueue } from './notifyStore'
|
||||
|
||||
const visible: Record<number, boolean> = reactive({})
|
||||
|
||||
watch(
|
||||
() => notifyQueue.slice(),
|
||||
(queue) => {
|
||||
queue.forEach(item => {
|
||||
if (visible[item.id] === undefined) visible[item.id] = true
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
39
src/components/notify/notifyStore.ts
Normal file
39
src/components/notify/notifyStore.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export interface NotifyOptions {
|
||||
color?: string
|
||||
timeout?: number
|
||||
variant?: string
|
||||
}
|
||||
|
||||
// 通知对象
|
||||
export interface NotifyItem {
|
||||
id: number
|
||||
message: string
|
||||
color: string
|
||||
timeout: number
|
||||
variant: string
|
||||
}
|
||||
|
||||
// 队列状态
|
||||
export const notifyQueue = reactive<NotifyItem[]>([])
|
||||
|
||||
// 全局可调用 notify
|
||||
let nextId = 1
|
||||
export function notify(message: string, options?: NotifyOptions) {
|
||||
const id = nextId++
|
||||
const item: NotifyItem = {
|
||||
id,
|
||||
message,
|
||||
color: options?.color ?? 'success',
|
||||
timeout: options?.timeout ?? 3000,
|
||||
variant: options?.variant ?? 'outlined'
|
||||
}
|
||||
notifyQueue.push(item)
|
||||
|
||||
// 自动移除
|
||||
setTimeout(() => {
|
||||
const index = notifyQueue.findIndex(i => i.id === id)
|
||||
if (index !== -1) notifyQueue.splice(index, 1)
|
||||
}, item.timeout)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import {z} from "zod";
|
||||
import {getDatabase} from "@/utils/db.ts";
|
||||
import {DBDefaultDateTime} from "@/utils/common.ts";
|
||||
import {DBDefaultDateTime} from "@/utils/zodCommon.ts";
|
||||
|
||||
export const GroupSchema = z.object({
|
||||
id: z.number(),
|
||||
id: z.number().nullable(),
|
||||
name: z.string(),
|
||||
url: z.string().nullable().optional(),
|
||||
arguments: z
|
||||
|
||||
55
src/entities/node.ts
Normal file
55
src/entities/node.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {z} from "zod";
|
||||
import {getDatabase} from "@/utils/db.ts";
|
||||
import {DBDefaultDateTime} from "@/utils/zodCommon.ts";
|
||||
|
||||
export const NodeSchema = z.object({
|
||||
id: z.number().nullable(),
|
||||
alias: z.string(),
|
||||
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,
|
||||
group_id: z.number(),
|
||||
});
|
||||
export type Node = z.infer<typeof NodeSchema>;
|
||||
|
||||
export class NodeRepository {
|
||||
async findAll(): Promise<Node[]> {
|
||||
const db = await getDatabase();
|
||||
const rows = await db.select(`SELECT *
|
||||
FROM "node"
|
||||
ORDER BY id DESC`);
|
||||
return NodeSchema.array().parse(rows);
|
||||
}
|
||||
|
||||
async insert(node: Node): Promise<void> {
|
||||
const db = await getDatabase();
|
||||
await db.execute(`INSERT INTO "node" (alias, arguments, group_id)
|
||||
VALUES (?, ?, ?)`, [
|
||||
node.alias,
|
||||
JSON.stringify(node.arguments),
|
||||
node.group_id
|
||||
]);
|
||||
}
|
||||
|
||||
async findByAlias(alias: string): Promise<Node[]> {
|
||||
const db = await getDatabase();
|
||||
const rows = await db.select(`SELECT *
|
||||
FROM "node"
|
||||
WHERE alias = ?`, [alias]);
|
||||
return NodeSchema.array().parse(rows);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const nodeRepository = new NodeRepository();
|
||||
@@ -1,9 +0,0 @@
|
||||
// 自定义日期时间格式 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" }
|
||||
);
|
||||
10
src/utils/zodCommon.ts
Normal file
10
src/utils/zodCommon.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// 自定义日期时间格式 yyyy-MM-dd HH:mm:ss
|
||||
import {z} from "zod";
|
||||
|
||||
export const DBDefaultDateTime = z
|
||||
.string()
|
||||
.nullable()
|
||||
.refine(
|
||||
val => 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"}
|
||||
);
|
||||
Reference in New Issue
Block a user