跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScriptAI大前端

Vue 3 重构 Dify 聊天前端:项目搭建与基础架构

介绍使用 Vue 3 和 TypeScript 从零构建类似 Dify 的 AI 聊天前端项目。内容涵盖项目初始化、Vite 配置、TypeScript 类型体系设计(消息、文件、Agent 思考过程、工作流追踪)、路由与 SSO 认证流程、Axios HTTP 客户端封装以及 Pinia 状态管理(用户与会话)。重点解决了 SSE 流式输出、Markdown 渲染及移动端适配的基础架构问题,为后续实现核心聊天功能奠定基础。

MongoKing发布于 2026/4/5更新于 2026/9/885 浏览
Vue 3 重构 Dify 聊天前端:项目搭建与基础架构

本系列教程将带你从零开始,用 Vue 3 + TypeScript 复刻一个类似 Dify 的 AI 聊天前端。上篇聚焦项目搭建、类型设计、路由认证、HTTP 封装和状态管理。

聊天页面

聊天输出

项目简介

背景

Dify 是一个开源的 LLM 应用开发平台,提供了对话式 AI 的后端服务。在实际项目中,我们往往需要自建前端来对接 Dify 后端 API 或 LLM 后端服务,实现定制化的聊天界面。

本项目的目标:用 Vue 3 构建一个生产级的 AI 聊天前端,具备以下能力:

  • SSE 流式输出(打字机效果)
  • Markdown 渲染 + 代码高亮
  • 用户认证
  • 文件/图片上传
  • 聊天会话历史管理
  • 工作流执行可视化
  • Agent 思考过程展示
  • 移动端响应式适配
技术栈
类别技术选型说明
框架Vue 3.4 + Composition API使用
语言TypeScript 5.3全量类型覆盖
构建Vite 5.1极速 HMR
状态管理Pinia 2.1Composition API 风格
UI 库Element Plus 2.5成熟的 Vue 3 组件库
HTTPAxios 1.6请求拦截 + 统一错误处理
Markdownmarked 12 + highlight.js 11GFM 渲染 + 代码高亮
安全DOMPurify 3.0XSS 防护
样式SCSSCSS 变量 + 响应式
项目结构
src/
├── api/ # API 层
│   ├── app.ts # 应用信息接口
│   ├── chat.ts # 聊天 API + SSE 流式
│   └── user.ts # 用户认证接口
├── assets/ # 静态资源
├── components/ 
│   ├── AgentThought.vue 
│   ├── ChatSidebar.vue 
│   ├── ConversationItem.vue 
│   ├── FeedbackButtons.vue 
│   ├── FileUpload.vue 
│   ├── MarkdownRenderer.vue 
│   ├── MessageActions.vue 
│   ├── SuggestedQuestions.vue 
│   └── WorkflowTracing.vue 
├── router/ 
│   └── index.ts
├── stores/ 
│   ├── conversation.ts 
│   └── user.ts 
├── styles/ 
├── utils/ 
│   └── request.ts 
├── views/ 
│   ├── ChatView.vue 
│   └── ErrorView.vue 
├── main.ts 
└── App.vue 
# 可复用组件(9 个)
# Agent 思考过程
# 侧边栏
# 会话条目
# 反馈按钮
# 文件上传
# Markdown 渲染
# 消息操作
# 建议问题
# 工作流可视化
# 路由配置
# Pinia 状态管理
# 会话状态
# 用户状态
# 全局样式
# 工具函数
# Axios 封装
# 页面组件
# 主聊天页面
# 错误页面
# 入口文件
# 根组件
整体数据流
用户输入 → ChatView → sendChatMessageSSE() → Dify API
↑ ↓
messages 数组 ← SSE 事件流(message/agent_thought/workflow)
↓
MarkdownRenderer → 实时渲染 AI 回复

1. 项目初始化

1.1 创建项目
npm create vite@latest dify-chat -- --template vue-ts
cd dify-chat
npm install
1.2 安装依赖
# 核心依赖
npm install vue-router pinia axios element-plus @element-plus/icons-vue
# Markdown 与代码高亮
npm install marked highlight.js dompurify
# 开发依赖
npm install -D sass @types/dompurify
1.3 Vite 配置

