1、如何封装一个模版式的简易脚手架?

1.1 了解并安装 yeoman-generator

Generators 是yeoman生态系统的积木,是通过yo命令运行而为终端用户生产文件的插件

文档:https://segmentfault.com/a/1190000005827971

1.2 准备一个打包项目模板,无关业务的

例如

  1. webpack
  2. |--build # webpack配置文件
  3. |--config # webpack可配置选项
  4. |--bankConfig # 从src中抽离出来的可配置选项
  5. |--bisConfig # 业务配置
  6. |--routeConfig.js # 路由配置
  7. |--customSrc # 基于src的定制目录
  8. |--src # 项目的开发目录
  9. |--assets # 项目的静态资源,参与webpack的编译
  10. |--external # 项目的外部资源,不参与webpack的编译
  11. |--lib # 项目的公共脚本
  12. |--BaseApi.ts # 公共请求
  13. |--BaseStorage.ts # 公共存储
  14. |--CommonRouter.ts # 公共路由
  15. |--CommonStore.ts # 公共状态
  16. |--extend.js # 扩展方法
  17. |--components # 项目的公共组件
  18. |--model # 项目的数据模型
  19. |--User # 用户类
  20. |--System # 系统类
  21. |--Utils # 工具类
  22. |--service # 项目的接口交互
  23. |--ApiUrl.ts # 接口路径
  24. |--User.ts # 用户接口
  25. |--Upload.ts # 文件上传接口
  26. |--mock # 项目的虚拟接口数据
  27. |--pages # 项目的页面模块,每个子目录对应一个vue单页面
  28. |--.html # 模板文件
  29. |--.js # 入口文件
  30. |--.less # 根样式
  31. |--.vue # 根组件
  32. |--router # 路由配置
  33. |--views # 路由组件
  34. |--store # 状态管理
  35. |--components # 复用组件
  36. |--shims-vue.d.ts # TypeScript识别.vue文件
  37. |--shims-tsx.d.ts # 允许在vue中编写.tsx文件
  38. |--package.json # 项目配置文件,包含项目的名称、版本号、命令行、第三方依赖等
  39. |--tsconfig.json # typescript配置文件
  40. |--.babelrc.js # babel配置文件
  41. |--.eslintrc.js # eslint配置文件
  42. |--.eslintignore # eslint忽略的文件列表
  43. |--.gitignore # git忽略的文件列表
  44. |--postcss.config.js # postcsss配置文件
  45. |--.editorconfig # 编辑器配置文件
  46. |--INSTALL.txt # 项目安装说明
  47. |--CHANGELOG.txt # 项目版本日志
  48. |--make-package.sh # 项目自动化打包
  49. |--deploy.sh # 项目自动化部署

1.3 初始化脚手架的 pageage.json

需要注意的点:
1、脚手架的 name 必须以 generator- 开头
2、keywords 属性必须包含 "yeoman-generator"

  1. {
  2. "name": "generator-xxx",
  3. "version": "0.0.1",
  4. "description": "简易脚手架",
  5. "scripts": {},
  6. "author": "xxxx",
  7. "private": false,
  8. "license": "ISC",
  9. "publishConfig": {
  10. "registry": "http://xxxxxx" // 私库/脚手架发布地址
  11. },
  12. "files": [
  13. "app" // 属性必须是一个在你的generator里面用到的文件和目录的数组。
  14. ],
  15. "keywords": [
  16. "yeoman-generator"
  17. ],
  18. "dependencies": {
  19. "yeoman-generator": "^3.2.0"
  20. }
  21. }

1.4 利用 yeoman-generator 编写脚手架生成脚本

1.4.1pageage.jsonfiles 定义的目录下 新建 index.js 入口文件 ,入口文件得导出一个 generator 继承

