Vue3+Ts项目之规范和其余配置

集成editorconfig配置

  1. 根目录创建.editorconfig文件

在vscode编辑器需要安装EditorConfig for VS Code插件

# https://editorconfig.org
# 代码风格设置

root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格 tabs | space
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
insert_final_newline = true # 去除行首的任意空白字符
trim_trailing_whitespace = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅md文件适用以下规则
insert_final_newline = false
trim_trailing_whitespace = false

使用prettier工具

  1. **安装prettier **
yarn add prettier -D
  1. 根目录创建.prettierrc文件
{
  "useTabs":false, # 适用tab缩进还是空格缩进
  "tabWidth":2, # tab是空格的情况下,是几个空格,选择2"printWidth":80, # 当行字符长度,推荐80
  "singleQuote":true, # 使用单引号还是双引号,true是单引号
  "trailingComma":"none", # 在多行输入的尾逗号是否添加,设置为none
  "semi":false # 语句末尾是否要教分号,默认为ture添加分号
}

  1. 根目录创建.prettierignore忽略文件
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

  1. vscode安装Prettier - Code formatter插件

eslint配置

.eslintrc.js文件中extend属性加入"plugin:prettier/recommended"

"plugin:prettier/recommended"

git提交代码格式化配置

npx husky-init && npm install 
// 如果报错标记“&&”不是此版本中的有效语句分隔符,原因是执行命令时使用的PowerShell版本低了
// 可以尝试这行命令
npx husky-init '&&' npm install 

或者用其他办法

yarn add husky -D
npx husky-init

在.husky下的pre-commit文件修改配置

npm run lint

vue.config.js配置

参考:https://cli.vuejs.org/zh/config/

const Version = new Date().getTime()
module.exports = {

  // 这是因为项目打包后,index会白屏,所以把路径改了
  publicPath: './',
  assetsDir: "static",
  
  css: {
    loaderOptions: {
      less: {
        javascriptEnabled: true
      },
      postcss: {
        plugins: [
          require('postcss-pxtorem')({
            rootValue: 75,
            propList: ['*'],
            selectorBlackList: ['el-','y-'],  // 跳过类名开头转换
            exclue: ['node_modules'], // 跳过文件夹转换
            unitPrecision: 2
          })
        ]
      }
    }
  },
  devServer: {
    open: true,
    hot: true,
    compress: true,
    disableHostCheck: true,
    port: 8080,
    proxy: {
      '/admin': {
        target: process.env.VUE_APP_BASEURL,
        changeOrigin: true,
        cookieDomainRewrite: 'localhost',
        secure: false,
        ws: true
      }
    }
  },
  
  // lintOnSave: false, // 取消elsin校验
  outputDir: 'admin-ui',
  configureWebpack: {
    output: {
      // 输出重构  打包编译后的 文件名称  【模块名称.版本号.时间戳】
      filename: `js/[name].${Version}.js`,
      chunkFilename: `js/[name].${Version}.js`
    }
  }
/*
chainWebpack: config => {
    config.plugin('provide').use(webpack.ProvidePlugin, [
      {
        'window.Quill': 'quill/dist/quill.js',
        Quill: 'quill/dist/quill.js',
      },
    ]);
    config.when(process.env.NODE_ENV === 'production', config => {
      config.optimization.minimizer('terser').tap(args => {
        // 注释console.*
        args[0].terserOptions.compress.drop_console = true;
        // remove debugger
        args[0].terserOptions.compress.drop_debugger = true;
        // 移除 console.log
        args[0].terserOptions.compress.pure_funcs = ['console.log'];
        // 去掉注释 如果需要看chunk-vendors公共部分插件,可以注释掉就可以看到注释了
        args[0].terserOptions.output = {
          comments: false,
        };
        return args;
      });
    });
    config.resolve.alias.set('components', resolve('src/components')).set('assets', resolve('src/assets')).set('@/', resolve('src')).end();
  },

*/
}

elementUI

安装

yarn add element-plus
  1. **全局引入 **
// 在main.ts中
import elementPlus from 'element-plus'
import 'element-plus/dist/index.css'

