前端开发常用配置,希望增加

103 阅读4分钟
path.ts
// 请求全局添加前置
// @ts-ignore
const env: any = process.env.NODE_ENV
export = {
  baseUrl: env === 'development' ? '/test' : '/api',
  proxy: '/api'
}
array.ts
// 对象转数组
export const parseToValueConfigArr = (obj: { [propName: string]: any }) => {
  return Object.keys(obj).map((k) => {
    return {
      name: obj[k],
      value: k
    }
  })
}
data_type.ts
// 获取数据类型,判断类型,获取唯一uid
const getType = (input: any) => {
  const type: string = Object.prototype.toString.call(input)
  return type.slice(8, type.length - 1)
}
export const isBoolean = (input: any): input is boolean => getType(input) === 'Boolean'
export const isNumber = (input: any): input is number => !isNaN(input) && getType(input) === 'Number'
export const isObject = (input: any): input is ({ [propName: string]: any }) => getType(input) === 'Object'
export const isString = (input: any): input is string => getType(input) === 'String'
export const isUndefined = (input: any): input is undefined => getType(input) === 'Undefined'
export const isArray = (input: any): input is any[] => getType(input) === 'Array'
export const getUid = () => Date.now() + Math.random().toString(16).slice(2)
clearInvalidParams.ts
// 过滤 undefined null '', 数字类型全部返回, 包括 0, 布尔值全部返回
export const c = (params: any) => {
  const newParams: any = {}
  Object.keys(params).forEach((key: any) => {
    if (typeof params[key] === 'number') {
      newParams[key] = params[key]
    }
    else if (typeof params[key] === 'boolean') {
      newParams[key] = params[key]
    }
    else if (params[key]) {
      newParams[key] = params[key]
    }
  })
  return newParams
}
copy.ts
// 原生复制
// 1.
export const copyToClip = (content: string) => {
  const aux = document.createElement('input');
  aux.setAttribute('value', content);
  document.body.appendChild(aux);
  aux.select();
  document.execCommand('copy');
  document.body.removeChild(aux);
  alert('复制成功')
}
// 2.
/**
 * 封装原生执行命令
 * @param command string
 * @param value string
 */
export const execCommand = (command: string, value: string) => {
  const input = document.createElement('input')
  input.setAttribute('value', value)
  input.setAttribute('id', 'copy-container')
  document.body.appendChild(input)
  input.select()
  const html = document.getElementById('copy-container')
  if (document.execCommand(command)) {
    document.execCommand(command)
    html && document.body.removeChild(html)
    return true
  }
  html && document.body.removeChild(html)
  return false
}

// 获取相对视窗的位置
export function getRect(dom: HTMLElement) {
  // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
  const rect = dom.getBoundingClientRect()
  let { left, top, bottom, right, width, height } = rect
  return { left, top, bottom, right, offsetHeight: width, offsetWidth: width }
}
date.ts
// 时间转换等
// @ts-ignore
import moment, { Moment } from 'moment'

/**
 * 验证时间字符有效
 * @param timeStr time string
 */
export function validateTime(time: string | number) {
  const invalidStr = '1971-01-01 00:00:00'
  const invalidNum = 31507200000
  return time !== invalidStr && time !== invalidNum && time !== '-' && moment(time).isValid()
}

/**
 * 转换为对应时区时间
 * @param timeStr time string
 * @param timeZone number
 */
export function adjustTime(timeStr: string, timeZone: number, showUTC?: boolean) {
  if (!validateTime(timeStr) || timeZone == 0 || !timeZone || timeZone == 480) {
    return timeStr
  }
  let beijingTimestamp = moment(timeStr).valueOf()
  let utcTimestamp = beijingTimestamp - 8 * 60 * 60 * 1000
  let targetTimestamp = utcTimestamp + timeZone * 60 * 1000
  return `${formatTime(moment(targetTimestamp))}${showUTC ? `(UTC${timeZone / 60})` : ''}`
}

/**
 * 日期转换统一格式
 * @param date Moment
 */
export function formatTime(date: Moment, formatSty: string = 'YYYY-MM-DD HH:mm:ss') {
  return date.format(formatSty)
}

/**
 * 距离转换可读字符(带单位)
 * @param distance number
 */
export function getDistanceStr(distance: number) {
  return distance >= 1000 ? `${(distance / 1000).toFixed(1)}km` : `${distance}m`
}

/**
 * 时间转换可读字符(带单位)
 * @param eta number
 */
export function getEtaStr(eta: number) {
  return eta >= 60 ? `${(eta / 60).toFixed(0)}min` : `${eta}s`
}
decoration.ts
// 节流函数、 防抖函数
/**
 * 节流函数
 * @param wait number
 * @param fn function
 */
