├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── compile ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.tsx ├── api │ ├── api.ts │ └── service.ts ├── assets │ ├── img │ │ └── time.png │ └── style │ │ └── index.scss ├── components │ └── CommonTitle │ │ └── index.vue ├── index.html ├── index.ts ├── lib │ ├── axios.ts │ └── utils.ts ├── mock │ └── index.ts ├── route.ts ├── routes │ ├── index │ │ ├── index.scss │ │ └── index.tsx │ └── test │ │ ├── index.scss │ │ └── index.tsx ├── store │ ├── index.ts │ └── modules │ │ └── global.ts └── types │ ├── images.d.ts │ ├── index.d.ts │ └── vue.d.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | "@babel/plugin-transform-runtime", 5 | "transform-vue-jsx", 6 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 7 | "@babel/plugin-proposal-class-properties", 8 | ] 9 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/**/* 2 | compile/* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | parser: "vue-eslint-parser", 4 | parserOptions: { 5 | parser: "@typescript-eslint/parser", 6 | sourceType: "module" 7 | }, 8 | extends: [ 9 | "airbnb-base", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | plugins: [ 13 | "vue", 14 | "@typescript-eslint" 15 | ], 16 | globals: { 17 | "use": "error", 18 | "window": "error", 19 | "document": true 20 | }, 21 | rules: { 22 | "@typescript-eslint/camelcase": "off", 23 | "@typescript-eslint/no-explicit-any": "off", 24 | "@typescript-eslint/explicit-function-return-type": "off", 25 | "@typescript-eslint/indent": ["error", 4], 26 | "@typescript-eslint/no-empty-interface": "off", 27 | "@typescript-eslint/array-type": "off", 28 | "@typescript-eslint/explicit-member-accessibility": 0, 29 | "vue/html-indent": ["error", 4], 30 | "vue/html-indent": "off", 31 | "vue/max-attributes-per-line": "off", 32 | "camelcase": "off", 33 | "arrow-parens": ["error", "as-needed"], 34 | "import/extensions": "off", 35 | "lines-between-class-members": "off", 36 | "indent": ["error", 4], 37 | "class-methods-use-this": "off", 38 | "import/prefer-default-export": "off", 39 | "import/newline-after-import": "off", 40 | "global-require": "off", 41 | "prefer-template": "off", 42 | "max-len": ["error", 200], 43 | "no-console": "off", 44 | "no-debugger": "warn", 45 | "newline-per-chained-call": "off", 46 | "object-curly-newline": "off", 47 | "no-param-reassign": "off", 48 | "no-restricted-syntax": [ 49 | "error", 50 | "LabeledStatement", 51 | "WithStatement" 52 | ], 53 | "no-underscore-dangle": "off", 54 | "import/no-unresolved": [ 55 | "error", 56 | { 57 | "ignore": ['components/'] 58 | } 59 | ] 60 | }, 61 | settings: { 62 | 'import/resolver': { 63 | webpack: { 64 | config: path.join(__dirname, './compile/webpack.dev.js') 65 | } 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | node_modules/ 5 | .npm 6 | build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-template 2 | 3 | Vue 移动端单页面开发模板 4 | 5 | Webpack4 + Babel7 + Typescript + JSX 6 | 7 | - 代码检查:ESLint 8 | - 路由管理:Vue Route 9 | - 状态管理:Vuex 10 | - 移动端适配方案:Flexible rem 11 | - HTTP:Axios 12 | 13 | ## Usage 14 | 15 | ``` 16 | git clone https://github.com/axuebin/vue-template.git 17 | ``` 18 | 19 | ``` 20 | cd vue-template 21 | npm install 22 | npm run dev 23 | npm run build 24 | ``` 25 | 26 | ## 项目结构 27 | 28 | ``` 29 | ├── compile // webpack 配置文件 30 | │   ├── webpack.common.js 31 | │   ├── webpack.dev.js 32 | │   └── webpack.prod.js 33 | ├── src 34 | │   ├── App.tsx 35 | │   ├── api // api 文件夹,管理请求相关 36 | │   │   ├── api.ts // api path 37 | │   │   └── service.ts // api service 38 | │   ├── assets // 静态文件 39 | │  │   ├── img // 静态图片 40 | │   │   └── style // 样式 41 | │   │   └── index.scss // 全局样式 42 | │   ├── components // 组件 43 | │   │   └── CommonTitle 44 | │   │   └── index.vue // vue 文件,不用 jsx 的写法 45 | │   ├── index.html // html 模板 46 | │   ├── index.ts // 入口文件 47 | │   ├── lib 48 | │   │   ├── axios.ts // 封装一下 axios 49 | │   │   └── utils.ts // 工具 50 | │   ├── route.ts // 路由管理 51 | │   ├── routes // 路由页面 52 | │   │   ├── index 53 | │   │   │   ├── index.scss 54 | │   │   │   └── index.tsx // jsx 写法 55 | │   │   └── test 56 | │   │   ├── index.scss 57 | │   │   └── index.tsx 58 | │   ├── store // store 文件夹 59 | │   │   ├── index.ts 60 | │   │   └── modules // 分 module 管理 store 61 | │   │   └── global.ts // 全局 store 62 | │   ├── types // 库定义 63 | │   │  ├── index.d.ts 64 | │   │  └── vue.d.ts 65 | │   └── mock // 本地接口 mock 文件 66 | │   ├── index.ts 67 | ├── package-lock.json 68 | ├── package.json 69 | ├── postcss.config.js // postcss 配置文件 70 | ├── tsconfig.json // ts 配置文件 71 | └── README.md 72 | ``` -------------------------------------------------------------------------------- /compile/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const { VueLoaderPlugin } = require('vue-loader'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | 7 | const env = process.env.NODE_ENV; 8 | const chunkSorts = ['vendor', 'app']; 9 | 10 | const resolve = p => path.resolve(__dirname, p); 11 | 12 | function styleLoaders(lang) { 13 | const loaders = [{ 14 | loader: 'css-loader' 15 | }, { 16 | loader: 'postcss-loader', 17 | options: { 18 | config: { 19 | path: resolve('postcss.config.js'), 20 | }, 21 | }, 22 | }] 23 | if (lang === 'scss') { 24 | loaders.push({ 25 | loader: 'sass-loader', 26 | options: { 27 | indentedSyntax: false, 28 | }, 29 | }); 30 | } 31 | if (env === 'production') { 32 | return ExtractTextPlugin.extract({ 33 | fallback: 'vue-style-loader', 34 | use: ['css-loader', 'sass-loader'], 35 | }); 36 | } 37 | return ['vue-style-loader'].concat(loaders); 38 | } 39 | 40 | 41 | module.exports = { 42 | entry: { 43 | app: resolve('../src/index.ts'), 44 | }, 45 | output: { 46 | path: resolve('../build'), 47 | filename: '[name].[hash:10].js', 48 | chunkFilename: '[id].[hash:10].js', 49 | }, 50 | resolve: { 51 | extensions: ['.js', '.ts', '.tsx', '.vue', '.json'], 52 | alias: { 53 | '@': resolve('../src'), 54 | vue$: 'vue/dist/vue.esm.js', 55 | }, 56 | }, 57 | plugins: [ 58 | new HtmlWebpackPlugin({ 59 | template: resolve('../src/index.html'), 60 | filename: './index.html', 61 | excludeChunks: [], 62 | chunksSortMode: (a, b) => chunkSorts.indexOf(a.names[0]) - chunkSorts.indexOf(b.names[0]), 63 | minify: { 64 | collapseWhitespace: true, 65 | removeComments: true, 66 | }, 67 | }), 68 | new VueLoaderPlugin(), 69 | new ExtractTextPlugin('[name].[hash:10].css'), 70 | new OptimizeCssAssetsPlugin({ 71 | assetNameRegExp: /\.css$/g, 72 | cssProcessor: require('cssnano'), 73 | cssProcessorPluginOptions: { 74 | preset: ['default', { discardComments: { removeAll: true } }], 75 | }, 76 | canPrint: true, 77 | }) 78 | ], 79 | module: { 80 | rules: [ 81 | { test: /\.vue$/, loader: 'vue-loader' }, 82 | { 83 | test: /\.js$/, 84 | loader: 'babel-loader', 85 | exclude: /node_modules/, 86 | query: { 87 | cacheDirectory: true, 88 | }, 89 | }, 90 | { 91 | test: /\.ts$/, 92 | use: [ 93 | { 94 | loader: 'babel-loader', 95 | }, 96 | { 97 | loader: 'ts-loader', 98 | options: { 99 | appendTsSuffixTo: ['\\.vue$'], 100 | }, 101 | }, 102 | ], 103 | }, 104 | { 105 | test: /\.tsx$/, 106 | exclude: /node_modules/, 107 | enforce: 'pre', 108 | use: [ 109 | 'babel-loader', 110 | { 111 | loader: 'ts-loader', 112 | options: { 113 | appendTsSuffixTo: ['\\.vue$'], 114 | }, 115 | }, 116 | ], 117 | }, 118 | { test: /\.css$/, use: styleLoaders() }, 119 | { test: /\.scss$/, use: styleLoaders('scss') }, 120 | { 121 | test: /\.(png|jpg|jpeg)$/, 122 | include: path.resolve(__dirname, '../src'), 123 | use: [ 124 | { 125 | loader: 'url-loader', 126 | options: { limit: 20240 }, 127 | }, 128 | { loader: 'img-loader' }, 129 | ], 130 | }, 131 | ], 132 | }, 133 | optimization: { 134 | splitChunks: { 135 | chunks: "all", 136 | }, 137 | }, 138 | }; 139 | -------------------------------------------------------------------------------- /compile/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const common = require('./webpack.common.js'); 5 | 6 | module.exports = merge(common, { 7 | mode: 'development', 8 | // devtool: 'inline-source-map', 9 | devServer: { 10 | contentBase: path.join(__dirname, '../src'), 11 | port: 7777, 12 | host: '0.0.0.0', 13 | hot: true, 14 | compress: true, 15 | }, 16 | module: { 17 | rules: [{ 18 | test: /\.(js|vue|ts|tsx)$/, 19 | loader: 'eslint-loader', 20 | exclude: /node_modules/, 21 | enforce: 'pre', 22 | }], 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /compile/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "types": "types/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "rimraf ./build/ && NODE_ENV=production webpack --config compile/webpack.prod.js", 10 | "dev": "NODE_ENV=development webpack-dev-server --config compile/webpack.dev.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "^7.4.5", 17 | "@babel/plugin-proposal-class-properties": "^7.4.4", 18 | "@babel/plugin-proposal-decorators": "^7.4.4", 19 | "@babel/plugin-transform-runtime": "^7.4.4", 20 | "@babel/preset-env": "^7.4.5", 21 | "@babel/preset-stage-2": "^7.0.0", 22 | "@babel/runtime": "^7.4.5", 23 | "@typescript-eslint/eslint-plugin": "^1.10.2", 24 | "@typescript-eslint/parser": "^1.10.2", 25 | "babel-core": "^7.0.0-bridge.0", 26 | "babel-loader": "^7.1.5", 27 | "babel-plugin-syntax-jsx": "^6.18.0", 28 | "babel-plugin-transform-vue-jsx": "^3.7.0", 29 | "css-loader": "^3.0.0", 30 | "eslint": "^5.16.0", 31 | "eslint-config-airbnb-base": "^13.1.0", 32 | "eslint-import-resolver-webpack": "^0.11.1", 33 | "eslint-loader": "^2.1.2", 34 | "eslint-plugin-import": "^2.17.3", 35 | "eslint-plugin-vue": "^5.2.2", 36 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 37 | "html-webpack-plugin": "^3.2.0", 38 | "node-sass": "^4.12.0", 39 | "optimize-css-assets-webpack-plugin": "^5.0.1", 40 | "postcss-loader": "^3.0.0", 41 | "regenerator-runtime": "^0.13.2", 42 | "sass-loader": "^7.1.0", 43 | "style-loader": "^0.23.1", 44 | "ts-loader": "^6.0.2", 45 | "typescript": "^3.5.2", 46 | "vue-loader": "^15.7.0", 47 | "vue-style-loader": "^4.1.2", 48 | "webpack": "^4.34.0", 49 | "webpack-cli": "^3.3.4", 50 | "webpack-dev-server": "^3.7.1", 51 | "webpack-merge": "^4.2.1" 52 | }, 53 | "dependencies": { 54 | "autoprefixer": "^9.6.0", 55 | "axios": "^0.19.0", 56 | "axios-mock-adapter": "^1.16.0", 57 | "img-loader": "^3.0.1", 58 | "lib-flexible": "^0.3.2", 59 | "postcss-px2rem": "^0.3.0", 60 | "url-loader": "^2.0.0", 61 | "vue": "^2.6.10", 62 | "vue-class-component": "^7.1.0", 63 | "vue-property-decorator": "^8.2.1", 64 | "vue-router": "^3.0.6", 65 | "vue-template-compiler": "^2.6.10", 66 | "vuex": "^3.1.1", 67 | "vuex-class": "^0.3.2" 68 | }, 69 | "browserslist": [ 70 | "last 20 version" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const px2remConfigs = { 2 | baseDpr: 1, 3 | remUnit: 37.5, 4 | onePxComment: '1px', 5 | forcePxComment: '!px', 6 | keepComment: '!no', 7 | forcePxProperty: ['font-size'], 8 | }; 9 | 10 | module.exports = { 11 | plugins: [ 12 | require('autoprefixer')(), 13 | require('postcss-px2rem')(px2remConfigs), 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'vue-property-decorator'; 3 | import '@/assets/style/index.scss'; 4 | 5 | @Component({}) 6 | export default class App extends Vue { 7 | render() { 8 | return
9 | 10 |
; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/api/api.ts: -------------------------------------------------------------------------------- 1 | const base = 'https://api.axuebin.com'; 2 | 3 | export const queryListPath = `${base}/blog/api/v1/list/query`; 4 | -------------------------------------------------------------------------------- /src/api/service.ts: -------------------------------------------------------------------------------- 1 | import axios from '@/lib/axios'; 2 | import { 3 | queryListPath, 4 | } from './api'; 5 | 6 | export const queryList = async (payload = {}): Promise => { 7 | const response = await axios(queryListPath, payload, 'get'); 8 | return response; 9 | }; 10 | -------------------------------------------------------------------------------- /src/assets/img/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axuebin/mobile-vue-typescript-template/699364005d72892193fdffbf10473a5144b40fb2/src/assets/img/time.png -------------------------------------------------------------------------------- /src/assets/style/index.scss: -------------------------------------------------------------------------------- 1 | body, html, #app { 2 | height: 100%; 3 | background-color: #F5F5F5; 4 | overflow-x: hidden; 5 | overflow-y: hidden; 6 | -webkit-overflow-scrolling: unset; 7 | } 8 | 9 | * { 10 | padding: 0; 11 | margin: 0; 12 | } -------------------------------------------------------------------------------- /src/components/CommonTitle/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | 22 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions, CreateElement } from 'vue'; 2 | import router from './route'; 3 | import store from './store/index'; 4 | import App from './App'; 5 | import 'lib-flexible/flexible'; 6 | 7 | if (process.env.NODE_ENV === 'development') { 8 | require('./mock'); 9 | } 10 | 11 | /* eslint-disable */ 12 | const vmOps: ComponentOptions = { 13 | el: '#app', 14 | router, 15 | store, 16 | render: (h: CreateElement) => h(App), 17 | } 18 | new Vue(vmOps); 19 | -------------------------------------------------------------------------------- /src/lib/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios from 'axios'; 2 | 3 | // 每个请求都需要携带的公参 4 | const commonQuery = {}; 5 | 6 | const axiosIns = Axios.create(); 7 | 8 | axiosIns.interceptors.request.use(config => config, error => Promise.reject(error)); 9 | 10 | const axios = (url, data: any = {}, method = 'get'): Promise => { 11 | const config: any = {}; 12 | config.method = method; 13 | config.url = url; 14 | config.params = commonQuery; 15 | const { headers } = data; 16 | delete data.headers; 17 | if (method === 'get') { 18 | config.params = { ...config.params, ...data }; 19 | } else { 20 | if (data.params) { 21 | config.params = { ...config.params, ...data.params }; 22 | delete data.params; 23 | } 24 | config.data = data; 25 | } 26 | 27 | // 可能这里会有一些 promise 28 | return Promise.all([]).then(() => { 29 | // 鉴权方式自定义 30 | config.headers = { 31 | ...headers, 32 | }; 33 | return axiosIns.request(config).then(response => { 34 | console.log(response.data); 35 | return response.data; 36 | }, error => { 37 | if (error.response && error.response.data && error.response.data.errors && error.response.data.errors.length > 0) { 38 | console.log(error.response.data.errors[0].message); 39 | } else { 40 | console.log('网络貌似有点问题,请稍后再试哦'); 41 | } 42 | return Promise.reject(error); 43 | }); 44 | }); 45 | }; 46 | 47 | export default axios; 48 | 49 | export { axiosIns }; 50 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | export function qs() { 2 | const { href } = window.location; 3 | const result = {}; 4 | let param = null; 5 | const reg = /[?&](.*?)=([^&#]*)/g; 6 | param = reg.exec(href); 7 | while (param) { 8 | try { 9 | result[param[1]] = decodeURIComponent(param[2]); 10 | } catch (e) { 11 | try { 12 | result[param[1]] = unescape(param[2]); 13 | } catch (escapeErr) { 14 | result[param[1]] = param[2]; // eslint-disable-line 15 | } 16 | } 17 | param = reg.exec(href); 18 | } 19 | return result; 20 | } 21 | -------------------------------------------------------------------------------- /src/mock/index.ts: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter'; 2 | import { axiosIns } from '@/lib/axios'; 3 | import { queryListPath } from '@/api/api'; 4 | const mock = new MockAdapter(axiosIns); 5 | 6 | mock.onGet(queryListPath).reply(200, { 7 | success: 0, 8 | data: [ 9 | { id: 1, name: 'Vue 移动端单页面开发模板', user: 'axuebin' }, 10 | { id: 2, name: 'typescipt vue 脚手架', user: 'xuebin' }, 11 | ], 12 | }); 13 | -------------------------------------------------------------------------------- /src/route.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router, { RouteConfig } from 'vue-router'; 3 | import App from './routes/index/index'; 4 | import Test from './routes/test/index'; 5 | 6 | Vue.use(Router); 7 | 8 | const routes: RouteConfig[] = [ 9 | { 10 | path: '/', 11 | name: 'index', 12 | component: App, 13 | }, 14 | { 15 | path: '/test', 16 | name: 'test', 17 | component: Test, 18 | }, 19 | ]; 20 | 21 | const router: Router = new Router({ 22 | base: '/', 23 | routes, 24 | }); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /src/routes/index/index.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | width: 375px; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | overflow-y: auto; 7 | -webkit-overflow-scrolling: touch; 8 | background-color: #FFFFFF; 9 | .msg { 10 | margin-top: 12px; 11 | font-size: 14px; 12 | color: #FF5151; 13 | display: flex; 14 | align-items: center; 15 | .time-img { 16 | width: 16px; 17 | height: 16px; 18 | margin-right: 4px; 19 | } 20 | } 21 | .btn { 22 | margin-top: 12px; 23 | padding: 0 12px; 24 | height: 30px; 25 | font-size: 14px; 26 | text-align: center; 27 | line-height: 30px; 28 | background-color: #FF5151; 29 | color: #FFFFFF; 30 | border-radius: 4px; 31 | } 32 | .vuex-test { 33 | margin-top: 12px; 34 | display: flex; 35 | align-items: center; 36 | .count { 37 | font-size: 16px; 38 | } 39 | &-btn { 40 | padding: 0 12px; 41 | height: 30px; 42 | margin-left: 24px; 43 | border: 1px solid #E5E5E5; /*!no*/ 44 | border-radius: 4px; 45 | text-align: center; 46 | line-height: 30px; 47 | } 48 | } 49 | .list { 50 | margin-top: 12px; 51 | &-item { 52 | margin-top: 6px; 53 | & > span { 54 | margin-left: 12px; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/routes/index/index.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { namespace } from 'vuex-class'; 3 | import { Component } from 'vue-property-decorator'; 4 | import CommonTitle from '@/components/CommonTitle/index.vue'; 5 | import { Me as GlobalMe } from '@/store/modules/global'; 6 | import { queryList } from '@/api/service'; 7 | import timeImg from '@/assets/img/time.png'; 8 | import '@/assets/style/index.scss'; 9 | import './index.scss'; 10 | 11 | const globalModule = namespace('global'); 12 | 13 | interface ListItem { 14 | id: number; 15 | name: string; 16 | user: string; 17 | } 18 | 19 | @Component({ 20 | components: { 21 | CommonTitle, 22 | }, 23 | }) 24 | export default class Index extends Vue { 25 | @globalModule.State('count') count: number; 26 | @globalModule.State('me') me: GlobalMe; 27 | @globalModule.Action('increment') onIncrement: Function; 28 | title: string = '首页'; 29 | msg: string = 'hello world'; 30 | list: ListItem[] = []; 31 | mounted() { 32 | this.queryList(); 33 | console.log(this.me.name); 34 | } 35 | async queryList() { 36 | await queryList().then(res => { 37 | if (res.success === 0) { 38 | this.list = res.data; 39 | } 40 | }).catch(err => { 41 | console.log('err', err); 42 | }); 43 | } 44 | routerPushTest() { 45 | this.$router.push({ path: 'test', query: { name: 'axuebin' } }); 46 | } 47 | onClickCountIncrement() { 48 | this.onIncrement(); 49 | } 50 | render() { 51 | const { title, msg, count, list } = this; 52 | return
53 | 54 |