app.use(elementPlus)
  1. 按需引入
// 添加插件
yarn add unplugin-vue-components
// 在vue.config.js
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
  // ...
  configureWebpack: {
    plugins: [
      Components({
        resolvers: [ElementPlusResolver()]
      })
    ]
  }
}

Ant Design Vue

yarn add ant-design-vue@next
yarn add babel-plugin-import
yarn add antd-iconfont
/**在babel.config.js添加*/
plugins: [
    [
      'import',
      { libraryName: 'ant-design-vue', libraryDirectory: 'es', style: 'css' } // 注意如果没有less-loader得自己装一下
    ]
  ]
/**某个js文件*/
// 组件
import {
  Button
} from 'ant-design-vue'
const components = [
  Button
]
// 图标
import {
  UserOutlined
} from '@ant-design/icons-vue'
const icons = {
  UserOutlined
}

export function setupAnt (app) {
  components.forEach(item => {
    app.use(item)
  })
  for (const key in icons) {
     app.component(key, icons[key])
   }
}
/**在main引入某个js文件并调用*/
import { setupAnt } from './utils/antDesign'

const app = createApp(App)

setupAnt(app)

rem自适应

/**针对web网页,没有对移动端进行适配*/
yarn add postcss-pxtorem@5.1.1
/**vue.config.js添加*/
module.exports = {
  css: {
    loaderOptions: {
      less: {
        javascriptEnabled: true
      },
      postcss: {
        plugins: [
          require('postcss-pxtorem')({
            rootValue: 75,
            propList: ['*'],
            exclue: ['node_modules'],
            unitPrecision: 2
          })
        ]
      }
    }
  },
  ...
 }
/**某个js文件定义函数*/
export const fun = function (doc: Document, win: Window) {
  const docEl = doc.documentElement
  const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
  const recalc = function () {
    const clientWidth = docEl.clientWidth
    if (!clientWidth) return
    // 这里是假设在1920px宽度设计稿的情况下,1rem = 20px
    // 可以根据实际需要修改
    docEl.style.fontSize = 100 * (clientWidth / 1920) + 'px'
    // docEl.style.fontSize = 100 * (clientWidth / 1440) + 'px'
  }
  if (!doc.addEventListener) return
  win.addEventListener(resizeEvt, recalc, false)
  doc.addEventListener('DOMContentLoaded', recalc, false)
}
/**main.js调用*/
import { fun } from '@/utils/public'
const app = createApp(App)
fun(document, window)

rsa加密解密

/**TS*/
import { JSEncrypt } from 'jsencrypt'
// rsa加密
export const rsaEncryption = function (data: any) {
  const Je = new JSEncrypt({ default_key_size: '1024' })
  Je.setPublicKey('密钥')
  if (data instanceof Object) {
    data = JSON.stringify(data)
  }
  return Je.encrypt(data)
}
// rsa解密
export const rsaDecrypt = function (data: string) {
  const Je = new JSEncrypt({ default_key_size: '1024' })
  Je.setPublicKey('密钥')
  return Je.decrypt(data)
}
/**JS*/
import jsencrypt from 'jsencrypt';

function rsaUtil(data) {
    let Je = new jsencrypt({
        default_key_size: 1024
    })
    Je.setPublicKey('密钥')
    if (data instanceof Object) {
        data = JSON.stringify(data);
    }
    return Je.encrypt(data);
}
export default rsaUtil;

水印

/**canvas.js*/

