Commit 62239d55 authored by wangxuxiao's avatar wangxuxiao
parents 5e4193bd c7bd7c02
Pipeline #15674 canceled with stage
{
"eggHelper.serverPort": 53395
}
\ No newline at end of file
---
title: TypeScript高级类型详解
date: 2022/4/22 17:30:00
tags:
- 前端
- typescript
author: 杨靖
---
## typescript的高级类型详解
当了解到typescript的基础类型外,在开发过程中,为了应对多变的复杂场景,我们需要了解一下typescript的高级类型。所谓高级类型,是typescript为了保证语言的灵活性,所使用的一下**语言特性**。这些特性有助于我们应对复杂多变的开发场景。
<!-- more -->
#### 一、交叉类型
将多个类型合并成一个类型,新的类型将具有所有类型的特性,所以交叉类型特别适用对象混入的场景。
```typescript
interface Dog{
run():void
}
interface Cat{
jump():void
}
let pet:Dog & Cat={
run(){},
jump(){}
}
```
#### 二、联合类型
声明的类型并不确定,可以为多个类型中的一个
```typescript
let a:number| string='111';
//限定变量的取值
let ba:'a' | 'b' | 'c'; //字符串的字面量联合类型
let ca:1|2|3 ; //数字的联合类型
```
对象的联合类型
```typescript
interface Dogs{
run():void
}
interface Cats{
jump():void
}
class dog implements Dogs{ //类实现接口
run(){}
eat(){}
}
class cat implements Cats{
jump(){}
eat(){}
}
enum Master {Boy,Girl};
function getPet(master:Master){
let pet=master===Master.Boy?new dog() : new cat();
//pet被推断为dog和cat的联合类型
//如果一个对象是联合类型,在类型未确定的时候,他就只能访问所有类型的共有成员,所以能访问eat()
pet.eat();
pet.run(); //报错
return pet;
}
```
#### 三、索引类型
```typescript
let obj = {
a: 1,
b: 2,
c: 3
}
// 获取对象中的指定属性的值集合
function getValues(obj: any, keys: string[]) {
return keys.map(key => obj[key])
}
// 抽取指定属性的值
console.log(getValues(obj, ['a','b'])) // [1, 2]
// 抽取obj中没有的属性:
console.log(getValues(obj, ['e','f'])) // [undefined, undefined]
```
虽然obj中并不包含e, f属性,但typescript编译器并未报错。此时使用typescript索引类型,对这种模式做类型约束。
**keyof**
keyof是索引类型查询操作符。
假设T是一个类型,那么keyof T产生的类型是T的属性名称字符串字面量类型构成的联合类型。
特别说明:T是数据类型,并非数据本身。
```typescript
// 定义一个接口Obj含有属性a,b
interface obj {
a: number
b: string
}
// 定义变量key,类型为keyof Obj
let key: keyof obj
```
T是一个类型,T[K] 表示类型T中属性K的类型
```typescript
interface obj {
a: number
b: string
}
// 定义变量key,类型为keyof Obj
let key: keyof obj
let value:obj['a']
```
所以上面的代码可以这样改造
```typescript
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key])
}
let obj = {
a: 1,
b: 2,
c: 3
}
// 抽取指定属性的值
console.log(getValues(obj, ['a','b'])) // [1, 2]
// 抽取obj中没有的属性:
console.log(getValues(obj, ['e','f'])) // [undefined, undefined]
```
#### 四、映射类型
typescript 允许将一个类型映射成另外一个类型
**Readonly**
Readonly是 typescript 内置的泛型接口,可以将一个接口的所有属性映射为只读:
```typescript
// 定义接口Obj
interface Obj {
a: number
b: string
c: boolean
}
// 使用类型别名定义类型ReadonlyObj
type ReadonlyObj = Readonly<Obj> // Readonly是TS内置的泛型接口
```
node_module/typescript/lib/lib.es5.d.ts 中可以看到 typescript 内部是如何实现 Readonly 的
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
```
从源码可以看出Readonly是一个可索引类型的泛型接口
1、索引签名为P in keyof T :
其中keyof T就是一个一个索引类型的查询操作符,表示类型T所有属性的联合类型
2、P in :
相当于执行了一个for in操作,会把变量P依次绑定到T的所有属性上
3、索引签名的返回值就是一个索引访问操作符 : T[P] 这里代表属性P所指定的类型
4、最后再加上Readonly就把所有的属性变成了只读,这就是Readonly的实现原理
**Partial**
将一个接口的所有属性映射为可选:
```typescript
interface Obj {
a: number
b: string
c: boolean
}
type PartialObj = Partial<Obj>
```
**Record**
Record会利用已有的类型,创建新属性的映射类型
```typescript
interface Obj {
a: number
b: string
c: boolean
}
type RecordObj = Record<'x' | 'y', Obj>
```
第一个参数是预定义的新属性,比如x,y
第二个参数就是已知类型
#### 五、条件类型
条件类型是一种由条件表达式所决定的类型 。条件类型使类型具有了不唯一性,同样增加了语言的灵活性
```typescript
T extends U ? X : Y
若类型T可被赋值给类型U,那么结果类型就是X类型,否则就是Y类型
```
```typescript
// 条件类型
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'Function' :
'object'
// 定义类型T1为条件类型,传入参数string,指定t1为string类型
type T1 = TypeName<string> // T1为 string
// 定义类型T2为条件类型,传入参数string[]
type T2 = TypeName<string[]> // T2为 object
```
---
title: webpack 多页面配置
date: 2022/4/21 8:50:20
tags:
- webpack
- 多页面
- 构建工具
author: 蔡海飞
---
基于业务本身考虑,针对企业官网性质、面向C端用户的PC网站。我们需要有良好的搜索体验,需要有良好的页面访问体验。对此,探讨webpack配置多页面开发工程的可能性。本篇文章,默认大家对于webpack已经具有一定的了解,可以完成一些基础的webpack配置操作。如有不清楚的,请[点击查看](https://www.webpackjs.com/)
<!-- more -->
整体配置,我将其拆分为**5**个部分的配置。分别是:
- webpack.config.js
所有配置信息的整合合并配置,和基础的webpack配置。
- config/plugins.js
webpack的所有插件配置信息
- config/proxy.js
将webpack中的代理信息单独拿出来进行配置管理
- config/rules.js
webpack的加载器配置模块独立配置管理
- router.js
这是新增自定义的路由配置,由于自定义路由地址和指定配置路由文件。
### config/rules.js
> 本部分配置,主要是为了给工程提供更多的适配,如ts、less、scss、postcss等功能。
```
/* eslint-disable no-undef */
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const rules = [
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve( __dirname, 'src' )], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require( 'eslint-friendly-formatter' ), // 指定错误报告的格式规范
},
},
{
test: /\.[tj]s?$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.tsx?$/, exclude: /node_modules/, use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
{ test: /\.ts$/, exclude: /node_modules/, use: ['ts-loader'] },
{
test: /\.ejs$/, use: [
{
loader: 'ejs-loader',
options: {
esModule: false,
variable: 'data',
},
},
],
},
{
test: /\.(sa|sc|le|c)ss$/,
use: [
{
// 把js中import导入的样式文件,单独打包成一个css文件,结合html-webpack-plugin,以link的形式插入到html文件中。
loader: MiniCssExtractPlugin.loader,
// options: {
// publicPath: '../',//设置publicPath,解决css文件中background背景图片路径问题
// },
},
// 把js中import导入的样式文件打包到js文件中,运行js文件时,将样式自动插入到<style标签中,style-loader不能和mini-css-extract-plugin同时使用
// 'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
'sass-loader',
],
},
{ test: /\.eot$|\.svg$|\.ttf$|\.woff$/, use: ['url-loader'], type: 'asset/resource', },
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name]_[hash]_[ext]'
},
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
]
module.exports = rules;
```
### config/plugins.js
这部分是用于完成webpack的插件配置工作的,我们这里提供了css合并分立插件、构建清空已有文件,最重要的是实现ejs模块化转html的工作。
```
/* eslint-disable no-undef */
const path = require('path'); // Node.js 处理文件路径的模块
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const glob = require('glob');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); // webpack 5.x 使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // minify extract js to css
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const router = require('./router');
const { routes } = router;
// const autoprefixer = require('autoprefixer');
// webpack.config.js 通用插件配置
const getHtmlWebpackPlugins = () => {
const mode = process.env.TEST_MODE || null;
console.log('get plugin mode:', mode)
if (mode && mode === 'template') {
// 模板测试
return glob.sync(path.resolve(__dirname, '../test/templates') + '/**/*.{jsx,ejs}').map(item => {
var prefixPath = path.resolve(__dirname, '..\/test\/templates');
var filePath = item.substring(prefixPath.length + 1, item.lastIndexOf('.'));
console.log('加载模板路径:', filePath);
return new HTMLWebpackPlugin({
template: item,
filename: filePath + '.html',
minify: {
// 压缩 HTML 文件
removeComments: true, // 移除 HTML 中的注释
collapseWhitespace: true, // 删除空白符与换行符
minifyCSS: true // 压缩内联 css
},
favicon: './config/icon.png',
title: 'title',
chunks: ['common', 'test/'+filePath],
inject: true,
})
});
}
return routes.map(item => {
console.log('loading template info:', JSON.stringify(item));
const filename = item.path.lastIndexOf('\/') === String(item.path).length - 1 ? item.path + 'index.html' : item.path + ".html";
const chunks = ['common'];
if (item.main) {
chunks.push(item.path.substring(1));
}
console.log('get chunks:',chunks)
return new HTMLWebpackPlugin({
template: path.resolve(__dirname, '../src/templates/' + item.template),
filename:filename.substring(1),
minify: {
// 压缩 HTML 文件
removeComments: true, // 移除 HTML 中的注释
collapseWhitespace: true, // 删除空白符与换行符
minifyCSS: true // 压缩内联 css
},
favicon: './config/icon.png',
title: item.name,
chunks,
inject: true,
})
})
}
const plugins = [
new webpack.ProvidePlugin({ // 配置shim预置依赖
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
// In case you imported plugins individually, you must also require them here:
Util: "exports-loader?Util!bootstrap/js/dist/util",
Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
}),
require('autoprefixer'), // autoprefixer可以自动在样式中添加浏览器厂商前缀,避免手动处理样式兼容问题
new MiniCssExtractPlugin({
// 类似 webpackOptions.output里面的配置 可以忽略 css 文件分离
filename: 'css/[name].[chunkhash:8].css',
chunkFilename: '[id].css',
}),
new CssMinimizerPlugin(), // 配置css-minimizer-webpack-plugin
new webpack.DefinePlugin({
'process.env.build_lang': `"${process.env.build_lang}"`,
}),
// 如果不想在 watch 触发增量构建后删除 index.html 文件,可以在 CleanWebpackPlugin 中配置 cleanStaleWebpackAssets 选项 来实现
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
...getHtmlWebpackPlugins(),
new webpack.HotModuleReplacementPlugin(),
]
module.exports = plugins;
```
### config/router.js
这里主要是路由的自定义配置,实现指定页面的指定js依赖的功能。
```
/* eslint-disable no-undef */
// 路由配置文件
const routes = [
{
path: '/index',
template: 'index.ejs',
name: '首页',
},
{
path: '/page/1',
template: 'page/1.ejs',
name: '测试页面',
main: '/pages/test.js',
},
]
module.exports = { routes };
```
### webpack.config.js
```
/* eslint-disable no-undef */
const path = require("path"); // Node.js 处理文件路径的模块
const glob = require("glob");
const plugins = require("./config/plugins"); // 配置插件
const rules = require("./config/rules"); // 加载器
const proxy = require("./config/proxy");
const router = require("./config/router");
const { routes } = router;
/**
* 入口文件
*/
const entries = function () {
const mode = process.env.TEST_MODE || null;
let map = {
common: ["@/app.js", "lodash"],
};
if (process.env.NODE_ENV === "development") {
map.hot = "webpack/hot/dev-server";
map.client = "webpack-dev-server/client/index.js?hot=true&live-reload=true";
}
if (mode && mode === "template") {
// 模板测试
// add test entries
glob
.sync(path.resolve(__dirname, "./test/templates") + "/**/*.{js,ts}")
.forEach((item) => {
let prefixPath = path.resolve(__dirname, "./test/templates");
let filePath = item.substring(
prefixPath.length + 1,
item.lastIndexOf("."),
);
console.log("loading module", filePath);
map["test/" + filePath] = item;
});
} else {
routes
.filter((item) => item.main)
.map((item) => {
const main = path.resolve(__dirname, "./src" + item.main);
console.log("loading entry file:", main);
map[item.path.substring(1)] = main;
});
}
return map;
};
module.exports = {
// 入口文件
// name: language, // 语言名称
entry: entries(),
watch: true,
watchOptions: {
poll: 1000, // 每秒询问多少次
aggregateTimeout: 500, //防抖 多少毫秒后再次触发
ignored: /node_modules/, //忽略时时监听
},
mode: process.env.NODE_ENV === "production" ? "none" : "development",
output: {
// 存放打包后的文件的位置
path: path.resolve(__dirname, "./dist"),
// 打包后的文件名
filename: "[name].bundle.[contenthash:8].js",
chunkFilename: "[name].js", // 代码拆分后的文件名
// clean: true,
// pathinfo: false,
publicPath: "/",
},
target: "web",
cache: {
type: "memory",
},
module: { rules: rules },
plugins: [
...plugins,
// new I18nPlugin(rq),
],
devServer: {
devMiddleware: {
index: true,
mimeTypes: { "text/html": ["phtml"] },
publicPath: "/dist",
serverSideRender: true,
writeToDisk: true,
},
static: "./dist",
proxy,
// publicPath: 'http://localhost:8080',
// port: 9000,
historyApiFallback: true,
compress: true,
hot: false,
client: false,
open: true, // when open is enabled, the dev server will open the browser.
},
resolve: {
extensions: [".ts", ".js", ".json"],
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
context: path.resolve(__dirname),
};
```
webpack.config.js是webpack打包的配置整合,可通过如上配置实现简单的多页面配置性编译,新增路由可通过在config/router.js中添加页面路由配置即可。
\ No newline at end of file
---
title: 用xlsx-style玩转Excel导出——像手动操作Excel表格一样用JS操作Excel
date: 2022/4/25 09:15:20
tags:
- 前端
- xlsx-style
- JavaScript
author: 李燕
---
JS里比较知名的Excel导出插件sheetJS免费版没办法修改样式,能修改样式的专业版又要收费,所以我们项目中基本都用xlsx-style实现定制化的Excel导出。
该插件实际上是sheetJS延伸出来的,虽然用了很多次了,但对于该该插件涉及的诸多概念、属性都不是很清晰。所以想通过本文的整理,对该插件的相关概念有一个完整的、清晰的认知,以更好地服务后续项目。
<!-- more -->
**一. 插件安装**
安装命令如下:
```js
npm i xlsx-style --save
```
但是安装完使用的时候会报错,解决办法如下:
找到\node_modules\xlsx-style\dist\cpexcel.js,将807行 的 var cpt = require(’./cpt’ + ‘able’);改成 var cpt = cptable即可。
**二、概念介绍**
(一)workbook Object
即工作簿对象,其常用属性如下:
1.`SheetNames` : 该属性的值是一个数组,用来存储工作表(sheet)的名称。
2.`Sheets` : 该属性的值是一个对象,用来存储所有工作表(sheet)对象。
通过`Sheets[SheetNames]`即可返回对应名称的工作表(sheet)对象。
(二)worsheet Object
即工作表对象,其常用属性如下:
1.每个不以`!`开头的属性,都代表一个单元格。例如`A1`属性,其值为`A1`单元格对象。
2.`!ref`: 该属性的值是一个字符串,用来表示工作表范围,如:`“A1:B5”`
3.`!cols`: 该属性的值是一个对象数组,用来设置列宽。如:
```js
[
{ wpx: 100 }, //设置第1列列宽为200像素
{ wch: 50 }, // 设置第2列列宽为30字符
]
```
4.`!merges`: 该属性的值是一个对象数组,用来合并单元格。如:
```js
[
{ // 合并B2到D4范围内的单元格
s: { c: 1, r: 1 }, // start:B2
e: { c: 3, r: 3 }, // end:D4
}
]
```
5.`!freeze`: 该属性的值是一个对象,用来冻结单元格(所谓冻结,即鼠标滚动时,该行/该列固定不动)。如:
```js
{
xSplit: '1', // 冻结列
ySplit: '1', // 冻结行
topLeftCell: 'B2', // 在未冻结区域的左上角显示的单元格,默认为第一个未冻结的单元格。
state: 'frozen',// 状态为冻结的
}
```
6.打印相关的属性
`!rowBreaks`: 该属性的值是一个数组,用来设置行分页。如:`[1,3]`,表示第1行为一页,第2行和第三行为一页,第三行之后为一页
`!colBreaks`: 该属性的值是一个数组,用来设置列分页。如:`[1,3]`,表示第1列为一页,第2列和第三列为一页,第三列之后为一页
`!pageSetup`: 该属性的值是一个对象,用来设置缩放大小和打印方向。如:
```js
{
scale: '100', // 缩放100%
orientation: 'portrait' //打印方向为纵向
}
// orientation 取值如下: 'portrait' - 纵向 'landscape' - 横向
```
`!printHeader`: 该属性的值是一个数组,用于分页时设置重复打印的表头,如`[1,3]`,表示需要重复打印的表头为第一行到第三行。
(三)cell Object
即单元格对象。格式如下:
```js
{
c: C, // C代表列号
r: R, // r代码行号
}
```
如果想表示单元格范围,则可以如下表示:
```js
{
s: S, // S表示第一个单元格对象
e:E, // e表示最后一个单元格对象
}
```
在工作表对象中设置一个单元对象,是以编码后的单元格为属性,进行设置。例如:
```js
A1: {
v: '123',
t: 's',
s: {}
}
```
`v`: 单元格的值
`t`: 单元格的类型,b布尔值、n数字、e错误、s字符串、d日期
`s`: 单元格的样式
**三、编码操作**
利用`XLSX.Utils`对象中的以下方法,可以对单元格和单元格范围进行转化,让我们方便地操作单元格。
```js
//编码行号
XLSX.utils.encode_row(2); //"3"
//解码行号
XLSX.utils.decode_row("2"); //1 /
/编码列标
XLSX.utils.encode_col(2); //"C"
//解码列标
XLSX.utils.decode_col("A"); //0
//编码单元格
XLSX.utils.encode_cell({ c: 1, r: 1 }); //"B2"
//解码单元格
XLSX.utils.decode_cell("B1"); //{c: 1, r: 0}
//编码单元格范围
XLSX.utils.encode_range({ s: { c: 1, r: 0 }, e: { c: 2, r: 8 } }); //"B1:C9"
//解码单元格范围
XLSX.utils.decode_range("B1:C9"); //{s:{c: 1, r: 0},e: {c: 2, r: 8}}
```
**四、样式设置**
设置单元格的样式,就是设置工作表对象中的单元格对象的s属性。这个属性的值也是一个对象。它有五个属性,具体如下:
`fill`: 填充属性,其子属性如下:
- patternType: 设置填充时的图案样式,取值如下:none、solid、darkGray、mediumGray、lightGray、gray125、gray0625、darkHorizontal、darkVertical、 darkDown、darkUp、darkGrid、darkTrellis、lightHorizontal、lightVertical、lightDown、lightUp、 lightGrid、lightTrellis
- bgColor: 设置填充时的图案背景色,是一个对象,取值如:{rgb: "FFFFAA00"}(十六进制ARGB值), 或{theme: "1"}(主题颜色的整数索引,默认是'0')
- bgColor: 设置填充时的图案前景色,取值同bgColor属性。
`font`: 字体属性,其子属性如下:
- name: 字体名称,值为字符串
- sz: 字号,值为数字
- color: 字体颜色,值为对象,同上述bgColor
- bold: 加粗,值为true 或 fasle
- underline:下划线,值为true 或 fasle
- italic:倾斜,值为true 或 fasle
- italic:倾斜,值为true 或 fasle
- strike:删除线,值为true 或 fasle
- vertAlign:上标或下标,值为'superscript'或 'subscript'
`numFmt`: 对数字类型的单元格进行格式化,其值为字符串或数字,如:{ numFmt: 'yyyy/m/d h:mm'} 或 { numFmt: '[=1]"男";[=0]"女"'},具体格式化规则参见Excel中设置单元格格式中的数字格式设置。
`alignment`: 对其属性,其子属下如下:
- vertical: 垂直对齐,值为"bottom"、"center"或 "top"
- horizontal: 水平对齐,值为"left"、"center"或 "right"
- wrapText:自动换行,值为true 或 false
- readingOrder:文字方向,值为数字0(根据内容决定)、1(从左到有) 或 2(从右到左)
- textRotation:文本旋转角度,值为数字,0至180或 255(垂直排列) ,默认为0
`alignment`: 对其属性,其子属下如下:
- top:上边框样式,值为对象,如:{style: 'thin', color:{rgb: "FFFFAA00"}},其中,style的值集包括:thin、medium、thick、dotted、hair、dashed、mediumDashed、dashDot、mediumDashDot、dashDotDot、mediumDashDotDot、slantDashDot、double
- bottom: 下边框样式,值同top
- left: 左边框样式,值同top
- right: 右边框样式,值同top
- diagonalUp: 上对角线,值为true 或 fasle
- diagonalDown: 下对角线,值为true 或 fasle
**五、数据输出**
xlsx-style有两个输出数据的方法 `write` 和 `writeFile` 方法。因为 writeFile方法需要基于 node 环境才可以使用,我们常用的是write方法,所以此处重点介绍以write方法。
```js
XLSX.write(workbook, wopts)
```
workbook参数即是我们要导出的工作簿对象,上面已经介绍过。
wopt参数也是一个对象,其属性如下:
`type`: 输出数据类型,值集包括:"base64"、"binary"(二进制字符串)、"buffer"(node.js缓冲区)、"file"(直接创建文件,node环境下有效)
`cellDates`:是否将日期存储为类型'd'(默认为'n'),默认为fasle
`bookType`: 工作簿的类型(xlsx、xlsm或 xlsb),默认为xlsx
`showGridLines`: 是否显示网格线,默认为true
`Props`: 可以设置为一个对象,存入以下与工作簿相关的信息,其子属性包括:title、subject(主题)、creator(创建者)、keywords(关键字)、description(描述)
**六、后记**
本篇文章仅仅是对xlsx-style相关的概念、属性和方法进行了一个详细的整理,至于如何利用xlsx-style玩转Excel实现定制化的导出,我们下篇文章再细说。
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment