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
/**
* @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: '' // 教师个人能力发展情况
}
/**
* 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