Skip to content

1.构建 express 项目

(一)安装包

(1)npm init -y

npm init -y" 命令用于初始化一个新的 Node.js 项目,并生成一个默认的 package.json 文件。

"npm init" 命令用于设置一个新的 Node.js 项目,并创建一个 package.json 文件,该文件是一个包含项目信息的元数据文件,如项目名称、版本、依赖项和其他配置细节。

"-y" 标志是 "--yes" 的简写,用于自动回答在初始化过程中通常出现的所有提示,这样可以快速生成一个默认的 package.json 文件,而无需手动输入。

(2)npm install express

(3)npm i mysql -s 用于连接 mysql

(二)请求路由的策略

路由分路径的策略:

引入路由:var route = express.Router();

注册某个路由:app.use("/post", post);

一般采用路由分包利用 index.js创建统一入口函数,然后引入app.js的策略

image-20230621110336223

使用import和 require 都可以

路由接收前端 request参数的策略:

分为params和 body 两种

route.put("/:id", (req, res) => {

let obj = req.body; //请求体中的数据用 body 接收

const id = req.params.id; //:id这种路径参数用 params 接收

})

(三)mysql数据库操作的策略

一般采用数据库连接池的策略:

使用连接池管理数据库连接,而不是每次请求都创建和释放连接。这可以提高数据库的性能和资源利用率。

js
const mysql = require("mysql");
const pool = mysql.createPool({
  connectionLimit: 10,
  host: "47.98.138.0",
  user: "todolist",
  password: "cAS5GtRc2RtkR5tE",
  database: "todolist", //数据库名称!(不是表名)
});

module.exports = pool;

可以检测连接是否成功:pool.getConnection((err, connection) => {})

如果查询结果较大,考虑使用分页来减少一次性返回大量数据的压力

数据库操作的传参策略:

(1)? 占位传参

js
pool.query(
       "SELECT * FROM data WHERE a = ? AND b = ?",
  			[a,b]
        function (error, results) {
           if (error) {
             reject(error);
           } else {
             resolve(results);
           }
        }
);

第一个参数为 sql,第二个为?对应的参数(一个直接写,多个写在数组里面),第三个为操作完成的回调函数

(2)字符串拼接传参 ——> 不推荐,容易 sql 注入!

js
pool.query(
       "SELECT * FROM data WHERE a =" + a +"AND b =" + b,
        function (error, results) {
           if (error) {
             reject(error);
           } else {
             resolve(results);
           }
        }
);

使用 promise 封装使用数据库连接池进行 sql 操作的统一方法:

executeQuery.js

js
const pool = require("./pool.js");
//下面用于检查数据库是否连接成功:可以省略
pool.getConnection((err, connection) => {
  if (err) {
    console.error("Error connecting to database:", err);
    return;
  }

  connection.query("SELECT 1", (err, result) => {
    connection.release(); // 释放连接

    if (err) {
      console.error("Error executing query:", err);
      return;
    }

    console.log("Database connection successful");
  });
});
//下面用于封装公共的连接池连接和请求方法
function executeQuery(query, params) {
  //接受请求链接参数,和 params 参数
  //返回一个 promise 对象,可以.then继续处理(其实这里也算是高阶函数+函数柯里化了)
  //好处:统一处理连接检测,统一处理连接释放
  return new Promise((resolve, reject) => {
    pool.getConnection((err, connection) => {
      if (err) {
        console.error("Error getting connection from pool:", err);
        reject(err); //连接失败的检测
        return;
      }
			//这里执行基本的数据库操作
      connection.query(query, params, (error, results) => {
        connection.release(); // 统一释放连接,很方便

        if (error) {
          console.error("Error executing query:", error);
          reject(error); //请求失败的检测
          return;
        }

        resolve(results); //请求成功,获得数据
      });
    });
  });
}

module.exports = executeQuery;

使用这个封装的promise方法:直接.then即可,或者用 try-catch + await-async 也是可以的

js
const query2 = "update data set is_did=1 where id=?";
executeQuery(query2, id)
  .then((results) => {
  //受影响的行数
  if (results.affectedRows >= 1) {
    res.send({
      code: 0,
    });
  }
})
  .catch((error) => {
  console.error("Error retrieving data:", error);
  res.status(500).send({
    code: -1,
    message: "Error retrieving data",
  });
});

请求回来的结果 results:

如果是更新某个字段,或者插入等操作:会返回受影响的行数:results.affectedRows

如果是查询的操作,会返回数组:如果只查了一个数据的某个字段,那么就results[0].属性名

(四)业务场景的总结

