Compare commits

...

10 Commits

Author SHA1 Message Date
0be8ff5d80 修复进本类型错误 2025-08-22 09:11:44 +08:00
80406ea9fc DP3创建基本页面,问题多多 2025-08-22 08:52:02 +08:00
f4a88349f8 初始化结构 2025-08-22 00:00:20 +08:00
Nicola Spadari
e3dd2a7c47 Merge branch 'develop' 2025-07-16 14:33:36 +02:00
Nicola Spadari
a1a3c29941 chore: bump pnpm version for ci 2025-07-16 14:33:18 +02:00
Nicola Spadari
cd187be35a Merge branch 'main' into develop 2025-07-16 14:32:03 +02:00
Nicola Spadari
d762044658 chore: bump version 2025-07-16 14:31:49 +02:00
Nicola Spadari
09100c53ca Merge branch 'develop' 2025-07-16 14:31:06 +02:00
Nicola Spadari
d7261725e8 fix: remove unused module 2025-07-16 14:30:48 +02:00
Nicola Spadari
f78b083553 chore: reference tsconfig from nuxt 4 2025-07-16 14:23:00 +02:00
27 changed files with 14184 additions and 13270 deletions

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.9.0
version: 10.13.1
- name: install frontend dependencies
run: pnpm install

View File

