iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >基于vite2+Vue3如何编写一个在线帮助文档工具
  • 209
分享到

基于vite2+Vue3如何编写一个在线帮助文档工具

2023-06-29 12:06:12 209人浏览 泡泡鱼
摘要

本篇内容主要讲解“基于vite2+vue3如何编写一个在线帮助文档工具”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于vite2+Vue3如何编写一个在线帮助文档工具”吧!技术栈vite: ^

本篇内容主要讲解“基于vite2+vue3如何编写一个在线帮助文档工具”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于vite2+Vue3如何编写一个在线帮助文档工具”吧!

    技术栈

    • vite: ^2.7.0

    • vue: ^3.2.23

    • axiOS: ^0.25.0 获取JSON格式的配置和文档

    • element-plus: ^2.0.2 UI库

    • nf-ui-elp": ^0.1.0 二次封装的UI库

    • @element-plus/icons-vue: ^0.2.4 图标

    • @kanGC/v-md-editor:"^2.3.13 md 编辑器

    • vite-plugin-prismjs: ^0.0.8 代码高亮

    • nf-state": ^0.2.4 状态管理

    • nf-WEB-storage": ^0.2.3 访问 indexedDB

    建立库项目(@naturefw/press-edit)实现文档的编写、浏览功能

    首先使用 vite2 建立一个 Vue3 的项目:

    • 安装 elementPlus 实现页面效果;

    • 安装 v-md-editor 实现 markdown 的编辑和显示;

    • 安装 @naturefw/storage 操作 indexedDB ,实现帮助文档的存储;

    • 安装 @naturefw/nf-state 实现状态管理;

    • 安装axios 用于加载 json文件,实现导入功能。

    • node写一个后端api,实现写入json文件的功能。

    注意:库项目需要安装以上插件,帮助文档项目只需要安装 @naturefw/press-edit 即可。

    两个状态:编辑和浏览

    一开始做了两个项目,分别实现编辑文档和显示文档的功能,但是后来发现,内部代码大部分是相同的,维护的时候有点麻烦,所以改为在编辑文档的项目里加入“浏览”的状态,然后设置切换的功能,这样便于内部代码的维护,以后成熟了可能会分为两个单独的项目。

    编辑状态的功能

    • 菜单维护

    • 文档维护

    • 文档展示

    • 导入导出

    • 在线编写/执行代码

    我喜欢在线编辑的方式,这样更省心,于是我用 el-menu 实现导航和左侧的菜单,然后加上了维护功能。 使用 v-md-editor 实现 Markdown 的编辑和显示。 然后用node写了一个后端API,实现保存 json文件的功能,这样就完美了。

    浏览状态的功能

    • 导航

    • 菜单

    • 文档展示

    • 执行代码

    就是在编辑状态的功能的基础上,去掉一些功能。或者其实可以反过来思考。

    实现导航

    首先参考 VuePress 设置一个json文件,用于加载和保存网站信息、导航信息。

    /public/docs/.nfpress/project.json

    {  "projectId": "1000",  "title": "nf-press-edit !",  "description": "这是一个在线编辑、展示文档的小工具",  "navi": [    {      "naviId": "1010",      "text": "指南",      "link": "menu"    },    {      "naviId": "1020",      "text": "组件",      "link": "menu"    },    {      "naviId": "1380",      "text": "gitee",      "link": "https://gitee.com/nfpress/nf-press-edit"    },    {      "naviId": "1390",      "text": "在线演示",      "link": "Https://nfpress.gitee.io/nf-press-edit/"    },    {      "naviId": "1395",      "text": "我要提意见",      "link": "https://gitee.com/nfpress/nf-press-edit/issues"    }  ]}
    • projectId:项目ID,可以用于区分不同的帮助文档项目。

    • navi: 存放导航项。

    • naviId: 关联到菜单。

    • text: 导航上显示的文字。

    • link: 连接方式或链接地址。menu:表示要打开对应的菜单;URL:在新页面里打开连接。

    然后做一个组件,用 el-menu 绑定数据渲染出来即可实现导航效果。

    /lib/navi/navi.vue

      <el-menu    :default-active="activeIndex2"    class="el-menu-demo"    mode="horizontal"    v-bind="$attrs"    :background-color="backgroundColor"    @select="handleSelect"  >    <el-menu-item      v-for="(item, index) in naviList"      :key="index"      :index="item.naviId"    >      {{item.text}}    </el-menu-item>  </el-menu>

    可以是多级的导航,暂时没有实现在线维护功能。

      import { ref } from 'vue'  import { ElMenu, ElMenuItem } from 'element-plus'  import { state } from '@naturefw/nf-state'     const props = defineProps({    'background-color': { // 默认背景色      type: String,      default: '#ece5d9'    },    itemProps: Object  })  // 获取状态和导航内容  const { current, naviList } = state  // 激活第一个导航项  const activeIndex2 = ref(naviList[0].naviId)    const handleSelect = (key, keyPath) => {    const navi = naviList.find((item) => item.naviId === key)    if (navi.link === 'menu') {      // 打开菜单      current.naviId = key    } else {      // 打开连接      window.open(navi.link, '_blank')    }  }

    @naturefw/nf-state

    自己写的一个轻量级状态管理,可以当做大号 Reactive 来使用,通过状态管理加载 project.json 然后绑定渲染。

    naviList

    导航列表,由状态管理加载。

    current

    当前激活的各种信息,比如“current.naviId”表示激活的导航项。

    实现菜单

    和导航类似,只是需要增加两个功能:n级分组和维护。

    首先参考 VuePress 设置一个json文件,保存菜单信息。

    /public/docs/.nfpress/menu.json

    [  {    "naviId": "1010",    "menus": [      {        "menuId": "110100",        "text": "介绍",        "description": "描述",        "icon": "FolderOpened",        "children": []      },      {        "menuId": "111100",        "text": "快速上手",        "description": "描述",        "icon": "FolderOpened",        "children": [          {            "menuId": 111120,            "text": "编辑文档项目",            "description": "",            "icon": "UserFilled",            "children": []          },          {            "menuId": 111130,            "text": "展示文档项目",            "description": "",            "icon": "UserFilled"          }        ]      }     ],    "ver": 1.6  },  {    "naviId": "1020",    "menus": [      {        "menuId": "21000",        "text": "导航(docNavi)",        "description": "描述",        "icon": "Star",        "children": []      }     ],    "ver": 1.5  }]
    • naviId: 关联导航项ID,可以是数字,也可以是其他字符。需要和导航项ID对应。

    • menus: 导航项对应的菜单项集合

    • menuId: 菜单项ID,关联一个文档,可以是数字或者英文。

    • text: 菜单项名称。

    • description: 描述,考虑以后用于查询。

    • icon: 菜单使用的图标名称。

    • children: 子菜单项目,没有的话可以去掉。

    • ver: 版本号,便于更新文档。

    然后用 el-menu 绑定数据渲染,因为要实现n级分组,所以做一个递归组件实现n级菜单的效果。

    实现n级分组菜单

    做一个递归组件实现n级分组的功能:

    /lib/menu/menu-sub-edit.vue

      <template v-for="(item, index) in subMenu">    <!--树枝-->    <template v-if="item.children && item.children.length > 0">      <el-sub-menu         :key="item.menuId + '_' + index"        :index="item.menuId"              >        <template #title>          <div >            <component              :is="$icon[item.icon]"                          >            </component>            <span>{{item.text}}</span>          </div>        </template>        <!--递归子菜单-->        <my-sub-menu2          :subMenu="item.children"          :dialogAddInfo="dialogAddInfo"          :dialogModInfo="dialogModInfo"        />      </el-sub-menu>    </template>    <!--树叶-->    <el-menu-item v-else      :index="item.menuId"      :key="item.menuId + 'son_' + index"    >      <template #title>        <div >          <span >            <component              :is="$icon[item.icon]"                          >            </component>            <span >{{item.text}}</span>          </span>        </div>      </template>    </el-menu-item>  </template>
      import { ElMenuItem, ElSubMenu } from 'element-plus'  // 展示子菜单 - 递归  import mySubMenu2 from './menu-sub.vue'  const props = defineProps({    subMenu: Array, // 要显示的菜单,可以n级    dialogAddInfo: Object, // 添加菜单    dialogModInfo: Object // 修改菜单  })
    • subMenu 要显示的子菜单项

    • dialogAddInfo 添加菜单的信息

    • dialogModInfo 修改菜单的信息

    实现菜单的维护功能

    这个就比较简单了,做个表单实现菜单的增删改即可,篇幅有限跳过。

    实现 Markdown 的编辑

    使用 v-md-editor 实现 Markdown 的编辑和展示,首先该插件非常好用,其次支持VuePress的主题。

    建立 /lib/md/md-edit.vue 实现编辑 Markdown 的功能:

      <v-md-editor    :toolbar="toolbar"    left-toolbar="undo redo clear | tip emoji code | h bold italic strikethrough quote | ul ol table hr | link image  | save | customToolbar"    :include-level="[1, 2, 3, 4]"    v-model="current.docInfo.md"    :height="editHeight + 'px'"    @save="mySave"  >  </v-md-editor>
      import { watch,ref  } from 'vue'  import { ElMessage, ElRadioGroup, ElRadioButton } from 'element-plus'  import mdController from '../service/md.js'    // 状态  import { state } from '@naturefw/nf-state'  // 获取当前激活的信息  const current = state.current  // 文档的加载和保存  const { loadDocById, saveDoc } = mdController()    // 可见的高度  const editHeight = document.documentElement.clientHeight - 200  // 单击 保存 按钮,实现保存功能  const mySave = (text, html) => {    saveDoc(current)  }  // 定时保存  let timeout = null  let isSaved = true  const timeSave = () => {    if (isSaved) {      // 保存过了,重新计时      isSaved = false    } else {      return // 有计时,退出    }    timeout = setTimeout(() => {      // 保存文档      saveDoc(current).then(() => {        ElMessage({          message: '自动保存文档成功!',          type: 'success',        })      })      isSaved = true    }, 10000)  }  // 定时保存文档  watch(() => current.docInfo.md, () => {    timeSave()  })  // 根据激活的菜单项,加载对应的文档  watch( () => current.menuId, async (id) => {    const ver = current.ver    loadDocById(id, ver).then((res) => {      // 找到了文档      Object.assign(current.docInfo, res)    }).catch((res) => {      // 没有文档      Object.assign(current.docInfo, res)    })  })
    • mdController 实现文档的增删改查的controller

    • timeSave 定时保存文档,避免忘记点保存按钮

    是不是挺简单的。

    实现在线编写代码并且运行的功能

    因为是基于Vue3建立的项目,而且也是为了写vue3相关的帮助文档,那么就有一个很实用的要求:在线写代码并且可以运行

    个人感觉这个功能还是很实用的,我知道有第三方网站提供了这种功能,但是网速有点慢,另外有一种大炮打蚊子的感觉,我只需要实现简单的代码演示。

    于是我基于 vue 的 defineAsyncComponent 写了一个简单版的在线编写代码且运行的功能:

    /lib/runCode/run.vue

      <div >    <async-comp></async-comp>  </div>
      import {    defineAsyncComponent,    ref, reactive,...    // 其他常用的vue内置指令  } from 'vue'  // 使用 eval编译js代码  const mysetup = `    (function setup () {      {[code]}    })  `    // 通过属性传入需要运行的代码和模板  const props = defineProps({    code: {      type: Object,      default: () => {        return {          js: '',          template: '',          style: ''        }      }    }  })  const code = props.code  // 使用 defineAsyncComponent 让代码运行起来  const AsyncComp = defineAsyncComponent(    () => new Promise((resolve, reject) => {        resolve({          template: code.template, // 设置模板          style: [code.style], // 大概是样式设置,但是好像没啥效果          setup: (props, ctx) => {            const tmpJs = code.js // 获取js代码            let fun = null // 转换后的函数            try {              if (tmpJs)                fun = eval(mysetup.replace('{[code]}', tmpJs)) // 用 eval 把 字符串 变成 函数            } catch (error) {              console.error('转换出现异常:', error)            }            const re = typeof fun === 'function' ? fun : () => {}            return {              ...re(props, ctx) // 运行函数,解构返回对象            }          }        })      })  )
    • defineAsyncComponent

    实用 defineAsyncComponent 加载组件,需要设置三个部分:模板、setup和style。

    • template: 字符串形式,可以直接传入

    • setup: js代码,可以用eval的方式进行动态编译。

    • style: 可以设置样式。

    这样即可让在线编写的代码运行起来,当然功能有限,只能用于一些简单的代码演示。

    导出

    以上这些功能都是基于 indexedDB 进行的,想要发布的话,需要先导出为json文件。

    因为浏览器里不能直接写文件,所以需要使用折中的方式:

    • 复制粘贴

    • 下载

    • 导出

    复制粘贴

    这个简单,用文本域显示json即可。

    下载

    使用 chrome 浏览器提供的下载功能下载文件。

      const uri = 'data:text/json;charset=utf-8,\ufeff' + encodeURIComponent(show.navi)  //通过创建a标签实现  var link = document.createElement("a")  link.href = uri  //对下载的文件命名  link.download = fileName  document.body.appendChild(link)  link.click()  document.body.removeChild(link)

    以上介绍的是内部原理,如果只是想简单使用的话,可以跳过,直接看下面的介绍。

    用后端写文件

    以上两种都不太方便,于是用node做了个简单的后端API,用于实现写入json文件的功能。

    代码放在了 api文件夹里,可以使用 yarn api运行。当然需要在 package.json 里做一下设置。

      "scripts": {    "dev": "vite",    "build": "vite build --mode project",    "lib": "vite build --mode lib",    "serve": "vite preview",    "api": "node api/server.js"  },

    实现一个帮助文档的项目

    上面介绍的是库项目的基本原理,我们要做帮助文档的时候,并不需要那么复杂。

    使用 vite2 建立一个vue3的项目,然后安装 @naturefw/press-edit,使用提供的组件即可方便的实现。

    main.js

    首先需要在 main.js 里面做一些设置。

    import { createApp } from 'vue'import App from './App.vue'// 设置 axios 的 baseUrlconst baseUrl = (document.location.host.includes('.gitee.io')) ?  '/doc-ui-core/' :  '/'// 轻量级状态// 设置 indexedDB 数据库,存放文档的各种信息。import { setupIndexedDB, setupStore } from '@naturefw/press-edit'// 初始化 indexedDB 数据库setupIndexedDB(baseUrl)  // UI库import ElementPlus from 'element-plus'// import 'element-plus/lib/theme-chalk/index.CSS'// import 'dayjs/locale/zh-cn'import zhCn from 'element-plus/es/locale/lang/zh-cn'// 二次封装import { nfElementPlus } from '@naturefw/ui-elp'// 设置iconimport installIcon from './icon/index.js'// 设置 Markdown 的配置函数import setMarkDown from './main-md.js'// 主题import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'const {  VueMarkdownEditor, // Markdown 的编辑器  VMdPreview // Markdown 的浏览器} = setMarkDown(vuepressTheme)const app = createApp(App)app.config.globalProperties.$ELEMENT = {  locale: zhCn,  size: 'small'}app.use(setupStore) // 状态管理  .use(nfElementPlus) // 二次封装的组件  .use(installIcon) // 注册全局图标  .use(ElementPlus, { locale: zhCn, size: 'small' }) // UI库  .use(VueMarkdownEditor) // markDown编辑器  .use(VMdPreview) // markDown 显示  .mount('#app')
    • baseUrl: 根据发布平台的情况进行设置,比如这里需要设置为:“/doc-ui-core/”

    • setupIndexedDB: 初始化 indexedDB 数据库

    • setupStore: 设置状态

    • element-plus:element-plus 可以不挂载,但是css需要 import 进来,这里采用CDN的方式引入。

    • nfElementPlus: 二次封装的组件,便于实现增删改查。

    • setMarkDown: 加载 v-md-editor ,以及需要的插件。

    • vuepressTheme: 设置主题。

    设置 Markdown

    因为 v-md-editor 相关设置比较多,所以设置了一个单独文件进行管理:

    /src/main-md.js

    // Markdown 编辑器import VueMarkdownEditor from '@kangc/v-md-editor'import '@kangc/v-md-editor/lib/style/base-editor.css'// 在这里引入,不被识别?// import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'import '@kangc/v-md-editor/lib/theme/style/vuepress.css'// 代码高亮import Prism from 'prismjs'// emojiimport createEmojiPlugin from '@kangc/v-md-editor/lib/plugins/emoji/index'import '@kangc/v-md-editor/lib/plugins/emoji/emoji.css'// 流程图// import createMermaidPlugin from '@kangc/v-md-editor/lib/plugins/mermaid/cdn'// import '@kangc/v-md-editor/lib/plugins/mermaid/mermaid.css'// todoListimport createTodoListPlugin from '@kangc/v-md-editor/lib/plugins/todo-list/index'import '@kangc/v-md-editor/lib/plugins/todo-list/todo-list.css'// 代码行号import createLineNumbertPlugin from '@kangc/v-md-editor/lib/plugins/line-number/index';// 高亮代码行import createHighlightLinesPlugin from '@kangc/v-md-editor/lib/plugins/highlight-lines/index'import '@kangc/v-md-editor/lib/plugins/highlight-lines/highlight-lines.css'// import createCopyCodePlugin from '@kangc/v-md-editor/lib/plugins/copy-code/index'import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css'// markdown 显示器import VMdPreview from '@kangc/v-md-editor/lib/preview'// import '@kangc/v-md-editor/lib/style/preview.css'export default function setMarkDown (vuepressTheme) {  // 设置 vuePress 主题  VueMarkdownEditor.use(vuepressTheme,    {      Prism,      extend(md) {        // md为 markdown-it 实例,可以在此处进行修改配置,并使用 plugin 进行语法扩展        // md.set(option).use(plugin);      },    }  )    // 预览  VMdPreview.use(vuepressTheme,    {      Prism,      extend(md) {        // md为 markdown-it 实例,可以在此处进行修改配置,并使用 plugin 进行语法扩展        // md.set(option).use(plugin);      },    }  )    // emoji  VueMarkdownEditor.use(createEmojiPlugin())  // 流程图  // VueMarkdownEditor.use(createMermaidPlugin())  // todoList  VueMarkdownEditor.use(createTodoListPlugin())  // 代码行号  VueMarkdownEditor.use(createLineNumbertPlugin())  // 高亮代码行  VueMarkdownEditor.use(createHighlightLinesPlugin())  //   VueMarkdownEditor.use(createCopyCodePlugin())    // 预览的插件  VMdPreview.use(createEmojiPlugin())  VMdPreview.use(createTodoListPlugin())  VMdPreview.use(createLineNumbertPlugin())  VMdPreview.use(createHighlightLinesPlugin())  VMdPreview.use(createCopyCodePlugin())    return {    VueMarkdownEditor,    VMdPreview  }}

    不多介绍了,可以根据需要选择插件。

    布局

    在App.vue文件里面进行整体布局

      <el-container>    <el-header>      <!--导航-->      <div >        <!--写网站loGo、标题等-->        <h2>nf-press</h2>      </div>      <div >        <!--写网站logo、标题等-->        <el-switch v-model="$state.current.isView" v-bind="itemProps"></el-switch>      </div>      <div >        <!--网站导航-->        <doc-navi ></doc-navi>      </div>    </el-header>    <el-container>      <!--左侧边栏-->      <el-aside width="330px">        <!--菜单-->        <doc-menu ></doc-menu>      </el-aside>      <el-main>        <!--文档区域-->        <component          :is="docControl[$state.current.isView]"        />      </el-main>    </el-container>  </el-container>
      import { reactive, defineAsyncComponent } from 'vue'  import { ElHeader, ElContainer ,ElAside, ElMain } from 'element-plus'  import { docMenu, docNavi, config } from '@naturefw/press-edit' // 菜单 导航  import docView from './views/doc.vue' // 显示文档  // 加载菜单子控件  const docControl = {    true: docView,    false: defineAsyncComponent(() => import('./views/main.vue')) // 修改文档  }  const itemProps = reactive({    'inline-prompt': true,    'active-text': '看',    'inactive-text': '写',    'active-color': '#378FEB',    'inactive-color': '#EA9712'  })
    • $state:全局状态,$state.current.isView 设置是否是浏览状态。

    • doc-navi:导航组件

    • doc-menu:菜单组件

    • docControl:根据状态选择加载显示组件或者编辑组件的字典。

    这种方式虽然有点麻烦,但是比较灵活,可以根据需要进行各种灵活设置,比如添加版权信息、备案信息、广告等内容。

    到此,相信大家对“基于vite2+Vue3如何编写一个在线帮助文档工具”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    --结束END--

    本文标题: 基于vite2+Vue3如何编写一个在线帮助文档工具

    本文链接: https://www.lsjlt.com/news/324857.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • C++ 生态系统中流行库和框架的贡献指南
      作为 c++++ 开发人员,通过遵循以下步骤即可为流行库和框架做出贡献:选择一个项目并熟悉其代码库。在 issue 跟踪器中寻找适合初学者的问题。创建一个新分支,实现修复并添加测试。提交...
      99+
      2024-05-15
      框架 c++ 流行库 git
    • C++ 生态系统中流行库和框架的社区支持情况
      c++++生态系统中流行库和框架的社区支持情况:boost:活跃的社区提供广泛的文档、教程和讨论区,确保持续的维护和更新。qt:庞大的社区提供丰富的文档、示例和论坛,积极参与开发和维护。...
      99+
      2024-05-15
      生态系统 社区支持 c++ overflow 标准库
    • c++中if elseif使用规则
      c++ 中 if-else if 语句的使用规则为:语法:if (条件1) { // 执行代码块 1} else if (条件 2) { // 执行代码块 2}// ...else ...
      99+
      2024-05-15
      c++
    • c++中的继承怎么写
      继承是一种允许类从现有类派生并访问其成员的强大机制。在 c++ 中,继承类型包括:单继承:一个子类从一个基类继承。多继承:一个子类从多个基类继承。层次继承:多个子类从同一个基类继承。多层...
      99+
      2024-05-15
      c++
    • c++中如何使用类和对象掌握目标
      在 c++ 中创建类和对象:使用 class 关键字定义类,包含数据成员和方法。使用对象名称和类名称创建对象。访问权限包括:公有、受保护和私有。数据成员是类的变量,每个对象拥有自己的副本...
      99+
      2024-05-15
      c++
    • c++中优先级是什么意思
      c++ 中的优先级规则:优先级高的操作符先执行,相同优先级的从左到右执行,括号可改变执行顺序。操作符优先级表包含从最高到最低的优先级列表,其中赋值运算符具有最低优先级。通过了解优先级,可...
      99+
      2024-05-15
      c++
    • c++中a+是什么意思
      c++ 中的 a+ 运算符表示自增运算符,用于将变量递增 1 并将结果存储在同一变量中。语法为 a++,用法包括循环和计数器。它可与后置递增运算符 ++a 交换使用,后者在表达式求值后递...
      99+
      2024-05-15
      c++
    • c++中a.b什么意思
      c++kquote>“a.b”表示对象“a”的成员“b”,用于访问对象成员,可用“对象名.成员名”的语法。它还可以用于访问嵌套成员,如“对象名.嵌套成员名.成员名”的语法。 c++...
      99+
      2024-05-15
      c++
    • C++ 并发编程库的优缺点
      c++++ 提供了多种并发编程库,满足不同场景下的需求。线程库 (std::thread) 易于使用但开销大;异步库 (std::async) 可异步执行任务,但 api 复杂;协程库 ...
      99+
      2024-05-15
      c++ 并发编程
    • 如何在 Golang 中备份数据库?
      在 golang 中备份数据库对于保护数据至关重要。可以使用标准库中的 database/sql 包,或第三方包如 github.com/go-sql-driver/mysql。具体步骤...
      99+
      2024-05-15
      golang 数据库备份 mysql git 标准库
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作