add: markdown viewer

This commit is contained in:
alivender 2025-05-16 20:35:43 +08:00
parent 48ae3b5975
commit 7dd5e2189f
5 changed files with 428 additions and 9 deletions

20
package-lock.json generated
View File

@ -10,8 +10,10 @@
"dependencies": { "dependencies": {
"@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.js": "^3.2.4",
"@types/lodash": "^4.17.16", "@types/lodash": "^4.17.16",
"all": "^0.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"log-symbols": "^7.0.0", "log-symbols": "^7.0.0",
"marked": "^12.0.0",
"mathjs": "^14.4.0", "mathjs": "^14.4.0",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"tinypool": "^1.0.2", "tinypool": "^1.0.2",
@ -1993,6 +1995,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/all": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz",
"integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==",
"license": "MIT"
},
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
@ -3061,6 +3069,18 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
} }
}, },
"node_modules/marked": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mathjs": { "node_modules/mathjs": {
"version": "14.4.0", "version": "14.4.0",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.4.0.tgz", "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.4.0.tgz",

View File

@ -16,8 +16,10 @@
"dependencies": { "dependencies": {
"@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.js": "^3.2.4",
"@types/lodash": "^4.17.16", "@types/lodash": "^4.17.16",
"all": "^0.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"log-symbols": "^7.0.0", "log-symbols": "^7.0.0",
"marked": "^12.0.0",
"mathjs": "^14.4.0", "mathjs": "^14.4.0",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"tinypool": "^1.0.2", "tinypool": "^1.0.2",

View File

@ -3,8 +3,7 @@
@mousedown="handleCanvasMouseDown" @mousedown="handleCanvasMouseDown"
@mousedown.middle.prevent="startMiddleDrag" @mousedown.middle.prevent="startMiddleDrag"
@wheel.prevent="onZoom" @wheel.prevent="onZoom"
@contextmenu.prevent="handleContextMenu"> @contextmenu.prevent="handleContextMenu"> <!-- 工具栏 -->
<!-- 工具栏 -->
<div class="absolute top-2 right-2 flex gap-2 z-30"> <div class="absolute top-2 right-2 flex gap-2 z-30">
<button class="btn btn-sm btn-primary" @click="openDiagramFileSelector"> <button class="btn btn-sm btn-primary" @click="openDiagramFileSelector">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -23,6 +22,11 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
添加组件 添加组件
</button> <button class="btn btn-sm btn-primary" @click="emit('toggle-doc-panel')">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{{ props.showDocPanel ? '属性面板' : '文档' }}
</button> </button>
</div> </div>
@ -148,11 +152,12 @@ function handleContextMenu(e: MouseEvent) {
} }
// //
const emit = defineEmits(['diagram-updated', 'component-selected', 'component-moved', 'component-delete', 'wire-created', 'wire-deleted', 'load-component-module', 'open-components']); const emit = defineEmits(['diagram-updated', 'component-selected', 'component-moved', 'component-delete', 'wire-created', 'wire-deleted', 'load-component-module', 'open-components', 'toggle-doc-panel']);
// //
const props = defineProps<{ const props = defineProps<{
componentModules: Record<string, any> componentModules: Record<string, any>;
showDocPanel?: boolean; //
}>(); }>();
// --- --- // --- ---

View File

@ -0,0 +1,207 @@
<script setup lang="ts">
import { computed } from 'vue';
import { marked } from 'marked';
const props = defineProps({
content: {
type: String,
required: true
}
});
const renderedContent = computed(() => {
if (!props.content) return '<p>没有内容</p>';
let processedContent = props.content;
// marked
const renderer = new marked.Renderer();
marked.setOptions({
renderer: renderer,
gfm: true,
breaks: true
});
return marked(processedContent);
});
</script>
<template>
<div class="markdown-content" v-html="renderedContent"></div>
</template>
<style scoped>
.markdown-content {
color: hsl(var(--bc));
line-height: 1.6;
padding: 1rem 1.5rem;
max-width: 100%;
}
.markdown-content :deep(img) {
max-width: 60%;
height: auto;
display: block;
margin: 1rem auto;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.markdown-content :deep(h1) {
margin-top: 2rem;
margin-bottom: 1rem;
color: hsl(var(--bc));
font-weight: 700;
font-size: 2rem;
line-height: 1.3;
padding-bottom: 0.5rem;
border-bottom: 1px solid hsl(var(--b2));
}
.markdown-content :deep(h2) {
margin-top: 1.8rem;
margin-bottom: 0.8rem;
color: hsl(var(--bc));
font-weight: 600;
font-size: 1.5rem;
line-height: 1.4;
padding-left: 0.5rem;
border-left: 4px solid hsl(var(--p));
}
.markdown-content :deep(h3) {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: hsl(var(--bc));
font-weight: 600;
font-size: 1.25rem;
line-height: 1.4;
padding-left: 1rem;
}
.markdown-content :deep(h4),
.markdown-content :deep(h5),
.markdown-content :deep(h6) {
margin-top: 1.2rem;
margin-bottom: 0.6rem;
color: hsl(var(--bc));
font-weight: 600;
font-size: 1.1rem;
line-height: 1.5;
padding-left: 1.5rem;
}
.markdown-content :deep(p) {
text-indent: 2em;
margin: 1rem 0;
color: hsl(var(--bc) / 0.8);
line-height: 1.8;
}
.markdown-content :deep(ul),
.markdown-content :deep(ol) {
padding-left: 2em;
margin: 0.75rem 0;
color: hsl(var(--bc) / 0.8);
}
.markdown-content :deep(li) {
margin: 0.4rem 0;
position: relative;
}
.markdown-content :deep(ul ul),
.markdown-content :deep(ul ol),
.markdown-content :deep(ol ul),
.markdown-content :deep(ol ol) {
margin: 0.4rem 0 0.4rem 1rem;
}
.markdown-content :deep(ul) {
list-style-type: disc;
}
.markdown-content :deep(ol) {
list-style-type: decimal;
}
.markdown-content :deep(ul ul) {
list-style-type: circle;
}
.markdown-content :deep(ul ul ul) {
list-style-type: square;
}
.markdown-content :deep(ul li::marker) {
color: hsl(var(--p));
}
.markdown-content :deep(pre) {
background-color: hsl(var(--b3));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
border: 1px solid hsl(var(--b2));
margin: 1rem 0;
}
.markdown-content :deep(code) {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
background-color: hsl(var(--b3));
padding: 2px 0.5rem;
border-radius: 0.25rem;
font-size: 0.9em;
color: hsl(var(--p));
}
.markdown-content :deep(table) {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
background-color: hsl(var(--b1));
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.markdown-content :deep(th),
.markdown-content :deep(td) {
border: 1px solid hsl(var(--b2));
padding: 0.75rem;
text-align: left;
}
.markdown-content :deep(th) {
background-color: hsl(var(--b2));
font-weight: 500;
color: hsl(var(--bc));
}
.markdown-content :deep(td) {
color: hsl(var(--bc) / 0.8);
}
.markdown-content :deep(blockquote) {
margin: 1rem 0;
padding: 0.5rem 1rem;
border-left: 4px solid hsl(var(--p));
background-color: hsl(var(--b2));
color: hsl(var(--bc) / 0.8);
font-style: italic;
}
.markdown-content :deep(hr) {
border: none;
border-top: 1px solid hsl(var(--b2));
margin: 1.5rem 0;
}
.markdown-content :deep(a) {
color: hsl(var(--p));
text-decoration: none;
transition: color 0.2s;
}
.markdown-content :deep(a:hover) {
color: hsl(var(--pf));
text-decoration: underline;
}
</style>

View File

@ -1,13 +1,12 @@
<template> <template>
<div class="h-screen flex flex-col overflow-hidden"> <div class="h-screen flex flex-col overflow-hidden">
<div class="flex flex-1 overflow-hidden relative"> <div class="flex flex-1 overflow-hidden relative">
<!-- 左侧图形化区域 --> <!-- 左侧图形化区域 --> <div class="relative bg-base-200 overflow-hidden h-full" :style="{ width: leftPanelWidth + '%' }">
<div class="relative bg-base-200 overflow-hidden h-full" :style="{ width: leftPanelWidth + '%' }"> <DiagramCanvas ref="diagramCanvas" :componentModules="componentModules" :showDocPanel="showDocPanel"
<DiagramCanvas ref="diagramCanvas" :componentModules="componentModules"
@component-selected="handleComponentSelected" @component-moved="handleComponentMoved" @component-selected="handleComponentSelected" @component-moved="handleComponentMoved"
@component-delete="handleComponentDelete" @wire-created="handleWireCreated" @wire-deleted="handleWireDeleted" @component-delete="handleComponentDelete" @wire-created="handleWireCreated" @wire-deleted="handleWireDeleted"
@diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu" @diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu"
@load-component-module="handleLoadComponentModule" /> @load-component-module="handleLoadComponentModule" @toggle-doc-panel="toggleDocPanel" />
</div> </div>
<!-- 拖拽分割线 --> <!-- 拖拽分割线 -->
@ -18,8 +17,12 @@
<!-- 右侧编辑区域 --> <!-- 右侧编辑区域 -->
<div class="bg-base-200 h-full overflow-hidden flex flex-col" :style="{ width: 100 - leftPanelWidth + '%' }"> <div class="bg-base-200 h-full overflow-hidden flex flex-col" :style="{ width: 100 - leftPanelWidth + '%' }">
<div class="overflow-y-auto flex-1"> <div class="overflow-y-auto flex-1">
<PropertyPanel :componentData="selectedComponentData" :componentConfig="selectedComponentConfig" <!-- 使用条件渲染显示不同的面板 -->
<PropertyPanel v-if="!showDocPanel" :componentData="selectedComponentData" :componentConfig="selectedComponentConfig"
@updateProp="updateComponentProp" @updateDirectProp="updateComponentDirectProp" /> @updateProp="updateComponentProp" @updateDirectProp="updateComponentDirectProp" />
<div v-else class="doc-panel overflow-y-auto bg-base-100 rounded-md h-full">
<MarkdownRenderer :content="documentContent" />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -36,6 +39,7 @@ import { ref, computed, onMounted, onUnmounted, shallowRef } from "vue"; // 引
import DiagramCanvas from "@/components/DiagramCanvas.vue"; import DiagramCanvas from "@/components/DiagramCanvas.vue";
import ComponentSelector from "@/components/ComponentSelector.vue"; import ComponentSelector from "@/components/ComponentSelector.vue";
import PropertyPanel from "@/components/PropertyPanel.vue"; import PropertyPanel from "@/components/PropertyPanel.vue";
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
import type { DiagramData, DiagramPart } from "@/components/diagramManager"; import type { DiagramData, DiagramPart } from "@/components/diagramManager";
import { import {
type PropertyConfig, type PropertyConfig,
@ -44,6 +48,164 @@ import {
generatePropsFromAttrs, generatePropsFromAttrs,
} from "@/components/equipments/componentConfig"; // } from "@/components/equipments/componentConfig"; //
// --- ---
const showDocPanel = ref(false);
const documentContent = ref('');
//
function toggleDocPanel() {
showDocPanel.value = !showDocPanel.value;
//
if (showDocPanel.value) {
fetchDocumentation();
}
}
//
function fetchDocumentation() {
// APIMarkdown
const mockDocContent = `
# FPGA WebLab 用户指南
## 简介
FPGA WebLab是一个基于Web的FPGA设计和实验平台允许用户在浏览器中进行FPGA设计仿真和验证该平台提供了丰富的组件库和直观的界面使FPGA学习和设计变得简单高效
## 功能特点
- **可视化设计**拖拽式界面无需编写底层代码
- **组件库**包含多种常用FPGA组件
- **实时验证**设计完成后可立即进行验证和测试
- **远程编程**支持远程将设计烧录到物理FPGA设备
- **协作功能**支持多人同时在线编辑和查看
## 快速入门
### 创建项目
1. 点击主界面的"新建项目"按钮
2. 输入项目名称和描述
3. 选择目标FPGA设备型号
4. 点击"创建"完成项目初始化
### 添加组件
1. 在左侧组件库中找到需要的组件
2. 将组件拖拽到设计区域
3. 通过右侧属性面板配置组件参数
### 连接组件
1. 点击一个组件的输出引脚
2. 拖动连线到目标组件的输入引脚
3. 松开鼠标完成连接
4. 连线会自动显示信号名称和类型
### 验证设计
1. 点击工具栏中的"验证"按钮
2. 系统会自动检查设计中的错误和警告
3. 如有问题可查看错误日志进行修复
### 模拟仿真
1. 点击"仿真"按钮进入仿真模式
2. 设置输入信号和仿真参数
3. 运行仿真并查看波形输出
4. 可通过时间轴查看不同时刻的信号状态
### 烧录到设备
1. 确保FPGA设备已连接
2. 点击"合成"按钮生成比特流文件
3. 点击"烧录"将设计下载到物理设备
4. 查看烧录日志确认操作成功
## 组件说明
### 基础逻辑组件
- **AND/OR/XOR门**基本逻辑门可设置输入数量
- **MUX多路复用器**数据选择器根据选择信号输出不同输入
- **触发器/寄存器**存储单元可选DTJK等不同类型
- **计数器**可配置位宽计数方向等参数
### 接口组件
- **按钮/开关**用户输入控制元件
- **LED指示灯**显示数字输出状态
- **七段数码管**显示数字信息
- **UART接口**串行通信接口
### 高级组件
- **RAM/ROM**存储模块可配置数据位宽和深度
- **PLL/DCM**时钟控制模块
- **DSP模块**数字信号处理单元
- **MCU集成**可嵌入微控制器核心
## 常见问题
### 设计无法编译
检查以下可能的原因
- 组件之间连线不完整
- 组件参数设置不正确
- 存在时序冲突
- 资源使用超出目标设备限制
### 模拟结果与预期不符
可能的解决方法
- 检查输入激励是否正确
- 验证时钟设置和复位信号
- 调整仿真时间步长
- 查看信号完整传播路径
### 设备烧录失败
常见原因及解决方法
- 检查USB连接和驱动安装
- 确认选择了正确的设备型号
- 验证生成的比特流文件是否有效
- 重新安装FPGA开发工具
## 高级技巧
### 层次化设计
对于复杂项目建议按功能模块划分设计层次创建子模块后再进行顶层连接
### 自定义组件
可以将常用电路打包为自定义组件方便在不同项目中重复使用
### 版本控制
定期保存设计快照利用版本历史功能跟踪设计变更
### 性能优化
- 减少关键路径上的组件数量
- 优化时钟树结构
- 合理布局减小互连延迟
- 使用流水线结构提高吞吐量
## 联系我们
如有任何问题或建议请通过以下方式联系我们
- 邮箱support@fpgaweblab.com
- 论坛forum.fpgaweblab.com
- 微信公众号FPGA_WebLab
感谢您使用FPGA WebLab
`;
//
documentContent.value = mockDocContent;
}
// --- --- // --- ---
const showComponentsMenu = ref(false); const showComponentsMenu = ref(false);
const diagramData = ref<DiagramData>({ const diagramData = ref<DiagramData>({
@ -730,4 +892,27 @@ body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* 文档面板样式 */
.doc-panel {
padding: 1.5rem;
max-width: 800px;
margin: 0 auto;
}
/* 文档切换按钮样式 */
.doc-toggle-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 50;
}
/* Markdown渲染样式调整 */
:deep(.markdown-content) {
padding: 1rem;
background-color: hsl(var(--b1));
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
</style> </style>