怎么做高端网站,北京大兴黄村网站建设,客户管理系统的功能,建设部网站注册人员整体实现方案 基于 Node.js 后端 express marked#xff0c;前端 Vite Vue3 mermaid #xff0c;实现 Markdown 文件浏览、上传、渲染#xff08;支持 Mermaid#xff09;的功能#xff0c;分为后端和前端两部分开发。 一、后端实现#xff08;Express 服务#xff0…整体实现方案基于 Node.js 后端 express marked前端 Vite Vue3 mermaid 实现 Markdown 文件浏览、上传、渲染支持 Mermaid的功能分为后端和前端两部分开发。一、后端实现Express 服务1. 项目初始化mkdir md-editor-server cd md-editor-server npm init -y npm install express cors multer1.4.4 npm install fs-extra path marked2. 核心代码server.jsconstexpressrequire(express);constcorsrequire(cors);constmulterrequire(multer);constfsrequire(fs-extra);constpathrequire(path);constmarkedrequire(marked);// 初始化Expressconstappexpress();app.use(cors());// 解决跨域app.use(express.json());// 配置项 constDEFAULT_MD_DIRpath.join(__dirname,md-files);// 确保默认目录存在fs.ensureDirSync(DEFAULT_MD_DIR);// 安全工具函数 functionvalidateAndNormalizeDir(dirPath){if(!dirPath||dirPath.trim()){returnDEFAULT_MD_DIR;}constabsPathpath.resolve(dirPath.trim());if(!fs.existsSync(absPath)){thrownewError(目录不存在:${absPath});}if(!fs.statSync(absPath).isDirectory()){thrownewError(不是有效的目录:${absPath});}fs.accessSync(absPath,fs.constants.R_OK);returnabsPath;}// 配置marked支持Mermaid constrenderernewmarked.Renderer();// 重写fence渲染逻辑识别mermaid代码块renderer.fence(code,lang){if(langmermaid){returndiv classmermaid${code}/div;}returnprecode classlanguage-${lang}${marked.utils.escape(code)}/code/pre;};// 配置markedmarked.setOptions({renderer,gfm:true,breaks:true,sanitize:false});// 文件上传配置 constuploadmulter({storage:multer.memoryStorage(),limits:{fileSize:5*1024*1024},// 5MBfileFilter:(req,file,cb){constextpath.extname(file.originalname).toLowerCase();if([.md,.markdown].includes(ext)){cb(null,true);}else{cb(newError(仅支持.md/.markdown格式文件),false);}}});// 接口实现 // 1. 获取MD文件列表app.get(/api/get-md-files,(req,res){try{constdirPathvalidateAndNormalizeDir(req.query.dirPath);constfilesfs.readdirSync(dirPath).filter(file{constextpath.extname(file).toLowerCase();returnext.md!file.startsWith(.);}).sort();res.json(files);}catch(err){console.error(获取文件列表失败:,err);res.status(400).json({error:err.message});}});// 2. 加载并渲染指定MD文件app.get(/api/load-md-file,(req,res){try{constfilenamedecodeURIComponent(req.query.filename||);if(!filename||filename.includes(..)||!filename.endsWith(.md)){returnres.status(400).json({error:无效的文件名});}constdirPathvalidateAndNormalizeDir(req.query.dirPath);constfilePathpath.join(dirPath,filename);if(!fs.existsSync(filePath)){returnres.status(404).json({error:文件不存在:${filename}});}fs.accessSync(filePath,fs.constants.R_OK);// 读取并渲染MD内容constmdContentfs.readFileSync(filePath,utf8);consthtmlmarked.parse(mdContent);res.json({html});}catch(err){console.error(加载MD文件失败:,err);res.status(400).json({error:err.message});}});// 3. 上传并渲染MD文件app.post(/api/upload-md,upload.single(mdFile),(req,res){try{if(!req.file){returnres.status(400).json({error:请选择要上传的MD文件});}constmdContentreq.file.buffer.toString(utf8);consthtmlmarked.parse(mdContent);res.json({html});}catch(err){console.error(上传文件失败:,err);res.status(500).json({error:err.message});}});// 启动服务constPORT5555;app.listen(PORT,(){console.log(后端服务运行在: http://localhost:${PORT});console.log(默认MD目录:${DEFAULT_MD_DIR});});二、前端实现Vite Vue31. 项目初始化npm create vitelatest md-editor-client -- --template vue cd md-editor-client npm install axios marked mermaid element-plus2. 核心配置vite.config.jsimport{defineConfig}fromvite;importvuefromvitejs/plugin-vue;importpathfrompath;exportdefaultdefineConfig({plugins:[vue()],server:{port:5173,proxy:{// 代理后端接口/api:{target:http://localhost:5555,changeOrigin:true,rewrite:(path)path.replace(/^\/api/,/api)}}},resolve:{alias:{:path.resolve(__dirname,src)}}});3. 全局注册ElementPlussrc/main.jsimport{createApp}fromvue;importAppfrom./App.vue;importElementPlusfromelement-plus;importelement-plus/dist/index.css;importmermaidfrommermaid;// 初始化mermaidmermaid.initialize({startOnLoad:true});constappcreateApp(App);app.use(ElementPlus);app.mount(#app);4. 核心组件src/App.vuetemplate div classlayout !-- 左侧文件列表 -- div classsidebar el-card shadowhover h3Markdown文件列表/h3 !-- 目录输入 -- el-input v-modeldirPath placeholder输入目录路径如 D:\docs suffix-iconFolder classdir-input / el-button typeprimary clickloadFileList classload-btn 加载目录 /el-button el-text typedanger v-ifdirError{{ dirError }}/el-text !-- 文件列表 -- el-list border classfile-list v-loadingloading el-list-item v-forfile in fileList :keyfile clickloadMdFile(file) classfile-item {{ file }} br /el-list-item el-list-item v-iffileList.length 0 !loading 该目录下无.md文件 /el-list-item /el-list /el-card /div !-- 右侧内容区 -- div classcontent el-card shadowhover classupload-card !-- 文件上传 -- el-upload classupload-demo action/api/upload-md :headers{ Content-Type: multipart/form-data } :accept.md,.txt :on-successhandleUploadSuccess :on-errorhandleUploadError :before-uploadbeforeUpload el-button typeprimary上传.md文件/el-button /el-upload /el-card !-- 渲染内容 -- el-card shadowhover classrender-card div idmarkdown-content v-htmlrenderedHtml classmarkdown-content /div /el-card /div /div /template script setup import { ref, reactive, onMounted, nextTick } from vue; // 新增 nextTick import axios from axios; import { ElMessage } from element-plus; import mermaid from mermaid; // 初始化 Mermaid优化配置 mermaid.initialize({ startOnLoad: false, // 关闭自动加载手动控制渲染时机 theme: default, securityLevel: loose // 放宽安全限制确保渲染正常 }); // 其他响应式数据保持不变 const dirPath ref(); const fileList ref([]); const renderedHtml ref(p请加载目录文件或上传文件进行渲染/p); const dirError ref(); const loading ref(false); // 路径校验函数保持不变 function isInvalidDirPath(path) { if (!path || path.trim() ) return false; const trimedPath path.trim(); const winDriveRegex /^[a-zA-Z]:[\\/]/; const unixRootRegex /^\/|^~/; const isWindows winDriveRegex.test(trimedPath); const isUnix unixRootRegex.test(trimedPath); if (!isWindows !isUnix) return true; const illegalChars /[|?*]/; if (illegalChars.test(trimedPath)) return true; return false; } // 加载文件列表函数保持不变 async function loadFileList() { if (dirPath.value isInvalidDirPath(dirPath.value)) { dirError.value 无效的目录路径格式; return; } loading.value true; dirError.value ; try { const params dirPath.value ? { dirPath: dirPath.value } : {}; const res await axios.get(/api/get-md-files, { params }); fileList.value res.data; ElMessage.success(文件列表加载成功); } catch (err) { dirError.value err.response?.data?.error || 加载失败; ElMessage.error(dirError.value); } finally { loading.value false; } } // 修复加载MD文件并渲染Mermaid async function loadMdFile(filename) { loading.value true; try { const params { filename, dirPath: dirPath.value }; const res await axios.get(/api/load-md-file, { params }); renderedHtml.value res.data.html; // 关键修复等待Vue DOM更新完成后再渲染Mermaid await nextTick(); renderMermaid(); // 手动触发Mermaid渲染 ElMessage.success(加载文件 ${filename} 成功); } catch (err) { ElMessage.error(err.response?.data?.error || 加载文件失败); } finally { loading.value false; } } // 修复上传文件成功后渲染Mermaid function handleUploadSuccess(res) { renderedHtml.value res.data.html; // 关键修复等待DOM更新后渲染 nextTick().then(() { renderMermaid(); }); ElMessage.success(文件上传并渲染成功); } // 新增手动渲染Mermaid的核心函数 function renderMermaid() { // 获取所有mermaid节点 const mermaidElements document.querySelectorAll(.mermaid); if (mermaidElements.length 0) return; // 逐个渲染每个mermaid节点 mermaidElements.forEach(async (el) { try { // 清空原有内容避免重复渲染 el.innerHTML ; // 获取原始Mermaid代码从后端渲染的HTML中代码是在.mermaid标签内的 const code el.textContent.trim(); // 手动渲染Mermaid const { svg } await mermaid.render(mermaid-${Date.now()}, code); el.innerHTML svg; } catch (err) { console.error(Mermaid渲染失败:, err); el.innerHTML span stylecolor: red;Mermaid语法错误: ${err.message}/span; } }); } // 其他函数保持不变 function beforeUpload(file) { const ext file.name.slice(file.name.lastIndexOf(.)).toLowerCase(); if (![.md, .txt].includes(ext)) { ElMessage.error(仅支持上传.md/.txt格式文件); return false; } return true; } function handleUploadError(err) { ElMessage.error(err.response?.data?.error || 文件上传失败); } onMounted(() { loadFileList(); }); /script style scoped .layout { display: flex; height: 100vh; gap: 20px; padding: 20px; box-sizing: border-box; } .sidebar { width: 30%; height: 100%; overflow: auto; } .content { width: 70%; height: 100%; overflow: auto; } .dir-input { margin-bottom: 10px; } .load-btn { margin-bottom: 10px; width: 100%; } .file-list { margin-top: 10px; max-height: 60vh; overflow: auto; } .file-item { cursor: pointer; transition: background 0.2s; } .file-item:hover { background: #f5f7fa; } .upload-card { margin-bottom: 20px; } .render-card { height: calc(100% - 140px); overflow: auto; } .markdown-content { padding: 20px; line-height: 1.8; } /* Markdown样式增强 */ .markdown-content pre { background: #f5f5f5; padding: 10px; border-radius: 4px; overflow: auto; } .markdown-content code { font-family: Consolas, Monaco, monospace; } .markdown-content .mermaid { margin: 20px 0; text-align: center; } .markdown-content .mermaid svg { max-width: 100%; height: auto; } /style三、运行说明启动后端进入md-editor-server目录执行node server.js启动前端进入md-editor-client目录执行npm run dev访问地址浏览器打开http://localhost:5173四、核心功能说明目录加载支持自定义目录路径前端预校验路径格式后端验证路径合法性文件列表展示指定目录下的所有.md文件点击文件可加载并渲染内容文件上传支持上传.md文件实时渲染内容包含Mermaid图表Mermaid支持识别Markdown中的 mermaid代码块自动渲染为流程图/时序图等安全校验拦截非法路径、非法文件名限制文件上传大小5MB五、扩展优化建议增加文件编辑功能结合monaco-editor实现文件保存/下载功能增加语法高亮使用highlight.js支持更多Markdown扩展如表格、公式增加目录导航、锚点跳转功能优化错误处理增加全局异常捕获增加暗黑模式切换