lizhi-a / markdown-notebook

一个可以使用 markdown 语法的笔记本,实现增删改查笔记功能。主要技术栈为 React + TypeScript + less 。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

项目启动

后台:node app.js
下载依赖: yarn
前端:yarn start

一、项目简介

该项目的主要功能是增删改查笔记,笔记可以使用 markdown 语法编写,笔记内容展示 markdown 语法编译后内容。

二、项目技术栈

前端:

  • React 作为前端框架
  • UmiJS 作为前端应用框架,并用 Umi 管理路由
  • Ant Design 作为前端UI框架
  • Axios + ahooks 的 useRequest 发送网络请求
  • TypeScript 进行类型约束
  • Less 编写样式

后台:

  • 采用 Node.js + Express 作为后台框架
  • 使用 mongodb 数据库

三、最终效果

  • 首页 在这里插入图片描述

  • 搜索结果 在这里插入图片描述

  • 新增笔记 在这里插入图片描述

  • 笔记详情 在这里插入图片描述

  • 编辑页面 在这里插入图片描述

四、文件目录结构说明

在这里插入图片描述在这里插入图片描述

前端:

  • assets:存放共用的图标、图片
  • common:存放共用的资源,如常量、全局 CSS、公共的 js 工具函数、context 文件
  • components:存放多个页面共享的组件
  • pages:划分各个页面
  • services:发送前端网络请求
  • .umirc.ts:配置文件,也可以配置前端路由

后台:

  • app.js:进行网络请求配置、挂载路由
  • router.js:后台路由
  • modules:存放数据库的 Scheme

五、核心技术

1. 引入 normalize.css 对样式进行初始化

主要解决各大浏览器默认样式不同的问题。同时相比 reset.css ,normalize.css 可以模块化引入、修复了浏览器的一些 bug、还没有复杂的继承链。

2. 引入 classnames 库,对 className 进行管理

可以 ==通过状态 state 实现对类名的控制==

import cs from 'classnames'

const [state, setState] = useState()
<Layout className={cs(
      globalStyles.layout,
      styles.layout, {
      'layout': state,
})} >

3. 使用 useContext 进行兄弟组件间的数据传输

还挺好用的,但是如果是深层传递数据,可能不能一下知道哪里提供数据、都提供了什么数据,会导致代码复杂一点。

// createContext
const TopicListContext = React.createContext({} as IContextProps)

// 提供数据
<TopicListContext.Provider value={{ searchResult, setSearchResult }}>
   ...
</TopicListContext.Provider >

// 使用数据
const { setSearchResult } = useContext(TopicListContext)

4. 定义公共组件 Header ,实现组件的复用

通过 props 的 isSearch 参数来判断是否显示搜索框。 通过获取 url 里的 id 来判断是否显示二级目录。 在这里插入图片描述 在这里插入图片描述

5. 使用 umi 进行路由管理

umi 具有约定式路由,它不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。 我用的是配置式路由,主要是自己实习外没使用过 umi 的路由,所以想试一试。

6. 引入 marked 库,对 markdown 语法进行解析

解析后的内容可以通过 dangerouslySetInnerHTML 对 div 进行 InnerHTML 的替换。

import { marked } from 'marked'

marked.setOptions({
  renderer: new marked.Renderer(),
  highlight: function (code) {
    return hljs.highlightAuto(code).value;
  },
  gfm: true, // 允许 Git Hub标准的markdown.
  pedantic: false, // 不纠正原始模型任何的不良行为和错误(默认为false)
  sanitize: false, // 对输出进行过滤(清理),将忽略任何已经输入的html代码(标签)
  // tables: true, // 允许支持表格语法(该选项要求 gfm 为true)
  breaks: true, // 允许回车换行(该选项要求 gfm 为true)
  smartLists: true, // 使用比原生markdown更时髦的列表
  smartypants: false, // 使用更为时髦的标点
})

<div dangerouslySetInnerHTML={{ __html: marked.parse(res.topic.article)}}>
</div>

7. 引入 moment 库,对时间进行格式化

import moment from 'moment'
import 'moment/locale/zh-cn'
// 使用中文时间
moment.locale('zh-cn')

{moment(topic?.created_time).format('LL')}

8. 后台部分

与博客系统的后台大致相同,使用技术可见该 博客

六、遇到的问题

  • 报错:参数 “props” 隐式具有 “any” 类型

对 props 参数进行类型限制。

type myProps = {
  isSearch: boolean;
}

export default function Header(props: myProps) {}
  • 报错:umi.js:373181 Uncaught Error: dangerouslySetInnerHTML does not make sense on < textarea>

设置 innerHTML 对 textarea 没有意义,因为 textarea 的内容都保存在它的 value 属性中,并且 textarea 不能呈现 HTML。 如果想在一个容器里放 HTML,可以使用 div、p 等。

  • antd form initialvalues 属性失效

initalvallues 只在第一次拿到初始值有效,不能初值是空之后再赋值,此时不会生效。不会根据其内容发生变化而进行对应更新。 此时我们可以通过 setFieldsValue 进行设置,setFieldsValue 可以动态的设置初始值。

const [form] = Form.useForm()
form.setFieldsValue({
    title: res.topic.title,
    model: res.topic.model
})

<Form form={form}>
	...
</Form>
  • form.setFieldsValue() 不能对 textarea 设置

组件被div或者其他容器包裹,可以通过 getFiledDecorator 解决。 参考博客

<Form.Item  name='article' >
    <div className={styles.markdownDiv}>
          <Input.TextArea />
    </div>
</Form.Item>

About

一个可以使用 markdown 语法的笔记本,实现增删改查笔记功能。主要技术栈为 React + TypeScript + less 。


Languages

Language:TypeScript 64.6%Language:JavaScript 25.3%Language:Less 10.0%