0. 起因

老板要搞个Log分析工具,数据存储选用的是Elasticsearch,起初想法是做个Kibana的插件,后来觉得依靠Kibana太庞大,而且后期想要把代码直接部署在GitHub page上,因此打算做成个独立的工具。最终选用了Node.js+React,主要原因是相中了一个React UI库EUI(Elastic Stack推出的一个开源UI库,风格与Kibana一样),毕竟,不是谁都能写出漂亮的UI,强如Linux之父Linus都表示“如果我被困在一个与世隔绝的岛上,逃离这座岛的唯一办法是写出漂亮的UI,那我估计就老死在岛上了”。
虽然最终由于内网权限问题没部署上GitHub page,但基本流程都跑通了。既然代码都写了,顺路写个总结吧。

1. 工程搭建

首先,我们需要安装Node.js,到Node.js官网随便下一个安装包安装,或者下载压缩包解压缩后手动设置环境变量使用。我比较喜欢直接使用压缩包,因为这样可以随意在多个版本间切换而且不用额外的工具辅助。例如在Ubuntu下下载压缩包解压缩并通过命令export PATH=$NODEJS_ROOT/bin:$PATH即完成了安装。安装完成后可以通过以下命令查看是否安装成功:

node --version
npm --version

安装完成后,按照以下结构建立一个目录:

my-app/package.jsonpublic/index.htmlsrc/index.js

其中my-app可以改成任意你喜欢的名字,剩余的部分名字必须与例子给出的一致,这是工程可以构建的前提。

然后,打开package.json,在其中填入以下内容并保存:

{"name": "eui-demo","version": "0.1.0","private": true,"dependencies": {"@testing-library/jest-dom": "^4.2.4","@testing-library/react": "^9.5.0","@testing-library/user-event": "^7.2.1","react": "^16.13.1","react-dom": "^16.13.1","react-scripts": "3.4.3"},"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject"},"eslintConfig": {"extends": "react-app"},"browserslist": {"production": [">0.2%","not dead","not op_mini all"],"development": ["last 1 chrome version","last 1 firefox version","last 1 safari version"]}
}

上面的内容中,dependenciesscripts这两个对象必须有,其他的可选择性添加,即最小要求如下:

{"dependencies": {"react": "^16.13.1","react-dom": "^16.13.1","react-scripts": "3.4.3"},"scripts": {"start": "react-scripts start",}
}

package.json文件准备好后执行以下命令:

npm install
npm start

执行完上面的命令,整个工程已经构建完毕,在浏览器中输入http://localhost:3000/即可访问你刚构建起来的应用,虽然目前这个应用什么也没做也没有显示任何内容。

当然,其实还有另一个更加简单的方法来构建应用,Node.js安装完成后只需要在终端输入一条命令即可 ,同样,my-app可以换成任意名字:

npx create-react-app my-app/

等待命令执行完毕即可。

2. Hello World?

如果工程是我们自己手动一步一步搭建起来的,通过浏览器访问http://localhost:3000/是什么都不会显示的。下一步,我们就需要让它显示点什么。编程嘛,就从Hello World开始吧。

首先,我们打开my-app/public/index.html,在里面输入一下信息并保存:

<html><head></head><body><div id='content'></div></body>
</html>

上面的超文本标记代码很简单,如果用浏览器打开的话还是什么也看不到。它只是为后续的React UI代码提供了一个挂载点 —— id为content的一个div,在我们的小例子中,对HTML的编辑就算完了,身下的就全部交给JS代码了。

接着,我们打开my-app/src/index.js,输入一下代码:

import React from 'react';
import ReactDOM from 'react-dom';function HelloWorld(props) {return (<div><p>Hello World!</p></div>)
}ReactDOM.render(<HelloWorld />,document.getElementById('content')
);

编辑完my-app/src/index.js并保存之后,我们在my-app目录中执行npm start,就可以在浏览器中看到如图1结果:

图1 Hello World