export function throttle(wait: number, fn: any) {
  let previous = 0
  let timer!: any
  return async function() {
    // @ts-ignore
    const context = this
    const args = arguments
    if (!previous) {
      previous = Date.now()
      fn.apply(context, args)
      return
    }
    if (Date.now() - previous < wait) {
      if (timer) {
        clearTimeout(timer)
      } else {
        timer = setTimeout(() => {
          previous = Date.now()
          fn.apply(context, args)
        })
      }
      return
    }
    previous = Date.now()
    fn.apply(context, args)
  }
}
/**
 * 防抖函数
 * @param wait number
 * @param fn function
 */
export function debounce(wait: number, fn: any) {
  let timer: any = null
  return (...args: any[]) => {
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
    timer = setTimeout(() => {
      fn.apply(null, args)
    }, wait)
  }
}
validate.ts
// 校验规则
/**
 * 校验名字输入规则函数
 * @param input string
 * @returns boolean
 */
export function validateNameInput(input: string) {
  if (!input || !/^[a-zA-Z_0-9]+$/gm.test(input)) {
    return new Error('只支持大小写字母、数字、下划线!')
  }
  return true
}

export const validate = [
  { required: true, message: '订阅配置名称为必填项', trigger: 'blur' },
  // { pattern: /(\w)$/, message: '只能输入英文、数字、下划线' },
  { pattern: /^[a-zA-Z_0-9-]+$/, message: '只能输入英文、数字、下划线、中划线' },
]

// 阻止浏览器回退
history.pushState(null, null, document.URL);
window.addEventListener('popstate', function () {
  history.pushState(null, null, document.URL);
});

localstorage.ts
// localstorage 存取
import { err } from './logger'

export const setLocal = (key: string, value: string) => {
  localStorage.setItem(key, value)
}

export const getLocal = (key: string, isParse?: boolean) => {
  const value = localStorage.getItem(key)
  if (isParse) {
    try {
      return JSON.parse(value)
    } catch (error) {
      err(error)
      return value
    }
    
  }
  return value
}
logger.ts
// console.log
const log = (value: any) => {
  console.log(value)
}
const info = (value: any) => {
  console.info(value)
}
const warn = (value: any) => {
  console.warn(value)
}
const err = (value: any) => {
  console.error(value)
}

export default {
  log,
  info,
  warn,
  err
}
export {
  log,
  info,
  warn,
  err
}
mapParse.ts
// 地图解析
export const decodeLine = (encoded: any) => {
  const len = encoded.length
  let index = 0
  const array = []
  let lat = 0
  let lng = 0
  try {
    while (index < len) {
      let b
      let shift = 0
      let result = 0
      do {
        b = encoded.charCodeAt(index++) - 63
        result |= (b & 0x1f) << shift
        shift += 5
      } 
      while (b >= 0x20)
      const dlat = ((result & 1) ? ~(result >> 1) : (result >> 1))
      lat += dlat
      shift = 0
      result = 0
      do {
        b = encoded.charCodeAt(index++) - 63
        result |= (b & 0x1f) << shift
        shift += 5
      }
      while (b >= 0x20)
      const dlng = ((result & 1) ? ~(result >> 1) : (result >> 1))
      lng += dlng

      array.push(lng / 100000 + ','+ lat / 100000)
    }
  } catch(ex) {
    //error in encoding.
    return []
  }
  return array
}

export const lngLatStringToLatLngLiteral = (str: any) => {
  const arr = str.split(',')
  arr[0] = Number(arr[0])
  arr[1] = Number(arr[1])
  return {
    lat: Number(arr[1]),
    lng: Number(arr[0])
  }
}
request.ts
// 封装统一请求
// @ts-ignore
import axios from 'axios'
import config from '../config/path'
const { baseUrl } = config

interface ResI {
  status: number
  data: any
}

export default async (
  url: string,
  method: 'GET' | 'POST' | 'PUT' | 'DELETE',
  body = {},
  config = {}
): Promise<ResI | any> => {
  const isGet = method === 'GET'
  try {
    const res: ResI = await axios(`${baseUrl}${url}`, {
      method,
      ...(isGet ? {
        params: body
      } : {
        data: body
      }),
      ...config,
      ...{timeout: 60000}
    })
    const { status, data } = res
    return status === 200 ? data : null
  } catch (error) {
    console.error(error)
    return null
  }
}
tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}
tslint.json
{
  "defaultSeverity": "warning",
  "extend": [
    "tslint: recommended"
  ],
  "linterOptions": {
    "exclude": [
      "node_modules/**"
    ]
  },
  "rules": {
    "indent": [true, "spaces", 2],
    "interface-name": false,
    "no-consecutive-blank-lines": false,
    "object-literal-sort-keys": false,
    "ordered-imports": false,
    "quotemark": [true, "single"],
    "semicolon": false,
    "trailing-comma": false,
    "variable-name": [
      false
    ],
    "no-empty": false,
    "member-access": [true, "no-public"],
    "no-console": false,
    "triple-equals": false,
    "max-line-length": [ false ],
    "object-literal-key-quotes": [true, "consistent"],
    "no-string-literal": false,
    "prefer-const": false,
    "strict-boolean-expressions": false,
    "no-unused-expression": false,
    "no-shadowed-variable": false
  }
}
vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