1.完成某个任务的接口:因为是把某个待办事项设置为完成/未完成,要分析两种情况,一种是已经是完成,要改成未完成,另一种相反

所以不是简单的根据 id更新 id_did

2.是否全部完成要单独给一个接口,每次初始化都要调用

3.全部完成的接口也是要分析两种情况,也要判断是否全部完成了,从而设置为全部完成/全部未完成

4.所以是否全部完成的接口和全部完成的接口应该调用一个公共方法,这个公共方法可以是 promise 的,以此来返回状态

公共方法:

js
const getAllCompleteFlag = () => {
  return new Promise((resolve, reject) => {
    //看不等于 1 的数量是多少,如果不等于 1 的是 0 的话,那就说明已经全部完成了,那么就应该设置为  0(全都未完成),否则全部设置 为 1
    const query1 = "SELECT COUNT(*) AS count FROM data WHERE is_did <> 1";
    executeQuery(query1)
      .then((results) => {
        //受影响的行数
        if (results[0].count === 0) {
          resolve(1); //全部完成
        } else {
          resolve(0); //没有全部完成
        }
      })
      .catch((error) => {
        resolve(2); //出错了
      });
  });
};

注意:下面这种写法是没有返回值的,不能正常返回数据(promise对象的 then 处理中的return不能作为外层函数的返回值,只能把promise对象包裹为 promise函数 然后里面resolve,这个resolve就是外层函数的返回值)

js
const getAllCompleteFlag = () => {
  //看不等于 1 的数量是多少,如果不等于 1 的是 0 的话,那就说明已经全部完成了,那么就应该设置为  0(全都未完成),否则全部设置 为 1
  const query1 = "SELECT COUNT(*) AS count FROM data WHERE is_did <> 1";
  executeQuery(query1)
    .then((results) => {
      console.log(results);
      //受影响的行数
      if (results[0].count === 0) {
        return 1; //全部完成
      } else {
        return 0; //没有全部完成
      }
    })
    .catch((error) => {
      return 2; //出错了
    });
};

因为它使用了异步操作(使用了 executeQuery 并返回一个 Promise 对象)。

由于异步操作的特性,函数无法立即返回结果(所以异步函数的 then 再return 已经来不及了,getAllCompleteFlag函数早就执行完了,所以收不到返回值,相当于没有任何 return)。在异步操作完成后,通过 Promise 的 thencatch 方法处理结果或错误。

如果你想获取 getAllCompleteFlag 函数的返回值,可以将其修改为返回一个 Promise 对象,并在调用该函数时使用 then 方法来获取结果。

(五)数据类型问题

1.如果数据库中的列是 datetime 类型,而你使用 Express 来进行数据存储,通常你应该将日期和时间值存储为 JavaScript 的 Date 对象

new Date();即可

2.在Date 类型进行前后端的数据传输时,会被转换为 string,并且会有精度损失!所以我们要自行进行 toString 转换,再进行传值

(1)所以前端要进行格式化的时候:要把 string 变为 Date

const timeString = "2023-06-20T10:30:00"; // 示例时间字符串 const time = new Date(timeString);

(2)前端在向后端传送 Date 对象的时候:要转换为 string

const date = new Date();

const dateString = date.toString();

(3)后端查出来的 Date 对象,可以用toISOString()转换为字符串

在 JavaScript 中,将日期对象转换为字符串时,使用默认的 toString() 方法会将日期对象转换为本地时区的日期和时间字符串。这可能会导致在显示时存在时区偏移,导致日期看起来比实际日期多一天。

为了避免这个问题,可以使用 toISOString() 方法将日期对象转换为 ISO 8601 格式的字符串,该格式表示日期和时间的标准。这样做可以确保在不同时区之间保持一致,并且不会引入时区偏移。

用date.toISOString()

(六)配置跨域

使用 cors 插件:npm i cors

js
const cors = require("cors");

//配置跨域,必须写在所有路由前面
//解决跨域的插件
app.use(cors());

直接配置:

js
//配置跨域,必须写在所有路由前面
app.all("*", function (req, res, next) {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  next();
});

2.vue2 中直接修改对象属性不会响应,需要使用$set方法

3.本地项目与一个新仓库(某个特定分支)进行关联并推送的步骤

(一)最完整的流程

1.如果本地项目已经推送到过其他的仓库,那么就把文件复制到新的文件夹再进行推送!

一定不要用之前推送过到其他仓库的文件进行直接推送

要新建一个新的空白文件夹,把前面的项目所有文件拷贝过来,然后再进行推送