@@ -33,17 +33,4 @@
}
}
.page-enter-active,
.page-leave-active {
@apply transition-opacity ease-in-out duration-300;
}
.layout-enter-active,
.layout-leave-active {
@apply transition-opacity ease-in-out duration-500;
}
.page-enter-from,
.page-leave-to,
.layout-enter-from,
.layout-leave-to {
@apply opacity-0;
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="top=1/5 pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -z-10" aria-hidden="true">
<div class="blob relative left-[calc(50%+36rem)] aspect-[1155/678] w-[72.1875rem] from-(--color-warning) to-(--color-success) bg-gradient-to-br opacity-30 -translate-x-1/2" />
<div class="blob relative left-[calc(50%+36rem)] aspect-[1155/678] w-[72.1875rem] from-(--color-error) to-(--color-success) bg-gradient-to-br opacity-30 -translate-x-1/2" />
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div class="pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -top-1/4 -z-10" aria-hidden="true">
<div class="blob relative left-[calc(50%-30rem)] aspect-[1155/678] w-[72.1875rem] rotate-30 from-(--color-warning) to-(--color-success) bg-gradient-to-tr opacity-30 -translate-x-1/2" />
<div class="blob relative left-[calc(50%-30rem)] aspect-[1155/678] w-[72.1875rem] rotate-30 from-(--color-error) to-(--color-success) bg-gradient-to-tr opacity-30 -translate-x-1/2" />
</div>
</template>

View File

@@ -0,0 +1,10 @@
import type { NodeDefinition } from "~/types/flow";
export default {
type: "delay",
label: "延时",
category: "控制",
inputs: [{ id: "in", label: "In", type: "flow" }],
outputs: [{ id: "out", label: "Out", type: "flow" }],
defaultConfig: { milliseconds: 1000 }
} as NodeDefinition;

View File

@@ -0,0 +1,81 @@
<template>
<div class="delay-node" :style="{ borderColor: node.data?.color || '#ff6b35' }">
<div class="node-header">
<svg class="node-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke-width="2" />
<path d="M12 6v6l4 2" stroke-width="2" stroke-linecap="round" />
</svg>
<span class="node-title">延时</span>
</div>
<div class="node-content">
<span class="delay-time">{{ node.data?.config?.milliseconds || 1000 }}ms</span>
</div>
<Handle
id="in"
type="target"
:position="Position.Top"
class="handle"
/>
<Handle
id="out"
type="source"
:position="Position.Bottom"
class="handle"
/>
</div>
</template>
<script setup lang="ts">
import { Handle, Position } from "@vue-flow/core";
const _props = defineProps<{
node: any
}>();
</script>
<style scoped>
.delay-node {
background: white;
border: 2px solid #ff6b35;
border-radius: 8px;
padding: 12px;
min-width: 120px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.node-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.node-icon {
width: 16px;
height: 16px;
color: #ff6b35;
}
.node-title {
font-weight: 600;
font-size: 14px;
color: #333;
}
.node-content {
text-align: center;
}
.delay-time {
font-size: 12px;
color: #666;
}
.handle {
width: 8px;
height: 8px;
background: #fff;
border: 2px solid #ff6b35;
border-radius: 50%;
}
</style>

View File

@@ -0,0 +1,10 @@
import type { NodeDefinition } from "~/types/flow";
export default {
type: "log",
label: "日志",
category: "工具",
inputs: [{ id: "in", label: "In", type: "flow" }],
outputs: [{ id: "out", label: "Out", type: "flow" }],
defaultConfig: { message: "Hello World" }
} as NodeDefinition;

View File

@@ -0,0 +1,84 @@
<template>
<div class="log-node" :style="{ borderColor: node.data?.color || '#10b981' }">
<div class="node-header">
<svg class="node-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke-width="2" />
<path d="M14 2v6h6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M16 13H8" stroke-width="2" stroke-linecap="round" />
<path d="M16 17H8" stroke-width="2" stroke-linecap="round" />
<path d="M10 9H8" stroke-width="2" stroke-linecap="round" />
</svg>
<span class="node-title">日志</span>
</div>
<div class="node-content">
<span class="log-message">{{ node.data?.config?.message || '输出日志' }}</span>
</div>
<Handle
id="in"
type="target"
:position="Position.Top"
class="handle"
/>
<Handle
id="out"
type="source"
:position="Position.Bottom"
class="handle"
/>
</div>
</template>
<script setup lang="ts">
import { Handle, Position } from "@vue-flow/core";
const _props = defineProps<{
node: any
}>();
</script>
<style scoped>
.log-node {
background: white;
border: 2px solid #10b981;
border-radius: 8px;
padding: 12px;
min-width: 120px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.node-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.node-icon {
width: 16px;
height: 16px;
color: #10b981;
}
.node-title {
font-weight: 600;
font-size: 14px;
color: #333;
}
.node-content {
text-align: center;
}
.log-message {
font-size: 12px;
color: #666;
}
.handle {
width: 8px;
height: 8px;
background: #fff;
border: 2px solid #10b981;
border-radius: 50%;
}
</style>

View File

@@ -0,0 +1,20 @@
import type { NodeDefinition } from "~/types/flow";
const nodeModules = import.meta.glob("./*/index.ts", { eager: true });
export const flowNodes: NodeDefinition[] = Object.values(nodeModules).map(
(module: any) => module.default
);
export const getNodeDefinitionsByCategory = () => {
const categories: Record<string, NodeDefinition[]> = {};
flowNodes.forEach((node) => {
if (!categories[node.category]) {
categories[node.category] = [];
}
categories[node.category].push(node);
});
return categories;
};

View File

@@ -1,5 +1,6 @@
<template>
<header class="top-0 z-10">
<UColorModeButton />
<UContainer class="md:py-2">
<UNavigationMenu
:items="mobileItems"

5
app/layouts/flow.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div>
<slot />
</div>
</template>

View File

@@ -44,9 +44,9 @@
<div class="flex justify-center">
<UButton
:to="app.repo"
to="/workflows/flow"
>
Star on GitHub
进入工作流
</UButton>
</div>
</div>

View File

@@ -14,7 +14,6 @@
category: "system",
description: "Read operating system informations."
});
const items = ref([
{
label: "System",

View File

@@ -0,0 +1,154 @@
<template>
<div class="h-screen flex flex-col">
<!-- Toolbar -->
<div class="h-12 bg-gray-100 border-b border-gray-200 flex items-center px-4 gap-3">
<UButton @click="saveFlow">
保存
</UButton>
<UButton @click="importFlow">
导入
</UButton>
<UButton color="primary" @click="executeNodes">
执行
</UButton>
</div>
<div class="flex flex-1 overflow-hidden">
<!-- Node Panel -->
<div class="w-80 bg-white border-r border-gray-200 overflow-y-auto p-4">
<h2 class="text-lg font-semibold mb-4">
节点库
</h2>
<div v-for="(nodes, category) in nodeCategories" :key="category" class="mb-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">
{{ category }}
</h3>
<div class="space-y-2">
<div
v-for="node in nodes"
:key="node.type"
class="p-3 border border-gray-200 rounded-md cursor-move bg-white hover:bg-gray-50 transition-colors"
draggable="true"
@dragstart="(e) => onDragStart(e, node)"
>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-blue-100 rounded" />
<span class="text-sm font-medium">{{ node.label }}</span>
</div>
<div class="text-xs text-gray-500 mt-1">
{{ node.inputs.length }}输入 / {{ node.outputs.length }}输出
</div>
</div>
</div>
</div>
</div>
<!-- Flow Canvas -->
<div class="flex-1 relative">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:node-types="nodeTypes"
:fit-view-on-init="true"
@dragover.prevent
@drop="onDrop"
@node-double-click="(event: NodeMouseEvent) => onNodeDoubleClick(event)"
>
<Background />
<Controls />
<MiniMap />
</VueFlow>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { NodeMouseEvent } from "@vue-flow/core";
import type { Component } from "vue";
import type { FlowNode } from "~/types/flow";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import { useVueFlow, VueFlow } from "@vue-flow/core";
import { MiniMap } from "@vue-flow/minimap";
import { v4 as uuidv4 } from "uuid";
import { computed, defineAsyncComponent, ref } from "vue";
import { getNodeDefinitionsByCategory } from "~/components/Flow/Nodes";
definePageMeta({
layout: "flow"
});
const nodeCategories = ref(getNodeDefinitionsByCategory());
const { nodes, edges, addNodes, project } = useVueFlow();
// 注册节点类型
const nodeTypes: Record<string, Component> = {
delay: defineAsyncComponent(() => import("~/components/Flow/Nodes/DelayNode.vue")),
log: defineAsyncComponent(() => import("~/components/Flow/Nodes/LogNode.vue"))
};
const allNodeDefinitions = computed(() => {
const categories = nodeCategories.value;
return Object.values(categories).flat();
});
const onDragStart = (event: DragEvent, node: any) => {
event.dataTransfer?.setData("application/vueflow", node.type);
event.dataTransfer!.effectAllowed = "move";
};
const onDrop = (event: DragEvent) => {
const nodeType = event.dataTransfer?.getData("application/vueflow");
if (!nodeType) return;
const position = project({ x: event.clientX, y: event.clientY });
const nodeDefinition = allNodeDefinitions.value.find((n) => n.type === nodeType);
if (!nodeDefinition) return;
const newNode: FlowNode = {
id: uuidv4(),
type: nodeType,
position,
data: {
config: { ...nodeDefinition.defaultConfig }
}
};
addNodes([newNode]);
};
const onNodeDoubleClick = (event: NodeMouseEvent) => {
console.log("编辑节点:", event.node);
// 这里可以打开右侧抽屉或弹窗编辑节点配置
};
const saveFlow = () => {
const flowData = {
nodes: nodes.value,
edges: edges.value
};
console.log("保存", flowData);
};
const importFlow = () => {
console.log("导入");
// 这里可以实现文件选择器读取 JSON 并还原画布
};
const executeNodes = () => {
const flowData = {
nodes: nodes.value,
edges: edges.value
};
console.log("执行", flowData);
};
</script>
<style>
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
</style>

32
app/types/flow.ts Normal file
View File

@@ -0,0 +1,32 @@
export interface NodeDefinition {
type: string
label: string
category: string
inputs: PortDefinition[]
outputs: PortDefinition[]
defaultConfig: Record<string, any>
}
export interface PortDefinition {
id: string
label: string
type: "flow" | "data"
}
export interface FlowNode {
id: string
type: string
position: { x: number, y: number }
data: {
config: Record<string, any>
color?: string
}
}
export interface FlowEdge {
id: string
source: string
target: string
sourceHandle?: string
targetHandle?: string
}

View File

@@ -1,9 +1,8 @@
export default defineNuxtConfig({
modules: [
"@vueuse/nuxt",
"@nuxt/ui",
"@nuxt/ui-pro",
"nuxt-svgo",
"nuxt-bits",
"reka-ui/nuxt",
"@nuxt/eslint"
],

View File

@@ -1,7 +1,7 @@
{
"name": "nuxtor",
"type": "module",
"version": "1.3.1",
"version": "1.4.0",
"private": true,
"packageManager": "pnpm@10.13.1",
"description": "Starter template for Nuxt 3 and Tauri 2",
@@ -23,14 +23,20 @@
"tauri:build:debug": "tauri build --debug"
},
"dependencies": {
"@nuxt/ui": "^3.2.0",
"@nuxt/ui-pro": "^3.2.0",
"@tauri-apps/api": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.0",
"@tauri-apps/plugin-notification": "^2.3.0",
"@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-store": "^2.3.0",
"@types/uuid": "^10.0.0",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.46.0",
"@vue-flow/minimap": "^1.5.4",
"nuxt": "^4.0.0",
"uuid": "^11.1.0",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
"zod": "^4.0.5"

960
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
src-tauri/Cargo.lock generated
View File

@@ -2174,7 +2174,7 @@ dependencies = [
[[package]]
name = "nuxtor"
version = "1.3.1"
version = "1.4.0"
dependencies = [
"serde",
"serde_json",

View File

@@ -1,6 +1,6 @@
[package]
name = "nuxtor"
version = "1.3.1"
version = "1.4.0"
description = "Starter template for Nuxt 3 and Tauri 2"
authors = [ "NicolaSpadari" ]
license = "MIT"

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Capabilities for the app window","local":true,"windows":["main","secondary"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":["-c",{"validator":"\\S+"}],"cmd":"sh","name":"exec-sh","sidecar":false}]},"notification:default","os:allow-platform","os:allow-arch","os:allow-family","os:allow-version","os:allow-locale","fs:allow-document-read","fs:allow-document-write","store:default","core:webview:allow-create-webview","core:webview:allow-create-webview-window"]}}
{ "main": { "identifier": "main", "description": "Capabilities for the app window", "local": true, "windows": ["main", "secondary"], "permissions": ["core:path:default", "core:event:default", "core:window:default", "core:app:default", "core:resources:default", "core:menu:default", "core:tray:default", "shell:allow-open", { "identifier": "shell:allow-execute", "allow": [{ "args": ["-c", { "validator": "\\S+" }], "cmd": "sh", "name": "exec-sh", "sidecar": false }] }, "notification:default", "os:allow-platform", "os:allow-arch", "os:allow-family", "os:allow-version", "os:allow-locale", "fs:allow-document-read", "fs:allow-document-write", "store:default", "core:webview:allow-create-webview", "core:webview:allow-create-webview-window"] } }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@
"devUrl": "http://localhost:3000"
},
"productName": "Nuxtor",
"version": "1.3.1",
"version": "1.4.0",
"identifier": "com.nicolaspadari.nuxtor",
"plugins": {},
"app": {
@@ -43,7 +43,7 @@
"height": 768,
"minWidth": 375,
"minHeight": 812,
"resizable": true,
"resizable": false,
"fullscreen": false
}
],

View File

@@ -1,13 +1,22 @@
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"strict": true
"module": "ESNext"
},
"include": [
".nuxt/nuxt.d.ts",
"env.d.ts",
"**/*"
]
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
],
"files": []
}

1
tsconfig.tsbuildinfo Normal file
View File

@@ -0,0 +1 @@
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"module":99,"target":99},"version":"5.7.3"}