const NODE_ENV = process.env.NODE_ENV
const outDir = NODE_ENV === 'test' ? 'test' : 'dist'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: './', // 类似publicPath, './' 避免打包后访问空白页面, 要加上, 防止访问不了
  build: {
    outDir, // 指定静态资源存放路径
    assetsDir: 'assets', // 是否构建source map 文件
    terserOptions: {
      // 生产环境移除 console, debugger
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  },
  resolve: {
    alias: {
      // 如果报__dirname 找不到, 安装 @types/node 包
      '@': path.resolve(__dirname, 'src'),
      '@views': path.resolve(__dirname, 'src/views'),
    }
  },
  server: {
    https: false, // 是否开启https
    open: true, // 是否自动打开浏览器
    host: '0.0.0.0',
    port: 3000, // 端口号, 默认3000
    proxy: {
      '/api': {
        target: 'http://localhost:8038', // 后台接口
        changeOrigin: true,
        secure: false, // 如果是https接口,需要配置这个参数
        ws: true, // websocket 支持
        // rewrite: path => path.replace(/^\/api/, '')
      }
    }
  },
  define: {
    'process.env': {
      NODE_ENV
    }
  },
  // 引入第三方配置
  optimizeDeps: {
    include: []
  }
})
pm2.json

{
  "apps": [{
    "name"       : "dinet-mysql-curd",
    "script"     : "./server/production/app.js",
    "instances"  : "4",
    "log_date_format"  : "YYYY-MM-DD HH-mm-ss",
    "max_memory_restart": "300M",
    "error_file": "./server/logs/node_std/stderr.log",
    "out_file": "./server/logs/node_std/stdout.log",
    "pid_file": "./server/logs/pid/tt.pid",
    "merge_logs" : false,
    "exec_mode"  : "cluster_mode",
    "env": {
      "NODE_ENV": "production"
    },
    "env_production": {
      "NODE_ENV": "production"
    }
  }]
}
package.json

{
  "name": "dinet-mysql-curd",
  "version": "0.0.0",
  "scripts": {
    "dev": "NODE_ENV=dev vite",
    "build": "NODE_ENV=production vite build",
    "serve": "vite preview",
    "server": "ts-node-dev --project server/tsconfig.json ./server/development/app.ts",
    "start": "tsc --project server/tsconfig.json && pm2 start pm2.json --env production",
    "stop": "pm2 stop pm2.json"
  },
  "dependencies": {
    "ant-design-vue": "^3.0.0-alpha.11",
    "axios": "^0.24.0",
    "dayjs": "^1.10.7",
    "koa": "^2.13.4",
    "koa-bodyparser": "^4.3.0",
    "koa-mount": "^4.0.0",
    "koa-router": "^10.1.1",
    "koa-session": "^6.2.0",
    "koa-static": "^5.0.0",
    "lodash-es": "^4.17.21",
    "mysql2": "^2.3.3",
    "node-fetch": "^3.1.0",
    "pm2": "^5.1.2",
    "sequelize": "^6.9.0",
    "sequelize-cli": "^6.3.0",
    "ts-node-dev": "^1.1.8",
    "vue": "^3.0.5",
    "vue-router": "^4.0.12"
  },
  "devDependencies": {
    "@types/koa": "^2.13.4",
    "@types/koa-bodyparser": "^4.3.4",
    "@types/koa-mount": "^4.0.1",
    "@types/koa-router": "^7.4.4",
    "@types/koa-session": "^5.10.4",
    "@types/koa-static": "^4.0.2",
    "@types/lodash-es": "^4.17.5",
    "@types/node-fetch": "^3.0.3",
    "@types/sequelize": "^4.28.10",
    "@vitejs/plugin-vue": "^1.2.5",
    "@vue/compiler-sfc": "^3.0.5",
    "less": "^4.1.2",
    "less-loader": "^10.2.0",
    "typescript": "^4.3.2",
    "vite": "^2.4.3",
    "vue-tsc": "^0.0.24"
  }
}
useElMenu.ts
import { Menu } from 'ant-design-vue'
import { Ref, ref, onMounted, watch, computed } from 'vue'
import { useRoute } from 'vue-router'

export const useMenuWithRoutr = () => {
  const route = useRoute()
  const isCollapse = ref(false)
  const menuRef = ref(null) as Ref<typeof Menu | null>
  const openKeys = ref([''])
  const selectedKeys = ref([''])
  const sRouter = computed(() => {
    const { path, meta } = route
    return { path, meta }
  })
  const onCollapse = () => {
    isCollapse.value = !isCollapse.value
  }
  const onActiveMenu = () => {
    const { path, meta } = sRouter.value
    const { index } = meta
    if (!index) return
    openKeys.value = [(index as string)]
    selectedKeys.value = [path]
  }
  watch(sRouter, () => {
    onActiveMenu()
  })
  onMounted(onActiveMenu)
  return {
    isCollapse,
    onCollapse,
    menuRef,
    onActiveMenu,
    openKeys,
    selectedKeys
  }
}