2.非常需要注意:一定要删除所有的.git文件,将所有提交记录清除,否则之前提交过的文件本次就提交不了了,导致文件提交推送的不全!

mac 中要用ls -a才可以看到 .git文件,执行以下命令删除 .git 文件夹:rm -rf .git

3.然后进行初始化 git init

4.然后切换本地的分支

git checkout -b 本地分支

5.如果本地和远程没有关联先用命令关联:(其实只要在 push 之前设置了即可)

移除与原仓库相关的远程仓库地址:

bash
git remote remove origin

查看关联的远程链接:git remote -v

image-20230621100820282

新增关联:

git remote add origin <新仓库的远程仓库地址>

git remote add origin http://gitlab.yx/general/react-manage-demo.git

注意:有时可能要用这个域名 git remote add origin http://gitlab.classba.com.cn/general/react-manage-demo.git

如果 vpn 不稳定的时候,这个域名可能更快

6.提交文件:

git add .

git commit -m “first commit”

这里一定要注意观察一下,提交的文件是否正确,是否有没有提交上去的文件

7.如果远程的空仓库只有一个 readme 文件(或者其他文件,如果有冲突的话推荐用 idea),那么提交之前一定要先拉取

直接拉取如果不行的话就用:

git pull origin 远程分支 --allow-unrelated-histories

git pull origin dev3 --allow-unrelated-histories

命令介绍:因为本地仓库没有接触过这个仓库,所以文件的历史没有任何相同的,必须用这个命令

git pull origin <远程分支> --allow-unrelated-histories 是一个 Git 命令,用于将远程仓库的更改合并到本地分支。--allow-unrelated-histories 参数是为了允许合并没有共同历史的两个分支。

当执行 git pull 命令时,Git 会尝试自动合并远程分支和本地分支的更改。然而,如果两个分支的提交历史没有共同的祖先,Git 默认会拒绝合并,因为它无法确定如何自动合并这些不相关的历史。这时需要使用 --allow-unrelated-histories 参数来告诉 Git 允许合并没有共同历史的分支。

注意:git pull origin 后面跟着的是远程分支名字

例如:远程仓库 originmain 分支 应该是 origin main

注意:origin 只是一个默认的远程仓库名称,对应于 origin 这个远程仓库名称,默认情况下,它会在本地创建一个名为 origin 的远程仓库引用。这个本地引用充当了与远程仓库通信的桥梁。

git remote show 命令来查看本地仓库的远程引用列表:

image-20230621102616030

8.有可能推送的时候需要用户名密码:

Username haowenhai

Password T5oRLaqylgQp

9.git push -u origin <本地分支名称>:<远程分支名称>

git push -u origin dev3:dev3

命令介绍:用于将本地分支的提交推送到远程仓库中的指定分支

  • git push: 将本地的提交推送到远程仓库。
  • -u origin: -u 参数用于将本地分支与指定的远程仓库(origin)关联起来,创建一个跟踪关系,以后可以直接使用 git push 来推送代码,无需再指定远程仓库和分支。
  • <本地分支名称>: 指定要推送的本地分支的名称。
  • :<远程分支名称>: 指定将本地分支推送到远程仓库的哪个分支,可以是已存在的分支,也可以是新的分支名称。

注意:如果本地分支名称和远程一样,那么可以不用写<本地分支名称>:<远程分支名称>,直接写<分支名称>即可

比如git push -u origin master 是将本地的 master 分支的提交推送到远程仓库(通常是名为 origin 的默认远程仓库)的 master 分支,并且建立跟踪关系

git push -u origin dev 是将本地的 dev 分支的提交推送到远程仓库(通常是名为 origin 的默认远程仓库)的 dev 分支,并且建立跟踪关系。

注意:-u 就是建立跟踪关系的意思

git push -u origin dev 命令中,-u--set-upstream 的简写形式。

--set-upstream 选项用于将本地分支与远程分支建立关联。当使用 -u--set-upstream 选项时,Git 会将本地分支推送到远程仓库,并且将远程分支设置为该本地分支的上游(upstream)分支。

(只有第一次推送需要写这个,后面直接 git push 即可,不用加-u origin <分支名称>了)

10.如果以上流程失败了,我们要重来,之前做如下事情:

  • git文件包括了我们所有的提交记录,一旦出了提交的问题,我们应该先把这个文件删掉,再重新开始

  • 可以在远程再新建一个分支,这个分支应该是空的,然后再重新走流程(否则需要覆盖远程的分支内容,或者进行 merge,比较麻烦)

(二)git clone的方法

