generated from kevisual/vite-react-template
Auto commit: 2026-03-19 01:46
This commit is contained in:
284
src/modules/mark-api.ts
Normal file
284
src/modules/mark-api.ts
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import { createQueryApi } from '@kevisual/query/api';
|
||||||
|
const api = {
|
||||||
|
"mark": {
|
||||||
|
/**
|
||||||
|
* mark list.
|
||||||
|
*
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.page - {number} 页码
|
||||||
|
* @param data.pageSize - {number} 每页数量
|
||||||
|
* @param data.search - {string} 搜索关键词
|
||||||
|
* @param data.markType - {string} mark类型,simple,wallnote,md,draw等
|
||||||
|
* @param data.sort - {"DESC" | "ASC"} 排序字段
|
||||||
|
*/
|
||||||
|
"list": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "list",
|
||||||
|
"description": "mark list.",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"page": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "页码",
|
||||||
|
"type": "number",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"pageSize": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "每页数量",
|
||||||
|
"type": "number",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "搜索关键词",
|
||||||
|
"type": "string",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"markType": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "mark类型,simple,wallnote,md,draw等",
|
||||||
|
"type": "string",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"default": "DESC",
|
||||||
|
"description": "排序字段",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"DESC",
|
||||||
|
"ASC"
|
||||||
|
],
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
*/
|
||||||
|
"getVersion": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "getVersion",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
*/
|
||||||
|
"get": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "get",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
*/
|
||||||
|
"update": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "update",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
* @param data.operate - {"update" | "delete"} 节点操作类型,update或delete
|
||||||
|
* @param data.data - {object} 要更新的节点数据
|
||||||
|
*/
|
||||||
|
"updateNode": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "updateNode",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
},
|
||||||
|
"operate": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"default": "update",
|
||||||
|
"description": "节点操作类型,update或delete",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"update",
|
||||||
|
"delete"
|
||||||
|
],
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "节点id"
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"description": "要更新的节点数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "要更新的节点数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
* @param data.nodeOperateList - {array} 要更新的节点列表
|
||||||
|
*/
|
||||||
|
"updateNodes": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "updateNodes",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
},
|
||||||
|
"nodeOperateList": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"operate": {
|
||||||
|
"default": "update",
|
||||||
|
"description": "节点操作类型,update或delete",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"update",
|
||||||
|
"delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"description": "要更新的节点数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"operate",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"description": "要更新的节点列表"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.id - {string} mark id
|
||||||
|
*/
|
||||||
|
"delete": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "delete",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"id": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "string",
|
||||||
|
"description": "mark id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建mark
|
||||||
|
*
|
||||||
|
* @param data - Request parameters
|
||||||
|
* @param data.name - {string} mark名称
|
||||||
|
* @param data.markType - {string} mark类型,simple,wallnote,md,draw等
|
||||||
|
* @param data.data - {object} mark数据
|
||||||
|
*/
|
||||||
|
"create": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "create",
|
||||||
|
"description": "创建mark",
|
||||||
|
"metadata": {
|
||||||
|
"args": {
|
||||||
|
"name": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "mark名称",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"markType": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "mark类型",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"description": "mark数据",
|
||||||
|
"type": "object",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取菜单
|
||||||
|
*/
|
||||||
|
"getMenu": {
|
||||||
|
"path": "mark",
|
||||||
|
"key": "getMenu",
|
||||||
|
"description": "获取菜单",
|
||||||
|
"metadata": {
|
||||||
|
"url": "/api/router",
|
||||||
|
"source": "query-proxy-api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
const queryApi = createQueryApi({ api });
|
||||||
|
export { queryApi };
|
||||||
@@ -49,7 +49,7 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
|||||||
path: '',
|
path: '',
|
||||||
license: '',
|
license: '',
|
||||||
description: '',
|
description: '',
|
||||||
visibility: 'Public'
|
visibility: 'public'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [open, reset])
|
}, [open, reset])
|
||||||
@@ -104,16 +104,16 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
|||||||
<Controller
|
<Controller
|
||||||
name="visibility"
|
name="visibility"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue="Public"
|
defaultValue="public"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<Select onValueChange={onChange} value={value}>
|
<Select onValueChange={onChange} value={value}>
|
||||||
<SelectTrigger id="visibility">
|
<SelectTrigger id="visibility">
|
||||||
<SelectValue placeholder="选择可见性" />
|
<SelectValue placeholder="选择可见性" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="Public">公开 (public)</SelectItem>
|
<SelectItem value="public">公开 (public)</SelectItem>
|
||||||
<SelectItem value="Private">私有 (private)</SelectItem>
|
<SelectItem value="private">私有 (private)</SelectItem>
|
||||||
<SelectItem value="Protected">保护 (protected)</SelectItem>
|
<SelectItem value="protected">保护 (protected)</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { useRepoStore } from '../store'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useShallow } from 'zustand/shallow'
|
|
||||||
import { get, set } from 'idb-keyval'
|
|
||||||
import { gitea } from '@/agents/app';
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
const SYNC_REPO_STORAGE_KEY = 'sync-repo-mapping'
|
|
||||||
|
|
||||||
export function SyncRepoDialog() {
|
|
||||||
const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore(useShallow((state) => ({
|
|
||||||
syncDialogOpen: state.syncDialogOpen,
|
|
||||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
|
||||||
selectedSyncRepo: state.selectedSyncRepo,
|
|
||||||
buildSync: state.buildSync,
|
|
||||||
})))
|
|
||||||
const [toRepo, setToRepo] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadSavedMapping = async () => {
|
|
||||||
if (syncDialogOpen && selectedSyncRepo) {
|
|
||||||
const currentPath = selectedSyncRepo.path || ''
|
|
||||||
// 从 idb-keyval 获取存储的映射
|
|
||||||
const mapping = await get<Record<string, string>>(SYNC_REPO_STORAGE_KEY)
|
|
||||||
// 如果有存储的值,使用存储的值,否则使用当前仓库路径
|
|
||||||
setToRepo(mapping?.[currentPath] || currentPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadSavedMapping()
|
|
||||||
}, [syncDialogOpen, selectedSyncRepo])
|
|
||||||
|
|
||||||
const handleSync = async () => {
|
|
||||||
if (!selectedSyncRepo || !toRepo.trim()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存映射到 idb-keyval
|
|
||||||
const currentPath = selectedSyncRepo.path || ''
|
|
||||||
const mapping = await get<Record<string, string>>(SYNC_REPO_STORAGE_KEY) || {}
|
|
||||||
mapping[currentPath] = toRepo
|
|
||||||
await set(SYNC_REPO_STORAGE_KEY, mapping)
|
|
||||||
|
|
||||||
await buildSync(selectedSyncRepo, { toRepo })
|
|
||||||
setSyncDialogOpen(false)
|
|
||||||
}
|
|
||||||
const onCreateRepo = async () => {
|
|
||||||
if (!toRepo.trim()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await gitea.repo.createRepo({ name: toRepo })
|
|
||||||
if (res.code !== 200 && res.code !== 409) {
|
|
||||||
// 409 表示仓库已存在,可以继续同步
|
|
||||||
throw new Error(`${res.message}`)
|
|
||||||
}
|
|
||||||
if (res.code === 200) {
|
|
||||||
toast.success('仓库创建成功,正在同步...')
|
|
||||||
} else {
|
|
||||||
toast.warning('仓库已存在,正在同步...')
|
|
||||||
}
|
|
||||||
handleSync()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建仓库失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Dialog open={syncDialogOpen} onOpenChange={setSyncDialogOpen}>
|
|
||||||
<DialogContent className="sm:max-w-125">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>同步仓库到 Gitea</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
将仓库 <span className="font-semibold text-neutral-900">{selectedSyncRepo?.path}</span> 同步到目标仓库
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-4 py-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="toRepo">目标仓库路径</Label>
|
|
||||||
<Input
|
|
||||||
id="toRepo"
|
|
||||||
placeholder="例如: kevisual/my-repo"
|
|
||||||
value={toRepo}
|
|
||||||
onChange={(e) => setToRepo(e.target.value)}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-neutral-500">
|
|
||||||
格式: owner/repo-name
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setSyncDialogOpen(false)}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onCreateRepo} disabled={!toRepo.trim()}>
|
|
||||||
先创建仓库再同步
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleSync}
|
|
||||||
disabled={!toRepo.trim()}
|
|
||||||
>
|
|
||||||
开始同步
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { RepoCard } from './components/RepoCard'
|
|||||||
import { EditRepoDialog } from './modules/EditRepoDialog'
|
import { EditRepoDialog } from './modules/EditRepoDialog'
|
||||||
import { CreateRepoDialog } from './modules/CreateRepoDialog'
|
import { CreateRepoDialog } from './modules/CreateRepoDialog'
|
||||||
import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog'
|
import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog'
|
||||||
import { SyncRepoDialog } from './modules/SyncRepoDialog'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { ExternalLinkIcon, Plus, RefreshCw, Search, Settings } from 'lucide-react'
|
import { ExternalLinkIcon, Plus, RefreshCw, Search, Settings } from 'lucide-react'
|
||||||
@@ -176,7 +175,6 @@ export const CommonRepoDialog = () => {
|
|||||||
onOpenChange={setShowCreateDialog}
|
onOpenChange={setShowCreateDialog}
|
||||||
/>
|
/>
|
||||||
<WorkspaceDetailDialog />
|
<WorkspaceDetailDialog />
|
||||||
<SyncRepoDialog />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,28 +57,11 @@ export const createDevConfig = (params: { repo?: string, event?: string, branch?
|
|||||||
return `##### 配置开始,保留注释 #####
|
return `##### 配置开始,保留注释 #####
|
||||||
.common_env: &common_env
|
.common_env: &common_env
|
||||||
env:
|
env:
|
||||||
# 使用环境变量管理密钥,推荐使用密钥仓库管理密钥, 详情见 readme.md
|
|
||||||
# 使用仓库密钥时,注释
|
|
||||||
## 可选 API-Key 配置(按需取消注释)
|
|
||||||
# MINIMAX_API_KEY: '' # Minimax 模型
|
|
||||||
# ZHIPU_API_KEY: '' # 智谱 AI
|
|
||||||
# BAILIAN_CODE_API_KEY: '' # 阿里云百炼
|
|
||||||
# VOLCENGINE_API_KEY: '' # 火山引擎
|
|
||||||
# CNB_API_KEY: '' # CNB API
|
|
||||||
# CNB_COOKIE: '' # 可选配置,用cnb.cool的cookie
|
|
||||||
|
|
||||||
# 可选应用配置
|
|
||||||
# FEISHU_APP_ID: '' # 飞书应用 ID
|
|
||||||
# FEISHU_APP_SECRET: '' # 飞书应用密钥
|
|
||||||
|
|
||||||
USERNAME: root
|
USERNAME: root
|
||||||
ASSISTANT_CONFIG_DIR: /workspace/kevisual # ASSISTANT_CONFIG_DIR 环境变量指定了配置文件所在的目录
|
|
||||||
# CNB_KEVISUAL_ORG: kevisual # 私密仓库使用环境配置(默认即可,默认为当前用户组CNB_GROUP_SLUG)
|
|
||||||
# CNB_KEVISUAL_APP: assistant-app # 可选配置(默认即可)
|
|
||||||
# CNB_OPENCLAW: openclaw # 仓库名(默认即可)
|
|
||||||
# CNB_OPENWEBUI: open-webui # 仓库名(默认即可)
|
|
||||||
imports:
|
imports:
|
||||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
- https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/.env
|
||||||
|
# - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh.yml
|
||||||
|
# - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh-config.yml
|
||||||
|
|
||||||
##### 配置结束 #####
|
##### 配置结束 #####
|
||||||
|
|
||||||
@@ -89,19 +72,29 @@ ${branch}:
|
|||||||
services:
|
services:
|
||||||
- vscode
|
- vscode
|
||||||
- docker
|
- docker
|
||||||
runner:
|
|
||||||
cpus: 16
|
|
||||||
#tags: cnb:arch:amd64:gpu
|
|
||||||
imports: !reference [.common_env, imports]
|
imports: !reference [.common_env, imports]
|
||||||
env: !reference [.common_env, env]
|
env: !reference [.common_env, env]
|
||||||
|
runner:
|
||||||
|
cpus: $RUN_CPU
|
||||||
|
#tags: cnb:arch:amd64:gpu
|
||||||
stages:
|
stages:
|
||||||
|
- name: 安装dev-cnb的仓库代码模块
|
||||||
|
script: |
|
||||||
|
cd /workspace && find . -mindepth 1 -delete
|
||||||
|
git init
|
||||||
|
git remote add origin https://cnb.cool/kevisual/dev-cnb
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
- name: 启动nginx
|
- name: 启动nginx
|
||||||
script: nginx
|
script: nginx
|
||||||
|
- name: 启动搜索服务
|
||||||
|
script: zsh -i -c 'bun src/cli.ts init start-meilisearch'
|
||||||
- name: 初始化开发机
|
- name: 初始化开发机
|
||||||
script: zsh /workspace/scripts/init.sh
|
script: zsh -i -c 'bun run start'
|
||||||
|
- name: 启动当前工作区
|
||||||
|
script: zsh -i -c 'cloud cnb keep-alive-current-workspace'
|
||||||
# endStages:
|
# endStages:
|
||||||
# - name: 结束阶段
|
# - name: 结束阶段
|
||||||
# script: bun /workspace/scripts/end.ts
|
# script: zsh -i -c 'bun run end'
|
||||||
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ import { query } from '@/modules/query';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { queryApi as cnbApi } from '@/modules/cnb-api'
|
import { queryApi as cnbApi } from '@/modules/cnb-api'
|
||||||
import { WorkspaceInfo } from '@kevisual/cnb'
|
import { WorkspaceInfo } from '@kevisual/cnb'
|
||||||
import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build';
|
import { createCommitBlankConfig, createDevConfig } from './build';
|
||||||
import { useLayoutStore } from '@/pages/auth/store';
|
import { useLayoutStore } from '@/pages/auth/store';
|
||||||
import { Query } from '@kevisual/query';
|
import { Query } from '@kevisual/query';
|
||||||
interface DisplayModule {
|
interface DisplayModule {
|
||||||
@@ -94,7 +94,6 @@ type State = {
|
|||||||
setSyncDialogOpen: (open: boolean) => void;
|
setSyncDialogOpen: (open: boolean) => void;
|
||||||
selectedSyncRepo: Data | null;
|
selectedSyncRepo: Data | null;
|
||||||
setSelectedSyncRepo: (repo: Data | null) => void;
|
setSelectedSyncRepo: (repo: Data | null) => void;
|
||||||
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
|
|
||||||
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
||||||
getItem: (repo: string) => Promise<any>;
|
getItem: (repo: string) => Promise<any>;
|
||||||
buildConfig: BuildConfig | null;
|
buildConfig: BuildConfig | null;
|
||||||
@@ -525,28 +524,6 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
workspaceLink: {},
|
workspaceLink: {},
|
||||||
buildSync: async (data, params) => {
|
|
||||||
const repo = data.path!;
|
|
||||||
const toRepo = params.toRepo;
|
|
||||||
const fromRepo = params.fromRepo;
|
|
||||||
if (!toRepo && !fromRepo) {
|
|
||||||
toast.error('请选择同步的目标仓库或来源仓库')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let event = toRepo ? 'api_trigger_sync_to_gitea' : 'api_trigger_sync_from_gitea';
|
|
||||||
const res = await cnbApi.cnb['cloud-build']({
|
|
||||||
repo: toRepo! || fromRepo!,
|
|
||||||
branch: 'main',
|
|
||||||
env: {} as any,
|
|
||||||
event: event,
|
|
||||||
config: createBuildConfig({ repo: toRepo! || fromRepo! }),
|
|
||||||
})
|
|
||||||
if (res.code === 200) {
|
|
||||||
toast.success('同步提交成功')
|
|
||||||
} else {
|
|
||||||
toast.error(res.message || '同步提交失败')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buildUpdate: async (data) => {
|
buildUpdate: async (data) => {
|
||||||
const res = await cnbApi.cnb['cloud-build']({
|
const res = await cnbApi.cnb['cloud-build']({
|
||||||
repo: data.path!,
|
repo: data.path!,
|
||||||
|
|||||||
@@ -1,30 +1,421 @@
|
|||||||
|
import { useWorkspaceStore, type WorkspaceState } from "./store";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { useMarkStore, useWorkspaceStore } from "./store";
|
import { useEffect, useState } from "react";
|
||||||
import { useEffect } from "react";
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const markStore = useMarkStore(useShallow(state => {
|
const workspaceStore = useWorkspaceStore(useShallow((state: WorkspaceState) => {
|
||||||
return {
|
return {
|
||||||
init: state.init,
|
|
||||||
list: state.list,
|
list: state.list,
|
||||||
|
loading: state.loading,
|
||||||
|
getList: state.getList,
|
||||||
|
createItem: state.createItem,
|
||||||
|
updateItem: state.updateItem,
|
||||||
|
deleteItem: state.deleteItem,
|
||||||
|
showCreateDialog: state.showCreateDialog,
|
||||||
|
setShowCreateDialog: state.setShowCreateDialog,
|
||||||
|
showEditDialog: state.showEditDialog,
|
||||||
|
setShowEditDialog: state.setShowEditDialog,
|
||||||
|
editingItem: state.editingItem,
|
||||||
|
setEditingItem: state.setEditingItem,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
const workspaceStore = useWorkspaceStore(useShallow(state => {
|
const [search, setSearch] = useState('');
|
||||||
return {
|
|
||||||
edit: state.edit,
|
|
||||||
setEdit: state.setEdit,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// @ts-ignore
|
workspaceStore.getList({ search });
|
||||||
markStore.init('cnb');
|
}, [search]);
|
||||||
}, []);
|
|
||||||
console.log('markStore.list', markStore.list);
|
const handleDelete = async (id: string) => {
|
||||||
|
if (confirm('确定要删除这个workspace吗?')) {
|
||||||
|
await workspaceStore.deleteItem(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
workspaceStore.getList({ search });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (item: any) => {
|
||||||
|
workspaceStore.setEditingItem(item);
|
||||||
|
workspaceStore.setShowEditDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
workspaceStore.setShowCreateDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ padding: '20px' }}>
|
||||||
<h1>Workspaces</h1>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||||
<p>This is the workspaces page.</p>
|
<h1>Workspaces</h1>
|
||||||
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索workspace..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
style={{ padding: '8px 12px', border: '1px solid #ddd', borderRadius: '4px', width: '200px' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleRefresh}
|
||||||
|
style={{ padding: '8px 16px', background: '#666', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCreate}
|
||||||
|
style={{ padding: '8px 16px', background: '#0070f3', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
创建Workspace
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{workspaceStore.loading ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px' }}>加载中...</div>
|
||||||
|
) : workspaceStore.list.length === 0 ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>暂无workspace数据</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '16px' }}>
|
||||||
|
{workspaceStore.list.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '16px',
|
||||||
|
background: '#fff',
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>{item.name || '未命名'}</h3>
|
||||||
|
<p style={{ margin: '0 0 8px 0', color: '#666', fontSize: '14px' }}>ID: {item.id}</p>
|
||||||
|
<p style={{ margin: '0 0 8px 0', color: '#999', fontSize: '12px' }}>
|
||||||
|
创建时间: {item.created_at ? new Date(item.created_at).toLocaleString() : '-'}
|
||||||
|
</p>
|
||||||
|
<p style={{ margin: '0 0 8px 0', color: '#999', fontSize: '12px' }}>
|
||||||
|
更新时间: {item.updated_at ? new Date(item.updated_at).toLocaleString() : '-'}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => handleEdit(item)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 12px',
|
||||||
|
background: '#0070f3',
|
||||||
|
color: '#fff',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDelete(item.id)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 12px',
|
||||||
|
background: '#ff4d4f',
|
||||||
|
color: '#fff',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建弹窗 */}
|
||||||
|
<CreateDialog
|
||||||
|
open={workspaceStore.showCreateDialog}
|
||||||
|
onClose={() => workspaceStore.setShowCreateDialog(false)}
|
||||||
|
onSubmit={workspaceStore.createItem}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 编辑弹窗 */}
|
||||||
|
<EditDialog
|
||||||
|
open={workspaceStore.showEditDialog}
|
||||||
|
item={workspaceStore.editingItem}
|
||||||
|
onClose={() => {
|
||||||
|
workspaceStore.setShowEditDialog(false);
|
||||||
|
workspaceStore.setEditingItem(null);
|
||||||
|
}}
|
||||||
|
onSubmit={workspaceStore.updateItem}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
// 创建弹窗组件
|
||||||
|
function CreateDialog({ open, onClose, onSubmit }: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: { name: string, data?: any }) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [data, setData] = useState('');
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setName('');
|
||||||
|
setData('');
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!name.trim()) {
|
||||||
|
alert('请输入名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
let parsedData = {};
|
||||||
|
if (data.trim()) {
|
||||||
|
try {
|
||||||
|
parsedData = JSON.parse(data);
|
||||||
|
} catch {
|
||||||
|
alert('JSON格式不正确');
|
||||||
|
setSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await onSubmit({ name: name.trim(), data: parsedData });
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0,0,0,0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 1000
|
||||||
|
}} onClick={onClose}>
|
||||||
|
<div style={{
|
||||||
|
background: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '24px',
|
||||||
|
width: '480px',
|
||||||
|
maxWidth: '90%'
|
||||||
|
}} onClick={e => e.stopPropagation()}>
|
||||||
|
<h2 style={{ margin: '0 0 20px 0' }}>创建Workspace</h2>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}>名称</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
placeholder="请输入名称"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}>数据 (JSON)</label>
|
||||||
|
<textarea
|
||||||
|
value={data}
|
||||||
|
onChange={e => setData(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={4}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
resize: 'vertical'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#fff',
|
||||||
|
color: '#666',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={submitting}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#0070f3',
|
||||||
|
color: '#fff',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: submitting ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: submitting ? 0.7 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{submitting ? '创建中...' : '创建'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑弹窗组件
|
||||||
|
function EditDialog({ open, item, onClose, onSubmit }: {
|
||||||
|
open: boolean;
|
||||||
|
item: any;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (id: string, data: { name?: string, data?: any }) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [data, setData] = useState('');
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && item) {
|
||||||
|
setName(item.name || '');
|
||||||
|
setData(item.data ? JSON.stringify(item.data, null, 2) : '');
|
||||||
|
}
|
||||||
|
}, [open, item]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!name.trim()) {
|
||||||
|
alert('请输入名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!item?.id) return;
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
let parsedData: any = undefined;
|
||||||
|
if (data.trim()) {
|
||||||
|
try {
|
||||||
|
parsedData = JSON.parse(data);
|
||||||
|
} catch {
|
||||||
|
alert('JSON格式不正确');
|
||||||
|
setSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await onSubmit(item.id, { name: name.trim(), data: parsedData });
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0,0,0,0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 1000
|
||||||
|
}} onClick={onClose}>
|
||||||
|
<div style={{
|
||||||
|
background: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '24px',
|
||||||
|
width: '480px',
|
||||||
|
maxWidth: '90%'
|
||||||
|
}} onClick={e => e.stopPropagation()}>
|
||||||
|
<h2 style={{ margin: '0 0 20px 0' }}>编辑Workspace</h2>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}>名称</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
placeholder="请输入名称"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}>数据 (JSON)</label>
|
||||||
|
<textarea
|
||||||
|
value={data}
|
||||||
|
onChange={e => setData(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={4}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
resize: 'vertical'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#fff',
|
||||||
|
color: '#666',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={submitting}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#0070f3',
|
||||||
|
color: '#fff',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: submitting ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: submitting ? 0.7 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{submitting ? '保存中...' : '保存'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|||||||
@@ -2,13 +2,143 @@ import { useMarkStore } from '@kevisual/api/store-mark';
|
|||||||
export { useMarkStore }
|
export { useMarkStore }
|
||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { queryApi } from '@/modules/mark-api';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
type WorkspaceItem = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
type WorkspaceState = {
|
type WorkspaceState = {
|
||||||
edit: boolean;
|
edit: boolean;
|
||||||
setEdit: (edit: boolean) => void;
|
setEdit: (edit: boolean) => void;
|
||||||
|
list: WorkspaceItem[];
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
// 弹窗状态
|
||||||
|
showCreateDialog: boolean;
|
||||||
|
setShowCreateDialog: (show: boolean) => void;
|
||||||
|
showEditDialog: boolean;
|
||||||
|
setShowEditDialog: (show: boolean) => void;
|
||||||
|
editingItem: WorkspaceItem | null;
|
||||||
|
setEditingItem: (item: WorkspaceItem | null) => void;
|
||||||
|
// 数据操作
|
||||||
|
getList: (params?: { search?: string, page?: number, pageSize?: number }) => Promise<void>;
|
||||||
|
createItem: (data: { name: string, data?: any }) => Promise<void>;
|
||||||
|
updateItem: (id: string, data: { name?: string, data?: any }) => Promise<void>;
|
||||||
|
deleteItem: (id: string) => Promise<void>;
|
||||||
|
getItem: (id: string) => Promise<WorkspaceItem | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useWorkspaceStore = create<WorkspaceState>((set) => ({
|
export type { WorkspaceState, WorkspaceItem };
|
||||||
|
|
||||||
|
export const useWorkspaceStore = create<WorkspaceState>((set, get) => ({
|
||||||
edit: false,
|
edit: false,
|
||||||
setEdit: (edit) => set({ edit }),
|
setEdit: (edit) => set({ edit }),
|
||||||
}));
|
list: [],
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
showCreateDialog: false,
|
||||||
|
setShowCreateDialog: (show) => set({ showCreateDialog: show }),
|
||||||
|
showEditDialog: false,
|
||||||
|
setShowEditDialog: (show) => set({ showEditDialog: show }),
|
||||||
|
editingItem: null,
|
||||||
|
setEditingItem: (item) => set({ editingItem: item }),
|
||||||
|
|
||||||
|
getList: async (params = {}) => {
|
||||||
|
const { page = 1, pageSize = 20, search } = params;
|
||||||
|
set({ loading: true });
|
||||||
|
try {
|
||||||
|
const res = await queryApi.mark.list({
|
||||||
|
markType: 'cnb',
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
sort: 'DESC'
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
set({ list: res.data?.list || [] });
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '获取列表失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取workspace列表失败', e);
|
||||||
|
toast.error('获取列表失败');
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createItem: async (data) => {
|
||||||
|
try {
|
||||||
|
const res = await queryApi.mark.create({
|
||||||
|
name: data.name,
|
||||||
|
markType: 'cnb',
|
||||||
|
data: data.data || {}
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('创建成功');
|
||||||
|
get().getList();
|
||||||
|
set({ showCreateDialog: false });
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '创建失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('创建失败', e);
|
||||||
|
toast.error('创建失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateItem: async (id, data) => {
|
||||||
|
try {
|
||||||
|
const res = await queryApi.mark.update({
|
||||||
|
id,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('更新成功');
|
||||||
|
get().getList();
|
||||||
|
set({ showEditDialog: false, editingItem: null });
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '更新失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('更新失败', e);
|
||||||
|
toast.error('更新失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteItem: async (id) => {
|
||||||
|
try {
|
||||||
|
const res = await queryApi.mark.delete({ id });
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('删除成功');
|
||||||
|
get().getList();
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '删除失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('删除失败', e);
|
||||||
|
toast.error('删除失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getItem: async (id) => {
|
||||||
|
try {
|
||||||
|
const res = await queryApi.mark.get({ id });
|
||||||
|
if (res.code === 200) {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '获取详情失败');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取详情失败', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user