init
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# Production
|
||||
dist
|
||||
out
|
||||
release
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
|
||||
# Build
|
||||
*.log
|
||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_custom_dir={{ version }}
|
||||
strict-ssl=false
|
||||
365
AGENTS.md
Normal file
365
AGENTS.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# AGENTS.md
|
||||
|
||||
Kintone Customize Manager 项目开发指南。
|
||||
|
||||
## 1. 构建命令
|
||||
|
||||
### 开发
|
||||
|
||||
```bash
|
||||
# 启动开发服务器(HMR + 热重载)
|
||||
npm run dev
|
||||
|
||||
# 类型检查
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
# 构建生产版本
|
||||
npm run build
|
||||
|
||||
# 打包应用
|
||||
npm run package:win # Windows
|
||||
npm run package:mac # macOS
|
||||
npm run package:linux # Linux
|
||||
```
|
||||
|
||||
### 代码质量
|
||||
|
||||
```bash
|
||||
# ESLint 检查
|
||||
npm run lint
|
||||
|
||||
# 格式化代码
|
||||
npm run format
|
||||
```
|
||||
|
||||
## 2. 项目结构
|
||||
|
||||
```
|
||||
kintone-customize-manager/
|
||||
├── src/
|
||||
│ ├── main/ # Electron 主进程
|
||||
│ │ ├── index.ts # 主进程入口
|
||||
│ │ ├── ipc-handlers.ts # IPC 通信处理
|
||||
│ │ ├── storage.ts # 文件系统操作
|
||||
│ │ ├── kintone-api.ts # Kintone API 封装
|
||||
│ │ ├── updater.ts # 自动更新逻辑
|
||||
│ │ └── config.ts # 配置管理
|
||||
│ ├── preload/ # Preload 脚本
|
||||
│ │ └── index.ts # 暴露 API 到渲染进程
|
||||
│ └── renderer/ # React 渲染进程
|
||||
│ └── src/
|
||||
│ ├── main.tsx # React 入口
|
||||
│ ├── App.tsx # 根组件
|
||||
│ ├── components/ # React 组件
|
||||
│ ├── hooks/ # 自定义 Hooks
|
||||
│ ├── stores/ # Zustand Stores
|
||||
│ ├── utils/ # 工具函数
|
||||
│ └── types/ # TypeScript 类型
|
||||
├── resources/ # 应用资源(图标等)
|
||||
└── build/ # 构建配置
|
||||
```
|
||||
|
||||
## 3. 路径别名
|
||||
|
||||
| 别名 | 路径 |
|
||||
|------|------|
|
||||
| `@renderer/*` | `src/renderer/src/*` |
|
||||
| `@main/*` | `src/main/*` |
|
||||
| `@preload/*` | `src/preload/*` |
|
||||
|
||||
使用示例:
|
||||
```typescript
|
||||
import { useStore } from '@renderer/stores'
|
||||
import { ipcHandler } from '@main/ipc-handlers'
|
||||
```
|
||||
|
||||
## 4. 代码风格
|
||||
|
||||
### 导入顺序
|
||||
|
||||
```typescript
|
||||
// 1. Node.js 内置模块
|
||||
import { join } from 'path'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
|
||||
// 2. 第三方库
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Button, Layout } from 'antd'
|
||||
|
||||
// 3. 项目内部模块(使用别名)
|
||||
import { useDomainStore } from '@renderer/stores'
|
||||
import { formatDate } from '@renderer/utils'
|
||||
|
||||
// 4. 相对导入
|
||||
import './styles.css'
|
||||
```
|
||||
|
||||
### 命名规范
|
||||
|
||||
| 类型 | 规范 | 示例 |
|
||||
|------|------|------|
|
||||
| 组件文件 | PascalCase | `DomainManager.tsx` |
|
||||
| 工具函数文件 | camelCase | `formatDate.ts` |
|
||||
| Store 文件 | camelCase + Store | `domainStore.ts` |
|
||||
| 类型文件 | camelCase | `types.ts` |
|
||||
| 组件名 | PascalCase | `DomainManager` |
|
||||
| 函数/变量 | camelCase | `handleSubmit` |
|
||||
| 常量 | UPPER_SNAKE_CASE | `MAX_FILE_SIZE` |
|
||||
| 类型/接口 | PascalCase | `DomainConfig` |
|
||||
|
||||
### TypeScript 规范
|
||||
|
||||
```typescript
|
||||
// 显式类型定义
|
||||
interface DomainConfig {
|
||||
id: string
|
||||
name: string
|
||||
domain: string
|
||||
username: string
|
||||
authType: 'password' | 'api_token'
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 函数返回类型
|
||||
function createWindow(): void { }
|
||||
|
||||
// 异步函数
|
||||
async function fetchDomains(): Promise<Domain[]> { }
|
||||
|
||||
// 避免使用 any,使用 unknown 或具体类型
|
||||
function parseResponse(data: unknown): DomainConfig {
|
||||
// 类型守卫
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
throw new Error('Invalid response')
|
||||
}
|
||||
return data as DomainConfig
|
||||
}
|
||||
```
|
||||
|
||||
### React 组件规范
|
||||
|
||||
```typescript
|
||||
// 函数组件优先
|
||||
interface DomainListProps {
|
||||
domains: Domain[]
|
||||
onSelect: (domain: Domain) => void
|
||||
}
|
||||
|
||||
function DomainList({ domains, onSelect }: DomainListProps) {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
// Hooks 放在组件顶部
|
||||
const { token } = theme.useToken()
|
||||
|
||||
// 事件处理函数使用 useCallback
|
||||
const handleClick = useCallback((domain: Domain) => {
|
||||
setSelectedId(domain.id)
|
||||
onSelect(domain)
|
||||
}, [onSelect])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* JSX */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DomainList
|
||||
```
|
||||
|
||||
### Zustand Store 规范
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
interface DomainState {
|
||||
domains: Domain[]
|
||||
currentDomain: Domain | null
|
||||
addDomain: (domain: Domain) => void
|
||||
removeDomain: (id: string) => void
|
||||
setCurrentDomain: (domain: Domain | null) => void
|
||||
}
|
||||
|
||||
export const useDomainStore = create<DomainState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
domains: [],
|
||||
currentDomain: null,
|
||||
addDomain: (domain) => set((state) => ({
|
||||
domains: [...state.domains, domain]
|
||||
})),
|
||||
removeDomain: (id) => set((state) => ({
|
||||
domains: state.domains.filter(d => d.id !== id)
|
||||
})),
|
||||
setCurrentDomain: (domain) => set({ currentDomain: domain })
|
||||
}),
|
||||
{ name: 'domain-storage' }
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## 5. 错误处理
|
||||
|
||||
### 主进程错误处理
|
||||
|
||||
```typescript
|
||||
// IPC 处理错误
|
||||
ipcMain.handle('fetch-domains', async () => {
|
||||
try {
|
||||
const domains = await fetchDomainsFromApi()
|
||||
return { success: true, data: domains }
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch domains:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 渲染进程错误处理
|
||||
|
||||
```typescript
|
||||
// 使用 Result 模式
|
||||
type Result<T> =
|
||||
| { success: true; data: T }
|
||||
| { success: false; error: string }
|
||||
|
||||
async function handleFetch(): Promise<Result<Domain[]>> {
|
||||
try {
|
||||
const result = await window.api.fetchDomains()
|
||||
if (!result.success) {
|
||||
message.error(result.error)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
||||
message.error(errorMsg)
|
||||
return { success: false, error: errorMsg }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. IPC 通信规范
|
||||
|
||||
### Preload 暴露 API
|
||||
|
||||
```typescript
|
||||
// preload/index.ts
|
||||
const api = {
|
||||
// 使用 invoke 进行双向通信
|
||||
fetchDomains: () => ipcRenderer.invoke('fetch-domains'),
|
||||
|
||||
// 使用 send 进行单向通信
|
||||
notify: (message: string) => ipcRenderer.send('notify', message),
|
||||
|
||||
// 监听事件
|
||||
onUpdate: (callback: (info: UpdateInfo) => void) =>
|
||||
ipcRenderer.on('update-available', (_, info) => callback(info))
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
```
|
||||
|
||||
### 类型定义
|
||||
|
||||
```typescript
|
||||
// preload/index.d.ts
|
||||
interface ElectronAPI {
|
||||
fetchDomains: () => Promise<Result<Domain[]>>
|
||||
notify: (message: string) => void
|
||||
onUpdate: (callback: (info: UpdateInfo) => void) => void
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
api: ElectronAPI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. UI 组件规范
|
||||
|
||||
### 使用 LobeHub UI + Ant Design
|
||||
|
||||
```typescript
|
||||
// 使用 antd-style 进行样式
|
||||
import { createStyles } from 'antd-style'
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
container: css`
|
||||
padding: ${token.paddingLG}px;
|
||||
background: ${token.colorBgContainer};
|
||||
border-radius: ${token.borderRadiusLG}px;
|
||||
`
|
||||
}))
|
||||
|
||||
function MyComponent() {
|
||||
const { styles } = useStyles()
|
||||
return <div className={styles.container}>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
### 国际化
|
||||
|
||||
```typescript
|
||||
// 使用中文默认
|
||||
import zhCN from 'antd/locale/zh_CN'
|
||||
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<App />
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
## 8. 安全规范
|
||||
|
||||
### 密码存储
|
||||
|
||||
```typescript
|
||||
// 使用 safeStorage 加密存储
|
||||
import { safeStorage } from 'electron'
|
||||
|
||||
// 加密
|
||||
const encrypted = safeStorage.encryptString(password)
|
||||
|
||||
// 解密
|
||||
const decrypted = safeStorage.decryptString(encrypted)
|
||||
```
|
||||
|
||||
### CSP 配置
|
||||
|
||||
```html
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self';
|
||||
script-src 'self';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self' data:;" />
|
||||
```
|
||||
|
||||
## 9. fnm 环境配置
|
||||
|
||||
所有 npm/npx 命令需要先加载 fnm 环境:
|
||||
|
||||
```bash
|
||||
# 方式一:使用 wrapper 脚本
|
||||
~/.config/opencode/node-fnm-wrapper.sh npm run dev
|
||||
|
||||
# 方式二:手动加载
|
||||
eval "$(fnm env --use-on-cd)" && npm run dev
|
||||
```
|
||||
|
||||
## 10. 注意事项
|
||||
|
||||
1. **ESM Only**: LobeHub UI 仅支持 ESM,确保 `tsconfig.json` 中 `"module": "ESNext"`
|
||||
2. **React 19**: 必须使用 `@types/react@^19.0.0` 和 `@types/react-dom@^19.0.0`
|
||||
3. **CSS 方案**: 使用 `antd-style`,不使用 Tailwind CSS
|
||||
4. **Context Isolation**: 必须启用 `contextIsolation: true`
|
||||
5. **禁止类型断言**: 避免使用 `as any`,优先使用类型守卫
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Kintone Customize Manager
|
||||
|
||||
Kintone 自定义资源管理器 - 用于管理和部署 Kintone 平台的自定义资源(JavaScript、CSS、Plugin)。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **框架**: Electron 30+
|
||||
- **前端**: React 19 + TypeScript
|
||||
- **构建工具**: electron-vite + electron-builder
|
||||
- **状态管理**: Zustand
|
||||
- **UI 组件库**: LobeHub UI + Ant Design 6
|
||||
- **CSS 方案**: antd-style
|
||||
- **代码编辑器**: CodeMirror 6
|
||||
|
||||
## 开发
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 启动开发服务器
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 构建生产版本
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
npm run package:win
|
||||
|
||||
# macOS
|
||||
npm run package:mac
|
||||
|
||||
# Linux
|
||||
npm run package:linux
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
kintone-customize-manager/
|
||||
├─ src/
|
||||
│ ├─ main/ # Electron 主进程
|
||||
│ ├─ preload/ # Preload 脚本
|
||||
│ └─ renderer/ # React 渲染进程
|
||||
│ └─ src/
|
||||
│ ├─ components/
|
||||
│ ├─ hooks/
|
||||
│ ├─ stores/
|
||||
│ ├─ utils/
|
||||
│ └─ types/
|
||||
├─ resources/ # 应用资源
|
||||
└─ build/ # 构建配置
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
909
REQUIREMENTS.md
Normal file
909
REQUIREMENTS.md
Normal file
@@ -0,0 +1,909 @@
|
||||
# Kintone Customize Manager - 产品需求文档
|
||||
|
||||
**版本**: 1.0.0
|
||||
**创建日期**: 2026-03-11
|
||||
**状态**: 草稿
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
|
||||
Kintone Customize Manager 是一款桌面应用程序,用于管理和部署 Kintone 平台的自定义资源(JavaScript、CSS、Plugin)。
|
||||
|
||||
### 1.2 目标用户
|
||||
|
||||
- Kintone 开发者
|
||||
- Kintone 管理员
|
||||
- 需要在多个 Kintone 环境间同步配置的团队
|
||||
|
||||
### 1.3 核心价值
|
||||
|
||||
- **高效部署**: 拖拽本地文件即可部署到 Kintone 应用
|
||||
- **版本管理**: 本地管理所有部署历史,支持快速切换和回滚
|
||||
- **多环境管理**: 统一管理多个 Kintone 实例(开发/测试/生产)
|
||||
- **资源备份**: 从 Kintone 下载已部署代码,支持灾难恢复
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术选型
|
||||
|
||||
### 2.1 技术栈
|
||||
|
||||
| 层级 | 技术 | 说明 |
|
||||
|------|------|------|
|
||||
| 框架 | Electron 30+ | 跨平台桌面应用 |
|
||||
| 前端 | React 19+ TypeScript | UI 框架 |
|
||||
| 构建工具 | electron-vite + electron-builder | 开发构建一体化方案 |
|
||||
| 状态管理 | Zustand | 轻量级状态管理,Electron 友好 |
|
||||
| UI 组件库 | LobeHub UI + Ant Design 6 | AIGC 应用组件库,内置暗黑模式 |
|
||||
| CSS 方案 | antd-style | CSS-in-JS,无需 Tailwind |
|
||||
| 代码编辑器 | CodeMirror 6 | 模块化编辑器,Diff 功能完善 |
|
||||
| 密码存储 | safeStorage + electron-store | Electron 内置安全存储 |
|
||||
| 自动更新 | electron-updater | 自动检查、下载、安装更新 |
|
||||
| 配置文件 | electron-store | 持久化存储 |
|
||||
|
||||
### 2.1.1 技术选型说明
|
||||
|
||||
#### 构建工具:electron-vite + electron-builder
|
||||
|
||||
**选择理由**:
|
||||
- **electron-vite**:2026年最佳 Electron 开发工具
|
||||
- 基于 Vite 5.0,开发体验极佳
|
||||
- 零配置,开箱即用
|
||||
- 完整的 HMR 和热重载支持
|
||||
- 社区活跃(5.3k+ stars)
|
||||
|
||||
- **electron-builder**:打包领域的事实标准
|
||||
- 自动更新开箱即用(electron-updater)
|
||||
- 跨平台支持完善
|
||||
- 社区最成熟(14k+ stars)
|
||||
|
||||
**替代方案**:
|
||||
- Electron Forge:官方维护,但 Vite 支持仍为实验性
|
||||
- 手动 Vite + Electron:配置复杂,不推荐
|
||||
|
||||
---
|
||||
|
||||
#### UI 组件库:LobeHub UI
|
||||
|
||||
**选择理由**:
|
||||
- 专为 AIGC 应用设计,内置丰富的业务组件
|
||||
- 完全基于 **Ant Design 6**,生态成熟
|
||||
- 使用 **antd-style** (CSS-in-JS),无需 Tailwind CSS
|
||||
- 内置暗黑模式、国际化支持
|
||||
- 已有 Electron 桌面应用成功案例(LobeHub Desktop)
|
||||
- 包含开发者工具组件:CodeEditor、Markdown 渲染、Highlighter
|
||||
|
||||
**技术要求**:
|
||||
- React 19+
|
||||
- ESM only
|
||||
|
||||
---
|
||||
|
||||
#### 状态管理:Zustand
|
||||
|
||||
**选择理由**:
|
||||
- **2026年市场领导者**:周下载量 18M+
|
||||
- 极简 API,学习曲线平缓
|
||||
- 轻量级(~3KB gzipped)
|
||||
- Electron 支持完善(@zubridge/electron)
|
||||
- 丰富的中间件:persist、devtools、immer
|
||||
- TypeScript 支持完善
|
||||
|
||||
**对比其他方案**:
|
||||
| 库 | 周下载量 | 推荐度 |
|
||||
|------|---------|-------|
|
||||
| Zustand | 18M | ⭐⭐⭐⭐⭐ |
|
||||
| Redux Toolkit | 9M | ⭐⭐⭐⭐ |
|
||||
| Jotai | 4M | ⭐⭐⭐⭐ |
|
||||
| MobX | - | ⭐⭐⭐ |
|
||||
| Recoil | 已归档 | ❌ |
|
||||
|
||||
---
|
||||
|
||||
#### 代码编辑器:CodeMirror 6
|
||||
|
||||
**选择理由**:
|
||||
- **包体积优势**:核心 ~300KB vs Monaco 5-10MB
|
||||
- **Electron 兼容性**:无已知问题
|
||||
- **Diff 功能**:`@codemirror/merge` 提供专业的 diff/merge 视图
|
||||
- **模块化设计**:按需加载扩展
|
||||
- **性能优异**:渲染速度快
|
||||
|
||||
**对比 Monaco Editor**:
|
||||
| 特性 | CodeMirror 6 | Monaco Editor |
|
||||
|------|--------------|--------------|
|
||||
| 包体积 | ~300KB ✅ | 5-10MB ❌ |
|
||||
| Electron 兼容 | ✅ 无问题 | ⚠️ 有坑 |
|
||||
| Diff 功能 | ✅ 完善 | ✅ 内置 |
|
||||
| 定制化 | ✅ 极强 | ⚠️ 难度大 |
|
||||
|
||||
---
|
||||
|
||||
#### 密码存储:safeStorage + electron-store
|
||||
|
||||
**选择理由**:
|
||||
- **safeStorage**:Electron 内置安全存储 API
|
||||
- macOS: Keychain Access
|
||||
- Windows: DPAPI
|
||||
- Linux: Secret Service (gnome-keyring/kwallet)
|
||||
|
||||
- **keytar 已弃用**(2022年12月归档):
|
||||
- 存在原生模块编译问题
|
||||
- Windows 平台有已知 bug
|
||||
- Linux 依赖复杂
|
||||
|
||||
**Linux 兼容性处理**:
|
||||
```typescript
|
||||
const backend = safeStorage.getSelectedStorageBackend();
|
||||
if (backend === 'basic_text') {
|
||||
// 警告用户:当前环境无安全存储
|
||||
console.warn('No secure storage available');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### CSS 方案:antd-style
|
||||
|
||||
**选择理由**:
|
||||
- LobeHub UI 和 Ant Design 6 官方推荐
|
||||
- CSS-in-JS,无需额外配置 Tailwind
|
||||
- 支持主题定制和暗黑模式
|
||||
- 性能优化良好
|
||||
|
||||
**无需 Tailwind CSS 的原因**:
|
||||
- LobeHub UI 不依赖 Tailwind
|
||||
- antd-style 提供完整的样式解决方案
|
||||
- 减少配置复杂度
|
||||
|
||||
|
||||
### 2.2 项目结构
|
||||
|
||||
```
|
||||
kintone-customize-manager/
|
||||
├─ src/
|
||||
│ ├─ main/ # Electron 主进程
|
||||
│ │ ├─ index.ts # 主进程入口
|
||||
│ │ ├─ipc-handlers.ts # IPC 通信处理
|
||||
│ │ ├─storage.ts # 文件系统操作
|
||||
│ │ ├─kintone-api.ts # Kintone API 封装
|
||||
│ │ ├─updater.ts # 自动更新逻辑
|
||||
│ │ └─config.ts # 配置管理
|
||||
│ ├─preload/ # Preload 脚本
|
||||
│ │ └─ index.ts
|
||||
│ ├─renderer/ # React 渲染进程
|
||||
│ │ ├─ main.tsx # 渲染进程入口
|
||||
│ │ ├─ App.tsx
|
||||
│ │ ├─ components/ # React 组件
|
||||
│ │ │ ├─ DomainManager/
|
||||
│ │ │ ├─ SpaceTree/
|
||||
│ │ │ ├─ AppDetail/
|
||||
│ │ │ ├─ FileUploader/
|
||||
│ │ │ ├─ CodeViewer/
|
||||
│ │ │ ├─ VersionHistory/
|
||||
│ │ │ └─ Settings/
|
||||
│ │ ├─ hooks/ # 自定义 Hooks
|
||||
│ │ ├─ stores/ # Zustand Stores
|
||||
│ │ ├─ utils/ # 工具函数
|
||||
│ │ └─ types/ # TypeScript 类型定义
|
||||
│ └─ types/ # 共享类型定义
|
||||
├─ resources/ # 应用资源
|
||||
└─ package.json
|
||||
```
|
||||
|
||||
### 2.3 本地存储结构
|
||||
|
||||
```
|
||||
~/.kintone-manager/
|
||||
├─ config.json # 应用配置
|
||||
├─ secure/ # 加密密码存储 (safeStorage + electron-store)
|
||||
├─ downloads/ # 从 Kintone 下载的文件
|
||||
│ └─ {domain}/{appId}/{timestamp}/
|
||||
│ ├─ PC/
|
||||
│ ├─ Mobile/
|
||||
│ └─ metadata.json
|
||||
├─ versions/ # 版本历史
|
||||
│ └─ {domain}/{appId}/{type}/
|
||||
│ └─ {timestamp}/
|
||||
│ └─ {filename}
|
||||
└─ logs/ # 操作日志
|
||||
└─ operations.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 多 Domain 管理
|
||||
|
||||
#### 3.1.1 功能描述
|
||||
|
||||
用户可以配置和管理多个 Kintone 实例(Domain),每个 Domain 包含访问所需的所有认证信息。
|
||||
|
||||
#### 3.1.2 功能需求
|
||||
|
||||
**FR-DOMAIN-001**: 创建 Domain
|
||||
- 用户可创建新的 Domain 配置
|
||||
- 必填字段:
|
||||
- 名称(自定义,如"生产环境")
|
||||
- Kintone 域名(如 company.kintone.com)
|
||||
- 用户名(邮箱格式)
|
||||
- 密码
|
||||
- 认证类型(密码认证 / API Token,默认密码认证)
|
||||
- 可选字段:
|
||||
- API Token(当认证类型为 API Token 时必填)
|
||||
- 备注
|
||||
|
||||
**FR-DOMAIN-002**: 编辑 Domain
|
||||
- 用户可修改已存在的 Domain 配置(除名称外均可修改)
|
||||
- 修改密码时需输入新密码
|
||||
|
||||
**FR-DOMAIN-003**: 删除 Domain
|
||||
- 用户可删除 Domain 配置
|
||||
- 删除前需确认
|
||||
- 删除 Domain 不会删除本地已存储的版本历史
|
||||
|
||||
**FR-DOMAIN-004**: 列出 Domain
|
||||
- 显示所有已配置的 Domain 列表
|
||||
- 显示每个 Domain 的连接状态(已连接/未连接/连接失败)
|
||||
- 显示 Domain 名称和域名
|
||||
|
||||
**FR-DOMAIN-005**: 切换 Domain
|
||||
- 用户可从 Domain 列表快速切换当前工作的 Domain
|
||||
- 切换后自动加载该 Domain 下的 Space 和 App 列表
|
||||
|
||||
**FR-DOMAIN-006**: 密码加密存储
|
||||
- 使用 keytar 将密码加密存储到系统密钥链
|
||||
- Windows: Credential Manager
|
||||
- macOS: Keychain
|
||||
- Linux: libsecret / Keyring
|
||||
|
||||
**FR-DOMAIN-007**: 连接状态检测
|
||||
- 启动时自动检测所有 Domain 的连接状态
|
||||
- 切换 Domain 时检测连接状态
|
||||
- 显示连接失败的原因(网络错误/认证失败/域名错误)
|
||||
|
||||
#### 3.1.3 数据结构
|
||||
|
||||
```typescript
|
||||
interface Domain {
|
||||
id: string; // UUID
|
||||
name: string; // 自定义名称
|
||||
domain: string; // Kintone 域名
|
||||
username: string; // 用户名(邮箱)
|
||||
authType: 'password' | 'api_token';
|
||||
apiToken?: string; // 可选,当 authType 为 api_token 时
|
||||
createdAt: string; // ISO 8601
|
||||
updatedAt: string; // ISO 8601
|
||||
}
|
||||
|
||||
// 密码存储在 keytar 中,key 为 `kintone-manager:${domain.id}`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 资源浏览
|
||||
|
||||
#### 3.2.1 功能描述
|
||||
|
||||
浏览当前 Domain 下的所有 Space 和 App,查看应用的自定义资源配置。
|
||||
|
||||
#### 3.2.2 功能需求
|
||||
|
||||
**FR-BROWSE-001**: 获取 Space 列表
|
||||
- 调用 Kintone API 获取当前 Domain 下的所有 Space
|
||||
- 显示 Space 名称和 ID
|
||||
- 支持按 Space 名称排序
|
||||
|
||||
**FR-BROWSE-002**: 获取 App 列表
|
||||
- 按 Space 分组显示 App 列表
|
||||
- 或显示所有 App(不分 Space)
|
||||
- 显示 App 名称、ID、创建时间
|
||||
- 支持按 App 名称搜索和过滤
|
||||
|
||||
**FR-BROWSE-003**: 获取 App 详情
|
||||
- 选择 App 后查看详细配置
|
||||
- 显示以下信息:
|
||||
- App 名称
|
||||
- App ID
|
||||
- 所属 Space
|
||||
- 创建时间
|
||||
- 最后更新时间
|
||||
|
||||
**FR-BROWSE-004**: 查看自定义资源配置
|
||||
- 查看 PC 端的 JavaScript 文件配置
|
||||
- 查看 PC 端的 CSS 文件配置
|
||||
- 查看移动端的 JavaScript 文件配置
|
||||
- 查看移动端的 CSS 文件配置
|
||||
- 查看已安装的 Plugin 列表(只读,后续版本支持管理)
|
||||
|
||||
**FR-BROWSE-005**: 查看文件详情
|
||||
- 文件名
|
||||
- 文件类型(JS/CSS)
|
||||
- 部署位置(PC/移动端)
|
||||
- 加载位置(Header/Body/Footer 等,仅 JS)
|
||||
- 文件大小
|
||||
- 最后更新时间
|
||||
|
||||
#### 3.2.3 Kintone API 端点
|
||||
|
||||
```
|
||||
# 获取 Space 列表
|
||||
GET /k/v1/space.json
|
||||
|
||||
# 获取 App 列表(按 Space)
|
||||
GET /k/v1/apps.json?space={spaceId}
|
||||
|
||||
# 获取 App 配置
|
||||
GET /k/v1/app.json?app={appId}
|
||||
|
||||
# 获取文件内容
|
||||
GET /k/v1/file.json?fileKey={fileKey}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 下载已部署代码
|
||||
|
||||
#### 3.3.1 功能描述
|
||||
|
||||
从 Kintone 下载已部署的 JavaScript 和 CSS 文件,保存到本地并加入版本历史。
|
||||
|
||||
#### 3.3.2 功能需求
|
||||
|
||||
**FR-DOWNLOAD-001**: 下载单个文件
|
||||
- 用户可从 App 详情页下载单个已部署的文件
|
||||
- 支持下载:
|
||||
- PC 端 JavaScript
|
||||
- PC 端 CSS
|
||||
- 移动端 JavaScript
|
||||
- 移动端 CSS
|
||||
|
||||
**FR-DOWNLOAD-002**: 批量下载
|
||||
- 用户可一次性下载 App 的所有配置文件
|
||||
- 可选择下载范围:
|
||||
- □ PC 端 JavaScript
|
||||
- □ PC 端 CSS
|
||||
- □ 移动端 JavaScript
|
||||
- □ 移动端 CSS
|
||||
- □ Plugin 配置(可选)
|
||||
|
||||
**FR-DOWNLOAD-003**: 文件查看
|
||||
- 下载前可在应用内预览文件内容(只读)
|
||||
- 使用 Monaco Editor 显示代码
|
||||
- 支持语法高亮
|
||||
|
||||
**FR-DOWNLOAD-004**: 保存到本地
|
||||
- 默认保存路径:`~/.kintone-manager/downloads/{domain}/{appId}/{timestamp}/`
|
||||
- 用户可自定义保存路径
|
||||
- 文件按 PC/Mobile 分类保存
|
||||
|
||||
**FR-DOWNLOAD-005**: 下载进度显示
|
||||
- 显示下载进度百分比
|
||||
- 显示已下载文件大小
|
||||
- 显示预计剩余时间
|
||||
- 支持取消下载
|
||||
|
||||
**FR-DOWNLOAD-006**: 自动加入版本历史
|
||||
- 下载完成后自动将文件加入版本历史
|
||||
- 版本历史记录下载时间和来源
|
||||
|
||||
**FR-DOWNLOAD-007**: 下载元数据
|
||||
- 保存下载元数据到 metadata.json
|
||||
- 包含:
|
||||
- 下载时间
|
||||
- Domain 信息
|
||||
- App 信息
|
||||
- 文件列表
|
||||
|
||||
#### 3.3.3 数据结构
|
||||
|
||||
```typescript
|
||||
interface DownloadParams {
|
||||
domainId: string;
|
||||
appId: string;
|
||||
fileType: 'pc_js' | 'pc_css' | 'mobile_js' | 'mobile_css';
|
||||
fileKeys: string[]; // Kintone 文件 key
|
||||
}
|
||||
|
||||
interface DownloadResult {
|
||||
success: boolean;
|
||||
path?: string; // 保存路径
|
||||
error?: string; // 错误信息
|
||||
}
|
||||
|
||||
interface DownloadMetadata {
|
||||
downloadedAt: string; // ISO 8601
|
||||
domain: string;
|
||||
domainId: string;
|
||||
appId: string;
|
||||
appName: string;
|
||||
spaceId: string;
|
||||
spaceName: string;
|
||||
files: {
|
||||
type: 'pc' | 'mobile';
|
||||
fileType: 'js' | 'css';
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
size: number;
|
||||
path: string;
|
||||
}[];
|
||||
source: 'kintone';
|
||||
notes?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 拖拽部署
|
||||
|
||||
#### 3.4.1 功能描述
|
||||
|
||||
用户可将本地 JavaScript 或 CSS 文件拖拽到应用中,选择部署位置后上传到 Kintone。
|
||||
|
||||
#### 3.4.2 功能需求
|
||||
|
||||
**FR-DEPLOY-001**: 文件拖拽
|
||||
- 支持拖拽本地文件到上传区域
|
||||
- 自动识别文件类型(JS/CSS)
|
||||
- 支持多文件拖拽
|
||||
- 显示拖拽文件的预览列表
|
||||
|
||||
**FR-DEPLOY-002**: 文件验证
|
||||
- 验证文件类型(仅支持 .js 和 .css)
|
||||
- 验证文件大小(单个文件最大 10MB)
|
||||
- 验证文件内容(非空)
|
||||
|
||||
**FR-DEPLOY-003**: 部署位置选择
|
||||
- JavaScript 文件可选位置:
|
||||
- PC 端 - Header
|
||||
- PC 端 - Body
|
||||
- PC 端 - Footer
|
||||
- 移动端 - Header
|
||||
- 移动端 - Body
|
||||
- 移动端 - Footer
|
||||
- CSS 文件可选位置:
|
||||
- PC 端
|
||||
- 移动端
|
||||
|
||||
**FR-DEPLOY-004**: 代码变更高亮对比
|
||||
- 部署前显示当前 Kintone 上的代码与新代码的对比
|
||||
- 使用 Monaco Editor Diff 模式
|
||||
- 显示新增、修改、删除的行
|
||||
|
||||
**FR-DEPLOY-005**: 部署前确认
|
||||
- 显示部署确认 Dialog
|
||||
- 显示以下信息:
|
||||
- 目标 Domain
|
||||
- 目标 App 名称和 ID
|
||||
- 部署文件列表
|
||||
- 部署位置
|
||||
- 文件大小变化
|
||||
- 代码行数变化
|
||||
- 用户确认后才会执行部署
|
||||
|
||||
**FR-DEPLOY-006**: 部署执行
|
||||
- 调用 Kintone API 更新应用配置
|
||||
- 部署成功后显示成功提示
|
||||
- 部署失败时显示详细错误信息
|
||||
|
||||
**FR-DEPLOY-007**: 自动备份当前版本
|
||||
- 部署前自动获取并保存当前 Kintone 上的配置
|
||||
- 备份保存到 `~/.kintone-manager/versions/{domain}/{appId}/backup/{timestamp}/`
|
||||
- 备份可用于回滚
|
||||
|
||||
**FR-DEPLOY-008**: 部署后处理
|
||||
- 部署成功后自动加入版本历史
|
||||
- 刷新 App 配置显示
|
||||
|
||||
#### 3.4.3 Kintone API 端点
|
||||
|
||||
```
|
||||
# 更新应用配置
|
||||
PUT /k/v1/app.json
|
||||
|
||||
# 请求体结构
|
||||
{
|
||||
"app": "{appId}",
|
||||
"javascript": {
|
||||
"pc": [
|
||||
{ "type": "file", "file": { "fileKey": "xxx" } }
|
||||
],
|
||||
"mobile": [...]
|
||||
},
|
||||
"stylesheet": {
|
||||
"pc": [...],
|
||||
"mobile": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.4 数据结构
|
||||
|
||||
```typescript
|
||||
interface DeployParams {
|
||||
domainId: string;
|
||||
appId: string;
|
||||
files: {
|
||||
content: string;
|
||||
fileName: string;
|
||||
fileType: 'js' | 'css';
|
||||
position: 'pc_header' | 'pc_body' | 'pc_footer' |
|
||||
'mobile_header' | 'mobile_body' | 'mobile_footer' |
|
||||
'pc_css' | 'mobile_css';
|
||||
}[];
|
||||
}
|
||||
|
||||
interface DeployResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
backupPath?: string; // 备份文件路径
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.5 版本管理
|
||||
|
||||
#### 3.5.1 功能描述
|
||||
|
||||
本地管理所有部署历史,支持版本查看、切换和回滚。
|
||||
|
||||
#### 3.5.2 功能需求
|
||||
|
||||
**FR-VERSION-001**: 版本列表
|
||||
- 按时间倒序显示所有版本
|
||||
- 显示版本信息:
|
||||
- 时间戳
|
||||
- 来源(本地上传 / Kintone 下载)
|
||||
- 文件类型(JS/CSS)
|
||||
- 文件大小
|
||||
- 部署位置
|
||||
- 备注/标签(可选)
|
||||
|
||||
**FR-VERSION-002**: 版本查看
|
||||
- 可查看任意历史版本的代码内容
|
||||
- 使用 Monaco Editor 只读模式显示
|
||||
- 支持语法高亮
|
||||
|
||||
**FR-VERSION-003**: 版本对比
|
||||
- 可选择两个版本进行对比
|
||||
- 使用 Monaco Editor Diff 模式
|
||||
- 显示差异行数和变化统计
|
||||
|
||||
**FR-VERSION-004**: 版本切换
|
||||
- 可将历史版本设置为当前版本(仅本地)
|
||||
- 切换后代码可在编辑器中查看和编辑
|
||||
|
||||
**FR-VERSION-005**: 版本回滚
|
||||
- 可将历史版本重新部署到 Kintone
|
||||
- 回滚流程与正常部署相同(确认 Dialog、备份当前版本等)
|
||||
|
||||
**FR-VERSION-006**: 版本标签
|
||||
- 可为版本添加标签(如 v1.0、production、hotfix 等)
|
||||
- 支持通过标签过滤版本
|
||||
|
||||
**FR-VERSION-007**: 版本删除
|
||||
- 可删除不需要的历史版本
|
||||
- 删除前需确认
|
||||
- 支持批量删除
|
||||
|
||||
**FR-VERSION-008**: 版本存储管理
|
||||
- 显示版本历史占用的磁盘空间
|
||||
- 支持清理旧版本释放空间
|
||||
|
||||
#### 3.5.3 数据结构
|
||||
|
||||
```typescript
|
||||
interface Version {
|
||||
id: string; // UUID
|
||||
domainId: string;
|
||||
appId: string;
|
||||
fileType: 'js' | 'css';
|
||||
position: string; // 部署位置
|
||||
filePath: string; // 本地文件路径
|
||||
content?: string; // 文件内容(可选,大文件可能不存储)
|
||||
size: number; // 文件大小(字节)
|
||||
source: 'upload' | 'download' | 'rollback';
|
||||
createdAt: string; // ISO 8601
|
||||
tags?: string[]; // 标签
|
||||
notes?: string; // 备注
|
||||
}
|
||||
|
||||
interface VersionHistory {
|
||||
appId: string;
|
||||
versions: Version[];
|
||||
totalSize: number; // 总大小(字节)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.6 自动更新
|
||||
|
||||
#### 3.6.1 功能描述
|
||||
|
||||
应用自动检查、下载和安装更新。
|
||||
|
||||
#### 3.6.2 功能需求
|
||||
|
||||
**FR-UPDATE-001**: 自动检查更新
|
||||
- 启动时自动检查最新版本
|
||||
- 每次运行检查一次(避免频繁请求)
|
||||
- 从 GitHub Releases 获取版本信息
|
||||
|
||||
**FR-UPDATE-002**: 更新通知
|
||||
- 发现新版本时显示通知
|
||||
- 显示更新内容(Release Notes)
|
||||
- 用户可选择立即更新或稍后提醒
|
||||
|
||||
**FR-UPDATE-003**: 后台下载
|
||||
- 用户确认后在后台下载更新包
|
||||
- 显示下载进度
|
||||
- 支持取消下载
|
||||
|
||||
**FR-UPDATE-004**: 安装更新
|
||||
- 下载完成后提示用户重启安装
|
||||
- 重启后自动安装更新
|
||||
- 安装完成后重新启动应用
|
||||
|
||||
**FR-UPDATE-005**: 手动检查更新
|
||||
- 设置中提供"检查更新"按钮
|
||||
- 手动检查更新逻辑与自动检查相同
|
||||
|
||||
#### 3.6.3 技术实现
|
||||
|
||||
```typescript
|
||||
// 使用 electron-updater
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
// 检查更新
|
||||
autoUpdater.checkForUpdates();
|
||||
|
||||
// 事件监听
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
// 显示更新提示
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
// 更新进度显示
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
// 提示用户重启安装
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7 自动备份
|
||||
|
||||
#### 3.7.1 功能描述
|
||||
|
||||
部署前自动备份 Kintone 上的当前配置。
|
||||
|
||||
#### 3.7.2 功能需求
|
||||
|
||||
**FR-BACKUP-001**: 部署前自动备份
|
||||
- 每次部署前自动备份当前 Kintone 配置
|
||||
- 备份包含所有已部署的 JS/CSS 文件
|
||||
- 备份路径:`~/.kintone-manager/versions/{domain}/{appId}/backup/{timestamp}/`
|
||||
|
||||
**FR-BACKUP-002**: 备份元数据
|
||||
- 保存备份元数据到 metadata.json
|
||||
- 包含备份时间、Domain、App、文件列表
|
||||
|
||||
**FR-BACKUP-003**: 备份清理
|
||||
- 保留最近 50 个备份
|
||||
- 或保留 30 天内的备份
|
||||
- 超出的备份自动清理
|
||||
|
||||
---
|
||||
|
||||
## 4. 非功能需求
|
||||
|
||||
### 4.1 性能需求
|
||||
|
||||
**NFR-PERF-001**: 启动时间
|
||||
- 冷启动时间 < 3 秒
|
||||
- 热启动时间 < 1 秒
|
||||
|
||||
**NFR-PERF-002**: 响应时间
|
||||
- UI 操作响应时间 < 100ms
|
||||
- Kintone API 调用超时时间 30 秒
|
||||
|
||||
**NFR-PERF-003**: 文件大小限制
|
||||
- 单个文件最大 10MB
|
||||
- 版本历史总大小限制 1GB(可配置)
|
||||
|
||||
### 4.2 安全需求
|
||||
|
||||
**NFR-SEC-001**: 密码存储
|
||||
- 使用 Electron safeStorage API 加密存储密码
|
||||
- 配合 electron-store 持久化加密数据
|
||||
- 密码不在配置文件中明文存储
|
||||
- 不记录密码到日志
|
||||
- Linux 环境检测并警告不安全存储
|
||||
|
||||
**NFR-SEC-002**: 通信安全
|
||||
- 仅支持 HTTPS 连接 Kintone
|
||||
- 不记录敏感信息到日志
|
||||
|
||||
**NFR-SEC-003**: 文件安全
|
||||
- 本地存储的文件设置适当的文件权限
|
||||
- 敏感配置文件限制访问权限
|
||||
|
||||
### 4.3 可靠性需求
|
||||
|
||||
**NFR-REL-001**: 错误处理
|
||||
- 所有 Kintone API 调用都有错误处理
|
||||
- 网络错误显示友好提示
|
||||
- 部署失败自动回滚
|
||||
|
||||
**NFR-REL-002**: 数据持久化
|
||||
- 配置变更立即保存到磁盘
|
||||
- 定期备份配置文件
|
||||
|
||||
**NFR-REL-003**: 崩溃恢复
|
||||
- 应用崩溃后重启不丢失数据
|
||||
- 未完成的部署操作可恢复或回滚
|
||||
|
||||
### 4.4 可用性需求
|
||||
|
||||
**NFR-USE-001**: 跨平台
|
||||
- 支持 Windows 10/11
|
||||
- 支持 macOS 10.15+
|
||||
- 支持 Ubuntu 20.04+
|
||||
|
||||
**NFR-USE-002**: 多语言
|
||||
- 初始版本仅支持中文
|
||||
- 预留国际化接口
|
||||
|
||||
**NFR-USE-003**: 暗黑模式
|
||||
- 支持暗黑模式
|
||||
- 跟随系统设置自动切换
|
||||
|
||||
---
|
||||
|
||||
## 5. 依赖管理
|
||||
|
||||
### 5.1 主要依赖
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"electron": "^30.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"@lobehub/ui": "^5.5.0",
|
||||
"antd": "^6.1.0",
|
||||
"motion": "^12.0.0",
|
||||
"antd-style": "^4.1.0",
|
||||
"zustand": "^5.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/merge": "^6.0.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"electron-store": "^10.0.0",
|
||||
"electron-updater": "^6.3.0",
|
||||
"lucide-react": "^0.563.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.0",
|
||||
"electron-vite": "^5.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"electron-builder": "^26.0.0",
|
||||
"@types/node": "^22.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Kintone API 依赖
|
||||
|
||||
- Kintone REST API
|
||||
- Kintone File API
|
||||
- 需要 API 访问权限
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险与假设
|
||||
|
||||
### 6.1 技术风险
|
||||
|
||||
**RISK-001**: Kintone API 限制
|
||||
- API 调用有频率限制
|
||||
- 文件大小有限制
|
||||
- 对策:实现重试机制和错误处理
|
||||
|
||||
**RISK-002**: 大文件处理
|
||||
- 大文件可能导致内存问题
|
||||
- 对策:使用流式处理,限制文件大小
|
||||
|
||||
- Linux 上可能无安全存储后端(无 gnome-keyring/kwallet)
|
||||
- 对策:检测并警告用户,提供降级方案
|
||||
|
||||
### 6.2 假设
|
||||
|
||||
**ASSUMP-001**: 用户有 Kintone 访问权限
|
||||
- 用户已有 Kintone 账号
|
||||
- 用户有应用管理权限
|
||||
|
||||
**ASSUMP-002**: 用户了解 Kintone 自定义功能
|
||||
- 用户了解 JS/CSS 部署位置的影响
|
||||
- 用户了解代码部署的风险
|
||||
|
||||
**ASSUMP-003**: 一般无并发修改
|
||||
- 不同用户不会同时修改同一应用
|
||||
- 暂不实现并发冲突检测
|
||||
|
||||
---
|
||||
|
||||
## 7. 后续版本规划
|
||||
|
||||
### 7.1 P1 功能(推荐后续追加)
|
||||
|
||||
- **备份恢复**: 导出/导入所有 Domain 配置
|
||||
- **批量操作**: 多应用批量部署、环境间同步
|
||||
- **操作日志**: 记录所有部署历史
|
||||
- **快捷键**: 提升操作效率
|
||||
- **快速搜索**: 全局搜索 App
|
||||
|
||||
### 7.2 P2 功能(未来扩展)
|
||||
|
||||
- **插件管理**: 上传/更新/配置 Plugin
|
||||
- **部署统计**: 部署次数、成功率等统计
|
||||
- **团队协作**: 配置共享、权限管理
|
||||
- **CLI 工具**: 命令行部署工具
|
||||
- **CI/CD 集成**: GitHub Actions 等集成
|
||||
|
||||
---
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 Kintone API 文档
|
||||
|
||||
- [Kintone REST API](https://developer.kintone.io/hc/en-us/articles/213149177/)
|
||||
- [Kintone File API](https://developer.kintone.io/hc/en-us/articles/213149217/)
|
||||
|
||||
### 8.2 相关技术文档
|
||||
|
||||
- [Electron 文档](https://www.electronjs.org/docs)
|
||||
- [electron-vite 文档](https://electron-vite.org/)
|
||||
- [LobeHub UI 文档](https://ui.lobehub.com/)
|
||||
- [safeStorage API](https://www.electronjs.org/docs/latest/api/safe-storage)
|
||||
- [CodeMirror 6 文档](https://codemirror.net/)
|
||||
- [Zustand 文档](https://zustand.docs.pmnd.rs/)
|
||||
- [electron-builder 文档](https://www.electron.build/)
|
||||
|
||||
### 8.3 术语表
|
||||
|
||||
| 术语 | 说明 |
|
||||
|------|------|
|
||||
| Domain | Kintone 实例,如 company.kintone.com |
|
||||
| Space | Kintone 空间,应用的容器 |
|
||||
| App | Kintone 应用 |
|
||||
| FileKey | Kintone 文件的唯一标识 |
|
||||
| IPC | Electron 进程间通信 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 变更历史
|
||||
|
||||
| 版本 | 日期 | 变更内容 | 作者 |
|
||||
|------|------|----------|------|
|
||||
| 1.0.0 | 2026-03-11 | 初始版本 | - |
|
||||
| 1.1.0 | 2026-03-11 | 更新技术栈:LobeHub UI、CodeMirror 6、safeStorage | - |
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
42
electron-builder.yml
Normal file
42
electron-builder.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
appId: com.kintone.customize-manager
|
||||
productName: Kintone Customize Manager
|
||||
directories:
|
||||
buildResources: build
|
||||
output: dist
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
win:
|
||||
executableName: Kintone Customize Manager
|
||||
target:
|
||||
- nsis
|
||||
nsis:
|
||||
artifactName: ${name}-${version}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
target:
|
||||
- dmg
|
||||
dmg:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
20
electron.vite.config.ts
Normal file
20
electron.vite.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src')
|
||||
}
|
||||
},
|
||||
plugins: [react()]
|
||||
}
|
||||
})
|
||||
17532
package-lock.json
generated
Normal file
17532
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
package.json
Normal file
55
package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "kintone-customize-manager",
|
||||
"version": "1.0.0",
|
||||
"description": "Kintone Customize Manager - Manage and deploy Kintone customization resources",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Kintone Developer",
|
||||
"homepage": "https://github.com/example/kintone-customize-manager",
|
||||
"scripts": {
|
||||
"dev": "electron-vite dev",
|
||||
"build": "electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"package": "electron-vite build && electron-builder",
|
||||
"package:win": "electron-vite build && electron-builder --win",
|
||||
"package:mac": "electron-vite build && electron-builder --mac",
|
||||
"package:linux": "electron-vite build && electron-builder --linux",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,json}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.3.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/merge": "^6.9.0",
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"@codemirror/view": "^6.36.0",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@lobehub/ui": "^5.5.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"antd": "^6.1.0",
|
||||
"antd-style": "^4.1.0",
|
||||
"electron-store": "^10.0.0",
|
||||
"electron-updater": "^6.3.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"motion": "^12.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zustand": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"electron": "^30.5.1",
|
||||
"electron-builder": "^26.0.0",
|
||||
"electron-vite": "^5.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"prettier": "^3.2.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
10
src/main/env.d.ts
vendored
Normal file
10
src/main/env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="electron-vite/node" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly MAIN_VITE_API_URL: string
|
||||
readonly MAIN_VITE_DEBUG: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
79
src/main/index.ts
Normal file
79
src/main/index.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
|
||||
function createWindow(): void {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
trafficLightPosition: { x: 15, y: 10 },
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
// Set app user model id for windows
|
||||
electronApp.setAppUserModelId('com.kintone')
|
||||
|
||||
// Default open or close DevTools by F12 in development
|
||||
// and ignore CommandOrControl + R in production.
|
||||
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
// IPC test
|
||||
ipcMain.on('ping', () => console.log('pong'))
|
||||
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here.
|
||||
11
src/preload/index.d.ts
vendored
Normal file
11
src/preload/index.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
api: {
|
||||
ping: () => void
|
||||
platform: NodeJS.Platform
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/preload/index.ts
Normal file
27
src/preload/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
ping: () => ipcRenderer.send('ping'),
|
||||
// Platform detection
|
||||
platform: process.platform,
|
||||
// Store APIs will be added here
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
// just add to the DOM global.
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore (define in dts)
|
||||
window.electron = electronAPI
|
||||
// @ts-ignore (define in dts)
|
||||
window.api = api
|
||||
}
|
||||
13
src/renderer/index.html
Normal file
13
src/renderer/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" />
|
||||
<title>Kintone Customize Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
94
src/renderer/src/App.tsx
Normal file
94
src/renderer/src/App.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useState } from 'react'
|
||||
import { Layout, Typography, theme } from 'antd'
|
||||
import {
|
||||
GithubOutlined,
|
||||
SettingOutlined,
|
||||
CloudServerOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
const { Header, Content, Sider } = Layout
|
||||
const { Title, Text } = Typography
|
||||
|
||||
function App() {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken()
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={(value) => setCollapsed(value)}
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 32,
|
||||
margin: 16,
|
||||
background: 'rgba(255, 255, 255, 0.2)',
|
||||
borderRadius: borderRadiusLG,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CloudServerOutlined style={{ fontSize: 20, color: '#fff' }} />
|
||||
{!collapsed && (
|
||||
<Text style={{ color: '#fff', marginLeft: 8, fontSize: 14 }}>
|
||||
Kintone
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Sider>
|
||||
<Layout style={{ marginLeft: collapsed ? 80 : 200, transition: 'all 0.2s' }}>
|
||||
<Header
|
||||
style={{
|
||||
padding: '0 24px',
|
||||
background: colorBgContainer,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
Kintone Customize Manager
|
||||
</Title>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<SettingOutlined style={{ fontSize: 18, cursor: 'pointer' }} />
|
||||
<GithubOutlined style={{ fontSize: 18, cursor: 'pointer' }} />
|
||||
</div>
|
||||
</Header>
|
||||
<Content style={{ margin: '24px 16px 0', overflow: 'initial' }}>
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: colorBgContainer,
|
||||
borderRadius: borderRadiusLG,
|
||||
}}
|
||||
>
|
||||
<Title level={3}>欢迎使用 Kintone Customize Manager</Title>
|
||||
<Text type="secondary">
|
||||
管理和部署 Kintone 平台的自定义资源(JavaScript、CSS、Plugin)
|
||||
</Text>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<Text>请在左侧添加 Domain 开始使用</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
10
src/renderer/src/env.d.ts
vendored
Normal file
10
src/renderer/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly MAIN_VITE_API_URL: string
|
||||
readonly MAIN_VITE_DEBUG: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
50
src/renderer/src/index.css
Normal file
50
src/renderer/src/index.css
Normal file
@@ -0,0 +1,50 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
/* macOS style window controls area */
|
||||
.titlebar {
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.titlebar button {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #424242;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #5a5a5a;
|
||||
}
|
||||
}
|
||||
27
src/renderer/src/main.tsx
Normal file
27
src/renderer/src/main.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { ConfigProvider, App as AntdApp } from 'antd'
|
||||
import zhCN from 'antd/locale/zh_CN'
|
||||
import { ThemeProvider } from '@lobehub/ui'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<ConfigProvider
|
||||
locale={zhCN}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: '#1677ff',
|
||||
borderRadius: 6,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ThemeProvider>
|
||||
<AntdApp>
|
||||
<App />
|
||||
</AntdApp>
|
||||
</ThemeProvider>
|
||||
</ConfigProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.node.json" },
|
||||
{ "path": "./tsconfig.web.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
17
tsconfig.node.json
Normal file
17
tsconfig.node.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
||||
"include": [
|
||||
"electron.vite.config.ts",
|
||||
"src/main/**/*",
|
||||
"src/preload/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["electron-vite/node"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@main/*": ["src/main/*"],
|
||||
"@preload/*": ["src/preload/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
13
tsconfig.web.json
Normal file
13
tsconfig.web.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/renderer/src/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@renderer/*": ["src/renderer/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user