考勤打卡系统技术复盘

考勤打卡系统技术复盘

lodash 的按需引入(vue cli 3.0)

安装

1
2
npm i --save lodash
npm i --save-dev lodash-webpack-plugin babel-plugin-lodash

babel.config.js

1
2
3
module.exports = {
plugins: ["lodash"],
};

vue.config.js

1
2
3
4
5
6
const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");
module.exports = {
chainWebpack: (config) => {
config.plugin("loadshReplace").use(new LodashModuleReplacementPlugin());
},
};

使用

1
import { debounce } from "lodash";

打包清除注释

vue.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
...
configureWebpack: {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false
},
compress: {
pure_funcs: ['console.log']
}
},
extractComments: false
})
]
}
},
...
}

使用 patch-package 修改 node_modules

安装

1
npm i patch-package --save-dev

package.json

1
2
3
"scripts": {
"postinstall": "patch-package"
}

修改 node_modules 里面的代码,然后执行命令

1
npx patch-package 包名

实现原理注意事项

2023-11-09 更新

  • 如果项目使用 pnpm 的话,patch-package 不能使用,可以使用 pnpm patch 和 pnpm commit-patch 给依赖打补丁
  • 使用pnpm-patch-i,确保 pnpm>=v7.11.0

vuex 持久化

vuex-persistedstate

使用说明

1
2
3
4
5
6
7
8
9
10
import createPersistedState from "vuex-persistedstate";

export default new Vuex.Store({
plugins: [
createPersistedState({
key: "vuex", // storge里的键值名
storage: window.sessionStorage,
}),
],
});

注意: commit 操作才会触发

动态注册自定义组件