在上面的代码中,我们做了三件事:

  1. 第一第二行代码分别从react以及react-dom这两个模块中导入了React以及ReactDOM这两个类。值得注意的是,虽然我们没有直接看到使用导入的React,但是这个导入语句是必须的,否则编译就会报错!
  2. 接下来,定义了一个名为HelloWorld的函数,这个函数在React中称为函数组件*(Function Components),它与类组件(Class Components)一起组成了React 渲染UI的核心。这个函数只有一个参数props,这个props实际上是一个字典,可以通过它传递任意参数给函数组件;函数返回一个描述如何显示UI的React元素,虽然看着像超文本标记语言(HTML),但是它却不是。它的名字叫做JSX(JavaScript eXtension),它是JavaScript语法的扩展。
  3. 第三步,就是将我们定义的函数组件通过ReactDOM.render()函数渲染出来。ReactDOM.render()需要一个挂载节点,在我们的例子中的挂载节点是前面提到的id为content的一个div,通过ReactDOM.render()渲染的界面都托管在React DOM中,由React DOM负责管理以及更新。

3. 实践

有了Hello World的铺垫,我们现在可以正式搭建一个简单点的应用了。我们选用的UI框架是Elastic UI,单然如果你有自己喜欢的其他框架也是可以的。

假设我们要搭建一个Markdown编辑器。我们确定它的结构如图2,我们需要用EUI实现我们的目标:

图2 Markdown 编辑器结构

3.1. 搭架子

我们在EUI中找到一个名叫Page的布局空间,其布局如图3,正好符合我们的期望:

图3 Page空间布局样式

同样,我们分别找到导航组件(tree-view)、标签组件(tabs)以及Markdown编辑框组件(markdown-editor),将它们搭积木一样组合起来,并做些调整就能得到如图4所示的界面:

图4 Markdwon editor界面预览

为了方便起见,我们将Page组件、导航栏组件、标签栏组件、编辑器组件代码放到独立JS文件中,分别命名为page.js, file-nav.js, tabs.js, markdown-editor.js,具体结构如下:

my-app/package.jsonpublic/index.htmlsrc/file-nav.jsindex.jsmarkdown-editor.jspage.jstabs.js

他们的代码分别如下所示:

