elfiee-fe-dev
1
总安装量
1
周安装量
#49175
全站排名
安装命令
npx skills add https://smithery.ai
Agent 安装分布
claude-code
1
Skill 文档
Elfiee å端å¼åæå
æ¦è§
Elfiee åç«¯ä½¿ç¨ React 18 + TypeScript + Vite æå»ºï¼éç¨ Zustand è¿è¡ç¶æç®¡çï¼Shadcn/ui + Tailwind CSS å¤çæ ·å¼ãå端éè¿ Tauri IPC ä¸ Rust å端éä¿¡ï¼ç±»åç± tauri-specta èªå¨çæã
ä¸å¤§ç¡¬æ§è§å
- åªè½éè¿ Zustand Actions æä½æ°æ® – ç»ä»¶ç¦æ¢ç´æ¥è°ç¨ TauriClient
- ç¦æ¢æå¨ç¼è¾ bindings.ts – ç± tauri-specta èªå¨çæ
- ä¸è¦ç´æ¥ä¿®æ¹ç¶æå¯¹è±¡ – å¿ é¡»éè¿ Actions
ç®å½ç»æ
src/
âââ bindings.ts # ãèªå¨çæãç¦æ¢æå¨ç¼è¾
âââ main.tsx # React å
¥å£
âââ App.tsx # 主åºç¨ç»ä»¶
âââ lib/
â âââ app-store.ts # ãæ ¸å¿ãZustand Store
â âââ tauri-client.ts # TauriClientï¼åªå¨ Actions ä¸ä½¿ç¨ï¼
â âââ utils.ts # å·¥å
·å½æ°ï¼cn çï¼
âââ components/
â âââ ui/ # Shadcn UI ç»ä»¶
â âââ editor/ # ç¼è¾å¨ç»ä»¶
â âââ dashboard/ # 仪表æ¿ç»ä»¶
âââ hooks/
â âââ use-toast.ts # Toast æç¤º
â âââ use-mobile.tsx # ç§»å¨ç«¯æ£æµ
âââ utils/
â âââ vfs-tree.ts # èææä»¶ç³»ç»å·¥å
·
âââ assets/ # éæèµæº
âââ images/
1. æ°æ®æµæ¶æ
ç»ä»¶ â Zustand Action â TauriClient â Tauri IPC â å端
â â
âââââââââââ State åå触åéæ°æ¸²æ ââââââââââââââââââ
å ³é®ç¹ï¼
- ç»ä»¶åªè°ç¨ Zustand Actionsï¼ä¸ç´æ¥ä½¿ç¨ TauriClient
- TauriClient åªå¨
app-store.tsç Actions å é¨ä½¿ç¨ - æ°æ®ä» Zustand Store 读åï¼UI èªå¨ååºç¶æåå
2. Zustand Store å¼å
2.1 Store ç»æ
// src/lib/app-store.ts
import { create } from 'zustand';
import { TauriClient } from '@/lib/tauri-client';
import type { Block, Editor } from '@/bindings';
interface AppStore {
// ============ State ============
blocks: Map<string, Block>;
editors: Map<string, Editor>;
activeFileId: string | null;
activeEditorId: string | null;
// ============ Actions ============
loadAllBlocks: (fileId: string) => Promise<void>;
createBlock: (fileId: string, name: string, type: string) => Promise<string>;
writeBlock: (fileId: string, blockId: string, content: string) => Promise<void>;
deleteBlock: (fileId: string, blockId: string) => Promise<void>;
}
export const useAppStore = create<AppStore>((set, get) => ({
// Initial State
blocks: new Map(),
editors: new Map(),
activeFileId: null,
activeEditorId: null,
// Actions
loadAllBlocks: async (fileId) => {
const blocks = await TauriClient.block.getAllBlocks(fileId);
const blocksMap = new Map(blocks.map(b => [b.block_id, b]));
set({ blocks: blocksMap });
},
createBlock: async (fileId, name, type) => {
const events = await TauriClient.block.createBlock(fileId, name, type);
const blockId = events[0].entity;
await get().loadAllBlocks(fileId); // éæ°å è½½
return blockId;
},
writeBlock: async (fileId, blockId, content) => {
await TauriClient.block.writeBlock(fileId, blockId, content, 'markdown');
const updatedBlock = await TauriClient.block.getBlock(fileId, blockId);
set((state) => {
const newBlocks = new Map(state.blocks);
newBlocks.set(blockId, updatedBlock);
return { blocks: newBlocks };
});
},
deleteBlock: async (fileId, blockId) => {
await TauriClient.block.deleteBlock(fileId, blockId);
set((state) => {
const newBlocks = new Map(state.blocks);
newBlocks.delete(blockId);
return { blocks: newBlocks };
});
},
}));
2.2 å¨ç»ä»¶ä¸ä½¿ç¨ Store
// src/components/editor/BlockEditor.tsx
import { useAppStore } from '@/lib/app-store';
import { Button } from '@/components/ui/button';
import { toast } from '@/hooks/use-toast';
function BlockEditor({ fileId, blockId }: Props) {
// ä» Store 读åç¶æ
const block = useAppStore((state) => state.blocks.get(blockId));
// è·å Actions
const writeBlock = useAppStore((state) => state.writeBlock);
const deleteBlock = useAppStore((state) => state.deleteBlock);
async function handleSave(content: string) {
try {
await writeBlock(fileId, blockId, content);
toast({ title: 'ä¿åæå' });
} catch (error) {
toast({
title: 'ä¿å失败',
description: error.message,
variant: 'destructive',
});
}
}
return (
<div>
<Editor value={block?.contents.markdown} onSave={handleSave} />
<Button onClick={() => deleteBlock(fileId, blockId)}>å é¤</Button>
</div>
);
}
3. ç±»åå®å ¨
3.1 ä» bindings.ts å¯¼å ¥ç±»å
// â
æ£ç¡®ï¼å¯¼å
¥èªå¨çæçç±»å
import type {
Block,
Command,
Event,
Editor,
MarkdownWritePayload,
CreateBlockPayload,
} from '@/bindings';
3.2 å¤ç JsonValue ç±»å
Block ç contents æ¯ JsonValueï¼éè¦ç±»åæè¨ï¼
const block: Block = ...;
// Markdown å
if (block.block_type === 'markdown') {
const markdown = (block.contents as { markdown?: string }).markdown || '';
}
// Code å
if (block.block_type === 'code') {
const code = (block.contents as { code?: string }).code || '';
const language = (block.contents as { language?: string }).language || 'plaintext';
}
3.3 使ç¨ç±»åå Payload
import type { MarkdownWritePayload } from '@/bindings';
// â
æ£ç¡®ï¼ä½¿ç¨ç±»åå Payload
const payload: MarkdownWritePayload = {
content: 'Hello World',
};
// TypeScript 伿£æ¥ç±»å
const wrongPayload: MarkdownWritePayload = {
content: { type: 'text' }, // â ç¼è¯é误
};
4. UI ç»ä»¶å¼å
4.1 ä½¿ç¨ Shadcn UI ç»ä»¶
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
function MyComponent() {
return (
<Card>
<CardHeader>
<CardTitle>æ é¢</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Input placeholder="è¾å
¥å
容" />
<Button>æäº¤</Button>
</CardContent>
</Card>
);
}
4.2 Tailwind CSS æ ·å¼
// ååºå¼è®¾è®¡
<div className="w-full md:w-1/2 lg:w-1/3">
{/* ç§»å¨ç«¯å
¨å®½ï¼å¹³æ¿ä¸åï¼æ¡é¢ä¸åä¹ä¸ */}
</div>
// ç¶æåä½
<button className="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 disabled:bg-gray-300">
Click me
</button>
// æ¡ä»¶ç±»å
import { cn } from '@/lib/utils';
<span className={cn(
'px-3 py-1 rounded-full text-sm',
variant === 'error' && 'bg-red-100 text-red-800',
variant === 'success' && 'bg-green-100 text-green-800',
)}>
{children}
</span>
4.3 éæèµæºä½¿ç¨
// â
åºç¨å
ç½®èµæºï¼ä½¿ç¨ import
import logoUrl from '@/assets/images/logo.svg';
<img src={logoUrl} alt="Logo" />
// â
ç¨æ·æ°æ®èµæºï¼ä½¿ç¨ convertFileSrc
import { convertFileSrc } from '@tauri-apps/api/core';
const url = convertFileSrc(filePath);
<img src={url} alt="User Image" />
5. é误å¤ç
5.1 å¨ç»ä»¶ä¸å¤çé误
import { toast } from '@/hooks/use-toast';
async function handleAction() {
try {
await someAction();
toast({ title: 'æä½æå' });
} catch (error) {
toast({
title: 'æä½å¤±è´¥',
description: error.message,
variant: 'destructive',
});
}
}
5.2 æéé误å¤ç
try {
await writeBlock(fileId, blockId, content);
} catch (error) {
if (error.message.includes('Permission denied')) {
toast({
title: 'æéä¸è¶³',
description: 'æ¨æ²¡ææéç¼è¾æ¤å',
variant: 'destructive',
});
} else {
toast({
title: 'ä¿å失败',
description: error.message,
variant: 'destructive',
});
}
}
6. æ§è½ä¼å
6.1 éæ©æ§è®¢é
// â é误ï¼è®¢é
æ´ä¸ª Store
function MyComponent() {
const store = useAppStore(); // ä»»ä½ååé½ä¼éæ°æ¸²æ
}
// â
æ£ç¡®ï¼åªè®¢é
éè¦çç¶æ
function MyComponent() {
const blockCount = useAppStore((state) => state.blocks.size);
}
// â
æ´å¥½ï¼ä½¿ç¨ shallow æ¯è¾
import { shallow } from 'zustand/shallow';
function MyComponent() {
const { blocks, editors } = useAppStore(
(state) => ({ blocks: state.blocks, editors: state.editors }),
shallow
);
}
7. 常è§é误ä¸é·é±
| é误 | åæ | è§£å³ |
|---|---|---|
| ç»ä»¶ç´æ¥è°ç¨ TauriClient | ç¶æä¸ä¸è´ | éè¿ Zustand Actions è°ç¨ |
| æå¨ç¼è¾ bindings.ts | 被è¦ç | å¨å端å®ä¹ç±»å |
| ç´æ¥ä¿®æ¹ç¶æå¯¹è±¡ | ä¸è§¦å渲æ | éè¿ Actions ä¿®æ¹ |
| è®¢é æ´ä¸ª Store | æ§è½é®é¢ | éæ©æ§è®¢é |
| å¿è®°å¤çé误 | ç¨æ·å°æ | try-catch + toast |
8. å¼åæ£æ¥æ¸ å
- ç»ä»¶åªè°ç¨ Zustand Actionsï¼ä¸ç´æ¥ä½¿ç¨ TauriClient
- TauriClient åªå¨
app-store.tsç Actions å é¨ä½¿ç¨ - 没ææå¨ç¼è¾
bindings.ts - Payload ç±»åä»
bindings.tså¯¼å ¥ - å¨ç»ä»¶ä¸ä½¿ç¨ try-catch å¤ç Action é误
- ä¸ç´æ¥ä¿®æ¹ç¶æå¯¹è±¡ï¼å¿ é¡»éè¿ Actions
- 使ç¨éæ©æ§è®¢é ä¼åæ§è½
- éæèµæºæ¾å¨
src/assets/å¹¶éè¿ import 使ç¨
9. éè¦åç«¯æ¯ææ¶
妿éè¦æ°åè½ä½ bindings.ts 䏿²¡æå¯¹åºæ¥å£ï¼
- ä¸è¦å°è¯å¨å端ç»è¿éå¶
- å¨å端添å Tauri Command æ Capability
- å¨ lib.rs 注åå½ä»¤åç±»å
- è¿è¡
pnpm tauri devçæ bindings.ts - å¨
app-store.tsæ·»å 对åºç Action - ç»ä»¶è°ç¨æ°ç Action
ç¸å ³ææ¡£
- 宿´è§èï¼
docs/mvp/guidelines/å端å¼åè§è.md - å¼åæµç¨ï¼
docs/mvp/guidelines/å¼åæµç¨.md - Tauri Spectaï¼
docs/guides/FRONTEND_DEVELOPMENT.md - æ¶ææ¦è§ï¼
docs/concepts/ARCHITECTURE_OVERVIEW.md