Commit 6a2c8003 by zsh

初始化项目

parents
{
// 业务代码使用
// 全局方式引入, 会污染全局环境
// 执行顺序: 从下往上, 从右往左
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": "10"
},
"corejs": "2",
"useBuiltIns": "usage" // 根据已使用的语法添加polyfill, 无需单独再引入(import '@babel/polyfill')
}]
],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
// 类库等代码使用, 且无需引入polyfill
// 闭包方式引入, 不会污染全局环境
// "plugins": [["@babel/plugin-transform-runtime", {
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false
// }]]
}
root = true
[*]
charset = uft-8
end_of_line = lf
indent_size = 4 # 代码缩进数量
indent_style = space # 代码缩进类型 - 空格
insert_final_newline = true # 保存文件时自动在文件末尾添加一行空行(需安装对应的插件 - EditorConfig for VSCode)
trim_trailing_whitespace = true # 自动删除行末尾的空格
{
"extends": "standard",
"plugins": [
"html"
],
"parser": "babel-eslint",
"rules": {
"indent": ["error", 4], // 缩进4行
"no-new": "off" // 允许使用 new 关键字
}
}
## 徐汇教育局项目
```
npm install
开发环境
npm run dev
打包部署
npm run build
```
### 页面名称
```
```
// 此文件是项目打包服务,用来构建一个全量压缩包
// 命令: npm run build
"use strict";
// node for loading
const ora = require("ora");
// rm-rf for node
const rm = require("rimraf");
// console for node
const chalk = require("chalk");
// path for node
const path = require("path");
// webpack
const webpack = require("webpack");
// webpack production setting
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"
)
);
});
});
// 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: {}
}
}
// 此文件主要是webpack开发环境和生成环境的通用配置
'use strict'
// 引入node path路径模块
const path = require('path')
const webpack = require('webpack')
// 引入webpack生产环境配置参数
const prodConfig = require('../config/config').build
const createVueLoaderOptions = require('./vue-loader.config')
// 拼接路径
function resolve(track) {
return path.join(__dirname, '..', track)
}
// 资源路径
function assetsPath(_path) {
return path.join(prodConfig.staticPath, _path)
}
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}"`
}
})
]
// webpack基本配置
module.exports = {
// 项目入口文件 -> webpack从此处开始构建
entry: path.resolve(__dirname, '../src/index.js'),
// 配置模块如何被解析
resolve: {
// 自动解析文件扩展名(补全文件后缀)(左 -> 右)
extensions: ['.js', '.vue', '.json'],
// 配置别名映射
alias: {
// 键后加上$,表示精准匹配!
vue$: 'vue/dist/vue.esm.js',
'@': resolve('src'),
utils: resolve('src/utils'),
components: resolve('src/components'),
public: resolve('public')
}
},
module: {
// 处理模块的规则(可在此处使用不同的loader来处理模块!)
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, 用来转义ES6
{
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/', // 静态资源输出路径
}
}
]
},
{
// 当前loader需要处理的文件类型(后缀)
test: /\.(eot|ttf|svg)$/, // iconfont字体文件
// 处理test中的文件类型需要用到的loader类型(名称)
use: {
loader: 'file-loader', // 处理静态资源类型
}
}
]
},
plugins: defaultPlugins.concat([])
}
// 该文件主要用于构建开发环境
'use strict'
// 引入node path路径模块
const path = require('path')
// 引入webpack
const webpack = require('webpack')
// 引入webpack开发环境配置参数
const devConfig = require('../config/config').dev
// 引入webpack基本配置
const baseConf = require('./webpack.base.conf')
// webpack配置合并模块,可简单的理解为与Object.assign()功能类似
const merge = require('webpack-merge')
// 创建html入口文件的webpack插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 编译提示的webpack插件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 发送系统通知的node模块
const notifier = require('node-notifier')
// 将webpack基本配置与开发环境配置合并!
const devConf = merge(baseConf, {
// 项目出口, webpack-dev-server 生成的包并没有写入硬盘, 而是放在内存中
output: {
// 文件名
filename: '[name].[hash:8].js',
// html引用资源路径,在dev-server中,引用的是内存中文件
publicPath: devConfig.publicPath
},
// 生成sourceMaps(方便调试)
devtool: devConfig.devtoolType,
// 启动一个express服务器,使我们可以在本地进行开发
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: {
// 处理模块的规则(可在此处使用不同的loader来处理模块)
rules: [
// 使用vue-style-loader!css-loader!postcss-loader处理以css结尾的文件
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
// 使用vue-style-loader!css-loader!postcss-loader处理以less结尾的文件!
{
test: /\.less$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'less-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
// 使用vue-style-loader!css-loader!postcss-loader处理以scss结尾的文件
{
test: /\.scss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
]
},
//使用element-ui
{
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
// 此文件主要用于构建生产环境的配置
'use strict'
// 引入node path路径模块
const path = require('path')
// 引入webpack
const webpack = require('webpack')
// 一个webpack配置合并模块,可简单的理解为与Object.assign()功能类似!
const merge = require('webpack-merge')
// 引入webpack生产环境配置参数
const prodConfig = require('../config/config').build
// 引入webpack基本配置
const baseConf = require('./webpack.base.conf')
// 创建html入口文件的webpack插件!
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 抽离出css的webpack插件
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 压缩css的webpack插件
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 拷贝文件的webpack插件
// const CopyWebpackPlugin = require('copy-webpack-plugin')
// 资源路径
function assetsPath(_path) {
return path.join(prodConfig.staticPath, _path)
}
// 将webpack基本配置与生产环境配置合并!
const prodConf = merge(baseConf, {
// 项目出口配置
output: {
// build后所有文件存放的位置
path: path.resolve(__dirname, '../dist'),
// html引用资源路径,可在此配置cdn引用地址!
publicPath: prodConfig.publicPath,
// 文件名
filename: assetsPath('js/[name].[chunkhash:8].js'),
// 用于打包require.ensure(代码分割)方法中引入的模块
chunkFilename: assetsPath('js/[name].[chunkhash:8].js')
},
// 生成sourceMaps(方便调试)
devtool: prodConfig.devtoolType,
module: {
// 处理模块的规则(可在此处使用不同的loader来处理模块!)
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: [
// 每个chunk头部添加vue-cli-init!
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
// 该文件主要用来配置构建开发环境和生产环境差异化的参数
const _path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// vue-loader基本配置
const baseVueLoaderConf = {
// 引入postcss插件
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
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
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'
}
}
File added
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>徐汇教育局</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
logo.png

6.69 KB

This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "webpack-vue",
"version": "1.0.0",
"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",
"start": "npm run dev",
"build": "cross-env NODE_ENV=production node 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"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.2.0",
"@babel/runtime": "^7.2.0",
"@babel/runtime-corejs2": "^7.7.4",
"autoprefixer": "^9.3.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.4",
"chalk": "^2.3.0",
"copy-webpack-plugin": "^4.3.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.7",
"eslint": "^4.16.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^4.0.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^4.0.0",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"less": "^2.7.3",
"less-loader": "^4.0.5",
"lodash": "^4.17.21",
"node-notifier": "^5.1.2",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.3.0",
"postcss-loader": "^2.0.10",
"rimraf": "^2.6.2",
"sass-loader": "^7.1.0",
"url-loader": "^0.6.2",
"vue-awesome-swiper": "^3.1.3",
"vue-loader": "^13.6.1",
"vue-style-loader": "^3.0.3",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.9.7",
"webpack-merge": "^4.1.1"
},
"dependencies": {
"axios": "^0.17.1",
"echarts": "^5.0.2",
"vue": "^2.5.13",
"vue-awesome-swiper": "^3.1.3",
"vue-count-to": "^1.0.13",
"vue-router": "^3.0.1",
"vue-seamless-scroll": "^1.1.23",
"vuex": "^3.0.1"
}
}
module.exports = {
plugins: [
require("autoprefixer")({
overrideBrowserslist: ['last 2 version', '>1%', 'ios 7']
})
]
};
<template>
<div id="app">
<div class="page-bg" id="pageBg"></div>
<router-view></router-view>
</div>
</template>
<script>
import { refreshScale } from './public/utils/refreshSize'
export default {
name: 'app',
data () {
return {
backgroundImage: {}
}
},
created () {
this.initPage()
},
mounted () {
refreshScale()
window.addEventListener('resize', refreshScale)
},
methods: {
/**
* @method initPage 页面初始化 - ajax请求示例
*/
async initPage () {
this.backgroundImage = {
// background: 'url(../../static/images/bg.png) 0% 0% / 100%',
filter: 'blur(0px)',
opacity: 1
}
}
}
}
</script>
<style lang="scss">
body {
overflow: hidden;
background-color: #011428;
}
#app {
position: fixed;
top: 0;
left: 50%;
right: 0;
bottom: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
user-select: none;
// background: url('../static/images/index/index-bg-bottom.jpg') no-repeat left top;
background-size: 1920px 1080px;
background-position: center bottom;
background-size: cover;
}
.page-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
</style>
<template>
<div
class="content-container"
:style="{ width: width, height: height }"
>
<div class="content-main">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'ContentContainer',
props: {
layout: {
type: Object,
default () {
return {}
}
}
},
data () {
return {
width: 433,
height: 165
}
},
created () {
const { width = '', height = '' } = this.layout
if (width && height) {
this.width = width + 'px'
this.height = height + 'px'
}
}
}
</script>
<style lang="less">
@width: 7px;
@height: 8px;
.content-container {
position: relative;
width: 100%;
height: 100%;
margin-right: auto;
margin-left: auto;
border: 1px solid #1a4670;
&:before, &:after {
content: "";
position: absolute;
width: @width;
height: @height;
right: -1px;
background-repeat: no-repeat;
background-position: 0 0;
}
&:before {
top: -1px;
right: -1px;
background-image: url('./images/angle-top-right.png');
}
&:after {
right: -1px;
bottom: -1px;
background-image: url('./images/angle-bottom-right.png');
}
.content-main {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
&:before, &:after {
content: "";
position: absolute;
width: @width;
height: @height;
left: -1px;
background-repeat: no-repeat;
background-position: 0 0;
}
&:before {
top: -1px;
background-image: url('./images/angle-top-left.png');
}
&:after {
bottom: -1px;
background-image: url('./images/angle-bottom-left.png');
}
}
}
</style>
<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 :title="title" />
<slot></slot>
</div>
</template>
<script>
import Header from '@/components/Header'
export default {
name: 'Layout',
data () {
return {
headerTabList: [],
currSelectedIndex: null // 当前tab选中的index
}
},
props: {
layoutConf: {
type: Object,
default () {
return {}
}
},
title: ''
},
components: {
Header
},
created () {
window.vm = this
},
methods: {}
}
</script>
<style lang="less" scoped>
.main-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
<template>
<div
:class="chartOpt.className"
:id="chartOpt.id"
:style="{width: chartOpt.width, height: chartOpt.height}"
></div>
</template>
<script>
export default {
name: 'chartEl',
props: {
chartOpt: {
type: Object,
required: true,
default () {
return {}
}
},
// 是否开启轮播
isMove: {
type: Boolean,
default: true
}
},
data () {
return {
chart: null // 生成charts容器
}
},
mounted () {
this.initChart()
},
watch: {
// 监听option变动, 重新渲染图表
'chartOpt.option': {
handler (newVal, oldVal) {
if (newVal) {
this.$nextTick(() => {
this.initChart()
})
}
},
deep: true // 深度监听
}
},
methods: {
/**
* @method initChart 初始化图表
*/
initChart () {
const { id = '', option = {} } = this.chartOpt
if (id && option) {
const currChartEl = document.getElementById(id)
if (!this.isMove) {
this.echarts.dispose(currChartEl) // 先销毁, 防止重复创建
}
this.chart = this.echarts.init(currChartEl)
// 渲染图表
this.chart.setOption(option, true)
setTimeout(() => { // echarts图表自适应屏幕宽度
window.addEventListener('resize', () => {
this.chart.resize()
})
}, 200)
}
}
}
}
</script>
import Vue from 'vue'
import VueRouter from 'vue-router'
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'
// 引入css
import '../static/styles/reset.css'
import './public/utils/refreshSize'
Vue.use(Vuex)
Vue.use(VueRouter)
Vue.use(axiosServer)
Vue.use(UsePlugin)
const router = createRouter() // 创建router
const store = createStore() // 创建Vuex
new Vue({
el: '#app',
router,
store,
axiosServer,
UsePlugin,
template: '<App/>',
components: { App }
})
export default {
data () {
return {}
},
created () {
window.vm = this
},
beforeDestroy () {
}
}
export default {
data () {
return {}
},
watch: {
option: {
handler (val, oldVal) {
if (val) {
this.initComponents && this.initComponents()
}
},
deep: true
}
},
created () {
window.vm = this
},
methods: {}
}
// 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 = {}
/**
* @method install Vue插件
* @param {*} Vue
* @param {Object} options, 注册插件初始化时传递的参数
* 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)
}
export default UsePlugin
// 新增弹框的title需要在此增加
const bulletTitle = {
managementAnalysis: '教育发展课题管理分析',
teacherDevelop: '教师专业发展专项活动情况',
TeachersPersonal: '教师个人专业能力发展情况',
TeachActive: '教研活动开展情况',
schoolQuality: '学校办学质量分析'
}
export default {
bulletTitle
}
import { merge } from 'lodash'
function deepMerge (targetConf = {}, newConf = {}) {
return merge({}, targetConf, newConf)
}
export {
deepMerge
}
// 设置页面缩放比
export function refreshScale () {
let baseWidth = document.documentElement.clientWidth
let baseHeight = document.documentElement.clientHeight
let appStyle = document.getElementById('app').style
// let pageBgStyle = document.getElementById('pageBg').style
let realRatio = baseWidth / baseHeight
let designRatio = 16 / 9
let scaleRate = baseWidth / 1920
if (realRatio > designRatio) {
scaleRate = baseHeight / 1080
}
appStyle.transformOrigin = 'left top'
appStyle.transform = `scale(${scaleRate}) translateX(-50%)`
appStyle.width = `${baseWidth / scaleRate}px`
// pageBgStyle.transformOrigin = 'left top'
// pageBgStyle.transform = `scale(${scaleRate}) translateX(-50%)`
// pageBgStyle.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
}
},
/**
* [replacePunctuation 替换字符串中的所有标点符号]
* @param {String} str 目标字符串
* @return {String} 去除标点符号的字符串
*/
replacePunctuation (str) {
if (typeof str !== 'string') {
str = str.toString()
}
return str.replace(/\+/g, ' ')
// return str.replace(/[\ |\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\||\\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]/g, " ");
},
/**
* [currDates 当前日期]
* @return {Object} y - 年, like => 2018
* m - 月, like => 03
* d - 日, like => 02
* h - 时, like => 09
* M - 分, like => 09
* s - 秒, like => 07
* ym - 年月, like => 201804
* ymd - 年月日, like => 20180402
*/
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
}
},
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
}
},
/**
* [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}`
},
/**
* [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
},
/**
* [modalOpenEvent Modal显示事件]
* @description Modal显示时禁止浏览器滚动, 同时设置body 'padding-right' 为 '滚动条实际宽度', 防止抖动
* @param {Boolean} bl 是否显示Modal
* true - 显示Modal, 同时禁止浏览器滚动
* false - 隐藏Modal, 同时允许浏览器滚动
*/
modalOpenEvent (bl) {
let self = this
let oBody = document.body
let scrollBarWidth = `${self.getScrollBarWidth()}px`
oBody.className = bl ? 'modal-open' : ''
oBody.style.cssText = bl ? `padding-right: ${scrollBarWidth}` : ''
},
/**
* 获取每月的活动事件
* 按月返回数据,由于有跨天的活动,所以需要处理活动日历数据,按日期展开ajax数据
* @param {*} data :shlib.events.month返回数据 result.datas
* @param {*} date : 日期对象{start: '2018-03-01',end:'2018-04-01'}
*/
getEventsData (data, date) {
if (data.length === 0) {
return []
}
let self = this
date.start = new Date(date.start + ' 00:00:00')
date.end = new Date(date.end + ' 00:00:00')
let tempList = []
// let events = []; // 日历活动提示
// let dates = []; // 保存日期列表
for (let j = 0, k = data.length; j < k; j++) {
tempList = tempList.concat(spreadData(data[j]))
}
tempList.sort(function (a, b) {
return a.date < b.date ? 1 : -1
})
return collectEventsData(tempList)
// 判断活动是否跨天,如果是,则展开数据
function spreadData (data) {
// console.log(data);
let startTime = new Date(data.v3)
let endTime = new Date(data.v4)
let formatStart = ''
let formatEnd = ''
let tempList = []
if (Date.parse(endTime) < Date.parse(startTime)) {
let tempTime = startTime
startTime = endTime
endTime = tempTime
formatStart = self.formatDate(endTime, '-')
formatEnd = self.formatDate(startTime, '-')
} else {
formatStart = self.formatDate(startTime, '-')
formatEnd = self.formatDate(endTime, '-')
}
if (formatStart.timerStr !== formatEnd.timerStr) {
// 活动跨天
let s = 0
let e = 0
if (Date.parse(startTime) < Date.parse(date.start)) {
// 当前活动开始日期小于当前月份的开始日期
s = date.start
} else {
s = startTime
}
if (Date.parse(endTime) > Date.parse(date.end)) {
// 当前活动结束日期大于当前月份的结束日期
e = new Date(date.end)
e.setDate(0)
} else {
e = endTime
}
// 活动跨天,进行展开
for (let j = 0, l = e.getDate() - s.getDate() + 1; j < l; j++) {
tempList.push({
date: Date.parse(s.getFullYear() + '-' + (s.getMonth() + 1) + '-' + (s.getDate() + j) + ' 00:' + j + ':00'),
header: s.getFullYear() + '-' + self.zeroPadding(s.getMonth() + 1) + '-' + self.zeroPadding(s.getDate() + j)
// id: data.id, // 活动详情页id
// cover: data.v7,
// title: data.v1,
// type: data.v5,
// organizers: data.v8 || '', // 实际暂无v8字段
// location: data.v6,
// time: self.timerHandle(data.v3, data.v4)
})
}
// console.log(tempList);
return tempList
} else {
// 同一天
return [{
date: Date.parse(data.v3),
header: startTime.getFullYear() + '-' + self.zeroPadding(startTime.getMonth() + 1) + '-' + self.zeroPadding(startTime.getDate())
// id: data.id, // 活动详情页id
// cover: data.v7,
// title: data.v1,
// type: data.v5,
// organizers: data.v8 || '', // 实际暂无v8字段
// location: data.v6,
// time: self.timerHandle(data.v3, data.v4)
}]
}
}
// 收集日历活动事件
function collectEventsData (data) {
let events = [] // 日历活动提示
let dates = [] // 保存日期列表
for (let j = 0, k = data.length; j < k; j++) {
const element = data[j]
let dname = element.header
// let dname = element.time.split(' ')[0];
if (!findDate(dname)) {
dates.push(dname)
findObj(dname)
}
}
// console.log(events);
return events // 活动日历事件列表
function findDate (dateName) { // 查找日期列表是否已经存储了日期
if (dates.indexOf(dateName) < 0) {
return false
}
return true
}
// 遍历列表找对象
function findObj (dateName) {
let eventitem = {
date: dateName,
eventCount: 0
}
for (let i = 0, l = data.length; i < l; i++) {
const item = data[i]
if (item.header === dateName) {
eventitem.eventCount++
}
}
events.unshift(eventitem)
}
}
},
/**
* 数组转对象
*/
array2obj (array, key) {
var resObj = {}
for (var i = 0; i < array.length; i++) {
resObj[array[i][key]] = array[i]
}
return resObj
},
// 生成随机字符串
randomStr (e) {
e = e || 6
var t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
var a = t.length
var 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
}
}
/**
* @file request/apis 所有接口url
*/
export default {
index: '/api1', // 徐汇区教师队伍整体发展情况分析
indexJYFZDetail: '/api1JYFZDetail', // 教育发展课题管理分析弹窗
indexTeacherHonerDetail: '/api1teacherHonerDetail', // 教师专业发展专项活动情况
indexMapModal: '/api1MapPanel', // 地图定位点弹窗详情
childGardon: '/api2', // 区域整体办学质量检测分析(幼儿园)
childGardonDetail: '/api2detail', // 学校办学质量分析(幼儿园)
childPrimaryMiddleSchool: '/api3', // 区域整体办学质量检测分析(小学、中学)
childMapModal: '/api2MapPanel', // (幼儿园)地图定位点弹窗详情
middleMapModal: '/api3MapPanel', // (小学、中学)地图定位点弹窗详情
childPrimaryMiddleSchoolDetail: '/api3detail', // 学校办学质量分析(小学、中学)
cultivate: '/api4', // 教师队伍专业能力培养分析
cultivateSearch: '/api4searchTeacher', // 教师队伍专业能力培养分析 - 搜索
cultivateDetail: '/api4teacherAbilityDetail', // 教师个人专业能力发展情况
teacherActivity: '/api4jiaoYanDetail' // 教研活动开展情况
}
/**
* 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
}
}
import Router from 'vue-router'
import routes from './routes'
// 处理服务端渲染内存泄漏问题
export default () => {
return new Router({
routes,
// mode: 'history', // 去除#
fallback: true, // 如果浏览器不支持history模式则自动转换为hash模式
// base: '/base/', // 基础路径: 在路由配置的path前添加base: localhost:8000/base/app
// 当前路由被激活的class
linkActiveClass: 'active-link', // 匹配到部分路径 /login
// 当前路由如果有子路由, 则当前如果匹配到子路由时
// 则当前子级路由的上级路由都会匹配到linkActiveClass, 而不会匹配到linkExactActiveClass
// 只有当前路由被完全匹配到才会显示linkExactActiveClass
linkExactActiveClass: 'exact-active-link', // 匹配到完整路径 /login/exact
// 路由跳转后的滚动事件
scrollBehavior (to, from, savedPosition) {
// console.log('scrollBehavior > ', to, from, savedPosition)
// 如果已经在页面中滚动过, 则返回该路由时会自动滚动到之前的滚动位置
if (savedPosition) {
return savedPosition
} else {
// 默认返回到最顶部
return { x: 0, y: 0 }
}
}
// query转为json字符串
// parseQuery (query) {
// // localhost:8000/a.html?a=xxx&b=yyy
// },
// // json对象转为query
// stringifyQuery (obj) {}
})
}
// 导入页面名称
export default [
{
path: '/',
redirect: '/index'
}
]
/**
* @file actions/index Vuex-actions
*/
export default {
actionOpenBullet: ({ commit }, data) => {
return commit('openBullet', data)
},
actionCloseBullet: ({ commit }) => {
return commit('closeBullet')
},
actionTeacherAbility: ({ commit }, payload) => {
commit('setTeacherAbility', payload)
}
}
/**
* @file getters/index Vuex-getters
*/
export default {
}
import Vuex from 'vuex'
import defaultState from './state' // 默认数据
import mutations from './mutations'
import getters from './getters'
import actions from './actions'
const isDev = process.env.NODE_ENV === 'development'
console.warn('当前运行环境 > ', process.env.NODE_ENV)
// Vuex返回函数形式作用
// 便于SSR时使用, 如果每次重新渲染都使用同一个store会造成内存泄漏
export default () => {
const store = new Vuex.Store({
strict: isDev, // 开发环境中使用, 规范代码, 只能通过提交mutations的方式修改state
state: defaultState,
mutations,
getters,
actions,
plugins: [],
modules: {}
})
return store
}
/**
* @file mutations/index Vuex-mutations
*/
import allTitle from '../../public/utils/constant'
export default {
openBullet: (state, data) => {
// 打开弹框
state.showBullet = true
// 关闭其他所有插槽
for (let key in state.bulletConTent) {
state.bulletConTent[key] = false
}
// 变更弹框的title
state.bulletTitle = allTitle.bulletTitle[data.type]
// 打开对应弹框的插槽
state.bulletConTent[data.type] = true
// 根据data和type进行特殊处理
if (data.type === 'schoolQuality' && data.schoolKey) {
// 学校办学质量分析
state.schoolKey = data.schoolKey
}
},
closeBullet: (state) => {
// 直接关闭弹框
state.showBullet = false
},
// 教师个人能力发展情况
setTeacherAbility (state, payload) {
state.teacherAbility = payload.key || ''
}
}
/**
* @file state/index Vuex-state
*/
export default {
showBullet: false, // 是否展示弹框(外部的大框)
bulletTitle: '教研活动开展情况', // 弹框名字
bulletConTent: {
managementAnalysis: false, // 教育发展课题管理分析
teacherDevelop: false, // 教师专业发展专项活动情况
schoolQuality: false, // 学校办学质量分析
TeachActive: false, // 教研活动开展情况
TeachersPersonal: false // 教师个人专业能力发展情况
},
schoolKey: '', // 学校办学质量分析的key(用于请求弹框内数据)
teacherAbility: '' // 教师个人能力发展情况
}
/**
* Less-mixins.less - CSS Snippets
* 合并less输出到一个css --- 引入页面的CSS名称.css
* @author xiaxiansheng@adinnet.cn
*/
// 文件路径
@lessPatch: "../../";
@cssPatch: ""; //nav bar logo
// Clearfix - 清除浮动
.clearfix() {
&:before,
&:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
}
// 垂直居中
.table-cell () {
display: table-cell;
vertical-align: middle;
}
// 快速浮动
.pull-left {
float: left !important;
}
.pull-right {
float: right !important;
}
// 展示与隐藏
.show {
display: block !important;
}
.hidden {
display: none !important;
visibility: hidden !important;
}
.invisible {
visibility: hidden;
}
// 水平居中
.text-align(@textAlign) {
text-align: @textAlign;
}
// 垂直居中
.vertical-align(@dir) {
vertical-align: @dir;
}
// 元素垂直居中(IE9+)
.verticalCenter(@position) {
position: @position;
top: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
}
// 元素垂直居中
.vertical-center(){ // 居中元素的父级
font-size: 0;
text-align: center;
&:before{
content: "";
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
}
.ele-middle{ // 需要居中的元素
display: inline-block;
font-size: medium;
*text-align: left; // 解决低版本的IE文字居右的问题
vertical-align: middle;
}
}
// text-overflow - 单行文本溢出显示省略号
.text-overflow() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// multiLine-overflow - 多行溢出显示省略号(webkit)
.multiLine-overflow(@lineClamp: 2) {
display: -webkit-box; // 必须结合的属性,将对象作为弹性伸缩盒子模型显示
-webkit-line-clamp: @lineClamp; // 设置显示多少行
-webkit-box-orient: vertical; // 必须结合的属性,设置或检索伸缩盒对象的子元素的排列方式
overflow : hidden;
text-overflow: ellipsis; // 可以用来多行文本的情况下,用省略号“…”隐藏超出范围的文本
}
// 三角箭头 - 未实际测试
// .caret {
// display: inline-block;
// width: 0;
// height: 0;
// margin-left: 2px;
// vertical-align: middle;
// border-top: @caret-width-base dashed;
// border-top: @caret-width-base solid ~"\9"; // IE8
// border-right: @caret-width-base solid transparent;
// border-left: @caret-width-base solid transparent;
// }
// // Reverse the caret
// .caret {
// border-top: 0;
// border-bottom: @caret-width-base dashed;
// border-bottom: @caret-width-base solid ~"\9"; // IE8
// content: "";
// }
// 只在一边或两边显示盒子阴影
.box-shadow() {
background-color: #FF8020;
width: 160px;
height: 90px;
margin-top: -45px;
margin-left: -80px;
position: absolute;
top: 50%;
left: 50%;
&:after {
content: "";
width: 150px;
height: 1px;
margin-top: 88px;
margin-left: -75px;
display: block;
position: absolute;
left: 50%;
z-index: -1;
-webkit-box-shadow: 0px 0px 8px 2px #000000;
-moz-box-shadow: 0px 0px 8px 2px #000000;
box-shadow: 0px 0px 8px 2px #000000;
}
}
// 用CSS动画实现省略号动画
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis 2s infinite;
content: "\2026"; /* ascii code for the ellipsis character */
}
@keyframes ellipsis {
from {
width: 2px;
}
to {
width: 15px;
}
}
// Border-radius - 圆角
.border-radius(@radius) {
border-radius: @radius;
}
.border-top-radius(@radius) {
border-top-right-radius: @radius;
border-top-left-radius: @radius;
}
.border-right-radius(@radius) {
border-bottom-right-radius: @radius;
border-top-right-radius: @radius;
}
.border-bottom-radius(@radius) {
border-bottom-right-radius: @radius;
border-bottom-left-radius: @radius;
}
.border-left-radius(@radius) {
border-bottom-left-radius: @radius;
border-top-left-radius: @radius;
}
// center-block - 块元素水平居中
.center-block() {
display: block;
margin-left: auto;
margin-right: auto;
}
// hide-text - 隐藏文本
.hide-text() {
font: ~"0/0" a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
// hide-text - 隐藏文本 v3.0.1
.text-hide() {
.hide-text();
}
// User select - 选中文字
.user-select(@select) {
-webkit-user-select: @select;
-moz-user-select: @select;
-ms-user-select: @select; // IE10+
user-select: @select;
}
// img-responsive - 响应式图片
.img-responsive() {
display: block;
max-width: 100%;
height: auto;
}
// Retina图片显示
.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
background-image: url("@{file-1x}");
@media
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and ( min--moz-device-pixel-ratio: 2),
only screen and ( -o-min-device-pixel-ratio: 2/1),
only screen and ( min-device-pixel-ratio: 2),
only screen and ( min-resolution: 192dpi),
only screen and ( min-resolution: 2dppx) {
background-image: url("@{file-2x}");
background-size: @width-1x @height-1x;
}
}
// Opcity
.opacity(@opacity) {
opacity: @opacity;
// IE8 filter
@opacity-ie: (@opacity * 100);
filter: ~"alpha(opacity=@{opacity-ie})";
}
// Reset filters for IE - 清除IE9-的渐变背景
.reset-filter() {
filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
}
// reset-text - 重置默认文本
.reset-text() {
font-family: @font-family-base;
// We deliberately do NOT reset font-size.
font-style: normal;
font-weight: normal;
letter-spacing: normal;
line-break: auto;
line-height: @line-height-base;
text-align: left; // Fallback for where `start` is not supported
text-align: start;
text-decoration: none;
text-shadow: none;
text-transform: none;
white-space: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
}
// size - 设置元素尺寸
// size1 - 宽高不等
.size(@width; @height) {
width: @width;
height: @height;
}
// size2 - 宽高相等(正方形)
.square(@size) {
.size(@size; @size);
}
// WebKit-style focus
.tab-focus() {
// Default
outline: thin dotted;
// WebKit
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
// bg-variant - 背景色及其操作状态
.bg-variant(@color) {
background-color: @color;
a&:hover,
a&:focus {
background-color: darken(@color, 10%);
}
}
// Vendor Prefixes
//
// All vendor mixins are deprecated as of v3.2.0 due to the introduction of
// Autoprefixer in our Gruntfile. They will be removed in v4.
// - Animations
// - Backface visibility
// - Box shadow
// - Box sizing
// - Content columns
// - Hyphens
// - Placeholder text
// - Transformations
// - Transitions
// - User Select
// Animations - CSS3动画
.animation(@animation) {
-webkit-animation: @animation;
-o-animation: @animation;
animation: @animation;
}
.animation-name(@name) {
-webkit-animation-name: @name;
animation-name: @name;
}
.animation-duration(@duration) {
-webkit-animation-duration: @duration;
animation-duration: @duration;
}
.animation-timing-function(@timing-function) {
-webkit-animation-timing-function: @timing-function;
animation-timing-function: @timing-function;
}
.animation-delay(@delay) {
-webkit-animation-delay: @delay;
animation-delay: @delay;
}
.animation-iteration-count(@iteration-count) {
-webkit-animation-iteration-count: @iteration-count;
animation-iteration-count: @iteration-count;
}
.animation-direction(@direction) {
-webkit-animation-direction: @direction;
animation-direction: @direction;
}
.animation-fill-mode(@fill-mode) {
-webkit-animation-fill-mode: @fill-mode;
animation-fill-mode: @fill-mode;
}
// Backface visibility
// Prevent browsers from flickering when using CSS 3D transforms.
// Default value is `visible`, but can be changed to `hidden`
.backface-visibility(@visibility){
-webkit-backface-visibility: @visibility;
-moz-backface-visibility: @visibility;
backface-visibility: @visibility;
}
// Drop shadows
//
// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
// supported browsers that have box shadow capabilities now support it.
.box-shadow(@shadow) {
-webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
box-shadow: @shadow;
}
// Box sizing
.box-sizing(@boxmodel) {
-webkit-box-sizing: @boxmodel;
-moz-box-sizing: @boxmodel;
box-sizing: @boxmodel;
}
// CSS3 Content Columns
.content-columns(@column-count; @column-gap: @grid-gutter-width) {
-webkit-column-count: @column-count;
-moz-column-count: @column-count;
column-count: @column-count;
-webkit-column-gap: @column-gap;
-moz-column-gap: @column-gap;
column-gap: @column-gap;
}
// Optional hyphenation
.hyphens(@mode: auto) {
word-wrap: break-word;
-webkit-hyphens: @mode;
-moz-hyphens: @mode;
-ms-hyphens: @mode; // IE10+
-o-hyphens: @mode;
hyphens: @mode;
}
// Placeholder text - 表单占位符
.placeholder(@color: @input-color-placeholder) {
// Firefox
&::-moz-placeholder {
color: @color;
opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
}
&:-ms-input-placeholder { color: @color; } // Internet Explorer 10+
&::-webkit-input-placeholder { color: @color; } // Safari and Chrome
}
// Transformations - 2D转换
.scale(@ratio) {
-webkit-transform: scale(@ratio);
-ms-transform: scale(@ratio); // IE9 only
-o-transform: scale(@ratio);
transform: scale(@ratio);
}
.scale(@ratioX; @ratioY) {
-webkit-transform: scale(@ratioX, @ratioY);
-ms-transform: scale(@ratioX, @ratioY); // IE9 only
-o-transform: scale(@ratioX, @ratioY);
transform: scale(@ratioX, @ratioY);
}
.scaleX(@ratio) {
-webkit-transform: scaleX(@ratio);
-ms-transform: scaleX(@ratio); // IE9 only
-o-transform: scaleX(@ratio);
transform: scaleX(@ratio);
}
.scaleY(@ratio) {
-webkit-transform: scaleY(@ratio);
-ms-transform: scaleY(@ratio); // IE9 only
-o-transform: scaleY(@ratio);
transform: scaleY(@ratio);
}
.skew(@x; @y) {
-webkit-transform: skewX(@x) skewY(@y);
-ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
-o-transform: skewX(@x) skewY(@y);
transform: skewX(@x) skewY(@y);
}
.translate(@x; @y) {
-webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9 only
-o-transform: translate(@x, @y);
transform: translate(@x, @y);
}
.translate3d(@x; @y; @z) {
-webkit-transform: translate3d(@x, @y, @z);
transform: translate3d(@x, @y, @z);
}
.rotate(@degrees) {
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees); // IE9 only
-o-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.rotateX(@degrees) {
-webkit-transform: rotateX(@degrees);
-ms-transform: rotateX(@degrees); // IE9 only
-o-transform: rotateX(@degrees);
transform: rotateX(@degrees);
}
.rotateY(@degrees) {
-webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9 only
-o-transform: rotateY(@degrees);
transform: rotateY(@degrees);
}
.perspective(@perspective) {
-webkit-perspective: @perspective;
-moz-perspective: @perspective;
perspective: @perspective;
}
.perspective-origin(@perspective) {
-webkit-perspective-origin: @perspective;
-moz-perspective-origin: @perspective;
perspective-origin: @perspective;
}
.transform-origin(@origin) {
-webkit-transform-origin: @origin;
-moz-transform-origin: @origin;
-ms-transform-origin: @origin; // IE9 only
transform-origin: @origin;
}
// Transitions - 过渡
.transition(@transition) {
-webkit-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
.transition-property(@transition-property) {
-webkit-transition-property: @transition-property;
transition-property: @transition-property;
}
.transition-delay(@transition-delay) {
-webkit-transition-delay: @transition-delay;
transition-delay: @transition-delay;
}
.transition-duration(@transition-duration) {
-webkit-transition-duration: @transition-duration;
transition-duration: @transition-duration;
}
.transition-timing-function(@timing-function) {
-webkit-transition-timing-function: @timing-function;
transition-timing-function: @timing-function;
}
.transition-transform(@transition) {
-webkit-transition: -webkit-transform @transition;
-moz-transition: -moz-transform @transition;
-o-transition: -o-transform @transition;
transition: transform @transition;
}
/**
* common.less 通用less
*/
@import './Less-mixins.less';
@import "./layout.less"; // 基础三栏布局
@headerHeight: 67px;
.clearfix {
.clearfix();
}
.img-responsive {
.img-responsive();
}
@leftSize: 530px;
@rightSize: 530px;
.layout-main {
// height: ~"calc(100% - 90px)";
.center, .left, .right {
float: left;
height: 100%;
}
.left {
position: relative;
width: @leftSize;
margin-left: -100%;
padding-left: 30px;
box-sizing: border-box;
padding-right: 30px;
}
.right {
position: relative;
padding-right: 30px;
padding-left: 20px;
width: @rightSize;
margin-left: -@rightSize;
font-size: 16px;
box-sizing: border-box;
padding-right: 30px;
box-sizing: border-box;
}
.center {
width: 100%;
box-sizing: border-box;
.content {
position: relative;
height: 100%;
margin-left: @leftSize;
margin-right: @rightSize;
box-sizing: border-box;
}
}
}
/**
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header,
menu, nav, output, ruby, section, summary,
time, mark, audio, video, input {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font-weight: 400;
vertical-align: baseline;
outline: none;
font-style:normal
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, menu, nav, section {
display: block;
}
body {
-webkit-font-smoothing: antialiased;
line-height: 1;
font-family: Arial, "Microsoft Yahei";
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
a {
text-decoration: none;
-webkit-backface-visibility: hidden;
}
li {
list-style: none;
}
input {
outline: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-appearance: none;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0px 100px white inset;
}
html, body {
width: 100%;
height: 100%;
scroll-behavior: smooth;
}
input, textarea {
user-select: text;
}
* {
-webkit-tap-highlight-color: transparent;
outline: none;
}
*:not(input,textarea) {
-webkit-touch-callout: none;
-webkit-user-select: none;
box-sizing: border-box;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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