export default class Ball {
    constructor(x, y, r,ctx) {
        this.x = x;
        this.y = y;
        this.r = r;
        this.ctx = ctx;
        this.color = this.getRandomColor();
        this.dx = parseInt(Math.random() * 10) - 5;
        this.dy = parseInt(Math.random() * 10) - 5;
    }
    render(){
        this.ctx.beginPath();
        this.ctx.fillStyle = this.color;
        this.ctx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
        this.ctx.fill();
    }
    getRandomColor() {
        const colorStr = '0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f';
        const colorArr = colorStr.split(',');
        const length = colorArr.length;
        let color = "#";
        for (let i = 0; i < 6; i++) {
            const random = parseInt(Math.random() * length);
            color += colorArr[random];
        }
        // console.log(color);
        return color;
    }
    update(arr){
        this.x -= this.dx;
        this.y -= this.dy;
        this.r -= 0.4;
        if(this.r<=0){
            for(let i = 0;i<arr.length;i++){
                if(arr[i]===this){
                    arr.splice(i,1);
                }
            }
        }
    }
}
/**waterMark.js*/
export default function __canvasWM({
  // 使用 ES6 的函数默认值方式设置参数的默认取值
  container = document.body,
  width = '100vw',
  height = '120%',
  textAlign = 'center',
  textBaseline = 'middle',
  font = "800 15px Microsoft Yahei",
  fillStyle = 'rgba(184, 184, 184, 0.5)',
  content = '请勿外传',
  rotate = '60',
  zIndex = 1000
} = {}) {
  const args = arguments[0];
  console.log(args);
  const canvas = document.createElement('canvas');
  canvas.setAttribute('width', width);
  canvas.setAttribute('height', height);
  const ctx = canvas.getContext("2d");
  ctx.textAlign = textAlign;
  // ctx.textBaseline = textBaseline;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.rotate(Math.PI / 360 * rotate);
  ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 15,80);
  const base64Url = canvas.toDataURL();
  const __wm = document.querySelector('.__wm');
  const watermarkDiv = __wm || document.createElement("div");
  const styleStr = `
    position:absolute;
    top:1rem;
    left:200px;
    width:calc(100% - 200px);
    height:calc(100% - 1rem);
    z-index:${zIndex};
    pointer-events:none;
    background-image:url('${base64Url}')`;
  watermarkDiv.setAttribute('style', styleStr);
  watermarkDiv.classList.add('__wm');
  if (!__wm) {
    container.style.position = 'relative';
    container.insertBefore(watermarkDiv, container.firstChild);
  }
  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  if (MutationObserver) {
    let mo = new MutationObserver(function () {
      const __wm = document.querySelector('.__wm');
      // 只在__wm元素变动才重新调用 __canvasWM
      if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
        // 避免一直触发
        mo.disconnect();
        mo = null;
      __canvasWM(JSON.parse(JSON.stringify(args)));
      }
    });

    mo.observe(container, {
      attributes: true,
      subtree: true,
      childList: true
    })
  }
}
/**main.js自调用*/
if (typeof module != 'undefined' && module.exports) {  //CMD
    module.exports = __canvasWM;
  } else if (typeof define == 'function' && define.amd) { // AMD
    define(function () {
      return __canvasWM;
    });
  } else {
    window.__canvasWM = __canvasWM;
  }

vuex固化

yarn add vuex-persistedstate
// 引入
import createPersistedState from "vuex-persistedstate"
export default new Vuex.Store({
  plugins: [createPersistedState({
    storage: window.sessionStorage,
    reducer(val) {
      const {
        value
      }
    }
  })],
  state,
  mutations,
  actions,
  getters,
  modules: {}
})

可更换接口

/**在无需编译的文件中定义接口变量,一般在public下建一个public.js*/
BASE_URL = 'api'
/**在接口文件使用*/
const baseUrl = process.env.NODE_ENV === 'development'?'api':window.BASE_URL

导出指令