还有一种方法是先 git clone远程的空项目(可能有一两个文件),然后再把文件该删的删该补的补,弄成我们希望的样子,然后最后再统一提交上去,可能也可以!并且相对简单一些!

(三)远程仓库提交的文件不对,我们不想要了,又不想在远程删除

我们可以用如下方法,把本次提交的完整的正确的内容覆盖远程的所有内容

  1. 执行以下命令,将仓库的 HEAD 重置为初始状态:清空.git文件的所有提交历史,让本地变成没有提交过的,并切换到一个新的分支

    bash
    git checkout --orphan <新分支名>
  2. 执行以下命令,将当前的更改添加到新的分支并进行提交:

    bash
    git commit -m "Initial commit"
  3. 执行以下命令,删除其他分支(除了当前分支):

    bash
    git branch -D <分支名>

    branch_name 替换为你想删除的分支名称,可以多次执行该命令以删除多个分支。

  4. 最后,执行以下命令来强制推送更改到远程仓库:(直接覆盖远程的)

    bash
    git push origin --force --all

    这将覆盖远程仓库的提交记录。

完成以上步骤后,你的仓库将不再包含之前的提交记录,相当于清空了所有的提交历史。请谨慎执行以上操作,确保你已经备份了重要的代码和数据。

4.React 使用 echarts

不要用原生的 echarts,容易有 bug,并且不好用,因为还要操作 dom

而用封装的库不需要操作 dom

安装echarts-for-react

bash
npm install --save echarts-for-react

使用

饼形图:

jsx
import React, { Component } from "react";
import "./index.css";
import ReactECharts from "echarts-for-react";
import { getPieData } from "../../api/analysis";
export default class Pie extends Component {
  state = {
    option: {},
  };
  getData = async () => {
    try {
      //await风格写法
      let res = await getPieData(); //调用方法
      let isDid = res.data[0];
      let isNotDid = res.data[1];
      let data = [
        {
          name: "已完成",
          value: isDid,
        },
        {
          name: "未完成",
          value: isNotDid,
        },
      ];
      this.setState({
        option: {
          title: {
            text: "完成/未完成百分比",
            left: "center",
          },
          tooltip: {
            trigger: "item",
          },
          legend: {
            orient: "vertical",
            left: "left",
          },
          series: [
            {
              name: "Access From",
              type: "pie",
              radius: "50%",
              data: data,
              roseType: "radius",
              emphasis: {
                itemStyle: {
                  shadowBlur: 10,
                  shadowOffsetX: 0,
                  shadowColor: "rgba(0, 0, 0, 0.5)",
                },
              },
              label: {
                show: true,
                formatter: function (e) {
                  return (
                    ((e.value / (isDid + isNotDid)) * 100).toFixed(1) + "%"
                  );
                },
              },
            },
          ],
        },
      });
    } catch (error) {
      console.log(error.message);
    }
  };
  componentDidMount() {
    this.getData();
  }
  render() {
    return (
      <div className="pie-container">
        <ReactECharts option={this.state.option} />
      </div>
    );
  }
}

5.css 实现放到dom自己身上的时候自己的 after 元素产生变化

css
.element::after {
  content: "After Element";
  /* 初始样式 */
}

.element:hover::after {
  /* 悬停时的样式 */
  /* 在这里定义 ::after 伪元素的样式变化 */
}

案例:实现鼠标放到导航上,下面的出现横条由中间向两边扩张的效果

html
<div className="data-wapper">
  <MyNavLink to="/analysis/line">添加量时间分布</MyNavLink>
  <MyNavLink to="/analysis/pie">完成量总体占比</MyNavLink>
</div>
css
.data-wapper>a {
  width: 200px;
  height: 50px;
  color: #000000;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  transition: all 0.3s linear;
  font-size: 15px;
  position: relative;
}

.data-wapper>a:after {
  width: 11px;
  height: 2px;
  background-color: #646cff;
  content: "";
  /* border: 1px #646cff solid; */
  position: absolute;
  bottom: 0;
  opacity: 0;
  transition: all 0.3s ease;
}

.data-wapper>a:hover {
  color: #535bf2;
}

.data-wapper>a:hover:after {
  opacity: 1;
  transform: scaleX(10); /*关键代码*/
  transform-origin: center; /*关键代码*/
}

6.includes() 方法和 substring()方法

includes

str.includes("Hello")

array.includes(3)

可以判断字符串也可以判断数组是否包含某个元素,会返回布尔值

substring

这个方法是截取字符串的前 n 位,等价于 slice

注意:slice 方法可以用于数组也可以用于字符串截取