Commit cf36fbe6 by zsh

添加子包

parent 5a97d94b
[submodule "runner"]
path = runner
url = git@123.207.147.179:vue-project/runner.git
/**
* @file 项目打包服务, 用来构建一个全量生产包
*/
'use strict'
const ora = require('ora')
const rm = require('rimraf')
const chalk = require('chalk')
const path = require('path')
const webpack = require('webpack')
const config = require('./webpack.prod.conf')
const rmFile = path.resolve(__dirname, '../dist')
// build start loading
const spinner = ora('building for production...')
spinner.start()
// 构建全量压缩包
rm(rmFile, function (err) {
if (err) throw err
webpack(config, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n'
)
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(
chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
" Opening index.html over file:// won't work.\n"
)
)
})
})
/**
* @file Vue-loader配置
*/
const { dev = {}, build = {} } = require('../config/config')
module.exports = (isDev) => {
return {
preserveWhitespace: true, // 清空多余空格
extractCSS: !isDev, // 将.vue中的css单独抽离出来
cssModules: { // 解决class命名空间冲突
localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]', // class命名
camelCase: true // 驼峰class命名
},
// hotReload: false // 是否关闭组件热重载, 根据环境变量生成
// 自定义loader模块
loaders: isDev ? dev.vueloaderConf : build.vueloaderConf,
preLoader: {
js: ''
},
postLoader: {}
}
}
/**
* @file webpack.base.conf 生产 & 开发环境通用配置
*/
'use strict'
const path = require('path')
const webpack = require('webpack')
const createVueLoaderOptions = require('./vue-loader.config')
// 拼接路径
function resolve (track) {
return path.join(__dirname, '..', track)
}
// 当前运行环境
const isDev = process.env.NODE_ENV === 'development'
const defaultPlugins = [
// 主要作用是在此处可以根据isdev配置process.env,一是可以在js代码中可以获取到process.env
// 二是webpack或则vue等根据process.env如果是development,会给一些特殊的错误提醒等,而这些特殊项在正式环境是不需要的
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: `"${process.env.NODE_ENV}"`
}
})
]
module.exports = {
entry: path.resolve(__dirname, '../src/index.js'),
resolve: {
// 自动解析文件扩展名(补全文件后缀)(左 -> 右)
extensions: ['.js', '.vue', '.json'],
// 配置别名映射
alias: {
// 键后加上$,表示精准匹配
vue$: 'vue/dist/vue.esm.js',
'@': resolve('src'),
utils: resolve('src/public/utils/utils'),
components: resolve('src/components'),
public: resolve('public')
}
},
module: {
rules: [
{
test: /\.(vue|js|jsx)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre' // 先使用eslint预处理对应的文件, 校验通过再使用对应的loader处理
},
{
test: /\.vue$/,
loader: 'vue-loader', // 处理.vue文件
options: createVueLoaderOptions(isDev)
},
{
test: /\.jsx$/,
loader: 'babel-loader' // 处理jsx文件
},
// 使用babel-loader来处理src下所有js文件, 详细babel配置在.babelrc
{
test: /\.js$/,
use: {
loader: 'babel-loader'
},
include: resolve('src')
},
// 使用url-loader(file-loader的一个再封装)对引入的图片进行编码,此处可将小于20480字节(20kb)的图片转为DataURL(base64),
// 大于limit字节的会调用file-loader进行处理
// 图片一般发布后都是长缓存,故此处文件名加入hash做版本区分
{
test: /\.(jpg|png|gif|svg|jpeg|txt|exl)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name]_[hash:8].[ext]',
outputPath: 'images/' // 静态资源输出路径
}
}
]
},
{
// iconfont字体文件
test: /\.(eot|ttf|TTF|svg|woff|woff2)(\?\S*)?$/,
// 处理test中的文件类型需要用到的loader类型(名称)
use: {
loader: 'file-loader'
}
}
]
},
plugins: defaultPlugins.concat([])
}
/**
* @file webpack.dev.conf 开发环境配置
*/
'use strict'
const path = require('path')
const webpack = require('webpack')
const devConfig = require('../config/config').dev // 开发环境配置
const baseConf = require('./webpack.base.conf')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const notifier = require('node-notifier')
const devConf = merge(baseConf, {
output: {
filename: '[name].[hash:8].js',
publicPath: devConfig.publicPath
},
devtool: devConfig.devtoolType,
devServer: {
// HMR控制台log等级
clientLogLevel: 'warning',
// 热加载
hot: true,
// 自动刷新
inline: true,
// 自动打开浏览器
open: false,
// 它依赖于HTML5 history API, 如果设置为true, 所有的跳转将指向index.html
historyApiFallback: true,
// 主机名
host: devConfig.host,
// 端口号
port: devConfig.port,
// 配置反向代理解决跨域
proxy: devConfig.proxyTable,
// 代码进行压缩。加快开发流程和优化的作用
compress: true,
// 在浏览器上全屏显示编译的errors或warnings
overlay: {
errors: true,
warnings: false
},
// 终端输出的只有初始启动信息, webpack 的警告和错误是不输出到终端的
quiet: false
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.less$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'less-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
loader: 'file-loader'
}
]
},
plugins: [
// 开启HMR(热替换功能, 替换更新部分, 不重载页面)
new webpack.HotModuleReplacementPlugin(),
// 显示模块相对路径
new webpack.NamedModulesPlugin(),
// 编译出错时, 该插件可跳过输出, 确保输出资源不会包含错误
// new webpack.NoEmitOnErrorsPlugin()
new HtmlWebpackPlugin({
title: 'vue-cli-init',
filename: 'index.html',
template: 'index.html',
// js资源插入位置, true表示插入到body元素底部
inject: true
}),
// 编译提示插件
new FriendlyErrorsPlugin({
// 编译成功提示
compilationSuccessInfo: {
message: [
`Your application is running here: http://${devConfig.host}:${devConfig.port}`
]
},
onErrors: function (severity, errors) {
if (severity !== 'error') {
return
}
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
// 编译出错时右下角弹出错误提示
notifier.notify({
title: 'vue-cli-init',
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'xc-cli.png')
})
},
clearConsole: true
})
]
})
module.exports = devConf
/**
* @file webpack.base.conf 生产环境配置
*/
'use strict'
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const prodConfig = require('../config/config').build // 生产环境配置
const baseConf = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 资源路径
function assetsPath (_path) {
return path.join(prodConfig.staticPath, _path)
}
const prodConf = merge(baseConf, {
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: prodConfig.publicPath,
filename: assetsPath('js/[name].[chunkhash:8].js'),
chunkFilename: assetsPath('js/[name].[chunkhash:8].js')
},
devtool: prodConfig.devtoolType,
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'postcss-loader'],
fallback: 'vue-style-loader'
})
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'less-loader', 'postcss-loader'],
fallback: 'vue-style-loader'
})
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'sass-loader', 'postcss-loader'],
fallback: 'vue-style-loader'
})
}
]
},
plugins: [
new webpack.BannerPlugin('vue-cli-init'),
// 压缩js
new webpack.optimize.UglifyJsPlugin({
parallel: true,
compress: {
warnings: false
}
}),
// 分离入口引用的css,不内嵌到js bundle中!
new ExtractTextPlugin({
filename: assetsPath('styles/[name].[contentHash:8].css'),
publicPath: '../../',
allChunks: false
}),
// 压缩css
new OptimizeCSSPlugin(),
// 根据模块相对路径生成四位数hash值作为模块id
new webpack.HashedModuleIdsPlugin(),
// 作用域提升,提升代码在浏览器执行速度
new webpack.optimize.ModuleConcatenationPlugin(),
// 抽离公共模块,合成一个chunk,在最开始加载一次,便缓存使用,用于提升速度
// 1. 第三方库chunk, 公共部分
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// 在node_modules的js文件
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0
)
}
}),
// 2. 缓存chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// 3.异步 公共chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
children: true,
// (选择所有被选 chunks 的子 chunks)
async: true,
// (创建一个异步 公共chunk)
minChunks: 3
// (在提取之前需要至少三个子 chunk 共享这个模块)
}),
// 将整个文件复制到构建输出指定目录下
// new CopyWebpackPlugin([
// {
// from: path.resolve(__dirname, '../static'),
// to: prodConfig.staticPath,
// ignore: ['.*']
// }
// ]),
// 生成html
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../dist/index.html'),
template: 'index.html',
favicon: path.resolve(__dirname, '../favicon.ico'),
// js资源插入位置, true表示插入到body元素底部
inject: true,
// 压缩配置
minify: {
// 删除Html注释
removeComments: true,
// 去除空格
collapseWhitespace: true,
// 去除属性引号
removeAttributeQuotes: true
},
// 根据依赖引入chunk
chunksSortMode: 'dependency'
})
]
})
module.exports = prodConf
/**
* @file 配置构建开发环境和生产环境差异化参数
*/
const _path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// vue-loader基本配置
const baseVueLoaderConf = {
postcss: {
config: {
path: _path.resolve('../')
}
},
// 转为require调用, 让webpack处理目标资源
transformToRequire: {
video: 'src',
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
// vue-loader开发环境配置
const devVueLoaderConf = Object.assign({}, baseVueLoaderConf, {
loaders: {
css: ['vue-style-loader', 'css-loader'],
less: ['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader']
},
cssSourceMap: true
})
// vue-loader生产环境配置
const buildVueLoaderConf = Object.assign({}, baseVueLoaderConf, {
loaders: ExtractTextPlugin.extract({
use: ['css-loader', 'postcss-loader', 'less-loader'],
fallback: 'vue-style-loader'
}),
cssSourceMap: false
})
// 开发 / 生产环境 配置参数
module.exports = {
dev: {
publicPath: '/',
devtoolType: '#cheap-module-eval-source-map',
vueloaderConf: devVueLoaderConf,
host: '127.0.0.1',
port: '8081',
// 开发环境跨域配置
proxyTable: {
'/apis': {
target: 'http://192.168.0.121:8881',
pathRewrite: {
'^/apis': '/'
},
changeOrigin: true // 是否跨域 - 开启代理, 在本地会创建一个虚拟服务端, 然后发送请求的数据, 并同时接收请求的数据, 这样服务端和服务端进行数据的交互就不会有跨域问题
}
}
},
build: {
// publicPath: '127.0.0.1', // 生产环境资源路径(部署后的访问url)
publicPath: '../../',
devtoolType: '#cheap-module-source-map',
vueloaderConf: buildVueLoaderConf,
host: '',
staticPath: 'static'
}
}
......@@ -4,9 +4,9 @@
"description": "",
"main": "index.js",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --history-api-fallback --config build/webpack.dev.conf.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --history-api-fallback --config runner/build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "cross-env NODE_ENV=production node build/build.prd.js",
"build": "cross-env NODE_ENV=production node runner/build/build.prd.js",
"lint": "eslint --ext .js --ext .jsx --ext .vue client/",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
"precommit": "npm run lint-fix"
......
Subproject commit d295deac16c7024d3df80eabf415f5080f83fdfa
......@@ -5,7 +5,7 @@
</template>
<script>
import { refreshScale } from './public/utils/refreshSize'
import { refreshScale } from 'runner/public/utils/refreshSize'
export default {
name: 'app',
......
<template>
<div class="chartNum">
<div class="box-item">
<li v-for="(item,index) in orderNum"
:class="{'number-item': !isNaN(item), 'mark-item': isNaN(item) }" :key="index">
        <span v-if="!isNaN(item)">
         <i ref="numberItem">0123456789</i>
       </span>
       <span class="comma" v-else>{{item}}</span>
</li>
</div>
</div>
</template>
<script>
export default {
name: 'CountRoll',
props: {
count: Number
},
data () {
return {
orderNum: []
}
},
mounted () {
this.toOrderNum(this.count)
this.increaseNumber()
},
deactivated () {
clearInterval(this.timer)
},
methods: {
// TODO: 定时器
increaseNumber () {
this.timer = setInterval(() => {
this.setNumberTransform()
}, 300)
},
// TODO:设置文字滚动
setNumberTransform () {
const numberItems = this.$refs.numberItem // 拿到数字的ref,计算元素数量
const numberArr = this.orderNum.filter(item => !isNaN(item))
// 结合CSS 对数字字符进行滚动
for (let index = 0; index < numberItems.length; index++) {
const elem = numberItems[index]
elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`
}
clearInterval(this.timer)
},
// TODO: 处理数字
toOrderNum (num) {
num = this.utils.formatNumber(num, num.length)
this.orderNum = num // 将其便变成数据,渲染至滚动数组
}
}
}
</script>
<style scoped lang='less'>
.chartNum {
.box-item {
position: relative;
font-size: 36px;
line-height: 48px;
text-align: center;
list-style: none;
color: #FFFFFF;
writing-mode: vertical-lr;
text-orientation: upright;
/* 默认逗号设置 */
 .mark-item {
width: 20px;
height: 48px;
border: 1px solid #2B79BC;
margin-right: 5px;
line-height: 10px;
font-size: 36px;
position: relative;
& > span {
position: absolute;
width: 100%;
bottom: 4px;
left: -2px;
writing-mode: vertical-rl;
text-orientation: upright;
}
}
/*滚动数字设置*/
.number-item {
width: 30px;
height: 48px;
display: flex;
list-style: none;
margin-right: 5px;
border-radius: 4px;
border: 1px solid #2B79BC;
color: #FFFFFF;
& > span {
position: relative;
display: inline-block;
margin-right: 10px;
width: 100%;
height: 100%;
writing-mode: vertical-rl;
text-orientation: upright;
overflow: hidden;
& > i {
font-style: normal;
position: absolute;
top: 6px;
left: 50%;
transform: translate(-50%, 0);
transition: transform 1s ease-in-out;
letter-spacing: 10px;
color: #FFFFFF;
}
}
}
.number-item:last-child {
margin-right: 0;
}
.comma {
bottom: 4px;
}
}
}
</style>
<template>
<CountTo
:startVal="startVal"
:endVal="endVal"
:duration="duration"
:decimals="decimals"
:separator="separator"
:suffix="suffix"
:prefix="prefix"
/>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
name: 'VueCountTo', // TODO: 数字变动组件
components: { CountTo },
props: {
autoplay: { type: Boolean, default: true }, // 自动播放 默认为 True
startVal: { type: Number, default: 0 }, // 起始值
endVal: { type: Number, default: 0 }, // 结束值
duration: { type: Number, default: 800 }, // 持续时间,以毫秒为单位
decimals: { type: Number, default: 0 }, // 要显示的小数位数
separator: { type: String, default: ',' }, // 分隔符
prefix: { type: String, default: '' }, // 前缀
suffix: { type: String, default: '' } // 后缀
}
}
</script>
<style scoped>
span {
font-family: "Century Gothic";
}
</style>
<template>
<div class="main-container">
<!-- 头部 -->
<Header v-bind="$attrs" />
<!-- 主内容 -->
<div id="layoutMain" class="layout-main">
<div
class="left"
:style="{
width: layoutSize.leftWidth,
minWidth: layoutSize.leftWidth
}"
ref="layoutLeft"
>
<slot name="left"></slot>
</div>
<div
class="center"
:style="layoutSize.center"
ref="layoutCenter"
>
<div class="content">
<slot name="center"></slot>
</div>
</div>
<div
class="right"
:style="{
width: layoutSize.rightWidth,
minWidth: layoutSize.rightWidth
}"
ref="layoutRight"
>
<slot name="right"></slot>
</div>
</div>
</div>
</template>
<script>
import { merge } from 'lodash'
import Header from 'components/Header'
export default {
name: 'Layout',
data () {
return {
// 布局尺寸
layoutSize: {
leftWidth: '2168px',
rightWidth: '2168px',
center: {
flex: 1
}
}
}
},
props: {
layoutConf: {
type: Object,
default () {
return {}
}
}
},
components: {
Header
},
created () {
window.vm = this
this.setPageSize()
},
methods: {
setPageSize () {
// 默认页面主体内容配置(不含Header)
this.layoutSize = merge({}, this.layoutSize, this.layoutConf)
}
}
}
</script>
<style lang="less">
@import "../assets/styles/less/common.less";
.main-container {
position: relative;
width: 100%;
height: 100%;
color: #fff;
font-size: 14px;
overflow: hidden;
}
// 主体内容
.layout-main {
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
.center {
flex: 1;
position: relative;
box-sizing: border-box;
}
.left, .right {
position: relative;
overflow: hidden;
box-sizing: border-box;
&:after {
content: "";
position: absolute;
top: 160px;
background-position: left top;
background-repeat: no-repeat;
}
}
.left {
padding-left: 30px;
padding-right: 118px;
// background: url('../../static/images/index/lineLeft.png') no-repeat right 160px;
&:after {
right: 0;
}
}
.right {
padding-left: 118px;
padding-right: 30px;
// background: url('../../static/images/index/lineRight.png') no-repeat left 160px;
&:after {
left: 0;
}
}
}
</style>
<template>
<div
:class="chartOpt.className"
:id="chartOpt.id"
:style="{width: chartOpt.width, height: chartOpt.height}"
></div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'chartEl',
props: {
chartOpt: {
type: Object,
required: true,
default () {
return {}
}
},
// 是否开启轮播
isMove: {
type: Boolean,
default: true
}
},
data () {
return {
chart: null // 生成charts容器
}
},
computed: {
...mapState(['innerWH'])
},
mounted () {
this.initChart()
},
watch: {
// 监听option变动, 重新渲染图表
'chartOpt.option': {
handler (newVal, oldVal) {
if (newVal) {
this.$nextTick(() => {
this.initChart()
})
}
},
deep: true // 深度监听
},
// 监听窗口缩放重置echarts
innerWH: {
handler () {
console.log('window resize')
this.chartResize()
},
deep: true
}
},
beforeDestroy () {
window.removeEventListener('resize')
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
/**
* @method initChart 初始化图表
*/
initChart () {
const { id = '', option = {} } = this.chartOpt || {}
if (id && option) {
const currChartEl = document.getElementById(id)
// 无需滚动的图表, 初始化时先销毁, 防止重复创建
if (!this.isMove) {
this.echarts.dispose(currChartEl)
}
if (!this.chart) {
this.chart = this.echarts.init(currChartEl)
}
// 渲染图表
this.chart.setOption(option, true)
}
},
/**
* @method chartResize 重置图表
*/
chartResize () {
this.chart && this.chart.resize()
}
}
}
</script>
......@@ -4,14 +4,16 @@ import Vuex from 'vuex'
import App from './App.vue'
import createStore from './store' // Vuex
import createRouter from './router/router'
import axiosServer from './request'
import UsePlugin from './public/plugins'
import axiosServer from 'runner/request'
import UsePlugin from 'runner/public/plugins'
import customPlugin from './public/plugins'
import utils from 'utils'
Vue.use(Vuex)
Vue.use(VueRouter)
Vue.use(axiosServer)
Vue.use(UsePlugin)
Vue.use(customPlugin)
const router = createRouter() // 创建router
const store = createStore() // 创建Vuex
......@@ -34,6 +36,7 @@ new Vue({
store,
axiosServer,
UsePlugin,
customPlugin,
template: '<App/>',
components: { App }
})
<template>
<div class="index-page">
<layout title="测试title">
<layout title="测试title123">
<!-- left -->
<div class="index-left" slot="left"></div>
<!-- center -->
......
// Vue插件(Vue plugin)
import utils from '../utils/utils'
import { merge } from 'lodash'
import chartEl from '@/components/echarts' // 基础Echarts组件
import Layout from '@/components/Layout.vue' // 布局组件
import CountTo from '@/components/CountTo.vue'
import CountRoll from '@/components/CountRoll.vue'
import vueSeamlessScroll from 'vue-seamless-scroll'
import ContentContainer from '@/components/ContentContainer'
import * as echarts from 'echarts'
const UsePlugin = {}
......@@ -18,23 +10,6 @@ const UsePlugin = {}
* Vue.use(UsePlugin, { global: true })
*/
UsePlugin.install = function (Vue, options = {}) {
// 全局公用方法
Vue.prototype.utils = utils
// 合并对象
Vue.prototype.deepMerge = merge
// 注册全局布局组件
Vue.component('layout', Layout)
// Echarts
Vue.prototype.echarts = echarts
// 基础Echarts组件
Vue.component('chart-el', chartEl)
// 数组滚动
Vue.component('count-to', CountTo)
// 数字分切滚动
Vue.component('count-roll', CountRoll)
// 无缝滚动插件
Vue.component('vueSeamlessScroll', vueSeamlessScroll)
// 内容容器
Vue.component('content-container', ContentContainer)
}
......
/**
* @method refreshScale 设置页面缩放比
* @param {Int|Number} designWidth 设计稿宽度, 默认: 1920
* @param {Int|Number} designHeight 设计稿高度, 默认: 1080
* @param {Int|Number} designWidthRatio 设计宽度比,默认: 16
* @param {Int|Number} designHeightRatio 设计高度比,默认: 9
*/
export function refreshScale (designWidth = 1920, designHeight = 1080, designWidthRatio = 16, designHeightRatio = 9) {
let baseWidth = document.documentElement.clientWidth
let baseHeight = document.documentElement.clientHeight
let appStyle = document.getElementById('app').style
let bodyStyle = document.body.style
let realRatio = baseWidth / baseHeight
let designRatio = designWidthRatio / designHeightRatio // 默认宽高比
let scaleRate = baseWidth / designWidth
if (realRatio > designRatio) {
scaleRate = baseHeight / designHeight
}
bodyStyle.overflow = 'hidden'
appStyle.position = 'fixed'
appStyle.transformOrigin = 'left top'
appStyle.transform = `scale(${scaleRate}) translateX(-50%)`
appStyle.width = `${baseWidth / scaleRate}px`
}
const type = function (o) {
var s = Object.prototype.toString.call(o)
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
}
const dataTypeArr = ['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp']
dataTypeArr.forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase()
}
})
export default type
/**
* @name [utils.js 工具方法]
*/
import type from './type' // 数据类型校验
export default {
...type,
/**
* [zeroPadding 数值小于10自动补零]
* @param {Number, String} num
* @return {String} 补零后数字值
*/
zeroPadding (num) {
return num < 10 ? `0${num}` : `${num}`
},
/**
* [formatDate 格式化时间]
* @param {Date} date
* @return {String} '年月日时'
* @example
* '20180319'
*/
formatDate (date = new Date(), separator = '') {
let self = this
let year = date.getFullYear()
let month = self.zeroPadding(date.getMonth() + 1)
let day = self.zeroPadding(date.getDate())
let hour = self.zeroPadding(date.getHours())
let minute = self.zeroPadding(date.getMinutes())
let dateItemObj = {
year,
month,
day,
hour,
minute
}
let finalParams
// 有分隔符
if (separator) {
finalParams = Object.assign({}, dateItemObj, {
timerStr: `${year}${separator}${month}${separator}${day}`
})
} else {
finalParams = Object.assign({}, dateItemObj, {
timerStr: `${year}${month}${day}${hour}`
})
}
return finalParams
},
timerHandle (start, end) {
let self = this
let startTime = new Date(start)
let endTime = new Date(end)
let formatStart = ''
let formatEnd = ''
// 如果结束日期小于开始日期,交换两个时间
if (Date.parse(endTime) < Date.parse(startTime)) {
formatStart = self.formatDate(endTime, '-')
formatEnd = self.formatDate(startTime, '-')
} else {
formatStart = self.formatDate(startTime, '-')
formatEnd = self.formatDate(endTime, '-')
}
let startHour = `${formatStart.hour}:00`
let endHour = `${formatEnd.hour}:00`
let tempDate
if (formatStart.timerStr === formatEnd.timerStr) { // 同一天
tempDate = `${formatEnd.timerStr} ${startHour} - ${endHour}`
} else {
tempDate = `${formatStart.timerStr} ${startHour} - ${formatEnd.timerStr} ${endHour}`
}
return tempDate
},
/**
* [splitArr 分割数组为指定数量一组]
* @param {Array} arr 目标数组
* @param {Int} groupLen 指定数量
* @return {Array} 分割后的数组
*/
splitArr (arr = [], groupLen = 2) {
if (!arr.length) return
let result = []
for (let i = 0, len = arr.length; i < len; i += groupLen) {
result.push(arr.slice(i, i + groupLen))
}
return result
},
/**
* [isEmpty 判断对象对否为空]
* @param {Object} target
* @return {Boolean} true - 空对象, false - 非空对象
*/
isEmpty (target) {
if (!target) {
return true
} else if (typeof target === 'object' && !Object.keys(target).length) {
return true
} else {
return false
}
},
/**
* [currDates 当前日期]
* @returns {Object} y - 年, like => 2021
* m - 月, like => 03
* d - 日, like => 02
* h - 时, like => 09
* M - 分, like => 09
* s - 秒, like => 07
* ym - 年月, like => 202104
* ymd - 年月日, like => 20210402
*/
currDates () {
let self = this
let date = new Date()
let y = date.getFullYear()
let m = `${self.zeroPadding(date.getMonth() + 1)}`
let d = `${self.zeroPadding(date.getDate())}`
let h = `${self.zeroPadding(date.getHours())}`
let M = `${self.zeroPadding(date.getMinutes())}`
let s = `${self.zeroPadding(date.getSeconds())}`
let ym = `${y}${m}`
let ymd = `${ym}${d}`
return {
y,
m,
d,
h,
M,
s,
ym,
ymd
}
},
/**
* @method getFirstEndDay 获取指定日期对应月份的第一天和最后一天
* @param {String|Date} assignDay 指定日期, 默认: 当前日期
* @param {Boolean} prevMonth 是否是上一个月, 默认: false
* @returns {Object}
* @param {String} firstDay 指定日期对应月份的第一天, 如: 2021-07-01
* @param {String} lastDay 指定日期对应月份的最后天, 如: 2021-07-31
* @param {String} 指定日期对应月份下个月的第一天, 如: 2021-07-01
*/
getFirstEndDay (assignDay = new Date(), prevMonth = false) {
let self = this
let nowdays = new Date(assignDay)
if (nowdays === 'Invalid Date') {
console.error('不合法的日期格式')
return ''
}
let y = nowdays.getFullYear()
let m = self.zeroPadding(prevMonth ? nowdays.getMonth() : nowdays.getMonth() + 1)
if (m === 0 && prevMonth) {
m = 12
y = y - 1
}
let firstDay = `${y}-${m}-01`
let myDate = new Date(y, m, 0)
let lastDay = `${y}-${m}-${myDate.getDate()}`
let nextMonthFirstDay = self.getDateStr(lastDay, 1)
return {
firstDay,
lastDay,
nextMonthFirstDay
}
},
/**
* @method getDateStr 获取指定日期前后的日期
* @param {String, Date} assignDay 指定日期, default: 当前日期
* @param {Int} addDayCount 指定天数, 0 - 今天, > 0 今天后的日期, < 0 今天前的日期
* @return {String} 指定日期前后的日期字符串
*/
getDateStr (assignDay, addDayCount) {
let self = this
let dd = new Date(assignDay)
dd.setDate(dd.getDate() + addDayCount) // 获取 addDayCount 后的日期
let y = dd.getFullYear()
let m = self.zeroPadding(dd.getMonth() + 1)
let d = self.zeroPadding(dd.getDate())
return `${y}-${m}-${d}`
},
/**
* @method getScrollBarWidth 获取滚动条实际宽度
* @return {Int} 滚动条实际宽度
*/
getScrollBarWidth () {
let noScroll
let scroll
let oDiv = document.createElement('div')
oDiv.style.cssText = 'position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;'
noScroll = document.body.appendChild(oDiv).clientWidth
oDiv.style.overflowY = 'scroll'
scroll = oDiv.clientWidth
document.body.removeChild(oDiv)
return noScroll - scroll
},
/**
* @method array2obj 数组转对象
* @param {Array} array 源数组
* @param {String} key 指定对象的key值
*/
array2obj (array, key) {
const resObj = {}
for (let i = 0; i < array.length; i++) {
const item = array[i]
resObj[item[key]] = item
}
return resObj
},
/**
* @method randomStr 生成随机字符串
* @param {Int|Number} e 随机字符串长度
* @returns {String} n 生成后的随机字符串
*/
randomStr (e) {
e = e || 6
const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
const a = t.length
let n = ''
for (let i = 0; i < e; i++) {
n += t.charAt(Math.floor(Math.random() * a))
}
return n
},
// TODO: 数字每隔3位加逗号。 返回数组
formatNumber (n) {
let b = parseInt(n).toString()
let list = []
if (b.length >= 4) {
let len = b.length
if (len <= 3) { return b }
let r = len % 3
let value = r > 0 ? b.slice(0, r) + ',' + b.slice(r, len).match(/\d{3}/g).join(',') : b.slice(r, len).match(/\d{3}/g).join(',')
list = value.match(/./g)
} else {
list = this.setSpecialNumber(n, n.length)
}
return list
},
// TODO: 数字切割
// TODO: 接受参数1: 要切割的数字; 参数2: 要切割的数字长度;
// TODO: 接受 返回一个数组。
setSpecialNumber (value, length) {
length = parseInt(length)
value = parseInt(value)
let arr = []
for (let x = 0; x < length; x++) {
arr[x] = 0
}
let i = 0
while (true) {
arr[i] = value % 10
value = parseInt(value / 10)
if (value === 0 || i === length - 1) break
i++
}
let list = []
let len = arr.length - 1
for (let y = 0; len >= 0; len--, y++) {
list[y] = arr[len]
}
return list
},
/**
* @method getTextWith 获取文字属性
* @param {String} text 文本
* @param {String} fontStyle 文本样式
* @returns
*/
getTextWith (
text = '',
fontStyle = '14px/1.6 "Microsoft YaHei"' // 设置字体大小和字体
) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.font = fontStyle
const elem = context.measureText(text)
return elem
},
/**
* @method debounce 函数防抖
* @param {Function} fn 回调函数
* @param {Int|Number} delay 延迟时间
*/
debounce (fn, delay) {
let timer = null // 声明计时器
return function () {
const context = this
const args = arguments
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
},
/**
* @method throttle 函数节流
* @param {Function} fn 回调函数
* @param {Int|Number} delay 延迟间隔
* @param {Object} ctx 当前函数执行上下文对象
*/
throttle (fn, delay = 1000, ctx) {
let isAvail = true
let count = false
let movement = null
return function () {
count = true
let args = arguments
if (isAvail) {
fn.apply(ctx, args)
isAvail = false
count = false
setTimeout(function () {
isAvail = true
}, delay)
}
if (count) {
clearTimeout(movement)
movement = setTimeout(function () {
fn.apply(ctx, args)
}, 2 * delay)
}
}
}
}
/**
* @file request/apis 所有接口url
*/
export default {}
/**
* vue-axios 封装(报错/权限/跳转/拦截/提示)
* 基础需求:
* 统一捕获接口报错 => axiso 内置拦截器
* 弹窗提示 => 自定义(如: `Element-ui Message` 组件)
* 报错重定向 => 路由钩子(根据实际需要配置)
* 基础权限 => 服务端过期时间戳和token, 以及借助路由钩子(根据实际需要决定是否配置)
* 表单序列化 => npm 模块 qs
*/
import axios from 'axios'
import qs from 'qs'
export const Axios = axios.create({
baseURL: '/', // 反向代理配置
timeout: 10000,
responseType: 'json',
withCredentials: true, // 是否允许携带cookie等
headers: {
'Content-Type': 'application/x-www-form-urlencodedcharset=utf-8'
}
})
// POST传参序列化(添加请求拦截器)
Axios.interceptors.request.use(
config => {
// 在发送请求之前做的事情
if (
config.method === 'post' ||
config.method === 'put' ||
config.method === 'delete'
) {
// 序列化
config.data = qs.stringify(config.data)
}
// 若是有权限token, 则给请求头加上token
if (window.localStorage.token) {
config.headers.Authorization = window.localStorage.token
}
return config
},
error => {
// 错误提示信息, 可以自定义, 如 `Element-ui Message` 弹窗组件
// `error.data.error.message` 根据实际返回错误数据信息调整
console.error(error.data.error.message)
}
)
// 返回状态判断(添加响应拦截器)
Axios.interceptors.response.use(
res => {
let data = res.data
return data
},
error => {
let errorInfo = error.data ? (error.data.error ? error.data.error.message : error.data) : {}
return Promise.reject(errorInfo)
}
)
// Vue-axios 中文文档 https://www.kancloud.cn/yunye/axios/234845
import apis from './apis' // 接口url
import { Axios } from './axiosPlugin' // axios请求
// 设置请求域名
const isPrd = process.env.NODE_ENV === 'production' // 默认生产环境
let domain = window.location.origin
if (!isPrd) {
domain = 'http://192.168.0.135:8881' // 开发环境跨域默认配置
}
/**
* @method fetch 获取接口请求
* @param {String} url 接口路径url(不包含请求域名), 必须
* @param {Object} params 请求入参, 非必须
* @param {Object} extra 请求方式其他配置, 如: 配置请求方式 - method: GET, 非必须
* @return {Promise} 返回Promise对象, 业务中自主处理
*/
const fetch = (url = '', params = {}, extra = {}) => {
try {
const currReqApi = apis[url] || ''
// 请求地址配置错误
if (!currReqApi) {
const errInfo = {
url,
code: '1',
message: '请求url不存在'
}
throw errInfo
}
const fullReqApi = domain + currReqApi
console.log('fetch-fullReqApi > ', fullReqApi)
return Axios({
method: extra.method ? extra.method.toLocaleLowerCase : 'GET',
url: fullReqApi,
data: params,
params // GET
})
} catch (error) {
error.code === '1' && console.error(`当前请求url > ${error.url} 不存在`)
throw error
}
}
// 对axios的实例重新封装成一个plugin, 方便 Vue.use(xxxx)
export default {
install (Vue, Option) {
// 将fetch挂载在全局
Vue.prototype.fetch = fetch
// 当前环境
Vue.prototype.isPrd = isPrd
}
}
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