export default class {
  constructor(option: any) {
    const that: any = this;
    that.standards = {
      strict: 'strict',
      loose: 'loose',
      html5: 'html5'
    };
    that.selectArray = []; // 存储select的
    that.counter = 0;
    that.settings = {
      standard: that.standards.html5,
      extraHead: '', // 附加在head标签上的额外元素,使用逗号分隔
      extraCss: '', // 额外的css逗号分隔
      popTitle: '', // 标题
      endCallback: null, // 成功打开后的回调函数
      ids: '' // 局部打印的id
    };
    Object.assign(that.settings, option);

    that.init();
  }
  init() {
    const that: any = this;
    that.counter++;
    that.settings.id = `printArea_${that.counter}`;
    const PrintAreaWindow = that.getPrintWindow(); // 创建iframe
    that.write(PrintAreaWindow.doc); // 写入内容
    that.print(PrintAreaWindow);
    that.settings.endCallback();

  }
  print(PAWindow: any) {
    const that: any = this;
    const paWindow = PAWindow.win;
    const _loaded = () => {
      paWindow.focus();
      paWindow.print();
      try {
        const box: any = document.getElementById(that.settings.id);
        const canvasList: any = that.elsdom.querySelectorAll('.canvasImg')
        console.log(that.elsdom)
        for (let i = 0; i < canvasList.length; i++) {
          const _parent = canvasList[i].parentNode
          _parent.removeChild(canvasList[i])
        }
        box.parentNode.removeChild(box);
      } catch (e) {
        console.log(e);
      }
    };
    if (window.ActiveXObject) {
      paWindow.onload = _loaded();
      return false;
    }
    paWindow.onload = () => {
      _loaded();
    };
  }
  write(PADocument: any, $ele: any) {
    const that: any = this;
    PADocument.open();
    PADocument.write(`${that.docType()}${that.getHead()}${that.getBody()}`);
    PADocument.close();

  }
  docType() {
    const that: any = this;
    if (that.settings.standard === that.standards.html5) {
      return '';
    }
    const transitional = that.settings.standard === that.standards.loose ? ' Transitional' : '';
    const dtd = that.settings.standard === that.standards.loose ? 'loose' : 'strict';

    return `${transitional}//EN" "http://www.w3.org/TR/html4/${dtd}.dtd">`;
  }
  getHead() {
    const that: any = this;
    let extraHead = '';
    let links = '';
    let style = '';
    if (that.settings.extraHead) {
      that.settings.extraHead.replace(/([^,]+)/g, (m: any) => {
        extraHead += m;
      });
    }
    // 复制所有link标签
    [].forEach.call(document.querySelectorAll('link'), function (item: any) {
      if (item.href.indexOf('.css') >= 0) {
        links += `${item.href}" >`;
      }
    });

    // 循环获取style标签的样式
    const domStyle = document.styleSheets;
    if (domStyle && domStyle.length > 0) {
      for (let i = 0; i < domStyle.length; i++) {
        try {
          if (domStyle[i].cssRules || domStyle[i].rules) {
            const rules = domStyle[i].cssRules || domStyle[i].rules;
            for (let b = 0; b < rules.length; b++) {
              style += rules[b].cssText;
            }
          }
        } catch (e) {
          // console.log(domStyle[i].href + e);
        }
      }
    }

    if (that.settings.extraCss) {
      that.settings.extraCss.replace(/([^,\s]+)/g, (m: any) => {
        links += `${m}">`;
      });

    }

    return `</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>that<span class="token punctuation">.</span>settings<span class="token punctuation">.</span>popTitle<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">${extraHead}${links}`;
  }
  getBody() {
    const that: any = this;
    let ids = that.settings.ids;
    ids = ids.replace(new RegExp("#", "g"), '');
    that.elsdom = that.beforeHanler(document.getElementById(ids));
    const ele = that.getFormData(that.elsdom);
    const htm = ele.outerHTML;
    return '' + htm + '';
  }
  // 克隆节点之前做的操作
  beforeHanler(elsdom: any) {
    const canvasList = elsdom.querySelectorAll('canvas');
    // canvas转换png图片
    for (let i = 0; i < canvasList.length; i++) {
      if (!canvasList[i].style.display) {
        const _parent = canvasList[i].parentNode
        const _canvasUrl = canvasList[i].toDataURL('image/png')
        const _img = new Image()
        _img.className = 'canvasImg'
        _img.style.display = 'none'
        _img.src = _canvasUrl
        // _parent.replaceChild(_img, canvasList[i])
        _parent.appendChild(_img)
      }
    }
    return elsdom
  }
  // 根据type去处理form表单
  getFormData(ele: any):any {
    const copy = ele.cloneNode(true);
    const copiedInputs = copy.querySelectorAll('input,select,textarea');
    const canvasImgList = copy.querySelectorAll('.canvasImg,canvas');
    let selectCount = -1;
    // 处理所有canvas
    for (let i = 0; i < canvasImgList.length; i++) {
      const _parent = canvasImgList[i].parentNode
      const item = canvasImgList[i]
      // 删除克隆后的canvas节点
      if (item.tagName.toLowerCase() === 'canvas') {
        _parent.removeChild(item)
      } else {
        item.style.display = 'block'
      }
    }
    // 处理所有输入框
    for (let i = 0; i < copiedInputs.length; i++) {
      const item = copiedInputs[i];
      let typeInput = item.getAttribute('type');
      const copiedInput = copiedInputs[i];
      // 获取select标签
      if (!typeInput) {
        typeInput = item.tagName === 'SELECT' ? 'select' : item.tagName === 'TEXTAREA' ? 'textarea' : '';
      }
      // 处理input框
      if (item.tagName === 'INPUT') {
        // 除了单选框 多选框比较特别
        if (typeInput === 'radio' || typeInput === 'checkbox') {
          copiedInput.setAttribute('checked', item.checked);
          // 
        } else {
          copiedInput.value = item.value;
          copiedInput.setAttribute('value', item.value);
        }
        // 处理select
      } else if (typeInput === 'select') {

        selectCount++;
        for (let b = 0; b < ele.querySelectorAll('select').length; b++) {
          const select = ele.querySelectorAll('select')[b]; // 获取原始层每一个select
          !select.getAttribute('newbs') && select.setAttribute('newbs', b) // 添加标识
          if (select.getAttribute('newbs') == selectCount) {
            const opSelectedIndex = ele.querySelectorAll('select')[selectCount].selectedIndex;
            item.options[opSelectedIndex].setAttribute('selected', true);

          }
        }
        // 处理textarea
      } else {
        copiedInput.innerHTML = item.value;
        copiedInput.setAttribute('html', item.value);
      }
    }

    return copy;
  }
  getPrintWindow() {
    const that: any = this;
    const f = that.Iframe();
    return {
      f: f,
      win: f.contentWindow || f,
      doc: f.doc
    };
  }
  Iframe() {
    const that: any = this;
    const frameId = that.settings.id;
    let iframe: any;
    try {
      iframe = document.createElement('iframe');
      document.body.appendChild(iframe);
      iframe.style.border = '0px';
      iframe.style.position = 'absolute';
      iframe.style.width = '0px';
      iframe.style.height = '0px';
      iframe.style.right = '0px';
      iframe.style.top = '0px';
      iframe.setAttribute('id', frameId);
      iframe.setAttribute('src', new Date().getTime());
      iframe.doc = null;
      iframe.doc = iframe.contentDocument ? iframe.contentDocument : (iframe.contentWindow ? iframe.contentWindow.document : iframe.document);
      iframe.onload = function () {
        const win = iframe.contentWindow || iframe;
        that.print(win);
      }
    } catch (e) {
      throw new Error(e + '. iframes may not be supported in that browser.');
    }

    if (iframe.doc == null) {
      throw new Error('Cannot find document.');
    }

    return iframe;
  }
}
# 使用
new Print({
  ids: id, // * 局部打印必传入id
  standard: "", // 文档类型,默认是html5,可选 html5,loose,strict
  extraHead: binding.value.extraHead, // 附加在head标签上的额外标签,使用逗号分隔
  extraCss: binding.value.extraCss, // 额外的css连接,多个逗号分开
  popTitle: "数据源列表", // title的标题 binding.value.popTitle
  endCallback() {
    // 调用打印之后的回调事件
    closeBtn = true;
  },
});

配置文件.env.development

# 默认
NODE_ENV = 'development'
BASE_URL = ""
# 自定义变量名必须VUE_APP_开头
VUE_APP_DEMO = "demo"

iconfont

![目录](https://img-blog.csdnimg.cn/08759e73173a4dd89aee5dda18b2266d.png#pic_center)
# main.ts引用
import '@/assets/iconfont/iconfont.css'
# 使用




你可能感兴趣的:(前后端分离,vue.js,vscode,javascript)