vite.config.ts 是整个项目的构建核心:

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  return {
    // 支持子路径部署(如 /chat-app/)
    base: env.VITE_APP_NGINX_SUB_PATH || '/',
    plugins: [vue()],
    css: {
      preprocessorOptions: {
        scss: {
          api: 'modern-compiler' // 使用现代 SCSS 编译器
        }
      }
    },
    resolve: {
      alias: {
        '@': '/src' // 路径别名,import xx from '@/utils/xx'
      }
    },
    server: {
      port: 3001,
      proxy: {
        // 开发环境将 /api 请求代理到后端
        '/api': {
          target: 'http://localhost:9000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    }
  }
})

关键设计决策:

  • 代理配置:前端 3001 端口,后端 9000 端口。开发时所有 /api/* 请求自动转发到后端,避免跨域问题。
  • 路径别名:@/ 映射到 src/,让导入路径更清晰。
  • 子路径部署:通过环境变量 VITE_APP_NGINX_SUB_PATH 控制 base path,适配不同部署环境。
1.4 环境变量

创建 .env 文件:

VITE_APP_TITLE=AI 智能助手
VITE_API_BASE_URL=/api
VITE_APP_NGINX_SUB_PATH=/

TypeScript 类型声明 src/env.d.ts:

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_API_BASE_URL: string
  readonly VITE_APP_NGINX_SUB_PATH: string
}

2. TypeScript 类型体系设计

类型设计是整个项目的基础。我们把所有聊天相关的类型集中在 src/api/chat.ts 中定义。

2.1 消息类型
// 前端使用的消息结构
export interface Message {
  id: string
  role: 'user' | 'assistant'
  content: string
  createAt?: number
  parentMessageId?: string
  messageFiles?: MessageFile[]
  agentThoughts?: AgentThought[]
  workflow_process?: WorkflowProcess
  feedback?: Feedback
  citation?: Citation[]
}

// 后端返回的消息结构(需要转换)
export interface BackendMessage {
  id: string
  conversationId: string
  parentMessageId: string
  inputs: Record<string, any>
  query: string // 用户问题
  answer: string // AI 回答
  messageFiles: MessageFile[]
  feedback: Feedback | null
  retrieverResources: any[]
  agentThoughts: AgentThought[]
  createdAt: number
  status: string
  error: string | null
}

为什么要区分 Message 和 BackendMessage?

后端返回的是'一轮对话'的结构(包含 query + answer),而前端渲染需要拆分为 user 和 assistant 两条独立消息。这个转换在 getMessages 函数中完成。

2.2 文件与附件
export interface MessageFile {
  id: string
  type: string // 'image' | 'document' 等
  url: string
  filename?: string
  size?: number
}
2.3 Agent 思考过程
export interface AgentThought {
  id: string
  thought: string // 思考内容
  tool?: string // 调用的工具名称
  tool_input?: string // 工具输入参数
  observation?: string // 工具执行结果
  message_files?: MessageFile[]
  created_at?: number
}
2.4 工作流追踪
export interface WorkflowProcess {
  status: string // 'running' | 'succeeded' | 'failed'
  tracing: WorkflowNode[]
}

export interface WorkflowNode {
  id: string
  node_id: string
  title: string // 节点名称,如'大模型'、'知识检索'
  status: string // 'running' | 'succeeded' | 'failed'
  inputs?: any
  outputs?: any
  elapsed_time?: number // 执行耗时(秒)
}
2.5 用户反馈
export interface Feedback {
  rating: 'like' | 'dislike' | null
  content?: string // 详细反馈内容
}
2.6 引用来源
export interface Citation {
  position: number
  document_id: string
  document_name: string
  data_source_type: string
  source_url?: string
}
2.7 请求与响应类型
export interface ChatRequest {
  appId: string
  query: string
  conversationId?: string // 有值表示继续已有会话
  inputs?: Record<string, any>
  files?: string[] // 已上传文件的 URL 列表
}
2.8 SSE 回调接口

这是整个流式聊天的核心类型定义——通过回调函数把不同类型的 SSE 事件分发到对应的处理逻辑:

export interface SSECallbacks {
  onMessage: (content: string, taskId?: string, messageId?: string) => void
  onThought: (thought: AgentThought) => void
  onFile: (file: MessageFile) => void
  onMessageEnd: (data: { messageId: string; conversationId: string; citation?: Citation[]; feedback?: Feedback }) => void
  onWorkflowStarted: (data: { workflowRunId: string; taskId: string; conversationId: string }) => void
  onNodeStarted: (node: WorkflowNode) => void
  onNodeFinished: (node: WorkflowNode) => void
  onWorkflowFinished: (data: { status: string; conversationId: string }) => void
  onTTSChunk: (audio: string) => void
  onSuggestedQuestions: (questions: string[]) => void
  onError: (error: string) => void
  onDone: () => void
}

这种设计的好处:SSE 解析逻辑与 UI 更新逻辑完全解耦。sendChatMessageSSE 函数只负责解析 SSE 流并触发回调,而 ChatView 组件通过回调函数决定如何更新 UI。


3. 路由与认证

3.1 路由设计

src/router/index.ts:

import { createRouter, createWebHistory } from 'vue-router'
import { getInfo, ssoLogin } from '@/api/user'
import { useUserStore } from '@/stores/user'

const router = createRouter({
  history: createWebHistory(import.meta.env.VITE_APP_NGINX_SUB_PATH),
  routes: [
    {
      path: '/',
      redirect: '/error?message=请从安溪专区访问'
    },
    {
      path: '/chat/:appId', // 动态路由,支持多应用
      name: 'Chat',
      component: () => import('@/views/ChatView.vue') // 懒加载
    },
    {
      path: '/error',
      name: 'Error',
      component: () => import('@/views/ErrorView.vue')
    }
  ]
})

设计要点:

  • /chat/:appId 使用动态路由,同一个前端可以对接不同的 AI 应用(每个应用有不同的 prompt、模型配置等)。
  • 路由懒加载(() => import()),首屏不会加载所有页面代码。
  • 根路径重定向到错误页,强制用户从指定入口访问。
3.2 SSO 单点登录

通过路由守卫 beforeEach 实现 SSO 认证流程:

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const ssoApp = to.query.appid as string
  const ssoCode = to.query.code as string
  let token = userStore.token // 开发环境自动 mock

  if (import.meta.env.DEV) {
    const devToken = 'eyJhbGciOiJIUzI1NiJ9...'
    userStore.setToken(devToken)
    userStore.setUserInfo({ ssoUserName: 'dev_user', nickName: '开发测试用户' })
    token = devToken
  }

  if (ssoApp && ssoCode && !token) {
    try {
      // 1. 用 SSO code 换取 token
      const newToken = await ssoLogin(ssoApp, ssoCode)
      userStore.setToken(newToken)
      // 2. 获取用户信息
      const userInfo = await getInfo()
      userStore.setUserInfo(userInfo)
      // 3. 去掉 URL 中的 code 参数,刷新路由
      const query = { ...to.query }
      delete query.code
      next({ ...to, query, replace: true })
    } catch (error) {
      next({ path: '/error', query: { message: 'SSO 登录失败,请重试' } })
    }
  } else if (!token && to.path !== '/error') {
    next({ path: '/error', query: { message: '未登录,请从门户网站进入' } })
  } else {
    next()
  }
})

SSO 认证流程图:

门户入口 → 带 appid + code 参数访问 /chat/xxx
↓
beforeEach 拦截 → 检测到 code
↓
ssoLogin(appid, code) → 获取 token
↓
getInfo() → 获取用户信息
↓
存储 token + userInfo → 重定向(去掉 code 参数)
↓
进入 ChatView 正常聊天

4. HTTP 客户端封装

4.1 Axios 实例

src/utils/request.ts:

import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { TOKEN_KEY, useUserStore } from '@/stores/user'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // '/api'
  timeout: 120000 // 2 分钟超时,AI 响应可能较慢
})
4.2 请求拦截器

核心功能:自动注入 Bearer Token。

request.interceptors.request.use((config) => {
  const token = localStorage.getItem(TOKEN_KEY)
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}, (error) => Promise.reject(error))

这里直接从 localStorage 读取 token,而不是从 Pinia store 读取,是为了避免循环依赖问题(store 还未初始化时就需要发请求)。

4.3 响应拦截器

统一处理后端返回的业务错误码:

request.interceptors.response.use(
  (res) => {
    const code = res.data.code || 200
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据直接返回
    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
      return res.data
    }
    const userStore = useUserStore()
    if (code === 401) {
      // 认证过期 → 清除登录状态 + 弹窗提示
      userStore.logout()
      return ElMessageBox.alert('登录状态已过期,请重新从门户点击访问!', '认证失败').then(() => Promise.reject('无效的会话'))
    } else if (code === 500) {
      ElMessage.error(msg)
      return Promise.reject(new Error(msg))
    } else if (code === 601) {
      ElMessage.warning(msg)
      return Promise.reject('error')
    } else if (code !== 200) {
      ElMessage.error(msg)
      return Promise.reject('error')
    } else {
      return res.data.data // 只返回业务数据
    }
  },
  (error) => {
    ElMessage.error(error)
    return Promise.reject(error)
  }
)

关键设计:

  • 响应拦截器已经'解包'了后端响应,API 函数拿到的直接就是 data 字段的值。
  • 401 时自动清除用户状态,引导用户重新登录。
  • 二进制数据(文件下载)特殊处理,直接返回完整 response。
4.4 API 函数示例

有了封装好的 request,API 函数写起来非常简洁:

// src/api/app.ts
export const getAppById = async (id: string): Promise<AppInfo> => {
  return await request.get(`/app/${id}`)
}

// src/api/user.ts
export const ssoLogin = async (ssoApp: string, ssoCode: string): Promise<string> => {
  return await request.get('/public/login/ssoLogin', {
    params: { ssoApp, ssoCode }
  })
}

export const getInfo = async (): Promise<UserInfo> => {
  return await request.get('/user/getInfo')
}

5. Pinia Store 设计

5.1 用户状态管理

src/stores/user.ts — 使用 Composition API 风格(defineStore + setup 函数):

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const TOKEN_KEY = 'ai-token'
export const USER_INFO_KEY = 'ai-userInfo'

export const useUserStore = defineStore('user', () => {
  const userInfo = ref<UserInfo | null>(null)
  const token = ref<string>('')

  // 设置 Token → 同时持久化到 localStorage
  const setToken = (value: string) => {
    token.value = value
    localStorage.setItem(TOKEN_KEY, value)
  }

  // 设置用户信息 → 同步持久化
  const setUserInfo = (info: UserInfo) => {
    userInfo.value = info
    localStorage.setItem(USER_INFO_KEY, JSON.stringify(info))
  }

  // 从 localStorage 恢复(页面刷新后)
  const initFromStorage = () => {
    const storedToken = localStorage.getItem(TOKEN_KEY)
    const storedUserInfo = localStorage.getItem(USER_INFO_KEY)
    if (storedToken) token.value = storedToken
    if (storedUserInfo) {
      try {
        userInfo.value = JSON.parse(storedUserInfo)
      } catch {
        userInfo.value = null
      }
    }
  }

  // 登出 → 清空所有
  const logout = () => {
    userInfo.value = null
    token.value = ''
    localStorage.removeItem(TOKEN_KEY)
    localStorage.removeItem(USER_INFO_KEY)
  }

  return {
    userInfo,
    token,
    setToken,
    setUserInfo,
    initFromStorage,
    logout
  }
})

设计要点:

  • Token 和 UserInfo 同时存在内存(ref)和 localStorage 中。内存保证响应式,localStorage 保证刷新后不丢失。
  • initFromStorage 在应用启动时调用(main.ts 中),确保路由守卫能拿到 token。
5.2 会话状态管理

src/stores/conversation.ts — 管理会话列表和消息:

import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import {
  getConversations,
  getMessages,
  deleteConversation as deleteConversationApi,
  pinConversation as pinConversationApi,
  unpinConversation as unpinConversationApi,
  updateConversationName
} from '@/api/chat'

export const useConversationStore = defineStore('conversation', () => {
  // === 状态 ===
  const appId = ref<string>('')
  const conversations = ref<ConversationItem[]>([])
  const pinnedConversations = ref<ConversationItem[]>([])
  const currentConversationId = ref<string>('')
  const currentMessages = ref<any[]>([])
  const isLoading = ref(false)
  const isLoadingMessages = ref(false)

  // === 计算属性 ===
  // 置顶 + 普通合并后的完整列表
  const allConversations = computed(() => [...pinnedConversations.value, ...conversations.value])

  // 当前选中的会话对象
  const currentConversation = computed(() => allConversations.value.find(c => c.id === currentConversationId.value))

  // === 核心方法 ===
  // 加载会话列表
  const loadConversations = async () => {
    if (!appId.value) return
    isLoading.value = true
    try {
      conversations.value = await getConversations(appId.value, 30)
    } finally {
      isLoading.value = false
    }
  }

  // 切换会话 → 加载该会话的消息
  const switchConversation = async (conversationId: string) => {
    if (conversationId === currentConversationId.value) return
    currentConversationId.value = conversationId
    currentMessages.value = []
    if (conversationId) await loadMessages(conversationId)
  }

  // 加载消息 → 关键转换:BackendMessage → Message
  const loadMessages = async (conversationId: string) => {
    if (!appId.value) return
    isLoadingMessages.value = true
    try {
      currentMessages.value = await getMessages(appId.value, conversationId)
    } finally {
      isLoadingMessages.value = false
    }
  }

  // 创建新会话
  const createNewConversation = () => {
    currentConversationId.value = ''
    currentMessages.value = []
  }

  // 删除会话 → 从列表移除 + 如果是当前会话则切换
  const deleteConversation = async (conversationId: string) => {
    await deleteConversationApi(conversationId)
    conversations.value = conversations.value.filter(c => c.id !== conversationId)
    pinnedConversations.value = pinnedConversations.value.filter(c => c.id !== conversationId)
    if (currentConversationId.value === conversationId) {
      createNewConversation()
    }
  }

  // 流式聊天时的实时更新方法
  const updateCurrentConversationId = (conversationId: string) => {
    currentConversationId.value = conversationId
  }

  const addMessage = (message: any) => {
    currentMessages.value.push(message)
  }

  const updateLastMessage = (content: string, extras?: any) => {
    const lastMsg = currentMessages.value[currentMessages.value.length - 1]
    if (lastMsg) {
      lastMsg.content = content
      Object.assign(lastMsg, extras)
    }
  }

  return {
    // 状态
    appId,
    conversations,
    pinnedConversations,
    currentConversationId,
    currentMessages,
    isLoading,
    isLoadingMessages,
    // 计算属性
    currentConversation,
    allConversations,
    // 方法
    setAppId,
    loadConversations,
    switchConversation,
    createNewConversation,
    deleteConversation,
    updateCurrentConversationId,
    addMessage,
    updateLastMessage,
    renameConversation,
    pinConversation,
    unpinConversation
  }
})

架构决策:

  1. 会话分两组管理(pinned / normal),计算属性合并展示。置顶操作就是数组间的移动。
  2. 消息更新使用 updateLastMessage,这是 SSE 流式更新的关键。每个 message 事件到达时,只需更新最后一条消息的 content。
  3. addMessage + updateLastMessage 组合使用:发送消息时先 addMessage 一个空的 assistant 消息占位,然后 SSE 回调不断 updateLastMessage 追加内容。
5.3 后端消息 → 前端消息的转换

这个转换发生在 src/api/chat.ts 的 getMessages 函数中:

export const getMessages = async (appId: string, conversationId: string): Promise<Message[]> => {
  const backendMessages: BackendMessage[] = await request.post('/chat/message/messages', {
    appId,
    conversationId,
    limit: 50
  })
  const messages: Message[] = []
  backendMessages.forEach(m => {
    // 后端一轮对话 = 一条 BackendMessage
    // 前端需要拆成 user + assistant 两条 Message
    messages.push({
      id: `${m.id}-user`,
      role: 'user',
      content: m.query, // 用户问题
      createAt: m.createdAt,
      messageFiles: m.messageFiles
    })
    messages.push({
      id: `${m.id}-assistant`,
      role: 'assistant',
      content: m.answer, // AI 回答
      createAt: m.createdAt,
      agentThoughts: m.agentThoughts,
      feedback: m.feedback || undefined,
      messageFiles: m.messageFiles
    })
  })
  return messages
}

小结

上篇我们完成了:

  1. 项目脚手架 — Vite + Vue 3 + TypeScript + Element Plus
  2. 类型体系 — 覆盖消息、文件、工作流、Agent 思考等所有业务实体
  3. 认证流程 — SSO 单点登录 + 路由守卫
  4. HTTP 封装 — 自动 Token 注入 + 统一错误处理
  5. 状态管理 — 用户 Store + 会话 Store,支持 localStorage 持久化

下一篇预告:中篇将深入核心功能——SSE 流式聊天的完整实现、ChatView 组件的核心逻辑、Markdown 渲染、文件上传和会话管理。

目录

  1. 项目简介
  2. 背景
  3. 技术栈
  4. 项目结构
  5. 整体数据流
  6. 1. 项目初始化
  7. 1.1 创建项目
  8. 1.2 安装依赖
  9. 核心依赖
  10. Markdown 与代码高亮
  11. 开发依赖
  12. 1.3 Vite 配置
  13. 1.4 环境变量
  14. 2. TypeScript 类型体系设计
  15. 2.1 消息类型
  16. 2.2 文件与附件
  17. 2.3 Agent 思考过程
  18. 2.4 工作流追踪
  19. 2.5 用户反馈
  20. 2.6 引用来源
  21. 2.7 请求与响应类型
  22. 2.8 SSE 回调接口
  23. 3. 路由与认证
  24. 3.1 路由设计
  25. 3.2 SSO 单点登录
  26. 4. HTTP 客户端封装
  27. 4.1 Axios 实例
  28. 4.2 请求拦截器
  29. 4.3 响应拦截器
  30. 4.4 API 函数示例
  31. 5. Pinia Store 设计
  32. 5.1 用户状态管理
  33. 5.2 会话状态管理
  34. 5.3 后端消息 → 前端消息的转换
  35. 小结

更多推荐文章

查看全部
  • 电商系统商品管理模块设计与实现
  • OpenClaw 集成飞书机器人部署指南
  • Web 自动化测试入门:从概念到百度搜索实战
  • ESP32 语音对话机器人:Coze 大模型与百度千帆集成实战
  • OpenClaw 在 Linux 环境下的安装、初始化与 Web UI 配置
  • 资深安全工程师推荐的9本黑客技术经典书籍
  • HDFS 编程实践:Shell 命令、Web 界面与 Java API 详解
  • RabbitMQ 入门:架构解析、环境搭建与 Java 实战
  • Rust 复合类型高级用法——结构体、枚举与模式匹配进阶
  • TRAE vs Qoder vs Cursor vs GitHub Copilot:谁才是真正的“AI 工程师”?
  • Java 入门教程
  • SenseVoice-Small ONNX 量化版快速部署及 Gradio 使用指南
  • 2026 年 3 月全球大模型全景:国产登顶、百万上下文与智能体爆发
  • 扣子(Coze)Skills+OpenClaw 实战:零基础构建 AI 智能体
  • 深入解析 C++ 轻量级 WebServer 实现
  • 让 Llama-Factory 真正跑在 Flash Attention 上:环境、配置与性能一探
  • 初识Java:语言特性与基础语法入门
  • Flutter 组件 sse_stream 在鸿蒙系统的适配与高并发背压处理方案
  • 自然语言处理在客户服务领域的应用与实战
  • Google AI Studio 使用指南:界面与参数配置详解

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online