// file-nav.js
import React from 'react';import { EuiIcon, EuiTreeView, EuiToken } from '@elastic/eui';export default () => {const showAlert = () => {alert('You squashed a bug!');};const items = [{label: 'src',id: 'src',icon: <EuiIcon type="folderClosed" />,iconWhenExpanded: <EuiIcon type="folderOpen" />,isExpanded: true,children: [{label: 'index.md',id: 'item_a',icon: <EuiIcon type="document" />,},{label: 'level2 folder',id: 'item_b',icon: <EuiIcon type="folderOpen" />,iconWhenExpanded: <EuiIcon type="folderOpen" />,children: [{label: 'monosodium_glutammate.md',id: 'item_cloud',icon: <EuiIcon type="document" />,},{label: "cobalt.md",id: 'item_bug',icon: <EuiIcon type="document" />,callback: showAlert,},],},{label: 'xxxxx folder',id: 'item_c',icon: <EuiIcon type="folderOpen" />,iconWhenExpanded: <EuiIcon type="folderOpen" />,children: [{label: 'Another Cloud.md',id: 'item_cloud2',icon: <EuiIcon type="document" />,},{label:'elastic_link.md',id: 'item_bug2',icon: <EuiIcon type="document" />,callback: showAlert,},],},],},{label: 'othter',id: 'src2',icon: <EuiIcon type="folderClosed" />,iconWhenExpanded: <EuiIcon type="folderOpen" />,isExpanded: true,},];return (<div style={{ width: '20rem' }}><EuiTreeView items={items} aria-label="eui-markdown-editor" /></div>);
};
// markdown-editor.js
import React, { useCallback, useState } from 'react';import {EuiMarkdownEditor,EuiSpacer,EuiCodeBlock,EuiButtonToggle,
} from '@elastic/eui';const initialContent = `## Hello world!Basic "github flavored" markdown will work as you'd expect.The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.- [ ] Checkboxes
- [x] Can be filled
- [ ] Or empty
`;const dropHandlers = [{supportedFiles: ['.jpg', '.jpeg'],accepts: itemType => itemType === 'image/jpeg',getFormattingForItem: item => {// fake an uploadreturn new Promise(resolve => {setTimeout(() => {const url = URL.createObjectURL(item);resolve({text: `![${item.name}](${url})`,config: { block: true },});}, 1000);});},},
];export default () => {const [value, setValue] = useState(initialContent);const [messages, setMessages] = useState([]);const [ast, setAst] = useState(null);const [isAstShowing, setIsAstShowing] = useState(false);const onParse = useCallback((err, { messages, ast }) => {setMessages(err ? [err] : messages);setAst(JSON.stringify(ast, null, 2));}, []);return (<><EuiMarkdownEditoraria-label="EUI markdown editor demo"value={value}onChange={setValue}height={400}onParse={onParse}errors={messages}dropHandlers={dropHandlers}/>{isAstShowing && <EuiCodeBlock language="json">{ast}</EuiCodeBlock>}</>);
};
// page.js
import React from 'react';import {EuiPage,EuiPageBody,EuiPageContent,EuiPageContentBody,EuiPageContentHeader,EuiPageContentHeaderSection,EuiPageHeader,EuiPageSideBar,
} from '@elastic/eui';import FileNav from './file-nav';
import MarkdownEditor from './markdown-editor';
import Tabs from './tabs';export default () => (<EuiPage><EuiPageSideBar><FileNav /></EuiPageSideBar><EuiPageBody component="div"><EuiPageHeader></EuiPageHeader><EuiPageContent><EuiPageContentHeader><EuiPageContentHeaderSection><Tabs /></EuiPageContentHeaderSection><EuiPageContentHeaderSection></EuiPageContentHeaderSection></EuiPageContentHeader><EuiPageContentBody><MarkdownEditor /></EuiPageContentBody></EuiPageContent></EuiPageBody></EuiPage>
);
// tabs.js
import React, { useState, Fragment } from 'react';import {EuiIcon,EuiTabs,EuiTab,EuiSpacer,
} from '@elastic/eui';const tabs = [{id: 'cobalt',name: (<span>cobalt.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'dextrose',name: (<span>dextrose.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'hydrogen',name: (<span>Hydrogen&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'monosodium_glutammate',name: (<span>monosodium_glutammate.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'elastic_link',name: (<span>elastic_link.md&nbsp;<span onClick={e =>{alert('close me?')}}><EuiIcon type="cross" /></span></span>),disabled: false,},
];export default () => {const [selectedTabId, setSelectedTabId] = useState('cobalt');const onSelectedTabChanged = id => {setSelectedTabId(id);};const renderTabs = () => {return tabs.map((tab, index) => (<EuiTab{...(tab.href && { href: tab.href, target: '_blank' })}onClick={() => onSelectedTabChanged(tab.id)}isSelected={tab.id === selectedTabId}disabled={tab.disabled}key={index}>{tab.name}</EuiTab>));};return (<Fragment><EuiTabs size="s">{renderTabs()}</EuiTabs></Fragment>);
};
// index.js
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
import "@elastic/eui/dist/eui_theme_light.css";import Editor from './page';ReactDOM.render(<Editor />,document.getElementById('content')
);

好了,到这里,我们的架子已经搭起来了,我们接下来就需要为他们注入灵魂,让各个组件之间互动起来。例如我们希望点击不同的文件标签,编辑框显示的是不同的文件内容。

3.2. 关联UI

点击不同的文件标签让编辑框显示不同内容主要涉及的就是UI如何更新自己的状态或者UI如何通知别的UI更新其状态。在React中,UI被前面提到的类组件(Class Component)以及函数组件(Function Component)分为一个个独立、可复用的模块。类组件和函数组件的形式分别如下:

// Class components
class ClazzComponent extends React.Component {constructor(props) {super(props);this.state = {};}render() {return <h1>Hello, {this.props.name}</h1>;}
}// Function Components
function FuncComponent(props) {return <h1>Hello, {props.name}</h1>;
}

从显示效果上看,它们是一完全一样的。类组件相对复杂但是拥有更多的特性,例如类组件就有一个state字典,通过setState方法类组件可以更新state的内容,一旦state改变了,那么直接或者间接使用state的React元素(React Elements)就会被更新。例如我们把上面的ClazzComponent做下修改:

// Class components
export default class ClazzComponent extends React.Component {constructor(props) {super(props);this.state = {what: 'World',};}render() {return <h1 onClick={e => this.setState({what: this.state.what + ' World'})}>Hello, {this.state.what}</h1>;}
}

当我们点击界面上的Hello, World,它就会变成Hello, World WorldHello, World World World等。函数组件相比于类组件最大的区别就是很多类组件有的特性它没有,例如它就没有state这个成员以及setState方法,因为理论上它是一个函数(至于说在JavaScript中“万物皆对象”,方法其实也是个对象,那不是本文关注的重点)。但是,在React中通过一些钩子函数,就能让函数组件具有类组件的一些特性,例如“state”。

让stateless的方法函数变得state,可以通过useState这个钩子函数。例如,我们把上面的示例也做下修改:

// Function Components
import React, {useState} from 'react';function FuncComponent(props) {const [name, setName] = useState('World');return <h1 onClick={e => setName(name + ' World')}>Hello, {name}</h1>;
}

经过修改,函数组价的的表现也和类组件一样了。首先,我们从React模块中导入useState这个钩子,然后我们在函数组件中通过它获得了一个厨师长name已经更新方法setName,这里namesetName可以是任意的名字。

知道了如何更新组件的状态,接下来我们就能着手进行我们的Markdown编辑器的编码了。篇幅有限,我们搞得简单点。主要分以下三步:

  1. 首先,我们定义一个回调函数,将这个回调函数注册到tabs.js的函数组件中;
  2. 然后,当tabs的标签有改变的时候,tabs调用我们注册的回调函数,并将被选中的tab的id传给我们回调函数,这样我们就能知道当前那个标签被选中了;
  3. 最后,在回调函数中,我们通过判断id知道用户希望显示的内容,通过useState导出的setContent方法通知Markdown编辑器控件更改其显示的内容。

具体代码如下所示,我们只更改了page.js, markdown-editor.j, tabs.js,其他代码保持不变:

// page.js
import React, {useState} from 'react';import {EuiPage,EuiPageBody,EuiPageContent,EuiPageContentBody,EuiPageContentHeader,EuiPageContentHeaderSection,EuiPageHeader,EuiPageSideBar,
} from '@elastic/eui';import FileNav from './file-nav';
import MarkdownEditor from './markdown-editor';
import Tabs from './tabs';
import tabs from './tabs';const tab1Content = `## Hello world!Basic "github flavored" markdown will work as you'd expect.The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.- [ ] Checkboxes
- [x] Can be filled
- [ ] Or empty
`;const tab2Content = `## I am tab two, name , not Tattoo!
#### I am tab two, not Tattoo!
`; const tab3Content = `
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).## Available ScriptsIn the project directory, you can run:### \`npm start\`Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.The page will reload if you make edits.<br />
You will also see any lint errors in the console.
`;export default () => {const [content, setContent] = useState('init content')let tabSelected = (tabId) => {if (tabId == 'cobalt')setContent(tab1Content);else if (tabId == 'dextrose')setContent(tab2Content);else if (tabId == 'hydrogen')setContent(tab3Content);}return (<EuiPage><EuiPageSideBar><FileNav /></EuiPageSideBar><EuiPageBody component="div"><EuiPageHeader></EuiPageHeader><EuiPageContent><EuiPageContentHeader><EuiPageContentHeaderSection><Tabs onTabSelected={tabSelected}/></EuiPageContentHeaderSection><EuiPageContentHeaderSection></EuiPageContentHeaderSection></EuiPageContentHeader><EuiPageContentBody><MarkdownEditor content={content}/></EuiPageContentBody></EuiPageContent></EuiPageBody></EuiPage>);
};
//markdown-editor.js
import React, { useCallback, useState } from 'react';import {EuiMarkdownEditor,EuiSpacer,EuiCodeBlock,EuiButtonToggle,
} from '@elastic/eui';const dropHandlers = [{supportedFiles: ['.jpg', '.jpeg'],accepts: itemType => itemType === 'image/jpeg',getFormattingForItem: item => {// fake an uploadreturn new Promise(resolve => {setTimeout(() => {const url = URL.createObjectURL(item);resolve({text: `![${item.name}](${url})`,config: { block: true },});}, 1000);});},},
];export default (props) => {const [value, setValue] = useState(props.content);const [messages, setMessages] = useState([]);const [ast, setAst] = useState(null);const [isAstShowing, setIsAstShowing] = useState(false);const onParse = useCallback((err, { messages, ast }) => {setMessages(err ? [err] : messages);setAst(JSON.stringify(ast, null, 2));}, []);return (<><EuiMarkdownEditoraria-label="EUI markdown editor demo"value={props.content}onChange={setValue}height={400}onParse={onParse}errors={messages}dropHandlers={dropHandlers}/>{isAstShowing && <EuiCodeBlock language="json">{ast}</EuiCodeBlock>}</>);
};
// tabs.js
import React, { useState, Fragment } from 'react';import {EuiIcon,EuiTabs,EuiTab,EuiSpacer,
} from '@elastic/eui';const tabs = [{id: 'cobalt',name: (<span>cobalt.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'dextrose',name: (<span>dextrose.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'hydrogen',name: (<span>Hydrogen&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'monosodium_glutammate',name: (<span>monosodium_glutammate.md&nbsp;<EuiIcon type="cross" /></span>),disabled: false,},{id: 'elastic_link',name: (<span>elastic_link.md&nbsp;<span onClick={e =>{alert('close me?')}}><EuiIcon type="cross" /></span></span>),disabled: false,},
];export default (props) => {const [selectedTabId, setSelectedTabId] = useState('cobalt');const onSelectedTabChanged = id => {setSelectedTabId(id);if (props && props.onTabSelected)props.onTabSelected(id);};const renderTabs = () => {return tabs.map((tab, index) => (<EuiTab{...(tab.href && { href: tab.href, target: '_blank' })}onClick={() => onSelectedTabChanged(tab.id)}isSelected={tab.id === selectedTabId}disabled={tab.disabled}key={index}>{tab.name}</EuiTab>));};return (<Fragment><EuiTabs size="s">{renderTabs()}</EuiTabs></Fragment>);
};

这样,我们点击不同的标签就能看到不同的内容了。

4. 托管

想要将我们的应用托管在GitHub,需要做一下几步:

  1. 执行npm build命令进行编译;
  2. 然后在将远程仓库中的gh-pages拉取到本地;
  3. 清空gh-pages分支;
  4. 对编译出的文件做些调整,因为编译的路径如果不做修改很多文件提示找不到;
  5. 将修改后的文件复制到gh-pages目录;
  6. 提交。

整个过程的命令如下(Linux下),唯一需要修改的就是仓库地址:

# 一下内容位于 my-app/Makefile
publish_github_pages:rm -rf ./buildrm -rf ./gh-pagesnpm run buildgit clone --depth=1 https://github.com/SunnyZhou-1024/eui-markdown-editor.git --branch gh-pages ./gh-pages 2>&1 > /dev/nullrm -rf ./gh-pages/*cp -R ./build/* ./gh-pages/sed -i -e "s/\/static/.\/static/g" ./gh-pages/index.htmlsed -i -e "s/\/favi/.\/favi/g" ./gh-pages/index.htmlsed -i -e "s/\/logo/.\/logo/g" ./gh-pages/index.htmlsed -i -e "s/\/mani/.\/mani/g" ./gh-pages/index.htmlgit -C ./gh-pages add --allgit -C ./gh-pages commit --amend --no-editgit -C ./gh-pages push --force origin gh-pages

5. 总结

由于篇幅所限,在本文例子中,只选取了一些关键的点来讲解,主要讲解的是从如何搭建一个React App以及React 元素如何更新,到将其部署到GitHub的整个流程。具体代码逻辑可能和一个真正的编辑器有很大出入,并且非常不完善,例如显示的内容应该来自文件而不是硬编码。

本文的目的并不是讲解如何写出漂亮的UI,这不是我擅长的;也不是深入的讲解React,这一部分我觉得React官网的文档已经非常完善了;更不是介绍如何使用EUI。本文的只是想以EUI为例,介绍如何通过现有的UI框架、工具链构建起一个可用,也还看得过去的网页应用。

本文例子代码位于个人GitHub仓库:https://github.com/zmychou/eui-markdown-editor

首发于个人微信公众号TensorBoy。微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取更多最新文章!
C++ | Python | Linux | 原理 | 源码,有一起玩耍的么?

6. References

[1] https://reactjs.org/docs/getting-started.html
[2] https://create-react-app.dev/docs/getting-started/
[3] https://elastic.github.io/eui/#/

使用Node.js+React+EUI快速搭建网页应用相关推荐

  1. node JS 之web服务器搭建,续

    本笔者之前发表了一篇关于如何通过Node JS运行环境运行express插件实现搭建简易的web服务器的文章 原文:node JS之web服务器搭建 今天,笔者本人用了点空闲时间,把之前开发的网站服务 ...

  2. node aws 内存溢出_如何使用Node.js和AWS快速创建无服务器RESTful API

    node aws 内存溢出 by Mark Hopson 马克·霍普森(Mark Hopson) 如何使用Node.js和AWS快速创建无服务器RESTful API (How to quickly ...

  3. 《Node.js入门》Windows 7下Node.js Web开发环境搭建笔记

    最近想尝试一下在IBM Bluemix上使用Node.js创建Web应用程序,所以需要在本地搭建Node.js Web的开发测试环境. 这里讲的是Windows下的搭建方法,使用CentOS 的小伙伴 ...

  4. Node.js + React + MongoDB 实现 TodoList 单页应用

    之前用 Ant Design 开发了一个项目,因此对 React 的特性有了一定的了解,React 使用封装组件的思想,组件各自维护自己的状态和 UI, 组件之间通过 props 传递数据和方法.当状 ...

  5. node.js 爬虫 实现爬取网页图片并保存到本地

    node.js 爬虫 实现爬取网页图片并保存到本地 没有废话直接看代码 /*** 请求网站数据* 将数据保存本地文件*/ //不同协议引用不同模块,http https const http = re ...

  6. [译]简单得不得了的教程-一步一步用 NODE.JS, EXPRESS, JADE, MONGODB 搭建一个网站

    原文: http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ 原文的源代码在此 太多的教程教你些一个Hello, World!了, ...

  7. 一个基于Node.js的本地快速测试服务器

    local-web-test 一个本地的基于node.js的测试服务器 支持快速的启动web容器服务 支持快速的发送get,post请求,并记录在/log文件夹下,并在控制台输出 支持一个函数完成co ...

  8. node.js博客GitHub搭建(hexo)

    教程参考官网提供的: https://hexo.io/zh-cn/ 教程: https://hexo.io/zh-cn/docs/ 我的node.js环境: hexo博客全程采用markdown进行编 ...

  9. Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件)...

    什么是Node.js?还服务器端javascript?对于这个概念我在这篇文章不做解释,可以自己去搜索了解下,服务器端js不是新技术,只是最近的node.js的火爆让他爆发了,我会在以后的文章里解释什 ...

最新文章

  1. linux格式化分区进程,linux磁盘分区格式化
  2. 梯度下降 最小二乘法 matlab,最小二乘法和梯度下降法的理解
  3. java异常处理图片_Java处理图片时出现异常
  4. Form表单标签的Enctype属性的编码格类型
  5. readonly的用法
  6. 波利亚名著《怎样解题》笔记:四步解题法
  7. mmap函数_Linux中的mmap映射 [二]
  8. [5-24]绿色精品软件每天更新[uc23整理]
  9. matlab开关占空比,高手指导 详解开关电源占空比选择与计算
  10. 联通服务器信号设置,联通手机服务器设置
  11. 春江花朝秋月夜,往往取酒还独倾——python函数进阶
  12. java 6面骰子_掷6面骰子6000次每个点数出现的概率
  13. 多节点Linux部署
  14. 浅谈虚拟机的垃圾回收
  15. YOLOv4 介绍及其模型优化方法
  16. (十二)Linux设置定时任务及开机自启动
  17. 这个世界,没有传奇(一)——挺住,就是一切
  18. 易语言让我东山再起 邓学彬(优秀文章)
  19. CAD怎么打出这种竖排文字
  20. Vscode——远程开发(Windows为主机,Ubuntu为服务器)

热门文章

  1. 各种消除眼袋的小方法
  2. 代码审计--Fortify 如何定位中间文件
  3. js代码 父页面调用子页面中的js方法,子页面调用父页面中的js方法
  4. java代码默认保存在哪里,java创建程序时程序保存在什么地方
  5. redis+mysql
  6. 机器人公敌过获奖没有_《机械公敌》观后感(学生作业)
  7. hypermesh中带有孔洞的长方体的网格划分
  8. 13基于双层优化的电动汽车日前-实时两阶段市场竞标
  9. Java毕设项目葡萄酒销售管理系统(java+VUE+Mybatis+Maven+Mysql)
  10. 2021年全球出租车调度软件收入大约402.3百万美元,预计2028年达到1189.3百万美元,2022至2028期间,年复合增长率CAGR为18.2%