feat: add new Flowme and FlowmeChannel management with CRUD operations and UI components
This commit is contained in:
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./dist/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
214
public/sse-test.html
Normal file
214
public/sse-test.html
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>SSE 测试</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.status.connected { background: #d4edda; color: #155724; }
|
||||||
|
.status.connecting { background: #fff3cd; color: #856404; }
|
||||||
|
.status.disconnected { background: #f8d7da; color: #721c24; }
|
||||||
|
.events {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
.event-item {
|
||||||
|
padding: 8px;
|
||||||
|
margin: 5px 0;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.event-item .time {
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.event-item .data {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>SSE / HTTP Stream 测试</h1>
|
||||||
|
<div>
|
||||||
|
<label>URL: </label>
|
||||||
|
<input type="text" id="url" value="http://localhost:4005/root/v3/" style="width: 400px;">
|
||||||
|
<label>类型: </label>
|
||||||
|
<select id="streamType">
|
||||||
|
<option value="sse">SSE (EventSource)</option>
|
||||||
|
<option value="stream">HTTP Stream (Fetch)</option>
|
||||||
|
</select>
|
||||||
|
<button onclick="connect()">连接</button>
|
||||||
|
<button onclick="disconnect()">断开</button>
|
||||||
|
</div>
|
||||||
|
<div id="status" class="status disconnected">未连接</div>
|
||||||
|
<h3>事件日志</h3>
|
||||||
|
<div id="events" class="events"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let eventSource = null;
|
||||||
|
let abortController = null;
|
||||||
|
|
||||||
|
async function connect() {
|
||||||
|
const url = document.getElementById('url').value;
|
||||||
|
const type = document.getElementById('streamType').value;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
alert('请输入 URL');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
const eventsDiv = document.getElementById('events');
|
||||||
|
eventsDiv.innerHTML = '';
|
||||||
|
updateStatus('connecting', '正在连接...');
|
||||||
|
|
||||||
|
if (type === 'sse') {
|
||||||
|
connectSSE(url);
|
||||||
|
} else {
|
||||||
|
connectStream(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectSSE(url) {
|
||||||
|
try {
|
||||||
|
eventSource = new EventSource(url);
|
||||||
|
|
||||||
|
eventSource.onopen = function() {
|
||||||
|
updateStatus('connected', '已连接 (SSE)');
|
||||||
|
addEvent('系统', 'SSE 连接已建立');
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onmessage = function(event) {
|
||||||
|
addEvent('消息', event.data, 'message');
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = function(error) {
|
||||||
|
// 如果 readyState 是 CLOSED,说明是后端主动关闭,不重连
|
||||||
|
if (eventSource.readyState === EventSource.CLOSED) {
|
||||||
|
updateStatus('disconnected', '连接已关闭');
|
||||||
|
addEvent('系统', '后端已关闭连接,无须重连');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是其他错误状态,显示错误信息,不自动重连
|
||||||
|
updateStatus('disconnected', '连接错误');
|
||||||
|
addEvent('错误', 'SSE 连接发生错误');
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.addEventListener('data', function(event) {
|
||||||
|
addEvent('自定义事件', event.data, 'data');
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
updateStatus('disconnected', '连接失败: ' + e.message);
|
||||||
|
addEvent('错误', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectStream(url) {
|
||||||
|
try {
|
||||||
|
abortController = new AbortController();
|
||||||
|
updateStatus('connected', '已连接 (Stream)');
|
||||||
|
addEvent('系统', 'HTTP Stream 连接已建立');
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
signal: abortController.signal,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/plain, text/event-stream'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
addEvent('系统', 'Stream 连接已关闭');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码并处理数据
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
|
// 按行处理
|
||||||
|
const lines = buffer.split('\n');
|
||||||
|
buffer = lines.pop() || '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
addEvent('数据', line, 'stream');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== 'AbortError') {
|
||||||
|
updateStatus('disconnected', '连接错误: ' + e.message);
|
||||||
|
addEvent('错误', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
eventSource = null;
|
||||||
|
}
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort();
|
||||||
|
abortController = null;
|
||||||
|
}
|
||||||
|
updateStatus('disconnected', '已断开');
|
||||||
|
addEvent('系统', '连接已关闭');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(type, message) {
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
statusDiv.className = 'status ' + type;
|
||||||
|
statusDiv.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEvent(type, data, eventType = '') {
|
||||||
|
const eventsDiv = document.getElementById('events');
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'event-item';
|
||||||
|
|
||||||
|
const time = new Date().toLocaleTimeString();
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="time">[${time}] ${eventType ? '[' + eventType + '] ' : ''}${type}</div>
|
||||||
|
<div class="data">${escapeHtml(data)}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
eventsDiv.insertBefore(item, eventsDiv.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
skills/page/SKILL.md
Normal file
9
skills/page/SKILL.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: new-page
|
||||||
|
description: 创建一个新页面
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考当前的文档
|
||||||
|
|
||||||
|
|
||||||
|
`./references/*.ts`
|
||||||
205
skills/page/references/page.tsx
Normal file
205
skills/page/references/page.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
'use client';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { appDomainStatus, useDomainStore } from './store/index ';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import { pick } from 'es-toolkit';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||||
|
import { LayoutUser } from '@/modules/layout/LayoutUser';
|
||||||
|
import { LayoutMain } from '@/modules/layout';
|
||||||
|
|
||||||
|
const TableList = () => {
|
||||||
|
const { list, setShowEditModal, setFormData, deleteDomain } = useDomainStore();
|
||||||
|
useEffect(() => {
|
||||||
|
// Initial load is handled by the parent component
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
|
<TableHead>域名</TableHead>
|
||||||
|
<TableHead>应用ID</TableHead>
|
||||||
|
<TableHead>UID</TableHead>
|
||||||
|
<TableHead>状态</TableHead>
|
||||||
|
<TableHead>操作</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{list.map((domain) => (
|
||||||
|
<TableRow key={domain.id}>
|
||||||
|
<TableCell>{domain.id}</TableCell>
|
||||||
|
<TableCell>{domain.domain}</TableCell>
|
||||||
|
<TableCell>{domain.appId}</TableCell>
|
||||||
|
<TableCell>{domain.uid}</TableCell>
|
||||||
|
<TableCell>{domain.status}</TableCell>
|
||||||
|
<TableCell className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setShowEditModal(true);
|
||||||
|
setFormData(domain);
|
||||||
|
}}>
|
||||||
|
<Pencil className="w-4 h-4 mr-1" />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteDomain(domain)}>
|
||||||
|
<Trash2 className="w-4 h-4 mr-1" />
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FomeModal = () => {
|
||||||
|
const { showEditModal, setShowEditModal, formData, updateDomain } = useDomainStore();
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
control,
|
||||||
|
setValue,
|
||||||
|
} = useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showEditModal) return;
|
||||||
|
if (formData?.id) {
|
||||||
|
reset(formData);
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
status: 'running',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [formData, showEditModal, reset]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: any) => {
|
||||||
|
const _formData = pick(data, ['domain', 'appId', 'status', 'id']);
|
||||||
|
if (formData.id) {
|
||||||
|
_formData.id = formData.id;
|
||||||
|
}
|
||||||
|
const res = await updateDomain(_formData);
|
||||||
|
if (res.code === 200) {
|
||||||
|
setShowEditModal(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showEditModal} onOpenChange={setShowEditModal}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>添加域名</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="p-4">
|
||||||
|
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">域名</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('domain', { required: '请输入域名' })}
|
||||||
|
placeholder="请输入域名"
|
||||||
|
className={errors.domain ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
{errors.domain && <span className="text-xs text-red-500">{errors.domain.message as string}</span>}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">应用ID</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('appId', { required: '请输入应用ID' })}
|
||||||
|
placeholder="请输入应用ID"
|
||||||
|
className={errors.appId ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
{errors.appId && <span className="text-xs text-red-500">{errors.appId.message as string}</span>}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">状态</label>
|
||||||
|
<Controller
|
||||||
|
name="status"
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select value={field.value || ''} onValueChange={field.onChange}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="请选择状态" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{appDomainStatus.map((item) => (
|
||||||
|
<SelectItem key={item} value={item}>{item}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit">提交</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const List = () => {
|
||||||
|
const { getDomainList, setShowEditModal, setFormData } = useDomainStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDomainList();
|
||||||
|
}, [getDomainList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 w-full h-full">
|
||||||
|
<div className="flex mb-4">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowEditModal(true);
|
||||||
|
setFormData({});
|
||||||
|
}}>
|
||||||
|
<Plus className="w-4 h-4 mr-1" />
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<TableList />
|
||||||
|
<FomeModal />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return <LayoutMain><List /></LayoutMain>;
|
||||||
|
}
|
||||||
92
skills/page/references/store/index .ts
Normal file
92
skills/page/references/store/index .ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
// 审核,通过,驳回
|
||||||
|
export const appDomainStatus = ['audit', 'auditReject', 'auditPending', 'running', 'stop'] as const;
|
||||||
|
|
||||||
|
type AppDomainStatus = (typeof appDomainStatus)[number];
|
||||||
|
type Domain = {
|
||||||
|
id: string;
|
||||||
|
domain: string;
|
||||||
|
appId?: string;
|
||||||
|
status: AppDomainStatus;
|
||||||
|
data?: any;
|
||||||
|
uid?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
interface Store {
|
||||||
|
getDomainList: () => Promise<any>;
|
||||||
|
updateDomain: (data: { domain: string; id: string; [key: string]: any }, opts?: { refresh?: boolean }) => Promise<any>;
|
||||||
|
deleteDomain: (data: { id: string }) => Promise<any>;
|
||||||
|
getDomainDetail: (data: { domain?: string; id?: string }) => Promise<any>;
|
||||||
|
list: Domain[];
|
||||||
|
setList: (list: Domain[]) => void;
|
||||||
|
formData: any;
|
||||||
|
setFormData: (formData: any) => void;
|
||||||
|
showEditModal: boolean;
|
||||||
|
setShowEditModal: (showEditModal: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDomainStore = create<Store>((set, get) => ({
|
||||||
|
getDomainList: async () => {
|
||||||
|
const res = await query.get({
|
||||||
|
path: 'app.domain.manager',
|
||||||
|
key: 'list',
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ list: res.data?.list || [] });
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
updateDomain: async (data: any, opts?: { refresh?: boolean }) => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'app.domain.manager',
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list = get().list;
|
||||||
|
set({ list: list.map((item) => (item.id === data.id ? res.data : item)) });
|
||||||
|
toast.success('更新成功');
|
||||||
|
if (opts?.refresh ?? true) {
|
||||||
|
get().getDomainList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '更新失败');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
deleteDomain: async (data: any) => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'app.domain.manager',
|
||||||
|
key: 'delete',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list = get().list;
|
||||||
|
set({ list: list.filter((item) => item.id !== data.id) });
|
||||||
|
toast.success('删除成功');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
getDomainDetail: async (data: any) => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'app.domain.manager',
|
||||||
|
key: 'get',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ formData: res.data });
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
list: [],
|
||||||
|
setList: (list: any[]) => set({ list }),
|
||||||
|
formData: {},
|
||||||
|
setFormData: (formData: any) => set({ formData }),
|
||||||
|
showEditModal: false,
|
||||||
|
setShowEditModal: (showEditModal: boolean) => set({ showEditModal }),
|
||||||
|
}));
|
||||||
@@ -62,7 +62,7 @@ export const AppDeleteModal = () => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Tips</DialogTitle>
|
<DialogTitle>Tips</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className='w-[400px]'>
|
<div className=''>
|
||||||
<p className='text-sm text-gray-500'>Delete App Introduce</p>
|
<p className='text-sm text-gray-500'>Delete App Introduce</p>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ const ShareModal = () => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>分享</DialogTitle>
|
<DialogTitle>分享</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className='flex flex-col gap-2 w-[400px] '>
|
<div className='flex flex-col gap-2 '>
|
||||||
<PermissionManager
|
<PermissionManager
|
||||||
value={permission}
|
value={permission}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const FomeModal = () => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>添加域名</DialogTitle>
|
<DialogTitle>添加域名</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="p-4 w-[500px]">
|
<div className="p-4">
|
||||||
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-sm font-medium">域名</label>
|
<label className="text-sm font-medium">域名</label>
|
||||||
|
|||||||
187
src/app/flowme/channel/page.tsx
Normal file
187
src/app/flowme/channel/page.tsx
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
'use client';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFlowmeChannelStore } from '../store/channel';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { pick } from 'es-toolkit';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||||
|
import { LayoutMain } from '@/modules/layout';
|
||||||
|
|
||||||
|
const TableList = () => {
|
||||||
|
const { list, setShowEdit, setFormData, deleteData } = useFlowmeChannelStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>标题</TableHead>
|
||||||
|
<TableHead>描述</TableHead>
|
||||||
|
<TableHead>标签</TableHead>
|
||||||
|
<TableHead>颜色</TableHead>
|
||||||
|
<TableHead>操作</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{list.map((item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
<TableCell>{item.title}</TableCell>
|
||||||
|
<TableCell>{item.description}</TableCell>
|
||||||
|
<TableCell>{item.tags?.join(', ')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: item.color }} />
|
||||||
|
{item.color}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setShowEdit(true);
|
||||||
|
setFormData(item);
|
||||||
|
}}>
|
||||||
|
<Pencil className="w-4 h-4 mr-1" />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteData(item.id)}>
|
||||||
|
<Trash2 className="w-4 h-4 mr-1" />
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormModal = () => {
|
||||||
|
const { showEdit, setShowEdit, formData, updateData } = useFlowmeChannelStore();
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
control,
|
||||||
|
} = useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showEdit) return;
|
||||||
|
if (formData?.id) {
|
||||||
|
reset(formData);
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
color: '#007bff',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [formData, showEdit, reset]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: any) => {
|
||||||
|
const _formData: any = pick(data, ['title', 'description', 'tags', 'link', 'data', 'color']);
|
||||||
|
if (formData.id) {
|
||||||
|
_formData.id = formData.id;
|
||||||
|
}
|
||||||
|
const res = await updateData(_formData);
|
||||||
|
if (res.code === 200) {
|
||||||
|
setShowEdit(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showEdit} onOpenChange={setShowEdit}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{formData?.id ? '编辑' : '添加'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="p-4">
|
||||||
|
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">标题</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('title')}
|
||||||
|
placeholder="请输入标题"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">描述</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('description')}
|
||||||
|
placeholder="请输入描述"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">颜色</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
{...control.register('color')}
|
||||||
|
placeholder="请输入颜色值"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="color"
|
||||||
|
{...control.register('color')}
|
||||||
|
className="w-12 h-10 p-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="submit">提交</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const List = () => {
|
||||||
|
const { getList, setShowEdit, setFormData } = useFlowmeChannelStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getList();
|
||||||
|
}, [getList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 w-full h-full">
|
||||||
|
<div className="flex mb-4">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowEdit(true);
|
||||||
|
setFormData({});
|
||||||
|
}}>
|
||||||
|
<Plus className="w-4 h-4 mr-1" />
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<TableList />
|
||||||
|
<FormModal />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return <LayoutMain><List /></LayoutMain>;
|
||||||
|
}
|
||||||
196
src/app/flowme/page.tsx
Normal file
196
src/app/flowme/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
'use client';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFlowmeStore } from './store/index';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { pick } from 'es-toolkit';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||||
|
import { LayoutMain } from '@/modules/layout';
|
||||||
|
|
||||||
|
const TableList = () => {
|
||||||
|
const { list, setShowEdit, setFormData, deleteData } = useFlowmeStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>标题</TableHead>
|
||||||
|
<TableHead>描述</TableHead>
|
||||||
|
<TableHead>标签</TableHead>
|
||||||
|
<TableHead>类型</TableHead>
|
||||||
|
<TableHead>来源</TableHead>
|
||||||
|
<TableHead>重要性</TableHead>
|
||||||
|
<TableHead>操作</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{list.map((item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
<TableCell>{item.title}</TableCell>
|
||||||
|
<TableCell>{item.description}</TableCell>
|
||||||
|
<TableCell>{item.tags?.join(', ')}</TableCell>
|
||||||
|
<TableCell>{item.type}</TableCell>
|
||||||
|
<TableCell>{item.source}</TableCell>
|
||||||
|
<TableCell>{item.importance}</TableCell>
|
||||||
|
<TableCell className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setShowEdit(true);
|
||||||
|
setFormData(item);
|
||||||
|
}}>
|
||||||
|
<Pencil className="w-4 h-4 mr-1" />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteData(item.id)}>
|
||||||
|
<Trash2 className="w-4 h-4 mr-1" />
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormModal = () => {
|
||||||
|
const { showEdit, setShowEdit, formData, updateData } = useFlowmeStore();
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
control,
|
||||||
|
} = useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showEdit) return;
|
||||||
|
if (formData?.id) {
|
||||||
|
reset(formData);
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
type: '',
|
||||||
|
source: '',
|
||||||
|
importance: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [formData, showEdit, reset]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: any) => {
|
||||||
|
const _formData: any = pick(data, ['title', 'description', 'type', 'source', 'importance', 'tags', 'link', 'data', 'channelId']);
|
||||||
|
if (formData.id) {
|
||||||
|
_formData.id = formData.id;
|
||||||
|
}
|
||||||
|
const res = await updateData(_formData);
|
||||||
|
if (res.code === 200) {
|
||||||
|
setShowEdit(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showEdit} onOpenChange={setShowEdit}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{formData?.id ? '编辑' : '添加'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="p-4">
|
||||||
|
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">标题</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('title')}
|
||||||
|
placeholder="请输入标题"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">描述</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('description')}
|
||||||
|
placeholder="请输入描述"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">类型</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('type')}
|
||||||
|
placeholder="请输入类型"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">来源</label>
|
||||||
|
<Input
|
||||||
|
{...control.register('source')}
|
||||||
|
placeholder="请输入来源"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">重要性</label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...control.register('importance', { valueAsNumber: true })}
|
||||||
|
placeholder="请输入重要性等级"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit">提交</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const List = () => {
|
||||||
|
const { getList, setShowEdit, setFormData } = useFlowmeStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getList();
|
||||||
|
}, [getList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 w-full h-full">
|
||||||
|
<div className="flex mb-4">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowEdit(true);
|
||||||
|
setFormData({});
|
||||||
|
}}>
|
||||||
|
<Plus className="w-4 h-4 mr-1" />
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<TableList />
|
||||||
|
<FormModal />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return <LayoutMain><List /></LayoutMain>;
|
||||||
|
}
|
||||||
103
src/app/flowme/store/channel.ts
Normal file
103
src/app/flowme/store/channel.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { toast as message } from 'sonner';
|
||||||
|
|
||||||
|
export type FlowmeChannelType = {
|
||||||
|
id: string;
|
||||||
|
uid?: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
link: string;
|
||||||
|
data: Record<string, any>;
|
||||||
|
color: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlowmeChannelStore = {
|
||||||
|
showEdit: boolean;
|
||||||
|
setShowEdit: (showEdit: boolean) => void;
|
||||||
|
formData: Partial<FlowmeChannelType>;
|
||||||
|
setFormData: (formData: Partial<FlowmeChannelType>) => void;
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
list: FlowmeChannelType[];
|
||||||
|
getList: () => Promise<void>;
|
||||||
|
updateData: (data: Partial<FlowmeChannelType>) => Promise<any>;
|
||||||
|
deleteData: (id: string) => Promise<void>;
|
||||||
|
detail: FlowmeChannelType | null;
|
||||||
|
setDetail: (detail: FlowmeChannelType | null) => void;
|
||||||
|
getDetail: (id: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFlowmeChannelStore = create<FlowmeChannelStore>((set, get) => {
|
||||||
|
return {
|
||||||
|
showEdit: false,
|
||||||
|
setShowEdit: (showEdit) => set({ showEdit }),
|
||||||
|
formData: {},
|
||||||
|
setFormData: (formData) => set({ formData }),
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
list: [],
|
||||||
|
getList: async () => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme-channel',
|
||||||
|
key: 'list',
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ list: res.data.list });
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateData: async (data) => {
|
||||||
|
const { getList } = get();
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme-channel',
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('Success');
|
||||||
|
set({ showEdit: false, formData: res.data });
|
||||||
|
getList();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
deleteData: async (id) => {
|
||||||
|
const { getList } = get();
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme-channel',
|
||||||
|
key: 'delete',
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
getList();
|
||||||
|
message.success('Success');
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detail: null,
|
||||||
|
setDetail: (detail) => set({ detail }),
|
||||||
|
getDetail: async (id) => {
|
||||||
|
set({ detail: null });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme-channel',
|
||||||
|
key: 'get',
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ detail: res.data });
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
107
src/app/flowme/store/index.ts
Normal file
107
src/app/flowme/store/index.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
'use client';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { toast as message } from 'sonner';
|
||||||
|
|
||||||
|
export type FlowmeType = {
|
||||||
|
id: string;
|
||||||
|
uid?: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
link: string;
|
||||||
|
data: Record<string, any>;
|
||||||
|
channelId?: string;
|
||||||
|
type: string;
|
||||||
|
source: string;
|
||||||
|
importance: number;
|
||||||
|
isArchived: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlowmeStore = {
|
||||||
|
showEdit: boolean;
|
||||||
|
setShowEdit: (showEdit: boolean) => void;
|
||||||
|
formData: Partial<FlowmeType>;
|
||||||
|
setFormData: (formData: Partial<FlowmeType>) => void;
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
list: FlowmeType[];
|
||||||
|
getList: () => Promise<void>;
|
||||||
|
updateData: (data: Partial<FlowmeType>) => Promise<any>;
|
||||||
|
deleteData: (id: string) => Promise<void>;
|
||||||
|
detail: FlowmeType | null;
|
||||||
|
setDetail: (detail: FlowmeType | null) => void;
|
||||||
|
getDetail: (id: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFlowmeStore = create<FlowmeStore>((set, get) => {
|
||||||
|
return {
|
||||||
|
showEdit: false,
|
||||||
|
setShowEdit: (showEdit) => set({ showEdit }),
|
||||||
|
formData: {},
|
||||||
|
setFormData: (formData) => set({ formData }),
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
list: [],
|
||||||
|
getList: async () => {
|
||||||
|
set({ loading: true });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme',
|
||||||
|
key: 'list',
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ list: res.data.list });
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateData: async (data) => {
|
||||||
|
const { getList } = get();
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme',
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('Success');
|
||||||
|
set({ showEdit: false, formData: res.data });
|
||||||
|
getList();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
deleteData: async (id) => {
|
||||||
|
const { getList } = get();
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme',
|
||||||
|
key: 'delete',
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
getList();
|
||||||
|
message.success('Success');
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detail: null,
|
||||||
|
setDetail: (detail) => set({ detail }),
|
||||||
|
getDetail: async (id) => {
|
||||||
|
set({ detail: null });
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'flowme',
|
||||||
|
key: 'get',
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ detail: res.data });
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user