components/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 处理首字母大写 dx => Dx
function changeStr(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/*
require.context(arg1,arg2,arg3)
arg1 - 读取文件的路径
arg2 - 是否遍历文件的子目录
arg3 - 匹配文件的正则
关于这个Api的用法,可以去查阅一下,用途也比较广泛
*/
const requireComponent = require.context("../components", false, /\.vue$/);
const component = {
install(Vue) {
requireComponent.keys().forEach((fileName) => {
const config = requireComponent(fileName);
// ./test.vue => test
const componentName = changeStr(
fileName.replace(/^\.\//, "").replace(/\.\w+$/, "")
);
// 动态注册该目录下的所有.vue文件
Vue.component(componentName, config.default || config);
});
},
};
export default component;

main.js

1
2
import component from "./components";
Vue.use(component);

axios 拦截器使用

  1. 请求头里添加 userid
  2. 请求结果统一处理
  3. cancelToken 取消请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import axios, { AxiosRequestConfig } from "axios";

const CancelToken = axios.CancelToken;

const service = axios.create({
baseURL: "/api",
});
service.interceptors.request.use(
(config: AxiosRequestConfig & { daemon?: any }) => {
if (!config["daemon"]) {
// 存储非守护请求的取消函数
config.cancelToken = new CancelToken((cancel) => {
(store.state.requestCancels as any[]).push(cancel);
});
}
config.headers["userId"] = tools.encrypt(store.getters.userId);
return config;
},
(error) => Promise.reject(error)
);
service.interceptors.response.use(
(res) => {
if (res.data.code === "200" && res.data.success) {
return res;
} else {
return Promise.reject(res.data.message);
}
},
(error) => Promise.reject(error)
);
export default service;
// 取消请求
export const cancelRequests = () => {
store.state.requestCancels.forEach((cancel: any) => {
cancel && cancel();
});
store.state.requestCancels = [];
};

支持 typescript

  1. 组件自定义属性需要在.d.ts 文件中扩展 ComponentOptions
  2. vue 原型上添加方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from "vue";
import { ConfigType, Dayjs, OptionType } from "dayjs";

declare module "vue/types/options" {
interface ComponentOptions<V extends Vue> {
[propName: string]: any;
}
}

type $dayjs = (date?: ConfigType, format?: OptionType) => Dayjs;

declare module "vue/types/vue" {
interface Vue {
$dayjs: $dayjs;
}
}

使用第三方库,没有类型声明文件报错处理

1
declare module "vue-hash-calendar";

支持 jsx

安装

1
npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props --save

babel.config.js

1
2
3
4
5
6
7
8
9
10
module.exports = {
presets: [
[
"@vue/babel-preset-jsx",
{
compositionAPI: true,
},
],
],
};

key 重新渲染组件

通过改变组件的 key 值,重新执行组件的生命周期

锁定 node 版本、包管理器

锁定 node 版本

  1. 通过在 package.json 中指定 engines 字段,可限定项目使用的 node 版本。
  2. 在项目根目录下的 .npmrc 文件中添加 engine-strict = true
1
2
3
4
// package.json
"engines": {
"node": "14.x || 16.x"
},

锁定包管理器

  1. npm install -D only-allow
  2. 在 package.json 文件中进行配置 scripts.preinstall , 允许输入的值 only-allow npm、only-allow pnpm、only-allow yarn
1
2
3
4
// package.json
"scripts": {
"preinstall": "only-allow npm",
}

node 管理工具

还在用 nvm 做 node 管理工具?快来试试 Volta 吧!

2014-03-16 更新

npm 使用踩坑

1
2
3
4
5
6
# 根据 package-lock.json 生成 pnpm-lock.yaml
pnpm import package-lock.json

# 安装依赖报证书错误
npm config set strict-ssl false
npm cache clean --force

vue.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const productionGzipExtensions = ["js", "css"];
const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const IS_PROD = process.env.NODE_ENV === "production";
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");

module.exports = {
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
// publicPath: '/dist/',
publicPath: "./",
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
outputDir: "dist",
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
// assetsDir: 'static',
lintOnSave: false,
productionSourceMap: false,
configureWebpack: (config) => {
if (IS_PROD) {
config.output.filename = "js/[name].[hash:8].js";
config.output.chunkFilename = "js/[name].[hash:8].js";
// 开启gzip压缩
config.plugins.push(
new CompressionWebpackPlugin({
filename: (info) => {
return `${info.path}.gz${info.query}`;
},
algorithm: "gzip",
threshold: 10240, // 只有大小大于该值的资源会被处理 10240
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
deleteOriginalAssets: false, // 删除原文件
})
);
config.plugins.push(new HardSourceWebpackPlugin()); // 2023.7.31补充,会导致打包修改代码不能正常更新
config.plugins.push(new SpeedMeasurePlugin());
}
config.optimization = {
// 分割代码块
splitChunks: {
cacheGroups: {
//公用模块抽离
common: {
chunks: "initial",
minSize: 0, //大于0个字节
minChunks: 2, //抽离公共代码时,这个代码块最小被引用的次数
},
//第三方库抽离
vendor: {
priority: 1, //权重
test: /node_modules/,
chunks: "initial",
minSize: 0, //大于0个字节
minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
},
},
},
// 删除注释
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
extractComments: false,
}),
],
};
//关闭 webpack 的性能提示
config.performance = {
hints: false,
};
},
chainWebpack: (config) => {
// 移除 prefetch 插件
config.plugins.delete("prefetch");
// 移除 preload 插件
config.plugins.delete("preload");
// 修复HMR
config.resolve.symlinks(true);

config.optimization.minimize(true);
config.optimization.splitChunks({
chunks: "all",
});

/* config
.plugin("webpack-bundle-analyzer")
.use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin); */

if (IS_PROD) {
const miniCssExtractPlugin = new MiniCssExtractPlugin({
filename: "css/[name].[hash:8].css",
chunkFilename: "css/[name].[hash:8].css",
});
config.plugin("extract-css").use(miniCssExtractPlugin);
config.plugin("loadshReplace").use(new LodashModuleReplacementPlugin());

config.module
.rule("images")
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use("image-webpack-loader")
.loader("image-webpack-loader")
.options({
bypassOnDebug: true,
})
.end()
.use("url-loader")
.loader("file-loader")
.options({
name: "img/[name].[hash:8].[ext]",
})
.end();
config.module
.rule("svg")
.test(/\.(svg)(\?.*)?$/)
.use("file-loader")
.loader("file-loader")
.options({
name: "img/[name].[hash:8].[ext]",
});
}
},
css: {
loaderOptions: {
postcss: {
plugins: [
require("postcss-pxtorem")({
rootValue: 37.5, // 换算的基数
propList: ["*"],
}),
],
},
},
// 是否使用css分离插件 ExtractTextPlugin
extract: IS_PROD,
// 开启 CSS source maps?
sourceMap: false,
// 启用 CSS modules for all css / pre-processor files.
modules: false,
},
runtimeCompiler: true,
devServer: {
port: 8071,
open: false, //配置自动启动浏览器
proxy: {
"/api-gateway": {
target: "https://workin.hanweb.com",
changeOrigin: true,
ws: true,
},
"/jpaas-jsam-api-server": {
target: "http://192.168.83.44:8936",
changeOrigin: true,
ws: true,
},
"/jpaas-j-im-web-server": {
target: "http://192.168.20.34:83",
changeOrigin: true,
ws: true,
},
"/jpaas-jetable-server": {
target: "http://192.168.83.221:8970",
changeOrigin: true,
ws: true,
},
},
},
};