{msg}

55 |
路由跳转测试页面
56 |
57 |

count: {count}

58 |
count +
59 |
60 | { 61 | list.length > 0 62 | ?
63 | { 64 | list.map(item =>
65 | {item.id} 66 | {item.name} 67 | {item.user} 68 |
) 69 | } 70 |
71 | : '' 72 | } 73 |
; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/routes/test/index.scss: -------------------------------------------------------------------------------- 1 | .test { 2 | width: 375px; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | overflow-y: auto; 7 | -webkit-overflow-scrolling: touch; 8 | background-color: #FFFFFF; 9 | .msg { 10 | margin-top: 12px; 11 | font-size: 14px; 12 | color: #FF5151; 13 | } 14 | .btn { 15 | margin-top: 12px; 16 | padding: 0 12px; 17 | height: 30px; 18 | font-size: 14px; 19 | text-align: center; 20 | line-height: 30px; 21 | background-color: #FF5151; 22 | color: #FFFFFF; 23 | border-radius: 4px; 24 | } 25 | .vuex-test { 26 | .count { 27 | margin-top: 12px; 28 | font-size: 16px; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/routes/test/index.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { namespace } from 'vuex-class'; 3 | import { Component } from 'vue-property-decorator'; 4 | import CommonTitle from '@/components/CommonTitle/index.vue'; 5 | import { qs } from '@/lib/utils'; 6 | import '@/assets/style/index.scss'; 7 | import './index.scss'; 8 | 9 | const globalModule = namespace('global'); 10 | 11 | @Component({ 12 | components: { 13 | CommonTitle, 14 | }, 15 | }) 16 | export default class Index extends Vue { 17 | @globalModule.State('count') count: number; 18 | title: string = '测试'; 19 | msg: string = 'hello world'; 20 | mounted() { 21 | console.log(qs()); 22 | } 23 | routerPushTest() { 24 | this.$router.push('/'); 25 | } 26 | render() { 27 | const { title, msg, count } = this; 28 | return
29 | 30 |

{msg}

31 |
路由跳转首页
32 |
33 |

count: {count}

34 |
35 |
; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import GlobalModule from './modules/global'; 4 | 5 | Vue.use(Vuex); 6 | 7 | const store = new Vuex.Store({ 8 | modules: { 9 | global: GlobalModule, 10 | }, 11 | }); 12 | 13 | export default store; 14 | -------------------------------------------------------------------------------- /src/store/modules/global.ts: -------------------------------------------------------------------------------- 1 | import { Commit } from 'vuex'; 2 | 3 | export interface Me { 4 | name: string; 5 | age: number; 6 | } 7 | 8 | export interface GlobalState { 9 | count: number; 10 | me: Me; 11 | } 12 | 13 | const state: GlobalState = { 14 | count: 0, 15 | me: { 16 | name: 'axuebin', 17 | age: 1, 18 | }, 19 | }; 20 | 21 | const mutations: any = { 22 | increment(states: any) { 23 | states.count += 1; 24 | }, 25 | }; 26 | 27 | const actions: any = { 28 | increment(context: { commit: Commit }) { 29 | context.commit('increment'); 30 | }, 31 | }; 32 | 33 | export default { 34 | namespaced: true, 35 | state, 36 | mutations, 37 | actions, 38 | }; 39 | -------------------------------------------------------------------------------- /src/types/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | declare module '*.gif'; 5 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*vue' { 2 | import Vue from 'vue/dist/vue.esm.js'; 3 | export default Vue; 4 | } 5 | 6 | // declare module '*.scss' { 7 | // const content: {[className: string]: string} 8 | // export default content 9 | // } 10 | -------------------------------------------------------------------------------- /src/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions, VNode } from 'vue'; 2 | 3 | declare module 'vue/types/options' { 4 | interface ComponentOptions { 5 | app?: any; 6 | } 7 | } 8 | 9 | declare global { 10 | namespace JSX { 11 | interface Element extends VNode { } 12 | interface ElementClass extends Vue { } 13 | interface IntrinsicElements { 14 | [elem: string]: any; 15 | } 16 | } 17 | } 18 | 19 | declare module 'vue/types/vue' { 20 | interface Vue { 21 | $app: any; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "jsxFactory": "h", 5 | "allowSyntheticDefaultImports": true, 6 | "experimentalDecorators": true, 7 | "strictFunctionTypes": false, 8 | "sourceMap": true, 9 | "module": "ESNext", 10 | "moduleResolution": "node", 11 | "target": "ES2016", 12 | "lib": [ 13 | "es2017", 14 | "es2015", 15 | "es2015.promise", 16 | "dom", 17 | ], 18 | "typeRoots": [ 19 | "./node_modules/@types", 20 | "./types" 21 | ], 22 | "baseUrl": ".", 23 | "paths": { 24 | "vue": ["node_modules/vue/types"], 25 | "@/*": ["src/*"], 26 | } 27 | }, 28 | "include": [ 29 | "src/**/*" 30 | ], 31 | "exclude": [ 32 | "node_modules", 33 | "compile", 34 | ], 35 | } 36 | --------------------------------------------------------------------------------