1.4.2 ye 执行顺序为 initializing -> prompting ->configuring -> default -> writing-> conflicts -> install -> end

  1. initializing -- 初始化方法(检查状态、获取配置等)
  2. prompting -- 获取用户交互数据(this.prompt())
  3. configuring -- 编辑和配置项目的配置文件
  4. default -- 如果generator内部还有不符合任意一个任务队列任务名的方法,将会被放在default这个任务下进行运行
  5. writing -- 填充预置模板
  6. conflicts -- 处理冲突(仅限内部使用)
  7. install -- 进行依赖的安装(egnpmbower
  8. end -- 最后调用,做一些clean工作

1.4.3 完整示例:

  1. /*
  2. * @Date: 2021-01-22 10:19:10
  3. * @LastEditTime: 2021-02-07 15:42:37
  4. * @Description: 脚手架生成器
  5. */
  6. const path = require("path");
  7. const fs = require("fs");
  8. const mkdirp = require("mkdirp");
  9. const Generator = require("yeoman-generator");
  10. module.exports = class extends Generator {
  11. // 初始化
  12. initializing() {
  13. // 项目属性的默认值
  14. this.props = {
  15. name: '',
  16. version: '0.0.1',
  17. description: '',
  18. author: ''
  19. };
  20. }
  21. // 接收用户输入
  22. prompting() {
  23. // 问题
  24. const questions = [
  25. {
  26. type: "input",
  27. name: "name",
  28. message: "请输入项目的英文名",
  29. },
  30. {
  31. type: "input",
  32. name: "version",
  33. message: "请输入项目的版本号",
  34. default: this.props.version
  35. },
  36. {
  37. type: "input",
  38. name: "description",
  39. message: "请输入项目的描述信息",
  40. },
  41. {
  42. type: "input",
  43. name: "author",
  44. message: "请输入项目的作者名称",
  45. }
  46. ]
  47. // 根据回答,生成新的项目属性
  48. return this.prompt(questions).then((answers) => {
  49. const { name } = answers;
  50. if (!name) {
  51. throw new Error('项目名称不能为空');
  52. } else {
  53. const fileList = fs.readdirSync('./');
  54. if (fileList.find(e => e == name)) {
  55. throw new Error('当前目录下存在同名项目');
  56. } else {
  57. this.props = answers;
  58. }
  59. }
  60. });
  61. }
  62. // 创建项目目录
  63. default() {
  64. const { name } = this.props;
  65. if (path.basename(this.destinationPath()) !== name) {
  66. mkdirp(name);
  67. this.destinationRoot(this.destinationPath(name));
  68. }
  69. }
  70. // 写入项目文件
  71. writing() {
  72. // 将模板文件复制到输出目录,并替换文件中的变量
  73. this.fs.copyTpl(
  74. this.templatePath("project/"),
  75. this.destinationPath(""),
  76. this.props,
  77. {},
  78. {
  79. globOptions: { dot: true }
  80. }
  81. );
  82. // 重命名文件,去掉下划线前缀
  83. this.fs.move(
  84. this.destinationPath("_package.json"),
  85. this.destinationPath("package.json"),
  86. );
  87. }
  88. }

2、基于 element-ui 封装组件遇到的问题

2.1 dialog 二次封装 出现 弹出又关闭的情况

场景:
dialog 组件需要根据对应的元素定位,并不是传统意义上的在整个屏幕居中展示

思路:
1、dialog 基于那个对应元素定位。也就是改变dialog最外层元素的定位方式

  1. .el-dialog__wrapper{
  2. position: absolute;
  3. }

2、由于 定位方式由 fixed 变成了 absolute 那最外层元素 el-dialog__wrapper 宽高都为 0,所以必须根据内部 宽高进行动态计算然后赋值给 el-dialog__wrapper
实现:

  1. // dialog 打开回调立马计算弹框内容宽高
  2. opened() {
  3. this.getDialogStyle();
  4. },
  5. getDialogStyle() {
  6. this.$nextTick(() => {
  7. const searchBox = this.$refs.SearchBox;
  8. if (!searchBox) return;
  9. const { width, height } = searchBox.getBoundingClientRect();
  10. this.dialogStyle = {
  11. width: width + 50 + "px",
  12. height: height + 50 + "px",
  13. zIndex: 2500,
  14. };
  15. });
  16. },
  17. // 将 dialogStyle 绑定在 dialog 最外层 el-dialog__wrapper 元素中

3、给 body 绑定事件 然后点击 body 关闭弹框

  1. document.body.addEventListener("click", (event) => {
  2. this.isShow = false;
  3. event.stopPropagation();
  4. });

遇到的问题:
1、点击按钮打开弹框,打开之后立马又关闭了
分析:应该是点击按钮的时候,由按钮点击 冒泡到 body 上,点击打开弹框的同时,也触发了 body 上的点击事件,触发了 弹框的关闭通知
尝试解决方法一:给使用的页面,有绑定点击事件的地方,添加阻止冒泡的方法,但是页面上绑定点击的地方太多了,总有漏掉的地方,这始终是个隐患。所以这种方式不得不舍弃掉
尝试解决办法二:通过伪元素,在 dialog 内容区域外层加一个 mask 遮罩 这样,点击 dialog 内容外层 ,其实跟之前一样,还是点击在 dialog 内部 ,这样就不用特意添加点击事件,防止冒泡事件的问题了

  1. .el-dialog__wrapper{
  2. &::before {
  3. content: "";
  4. position: fixed;
  5. width: 100vw;
  6. height: 100vh;
  7. top: 0;
  8. right: 0;
  9. bottom: 0;
  10. left: 0;
  11. overflow: auto;
  12. margin: 0;
  13. }
  14. }