├── static
└── .gitkeep
├── src
├── filters
│ ├── index.js
│ └── global.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ └── app.js
├── views
│ └── index.vue
├── App.vue
├── assets
│ └── scss
│ │ ├── reset.scss
│ │ └── index.scss
├── main.js
├── router
│ └── index.js
├── components
│ └── layout
│ │ ├── transition.vue
│ │ └── app.vue
└── utils
│ └── axios.js
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── .editorconfig
├── .gitignore
├── .babelrc
├── .postcssrc.js
├── index.html
├── README.md
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 相关过滤器
3 | */
4 | import './global.js'
5 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | direction: state => state.app.direction,
3 | }
4 | export default getters
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
18 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {},
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import app from './modules/app'
4 | import getters from './getters'
5 |
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | modules: {
10 | app,
11 | },
12 | getters
13 | })
14 |
15 | export default store
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | const app = {
2 | state: {
3 | direction: 'tip', //tip或者swiper(点击返回还是滑动返回)
4 | },
5 | mutations: {
6 | SET_DIRECTION: (state, direction) => {
7 | state.direction = direction
8 | }
9 | },
10 | }
11 |
12 | export default app
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | vue_vux_cli2
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/assets/scss/reset.scss:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | html,body,div,p,ul,li,dl,dt,dd,em,i,span,a,img,input,h1,h2,h3,h4,h5 {margin:0;padding:0;box-sizing: border-box;}
3 | h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}
4 | button,input,select,textarea{font-size:100%;outline:none}
5 | img { border: 0 none; vertical-align: top; pointer-events: none; }
6 | i, em, b{ font-style: normal; }
7 | ol, ul { list-style: none; }
8 | table { border-collapse: collapse; border-spacing: 0; }
9 | a { text-decoration: none; color: #666; }
10 | a:hover, a:active {outline: none;}
11 | input[type="text"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App'
3 | import router from './router'
4 | import store from './store'
5 | import '@/filters'
6 | import "babel-polyfill"
7 | import 'lib-flexible/flexible'
8 | import '@/assets/scss/index.scss'
9 |
10 | import FastClick from 'fastclick'
11 | FastClick.attach(document.body)
12 |
13 | import VueScroller from 'vue-scroller'
14 | Vue.use(VueScroller)
15 |
16 | import transition from '@/components/layout/transition';
17 | Vue.component('lg-transition', transition);
18 |
19 | Vue.config.productionTip = false
20 |
21 | /* eslint-disable no-new */
22 | new Vue({
23 | el: '#app',
24 | router,
25 | store,
26 | components: { App },
27 | template: ''
28 | })
29 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import store from '@/store'
4 | import Index from '@/views/index'
5 |
6 | Vue.use(Router)
7 |
8 | Router.prototype.goBack = function (val) {
9 | store.commit('SET_DIRECTION', val);
10 | if(store.state.direction == 'tip'){
11 | window.history.go(-1);
12 | }else{
13 | setTimeout(()=>{window.history.go(-1)},50);
14 | }
15 | }
16 |
17 | const router = new Router({
18 | routes: [
19 | {
20 | path: '/',
21 | name: 'Index',
22 | component: Index,
23 | }
24 | ]
25 | })
26 |
27 | router.afterEach((to, from) => {
28 | if(store.state.direction !== 'tip')
29 | store.commit('SET_DIRECTION', 'tip');
30 | })
31 |
32 | export default router;
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 本项目是在vue-cli基础上根据公司自身的情况打造的移动端脚手架,只需运行脚手架,就可以初始化整个项目。
2 |
3 | ### 项目技术架构
4 | ***
5 | * Vue.js : 前端页面展示
6 | * Vuex : 全局状态通信
7 | * axios:网络请求
8 | * Vue-router:路由跳转
9 | * Vux:移动端UI组件库
10 | * fastclick:去除点击延时
11 | * vue-scroller:刷新组件
12 | * reset.css:初始化浏览器样式
13 | * lib-flexible:移动端rem布局
14 | * px2rem-loader:自动将px转换为rem
15 | ***
16 | ### 参考资料
17 |
18 | * [vue-cli 配置flexible](https://segmentfault.com/a/1190000011883121#articleHeader2)
19 | * [使用Flexible实现手淘H5页面的终端适配](https://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html)
20 | * [vue-打造webapp项目的脚手架](https://www.jianshu.com/p/00b2bd13b075)
21 |
22 | ### 移动端适配
23 |
24 | * 根据不同屏幕动态写入font-size,以rem作为宽度单位,固定布局视口。
25 |
26 | 首先设置理想视口:
27 | ``
28 | 接下来计算 html 元素的 font-size,将可视视口的宽度乘以一个系数:
29 |
30 | > * 喜欢的请点心,关注,star ,fork,这些是我坚持下去的动力
31 |
--------------------------------------------------------------------------------
/src/assets/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import './reset.scss';
2 |
3 | html,
4 | body,
5 | #app{
6 | position: relative;
7 | width: 100%;
8 | height: 100%;
9 | }
10 | html, body {
11 | user-select: none;
12 | -webkit-touch-callout: none;
13 | -webkit-tap-highlight-color:rgba(0,0,0,0);
14 | -webkit-text-size-adjust:100%;
15 | -ms-text-size-adjust:100%;
16 | }
17 | body{
18 | font-family:"Helvetica Neue",Helvetica, Arial,sans-serif;
19 | font-size: 14px !important;
20 | line-height:1;
21 | -webkit-overflow-scrolling:touch;
22 | overflow-x: hidden;
23 | max-width: 1080Px;
24 | background: #f6f6f6;
25 | margin: 0 auto;
26 | }
27 | .container-view{
28 | position: absolute;
29 | left: 0;
30 | top: 0;
31 | width: 100%;
32 | height: 100%;
33 | background: #fff;
34 | z-index: 100;
35 | }
36 | .scroller{
37 | height: auto !important;
38 | top: 46px !important;
39 | bottom:0px !important;
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/components/layout/transition.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
24 |
25 |
--------------------------------------------------------------------------------
/src/utils/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | // axios 配置
4 | var instance = axios.create({
5 | headers:{
6 | 'Content-Type': 'application/json',
7 | },
8 | timeout: 30000,
9 | baseURL: '', //接口请求地址
10 | withCredentials: true //允许携带cookie
11 | })
12 |
13 | // 添加请求拦截器
14 | instance.interceptors.request.use(config => {
15 | return config
16 | }, error => {
17 | return Promise.reject(error);
18 | })
19 |
20 | // 添加响应拦截器
21 | instance.interceptors.response.use(response => {
22 | return response;
23 | }, error => {
24 | return Promise.reject(error);
25 | });
26 |
27 | export default instance;
28 |
29 | /**
30 | * 封装所有请求
31 | * @param methed
32 | * @param url
33 | * @param data
34 | * @param headers
35 | * @returns {Promise}
36 | */
37 | export function request(methed,url, data = {},headers) {
38 | return new Promise((resolve, reject) => {
39 | instance({
40 | method: methed || 'post',
41 | url:url,
42 | data: methed !== 'get' ? data : '',
43 | params: methed === 'get' ? data :'',
44 | headers: headers || {'Content-Type':'application/json'},
45 | })
46 | .then(response => {
47 | resolve(response.data);
48 | })
49 | .catch(err => {
50 | reject(err)
51 | })
52 | })
53 | }
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: './',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: false,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue_vux_cli2",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "clghy <503773902@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "axios": "^0.19.0",
14 | "fastclick": "^1.0.6",
15 | "lib-flexible": "^0.3.2",
16 | "vue": "^2.5.2",
17 | "vue-router": "^3.0.1",
18 | "vue-scroller": "^2.2.4",
19 | "vuex": "^3.1.1",
20 | "vux": "^2.9.4"
21 | },
22 | "devDependencies": {
23 | "autoprefixer": "^7.1.2",
24 | "babel-core": "^6.22.1",
25 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
26 | "babel-loader": "^7.1.1",
27 | "babel-plugin-syntax-jsx": "^6.18.0",
28 | "babel-plugin-transform-runtime": "^6.22.0",
29 | "babel-plugin-transform-vue-jsx": "^3.5.0",
30 | "babel-polyfill": "^6.26.0",
31 | "babel-preset-env": "^1.3.2",
32 | "babel-preset-stage-2": "^6.22.0",
33 | "chalk": "^2.0.1",
34 | "copy-webpack-plugin": "^4.0.1",
35 | "css-loader": "^0.28.0",
36 | "extract-text-webpack-plugin": "^3.0.0",
37 | "file-loader": "^1.1.4",
38 | "friendly-errors-webpack-plugin": "^1.6.1",
39 | "html-webpack-plugin": "^2.30.1",
40 | "less-loader": "^5.0.0",
41 | "node-notifier": "^5.1.2",
42 | "node-sass": "^4.12.0",
43 | "optimize-css-assets-webpack-plugin": "^3.2.0",
44 | "ora": "^1.2.0",
45 | "portfinder": "^1.0.13",
46 | "postcss-import": "^11.0.0",
47 | "postcss-loader": "^2.0.8",
48 | "postcss-url": "^7.2.1",
49 | "px2rem-loader": "^0.1.9",
50 | "rimraf": "^2.6.0",
51 | "sass-loader": "^7.1.0",
52 | "semver": "^5.3.0",
53 | "shelljs": "^0.7.6",
54 | "uglifyjs-webpack-plugin": "^1.1.1",
55 | "url-loader": "^0.5.8",
56 | "vue-loader": "^13.3.0",
57 | "vue-style-loader": "^3.0.1",
58 | "vue-template-compiler": "^2.5.2",
59 | "vux-loader": "^1.2.9",
60 | "webpack": "^3.6.0",
61 | "webpack-bundle-analyzer": "^2.9.0",
62 | "webpack-dev-server": "^2.9.1",
63 | "webpack-merge": "^4.1.0"
64 | },
65 | "engines": {
66 | "node": ">= 6.0.0",
67 | "npm": ">= 3.0.0"
68 | },
69 | "browserslist": [
70 | "> 1%",
71 | "last 2 versions",
72 | "not ie <= 8"
73 | ]
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/layout/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/filters/global.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | /**
3 | * 四舍五入
4 | */
5 | Vue.filter('decimal', function(value, num = 2) {
6 | var pow = Math.pow(10, num);
7 | return Math.round(value*pow)/pow;
8 | });
9 |
10 | /**
11 | * 制保留2位小数,如:2,会在2后面补上00.即2.00
12 | */
13 | Vue.filter('toDecimal', function(value,num=2) {
14 | if(num<=0) num = 2;
15 | var len = value.toString();
16 | var kk = len.indexOf('.');
17 | var f = len.substr(0,num+kk+1);
18 | //var pow = Math.pow(10, 5);
19 | //var f = (value*pow)/pow;
20 | var s = f.toString();
21 | var rs = s.indexOf('.');
22 | if (rs < 0) {
23 | rs = s.length;
24 | s += '.';
25 | }
26 | while (s.length <= rs + 2) {
27 | s += '0';
28 | }
29 | return s;
30 | });
31 |
32 | /**
33 | * 截取字符串
34 | * txt 字符串
35 | * num 字符串显示的长度
36 | */
37 | Vue.filter('txtSub',function(str,num = 10) {
38 | if(str != '' && str != undefined && str != null) {
39 | var len = 0;
40 | for (var i=0; i= 0x0001 && c <= 0x007e) || (0xff60<=c && c<=0xff9f)) {
43 | len++;
44 | }else {
45 | len+=2;
46 | }
47 | }
48 | if(len > num){
49 | return str.substring(0,num)+'...';
50 | }else{
51 | return str;
52 | }
53 | }
54 | });
55 |
56 | /**
57 | * 截取日期字符串 保留月份和日 s 5,e 5
58 | * 截取日期字符串 保留年月日 s 0,e 10
59 | * 截取日期字符串 保留月日和时间 s = 5, e = 11
60 | */
61 | Vue.filter('formatDateString',function(val, s = 5, e = 5){
62 | if(val != '' && val != undefined && val != null) {
63 | return val.substr(s,e);
64 | }
65 | });
66 |
67 | /**
68 | * 日期格式化
69 | * value 日期
70 | * type 展示类型
71 | */
72 | Vue.filter('formatDate',function(value,type = 1) {
73 | if(value != '' && value != undefined && value != null) {
74 | var weekday=["星期日","星期一","星期二","星期三","星期四","星期五","星期六"];
75 | var date = new Date(value);
76 | var Y = date.getFullYear();
77 | var m = date.getMonth() + 1;
78 | var d = date.getDate();
79 | var H = date.getHours();
80 | var i = date.getMinutes();
81 | var s = date.getSeconds();
82 | var k = date.getDay();
83 | var str = '';
84 | if (m < 10) {
85 | m = '0' + m;
86 | }
87 | if (d < 10) {
88 | d = '0' + d;
89 | }
90 | if (H < 10) {
91 | H = '0' + H;
92 | }
93 | if (i < 10) {
94 | i = '0' + i;
95 | }
96 | if (s < 10) {
97 | s = '0' + s;
98 | }
99 | if(type == 1) {
100 | str = Y + '-' + m + '-' + d + ' ' + H + ':' + i + ':' + s;
101 | } else if (type == 2) {
102 | str = Y + '-' + m + '-' + d + ' ' + H + ':' + i;
103 | } else if (type == 3) {
104 | str = Y + '-' + m + '-' + d + ' ' + H ;
105 | } else if (type == 4) {
106 | str = Y + '-' + m + '-' + d ;
107 | } else if (type == 5) {
108 | str = Y + '-' + m ;
109 | } else if (type == 6) {
110 | str = Y ;
111 | } else if(type == 7){
112 | str = weekday[k];
113 | }
114 | return str;
115 | }
116 | });
117 |
118 | /**
119 | * 根据日期显示时间格式
120 | * value 日期
121 | * type 显示格式(默认:今天以内为小时前,1小时内为分钟前,2天以内为昨天,今天,2天以外显示日期格式:08-31)
122 | */
123 | Vue.filter('DateForm',function(value,type = 1) {
124 | if(value != '' && value != undefined && value != null) {
125 | var value = value.replace(/\-/g, "/");
126 | var val = new Date(value).getTime();
127 | var now = new Date().getTime();
128 | var cha = parseInt((now-val)/(1000*60)); //计算差值 到分钟
129 | var cha1 = parseInt((now-val)/(1000*60*60*24));;//计算差值到天
130 | var str = '';
131 | if(type == 1){
132 | if(cha<1*60){
133 | str = cha+'分钟前';
134 | }else if(cha <24*60){
135 | str = parseInt((now-val)/(1000*60*60))+'小时前';
136 | }else if(cha1 == 1){
137 | str = '昨天';
138 | }else if(cha1 == 2){
139 | str = '前天';
140 | }else{
141 | str = value.substr(5,5);
142 | }
143 | }
144 | return str;
145 | }
146 | });
147 |
148 | /**
149 | * 根据日期获取距离当前的天数
150 | */
151 | Vue.filter('dateDiff',function(endDate,type = 1) {
152 | if(endDate != '' && endDate != undefined && endDate != null) {
153 | var startDate = new Date().getTime(); //获取当前的日期,这里显示的是时间戳
154 | var times = new Date(endDate) - startDate;
155 | var get_Day = '';
156 | if(type == 1){
157 | get_Day = parseInt(times/(3600*24*1000));
158 | }else if(type == 2){
159 | get_Day = parseInt(times/(3600*1000));
160 | }
161 | return get_Day;
162 | }
163 | });
164 |
165 | /**
166 | * 隐藏手机号中间四位,变成*星号
167 | */
168 | Vue.filter('phoneStar',function(val){
169 | if(val != '' && val != undefined && val != null) {
170 | var reg = /^(\d{3})\d{4}(\d{4})$/;
171 | return val.replace(reg,"$1****$2");
172 | }
173 | });
174 |
175 | /**
176 | * decodeURIComponent转码
177 | */
178 | Vue.filter('decodeString',function(val){
179 | if(val != '' && val != undefined && val != null) {
180 | return decodeURIComponent(val);
181 | }
182 | });
183 |
184 | /**
185 | * 数字格式化,为空则显示为0
186 | */
187 | Vue.filter('numberString',function(val,num = 0){
188 | if(val != '' && val != undefined && val != null) {
189 | return val;
190 | }else{
191 | return num;
192 | }
193 | });
194 |
195 | /**
196 | * 翻转字符串
197 | */
198 | Vue.filter('reverse', function(str) {
199 | return str.split('').reverse().join('');
200 | });
201 |
202 | /**
203 | * 对用户输入的字符串进行反XSS处理,去掉onclick, javascript:alert,