├── .babelrc.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── integration
│ └── examples
│ │ └── index.spec.js-temp
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── dist
├── vue-router-cache.esm.js
├── vue-router-cache.js
├── vue-router-cache.min.js
└── vue-router-cache.umd.js
├── examples
└── base
│ ├── .babelrc
│ ├── .editorconfig
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .npmrc
│ ├── .postcssrc.js
│ ├── README.md
│ ├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── svgtofont.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ ├── webpack.prod.conf.js
│ └── webpack.test.conf.js
│ ├── config
│ ├── dev.env.js
│ ├── index.js
│ ├── prod.env.js
│ └── test.env.js
│ ├── dist
│ ├── index.html
│ └── static
│ │ ├── css
│ │ └── app.ee62648eb41f28c6dcc3f65bbed9a6ec.css
│ │ ├── favicon.ico
│ │ └── js
│ │ ├── 0.e5d2bd9b54edcb265d00.js
│ │ ├── 1.85e93daacd5d5ee52249.js
│ │ ├── 2.ce5276a4acd7fba9efd3.js
│ │ ├── 3.887f5c911af41e18021a.js
│ │ ├── 4.c2ab346742beedb49c80.js
│ │ ├── 5.56ad8f276acac6f40f5b.js
│ │ ├── 6.1a7fe521fe0160fd7f46.js
│ │ ├── app.3a0004ed54250ecc1b0b.js
│ │ ├── manifest.1b9b4c5812c25f16ba1e.js
│ │ └── vendor.1bd63756866cd79c1966.js
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── App.vue
│ ├── api
│ │ └── list.js
│ ├── assets
│ │ └── logo.png
│ ├── common
│ │ ├── data
│ │ │ └── list.js
│ │ ├── fonts
│ │ │ ├── common-icon.css
│ │ │ ├── common-icon.eot
│ │ │ ├── common-icon.less
│ │ │ ├── common-icon.svg
│ │ │ ├── common-icon.symbol.svg
│ │ │ ├── common-icon.ttf
│ │ │ ├── common-icon.woff
│ │ │ └── common-icon.woff2
│ │ ├── helpers
│ │ │ ├── dom.js
│ │ │ ├── utils.js
│ │ │ └── vconsole.js
│ │ ├── less
│ │ │ ├── animation.less
│ │ │ ├── base.less
│ │ │ ├── index.less
│ │ │ ├── mixin.less
│ │ │ ├── popup-transition.less
│ │ │ ├── reset.less
│ │ │ └── router-transition.less
│ │ ├── mixins
│ │ │ ├── recover-webview.js
│ │ │ └── router-transition.js
│ │ └── svg
│ │ │ ├── github.svg
│ │ │ └── location.svg
│ ├── components
│ │ ├── btn.vue
│ │ └── color-list.vue
│ ├── config.js
│ ├── main.js
│ ├── pages
│ │ ├── main
│ │ │ ├── children
│ │ │ │ ├── config.vue
│ │ │ │ └── enter.vue
│ │ │ ├── index.vue
│ │ │ └── routes.js
│ │ ├── router.js
│ │ └── test-case
│ │ │ ├── children
│ │ │ ├── letter-detail.vue
│ │ │ ├── letter-list.vue
│ │ │ ├── mixins
│ │ │ │ └── scroll.js
│ │ │ ├── number-detail.vue
│ │ │ └── number-list.vue
│ │ │ ├── index.vue
│ │ │ └── routes.js
│ ├── plugins
│ │ ├── vi-ui
│ │ │ ├── common
│ │ │ │ ├── helpers
│ │ │ │ │ ├── bom.js
│ │ │ │ │ ├── create-api.js
│ │ │ │ │ ├── dom.js
│ │ │ │ │ ├── ease.js
│ │ │ │ │ └── utils.js
│ │ │ │ ├── mixins
│ │ │ │ │ ├── duration.js
│ │ │ │ │ ├── popup.js
│ │ │ │ │ └── visibility.js
│ │ │ │ └── stylus
│ │ │ │ │ ├── base.styl
│ │ │ │ │ ├── index.styl
│ │ │ │ │ ├── mixin.styl
│ │ │ │ │ └── var
│ │ │ │ │ ├── color.styl
│ │ │ │ │ ├── ease.styl
│ │ │ │ │ ├── font-size.styl
│ │ │ │ │ └── z-index.styl
│ │ │ ├── components
│ │ │ │ ├── vi-collapse
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── transition-event.js
│ │ │ │ │ ├── vi-collapse-transition-group.js
│ │ │ │ │ ├── vi-collapse-transition.js
│ │ │ │ │ └── vi-collapse.vue
│ │ │ │ ├── vi-confirm
│ │ │ │ │ ├── api.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-confirm.vue
│ │ │ │ ├── vi-loading
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-loading.vue
│ │ │ │ ├── vi-page
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-page.vue
│ │ │ │ ├── vi-popup
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-popup.vue
│ │ │ │ ├── vi-scroll
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-scroll.vue
│ │ │ │ └── vi-switch
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vi-switch.vue
│ │ │ └── index.js
│ │ └── vue-router-cache
│ │ │ ├── vue-router-cache.esm.js
│ │ │ ├── vue-router-cache.js
│ │ │ ├── vue-router-cache.min.js
│ │ │ └── vue-router-cache.umd.js
│ ├── rem.js
│ └── store
│ │ ├── cache
│ │ └── local-storage
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── modules
│ │ └── router-transition
│ │ └── router-transition.js
│ └── static
│ ├── .gitkeep
│ └── favicon.ico
├── package.json
├── scripts
├── build.js
├── config.js
├── dev.js
├── publish.sh
└── util.js
├── src
├── api
│ └── router-cache.js
├── components
│ └── router-cache.js
├── config
│ └── index.js
├── history
│ ├── history-direction-name.js
│ ├── history-stack.js
│ └── history-state-event.js
├── index.js
├── router-middle
│ └── index.js
├── store
│ └── index.js
└── util
│ ├── env.js
│ ├── events.js
│ ├── lang.js
│ ├── log.js
│ └── stack.js
└── test
└── util
└── stack.test.js
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | require('@babel/preset-env'),
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | /test/
6 | /examples
7 | /node_modules
8 | /cypress
9 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | 'ecmaVersion': 2017
7 | },
8 | env: {
9 | 'node': true,
10 | 'commonjs': true,
11 | },
12 | extends: [
13 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
14 | 'standard'
15 | ],
16 | rules: {
17 | 'arrow-parens': 'off',
18 | 'comma-dangle': 'off',
19 | 'eol-last': 'off',
20 | 'generator-star-spacsing': 'off',
21 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
22 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
23 | 'space-before-function-paren': ['error', {
24 | 'anonymous': 'always',
25 | 'named': 'never',
26 | 'asyncArrow': 'always'
27 | }],
28 | 'lines-between-class-members': 'off',
29 | 'prefer-const': 'error',
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.log
2 | node_modules/
3 | /package-lock.json
4 | /yarn.lock
5 | /.npmrc
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /build
2 | node_modules
3 | /examples
4 | /test
5 | /scripts
6 | /.npmrc
7 | /cypress
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10.13.0"
4 | branches:
5 | only:
6 | - master
7 | - test
8 | install:
9 | - npm install
10 | script:
11 | - npm run test
12 | - npm run build
13 | - cd examples/base
14 | - npm install
15 | - npm run svgtofont
16 | - npm run build
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-present kallsave
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | vue-router-cache
2 | ========================================
3 | 一个实现原生app前进刷新后退缓存并提供浏览器路由方向、灵活手动管理缓存api的vue-router插件
4 |
5 | 特性
6 | ------------
7 |
8 | - 提供浏览器路由方向(forward、back、replace)
9 | - 在知道路由是forward、back、replace的基础上,浏览器进入新页面缓存新页面,浏览器触发后退(back)时自动删除离开页面缓存,从而实现前进刷新后退缓存
10 | - 对累积的页面缓存可能导致的内存泄漏做了保护机制,提供max参数,当页面缓存达到max,会自动把最后方的页面缓存删除
11 | - 提供方便使用的多例模式(类似小程序页面)和手动清除页面缓存的单例模式
12 | - 单例提供管理缓存的api方法,这些方法的参数和vue-router的push方法的参数一致,这样做保证了代码的可读性
13 | - 支持嵌套路由 (嵌套路由请在最里层的router-view包裹router-cache,外层的router-view不要包裹router-cache)
14 |
15 | 在线案例
16 | ------------
17 | - [Base demo](https://kallsave.github.io/vue-router-cache/examples/base/dist/#/main/enter)
18 | - [Demo address](https://github.com/kallsave/vue-router-cache/tree/master/examples/base)
19 |
20 | 安装
21 | ------------
22 | npm install vue-router-cache --save
23 |
24 | 初始化的配置
25 | -----------
26 | ```javascript
27 | import router from './router'
28 | import VueRouterCache from 'vue-router-cache'
29 |
30 | Vue.use(VueRouterCache, {
31 | router: router,
32 | max: 10,
33 | // 多例模式
34 | isSingleMode: false,
35 | isDebugger: true,
36 | directionKey: 'direction',
37 | getHistoryStack() {
38 | const str = window.sessionStorage.getItem('historyStack')
39 | return JSON.parse(str)
40 | },
41 | setHistoryStack(history) {
42 | const str = JSON.stringify(history)
43 | window.sessionStorage.setItem('historyStack', str)
44 | }
45 | })
46 | ```
47 |
48 | ```javascript
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | ```
62 |
63 | 浏览器路由方向
64 | -----------
65 | ```javascript
66 |
67 | // 判断浏览器路由方向对相应的方向结合transition做过渡动画
68 | watch: {
69 | $route: {
70 | handler(to, from) {
71 | this.transitionDuration = TRANSITION_DURATION
72 | if (to.params.direction === 'back') {
73 | this.transitionName = 'move-left'
74 | this.transitionMode = ''
75 | } else if (to.params.direction === 'forward') {
76 | this.transitionName = 'move-right'
77 | this.transitionMode = ''
78 | } else {
79 | // replace或者第一次进来时
80 | this.transitionName = ''
81 | this.transitionMode = ''
82 | }
83 | },
84 | }
85 | }
86 | ```
87 |
88 | 多例模式下,后退的页面总是缓存的,不能手动删除缓存,利用activated钩子更新局部数据
89 | -----------
90 | ```javascript
91 | // back的时候如果想更新数据利用activated
92 | export default {
93 | activated() {
94 |
95 | },
96 | }
97 | ```
98 |
99 | 单例模式下,清除页面缓存方法的例子
100 | -----------
101 | ```javascript
102 | // 在vue组件实例中清除缓存
103 |
104 | // 从详情页修改了数据需要回退到列表页时手动删除列表页的缓存让列表页刷新的例子
105 | export default {
106 | methods: {
107 | // 方式1: 用back的方式,推荐
108 | back() {
109 | // remove的参数是location,和this.$router.push的参数是一样的
110 | this.$routerCache.remove({name: 'mainNumberList'})
111 |
112 | // 或者用路径的形式作为参数:
113 | // this.$routerCache.remove('/main/number-list')
114 |
115 | // 或者用路径的形式作为参数:
116 | // this.$routerCache.remove({
117 | // path: '/main/number-list'
118 | // })
119 |
120 | this.$router.back()
121 | },
122 | // 方式2: 用push的方式,但是浏览器会比使用back的方式多出额外的历史记录
123 | push() {
124 | // 直接使用push
125 | this.$router.push({name: 'mainNumberList'})
126 | },
127 | // 方式3: 用replace的方式
128 | replace() {
129 | // 直接使用replace
130 | this.$router.replace({name: 'mainNumberList'})
131 | }
132 | }
133 | }
134 | ```
135 |
136 | 单例模式下,js文件清除页面缓存方法的例子
137 | -----------
138 | ```javascript
139 | // 在js文件中清除缓存
140 | import VueRouterCache from 'vue-router-cache'
141 | // 删除路由名字为mainNumberList的页面缓存
142 | VueRouterCache.routerCache.remove({name: 'mainNumberList'})
143 | ```
144 |
145 | 或者不清除缓存,利用activated钩子更新局部数据,这样代码更好维护
146 | -----------
147 | ```javascript
148 | export default {
149 | activated() {
150 |
151 | },
152 | }
153 | ```
154 |
155 | 不希望当前页面走缓存
156 | -----------
157 | ```javascript
158 | import VueRouterCache from 'vue-router-cache'
159 |
160 | export default {
161 | beforeRouteEnter(to, from, next) {
162 | // 注意,一定要在render之前执行skip
163 | // 推荐在beforeRouteEnter钩子上使用
164 | VueRouterCache.routerCache.skip()
165 | next()
166 | },
167 | }
168 | ```
169 |
170 | 配置说明
171 | -----------
172 | |arg name|type|description|default|necessary|
173 | |:--:|:--:|:----------|:--:|:--:|
174 | |参数名称|参数类型|描述|默认值|必需|
175 | |router|router|vue-router的实例||是|
176 | |max|Number Int|缓存的最大数量,如果超过这个数量,会自动删除最后方的缓存|Infinity|否|
177 | |isSingleMode|Boolean|是否是单例模式|true|否|
178 | |isDebugger|Boolean|开始debugger模式,可以看到系统存在的页面缓存和使用中的页面缓存|false|否|
179 | |directionKey|String|挂载在$route.params[directionKey]上的key名|'direction'|否|
180 | |getHistoryStack|Function|判断浏览器路由方向时,如果需要刷新后仍能有效判断,用于做本地储存|noop|否|
181 | |setHistoryStack|Function|判断浏览器路由方向时,如果需要刷新后仍能有效判断,用于做本地储存|noop|否|
182 |
183 |
184 | 方法说明
185 | -----------
186 | |method name|arg|description|can use in when isSingleMode is false|
187 | |:--:|:--:|:----------|:--:|
188 | |方法名称|参数|描述|多例模式下是否可以使用|
189 | |remove|location|删除参数页面的缓存|否|
190 | |removeBackUntil|location|删除当前页面直到参数页面的缓存(不包括参数页面)|否|
191 | |removeBackInclue|location|删除当前页面一直到参数页面的缓存(包括参数页面)|否|
192 | |removeBackByIndex|number|删除从当前页面开始第n个页面的缓存|是|
193 | |removeExclude|location|删除除了参数页面以外的所有页面的缓存|否|
194 | |removeAll||删除所有页面的缓存|是|
195 | |getStore||查看系统中的页面缓存|是|
196 | |has|location|查看系统中的页面缓存是否有参数页面|否|
197 | |skip||当前页面不走页面缓存,注意最好在beforeRouteEnter钩子上执行|是|
198 |
199 | 页面重刷后依然想记住页面的前进后退关系的配置
200 | -----------
201 | ```javascript
202 | import router from './router'
203 | import VueRouterCache from 'vue-router-cache'
204 |
205 | Vue.use(VueRouterCache, {
206 | router: router,
207 | // 在配置VueRouterCache的时候使用getHistoryStack和setHistoryStack函数
208 | // 把历史记录存在本地储存里(由于不同端的本地储存不一样,所以自行选择储存的方式)
209 | getHistoryStack() {
210 | const str = window.sessionStorage.getItem('historyStack')
211 | return JSON.parse(str)
212 | },
213 | setHistoryStack(history) {
214 | const str = JSON.stringify(history)
215 | window.sessionStorage.setItem('historyStack', str)
216 | }
217 | })
218 | ```
219 |
220 | 单例模式和多例模式对比
221 | -----------
222 | - 最大的区别是单例模式可以手动删除页面缓存,而多例模式不能手动删除页面缓存而是通过activated来更新局部数据
223 |
224 | - 单例模式是指系统中一个路由对应的组件(缓存实例)只存在一个,比如A=>B=>C=>A,系统中只会存在3个缓存实例,所以路由和缓存实例(key)是一一对应的。
225 | - 多例模式是指系统中一个路由对应的组件(缓存实例)可以存在多个,比如A=>B=>C=>A,系统中会存在4个缓存实例,所以路由和缓存实例(key)是一对多的关系。
226 | - 多例模式系统代码好维护。(加载过的页面都有不同的缓存)
227 | - 单例模式系统的性能要高(加载过的同一个页面只会有一个缓存),有手动清除缓存的api,如果A要回跳C页面需要C页面刷新,可以调用api清除C页面缓存然后back到C页面。也可以直接push到C页面(但是这样浏览器会比执行back多存历史记录)。但是代码会不好维护。
228 |
229 |
230 | 建议
231 | -----------
232 | - 用法虽然多,但是为了代码的可维护性和降低复杂度,建议不要使用手动删除缓存的方式,而是通过activated去更新局部数据,类似小程序的页面模式
233 |
234 |
235 | 这个插件开发是需要关闭webpack热更新
236 | -----------
237 | 在开发环境使用这个插件保存代码会出现白屏,这是因为webpack热更新会分析改动代码来决定哪个模块热更新还是重刷,
238 | 这需要写webpack的相关loader,一个简单的做法是关闭webpack热更新只使用webpack重刷就正常使用啦
239 | ```javascript
240 | // in webpack.dev.conf.js
241 |
242 | devServer: {
243 | ...
244 | hot: false,
245 | ...
246 | },
247 |
248 | ```
249 | 更多介绍
250 | ------------
251 | [原理解析](https://juejin.im/post/5dccdb4a51882510ce752164)
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "viewportWidth": 750,
3 | "viewportHeight": 1334
4 | }
5 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/examples/index.spec.js-temp:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // describe('基本', () => {
4 |
5 | // context('列表到详情,返回详情列表页被缓存', () => {
6 |
7 | // it('用例', () => {
8 | // cy.visit('http://192.168.1.101:8011/#/test-case/number-list')
9 | // const divActive = cy.get('#item0')
10 | // divActive.click()
11 | // cy.url().should('include', 'http://192.168.1.101:8011/#/test-case/number-detail/0')
12 | // cy.go('back').then(() => {
13 | // cy.get('#item0').should('have.css', 'background', 'rgb(255, 215, 0) none repeat scroll 0% 0% / auto padding-box border-box')
14 | // })
15 | // })
16 |
17 | // })
18 | // })
19 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/examples/base/.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 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["transform-vue-jsx", "istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/base/.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 |
--------------------------------------------------------------------------------
/examples/base/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | /test/unit/coverage/
6 | /src/plugins/
7 |
--------------------------------------------------------------------------------
/examples/base/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | 'arrow-parens': 'off',
25 | 'comma-dangle': 'off',
26 | 'eol-last': 'off',
27 | // allow async-await
28 | 'generator-star-spacing': 'off',
29 | // allow debugger during development
30 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
31 | 'space-before-function-paren': ['error', {
32 | 'anonymous': 'always',
33 | 'named': 'never',
34 | 'asyncArrow': 'always'
35 | }],
36 | 'lines-between-class-members': 'off',
37 | 'prefer-const': 'error',
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/base/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | /test/unit/coverage/
7 | /test/e2e/reports/
8 | selenium-debug.log
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/examples/base/.npmrc:
--------------------------------------------------------------------------------
1 | loglevel=http
2 |
--------------------------------------------------------------------------------
/examples/base/.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 |
--------------------------------------------------------------------------------
/examples/base/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | > A Vue.js project
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 |
17 | # build for production and view the bundle analyzer report
18 | npm run build --report
19 |
20 | # run unit tests
21 | npm run unit
22 |
23 | # run e2e tests
24 | npm run e2e
25 |
26 | # run all tests
27 | npm test
28 | ```
29 |
30 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
31 |
--------------------------------------------------------------------------------
/examples/base/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/examples/base/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/examples/base/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/build/logo.png
--------------------------------------------------------------------------------
/examples/base/build/svgtofont.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const svgtofont = require('svgtofont')
4 | const glob = require('glob-all')
5 |
6 | function resolve(dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | function existsSync(filePath) {
11 | try {
12 | fs.statSync(filePath, fs.constants.R_OK | fs.constants.W_OK)
13 | return true
14 | } catch (err) {
15 | return false
16 | }
17 | }
18 |
19 | function mkdirSync(dirname) {
20 | if (existsSync(dirname)) {
21 | return true
22 | } else {
23 | if (mkdirSync(path.dirname(dirname))) {
24 | fs.mkdirSync(dirname)
25 | return true
26 | }
27 | }
28 | }
29 |
30 | const SVG_FOLDER = 'svg'
31 | const FONTS_FOLDER = 'fonts'
32 |
33 | const COMMON_FOLDER = 'common'
34 | const COMMON_PREFIX = 'common-icon'
35 |
36 | let distRuler = new RegExp(`(.*\/)?${SVG_FOLDER}`, 'g')
37 | let fontNameRuler = new RegExp(`(.*\/)?(.*)?\/${SVG_FOLDER}`, 'g')
38 |
39 | let fontFiles = glob.sync([
40 | resolve(`src/${COMMON_FOLDER}/**/${SVG_FOLDER}`),
41 | ])
42 |
43 | async function create() {
44 | for (let i = 0; i < fontFiles.length; i++) {
45 | await (() => {
46 | return new Promise((resolve, reject) => {
47 | let item = fontFiles[i]
48 | let dist = item.replace(distRuler, `$1${FONTS_FOLDER}`)
49 | let fontName = ''
50 | if (item.indexOf(COMMON_FOLDER) !== -1) {
51 | fontName = COMMON_PREFIX
52 | }
53 | let reg = new RegExp(`${SVG_FOLDER}`)
54 | let fontsFolderPath = item.replace(reg, `${FONTS_FOLDER}`)
55 | mkdirSync(fontsFolderPath)
56 | svgtofont({
57 | src: path.resolve(item),
58 | dist: path.resolve(dist),
59 | fontName,
60 | css: true,
61 | startNumber: 20000,
62 | emptyDist: false,
63 | }).then(() => {
64 | console.log('done')
65 | resolve()
66 | }).catch((err) => {
67 | console.log('err', err)
68 | })
69 | })
70 | })()
71 | }
72 | }
73 |
74 | create()
75 |
--------------------------------------------------------------------------------
/examples/base/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders(loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | const stylusOptions = {
58 | 'resolve url': true
59 | }
60 |
61 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
62 | return {
63 | css: generateLoaders(),
64 | postcss: generateLoaders(),
65 | less: generateLoaders('less'),
66 | sass: generateLoaders('sass', { indentedSyntax: true }),
67 | scss: generateLoaders('sass'),
68 | stylus: generateLoaders('stylus', stylusOptions),
69 | styl: generateLoaders('stylus', stylusOptions)
70 | }
71 | }
72 |
73 | // Generate loaders for standalone style files (outside of .vue)
74 | exports.styleLoaders = function (options) {
75 | const output = []
76 | const loaders = exports.cssLoaders(options)
77 |
78 | for (const extension in loaders) {
79 | const loader = loaders[extension]
80 | output.push({
81 | test: new RegExp('\\.' + extension + '$'),
82 | use: loader
83 | })
84 | }
85 |
86 | return output
87 | }
88 |
89 | exports.createNotifierCallback = () => {
90 | const notifier = require('node-notifier')
91 |
92 | return (severity, errors) => {
93 | if (severity !== 'error') return
94 |
95 | const error = errors[0]
96 | const filename = error.file && error.file.split('!').pop()
97 |
98 | notifier.notify({
99 | title: packageConfig.name,
100 | message: severity + ': ' + error.name,
101 | subtitle: filename || '',
102 | icon: path.join(__dirname, 'logo.png')
103 | })
104 | }
105 | }
106 |
107 | exports.getIPAddress = () => {
108 | let interfaces = require('os').networkInterfaces()
109 | for (let devName in interfaces) {
110 | let iface = interfaces[devName]
111 | for (let i = 0; i < iface.length; i++) {
112 | let alias = iface[i]
113 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
114 | return alias.address;
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/examples/base/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: false
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | },
22 | // 增加css-module规则
23 | cssModules: {
24 | // vue-loader的localIdentName会自带_0
25 | localIdentName: '[local]-[hash:base64:5]',
26 | // 增加-转驼峰
27 | // camelCase: true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/base/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 | const PostCompilePlugin = require('webpack-post-compile-plugin')
7 | const TransformModulesPlugin = require('webpack-transform-modules-plugin')
8 |
9 | function resolve(dir) {
10 | return path.join(__dirname, '..', dir)
11 | }
12 |
13 | const createLintingRule = () => ({
14 | test: /\.(js|vue)$/,
15 | loader: 'eslint-loader',
16 | enforce: 'pre',
17 | include: [resolve('src'), resolve('test')],
18 | options: {
19 | formatter: require('eslint-friendly-formatter'),
20 | emitWarning: !config.dev.showEslintErrorsInOverlay
21 | }
22 | })
23 |
24 | module.exports = {
25 | context: path.resolve(__dirname, '../'),
26 | entry: {
27 | app: './src/main.js'
28 | },
29 | output: {
30 | path: config.build.assetsRoot,
31 | filename: '[name].js',
32 | chunkFilename: '[name].js',
33 | publicPath: process.env.NODE_ENV === 'development'
34 | ? config.dev.assetsPublicPath
35 | : config.build.assetsPublicPath
36 | },
37 | resolve: {
38 | extensions: ['.js', '.vue', '.json'],
39 | alias: {
40 | 'vue$': 'vue/dist/vue.esm.js',
41 | '@': resolve('src'),
42 | 'vue-router-cache': resolve('src/plugins/vue-router-cache/vue-router-cache.esm.js'),
43 | }
44 | },
45 | module: {
46 | rules: [
47 | ...(config.dev.useEslint ? [createLintingRule()] : []),
48 | {
49 | test: /\.vue$/,
50 | loader: 'vue-loader',
51 | options: vueLoaderConfig,
52 | },
53 | {
54 | test: /\.js$/,
55 | loader: 'babel-loader',
56 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
57 | },
58 | {
59 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
60 | loader: 'url-loader',
61 | options: {
62 | limit: 10000,
63 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
64 | },
65 | },
66 | {
67 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
68 | loader: 'url-loader',
69 | options: {
70 | limit: 10000,
71 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
72 | }
73 | },
74 | {
75 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
76 | loader: 'url-loader',
77 | options: {
78 | limit: 10000,
79 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
80 | }
81 | }
82 | ]
83 | },
84 | plugins: [
85 | new PostCompilePlugin(),
86 | new TransformModulesPlugin()
87 | ],
88 | node: {
89 | // prevent webpack from injecting useless setImmediate polyfill because Vue
90 | // source contains it (although only uses it if it's native).
91 | setImmediate: false,
92 | // prevent webpack from injecting mocks to Node native modules
93 | // that does not make sense for the client
94 | dgram: 'empty',
95 | fs: 'empty',
96 | net: 'empty',
97 | tls: 'empty',
98 | child_process: 'empty'
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/examples/base/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | // const HOST = process.env.HOST
14 | // 增加这里,获得本地IP地址,让webpack服务器支持本地远程访问
15 | const HOST = process.env.HOST || utils.getIPAddress()
16 | const PORT = process.env.PORT && Number(process.env.PORT)
17 |
18 | const devWebpackConfig = merge(baseWebpackConfig, {
19 | module: {
20 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
21 | },
22 | // cheap-module-eval-source-map is faster for development
23 | devtool: config.dev.devtool,
24 |
25 | // these devServer options should be customized in /config/index.js
26 | devServer: {
27 | clientLogLevel: 'warning',
28 | historyApiFallback: {
29 | rewrites: [
30 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
31 | ],
32 | },
33 | hot: false,
34 | contentBase: false, // since we use CopyWebpackPlugin.
35 | compress: true,
36 | host: HOST || config.dev.host,
37 | port: PORT || config.dev.port,
38 | open: config.dev.autoOpenBrowser,
39 | overlay: config.dev.errorOverlay
40 | ? { warnings: false, errors: true }
41 | : false,
42 | publicPath: config.dev.assetsPublicPath,
43 | proxy: config.dev.proxyTable,
44 | quiet: true, // necessary for FriendlyErrorsPlugin
45 | watchOptions: {
46 | poll: config.dev.poll,
47 | }
48 | },
49 | plugins: [
50 | new webpack.DefinePlugin({
51 | 'process.env': require('../config/dev.env')
52 | }),
53 | new webpack.HotModuleReplacementPlugin(),
54 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
55 | new webpack.NoEmitOnErrorsPlugin(),
56 | // https://github.com/ampedandwired/html-webpack-plugin
57 | new HtmlWebpackPlugin({
58 | filename: 'index.html',
59 | template: 'index.html',
60 | inject: true
61 | }),
62 | // copy custom static assets
63 | new CopyWebpackPlugin([
64 | {
65 | from: path.resolve(__dirname, '../static'),
66 | to: config.dev.assetsSubDirectory,
67 | ignore: ['.*']
68 | }
69 | ])
70 | ]
71 | })
72 |
73 | module.exports = new Promise((resolve, reject) => {
74 | portfinder.basePort = process.env.PORT || config.dev.port
75 | portfinder.getPort((err, port) => {
76 | if (err) {
77 | reject(err)
78 | } else {
79 | // publish the new Port, necessary for e2e tests
80 | process.env.PORT = port
81 | // add port to devServer config
82 | devWebpackConfig.devServer.port = port
83 |
84 | // Add FriendlyErrorsPlugin
85 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
86 | compilationSuccessInfo: {
87 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
88 | },
89 | onErrors: config.dev.notifyOnErrors
90 | ? utils.createNotifierCallback()
91 | : undefined
92 | }))
93 |
94 | resolve(devWebpackConfig)
95 | }
96 | })
97 | })
98 |
--------------------------------------------------------------------------------
/examples/base/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = process.env.NODE_ENV === 'testing'
15 | ? require('../config/test.env')
16 | : require('../config/prod.env')
17 |
18 | const webpackConfig = merge(baseWebpackConfig, {
19 | module: {
20 | rules: utils.styleLoaders({
21 | sourceMap: config.build.productionSourceMap,
22 | extract: true,
23 | usePostCSS: true
24 | })
25 | },
26 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
31 | },
32 | plugins: [
33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
34 | new webpack.DefinePlugin({
35 | 'process.env': env
36 | }),
37 | new UglifyJsPlugin({
38 | uglifyOptions: {
39 | compress: {
40 | warnings: false
41 | }
42 | },
43 | sourceMap: config.build.productionSourceMap,
44 | parallel: true
45 | }),
46 | // extract css into its own file
47 | new ExtractTextPlugin({
48 | filename: utils.assetsPath('css/[name].[contenthash].css'),
49 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
53 | allChunks: true,
54 | }),
55 | // Compress extracted CSS. We are using this plugin so that possible
56 | // duplicated CSS from different components can be deduped.
57 | new OptimizeCSSPlugin({
58 | cssProcessorOptions: config.build.productionSourceMap
59 | ? { safe: true, map: { inline: false } }
60 | : { safe: true }
61 | }),
62 | // generate dist index.html with correct asset hash for caching.
63 | // you can customize output by editing /index.html
64 | // see https://github.com/ampedandwired/html-webpack-plugin
65 | new HtmlWebpackPlugin({
66 | filename: process.env.NODE_ENV === 'testing'
67 | ? 'index.html'
68 | : config.build.index,
69 | template: 'index.html',
70 | inject: true,
71 | minify: {
72 | removeComments: true,
73 | collapseWhitespace: true,
74 | removeAttributeQuotes: true
75 | // more options:
76 | // https://github.com/kangax/html-minifier#options-quick-reference
77 | },
78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
79 | chunksSortMode: 'dependency'
80 | }),
81 | // keep module.id stable when vendor modules does not change
82 | new webpack.HashedModuleIdsPlugin(),
83 | // enable scope hoisting
84 | new webpack.optimize.ModuleConcatenationPlugin(),
85 | // split vendor js into its own file
86 | new webpack.optimize.CommonsChunkPlugin({
87 | name: 'vendor',
88 | minChunks (module) {
89 | // any required modules inside node_modules are extracted to vendor
90 | return (
91 | module.resource &&
92 | /\.js$/.test(module.resource) &&
93 | module.resource.indexOf(
94 | path.join(__dirname, '../node_modules')
95 | ) === 0
96 | )
97 | }
98 | }),
99 | // extract webpack runtime and module manifest to its own file in order to
100 | // prevent vendor hash from being updated whenever app bundle is updated
101 | new webpack.optimize.CommonsChunkPlugin({
102 | name: 'manifest',
103 | minChunks: Infinity
104 | }),
105 | // This instance extracts shared chunks from code splitted chunks and bundles them
106 | // in a separate chunk, similar to the vendor chunk
107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
108 | new webpack.optimize.CommonsChunkPlugin({
109 | name: 'app',
110 | async: 'vendor-async',
111 | children: true,
112 | minChunks: 3
113 | }),
114 |
115 | // copy custom static assets
116 | new CopyWebpackPlugin([
117 | {
118 | from: path.resolve(__dirname, '../static'),
119 | to: config.build.assetsSubDirectory,
120 | ignore: ['.*']
121 | }
122 | ])
123 | ]
124 | })
125 |
126 | if (config.build.productionGzip) {
127 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
128 |
129 | webpackConfig.plugins.push(
130 | new CompressionWebpackPlugin({
131 | asset: '[path].gz[query]',
132 | algorithm: 'gzip',
133 | test: new RegExp(
134 | '\\.(' +
135 | config.build.productionGzipExtensions.join('|') +
136 | ')$'
137 | ),
138 | threshold: 10240,
139 | minRatio: 0.8
140 | })
141 | )
142 | }
143 |
144 | if (config.build.bundleAnalyzerReport) {
145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
147 | }
148 |
149 | module.exports = webpackConfig
150 |
--------------------------------------------------------------------------------
/examples/base/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // This is the webpack config used for unit tests.
3 |
4 | const utils = require('./utils')
5 | const webpack = require('webpack')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 |
9 | const webpackConfig = merge(baseWebpackConfig, {
10 | // use inline sourcemap for karma-sourcemap-loader
11 | module: {
12 | rules: utils.styleLoaders()
13 | },
14 | devtool: '#inline-source-map',
15 | resolveLoader: {
16 | alias: {
17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
19 | 'scss-loader': 'sass-loader'
20 | }
21 | },
22 | plugins: [
23 | new webpack.DefinePlugin({
24 | 'process.env': require('../config/test.env')
25 | })
26 | ]
27 | })
28 |
29 | // no need for app entry during tests
30 | delete webpackConfig.entry
31 |
32 | module.exports = webpackConfig
33 |
--------------------------------------------------------------------------------
/examples/base/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 |
--------------------------------------------------------------------------------
/examples/base/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: '0.0.0.0', // can be overwritten by process.env.HOST
17 | port: 8011, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: true,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-module-eval-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | cssSourceMap: true
44 | },
45 |
46 | build: {
47 | // Template for index.html
48 | index: path.resolve(__dirname, '../dist/index.html'),
49 |
50 | // Paths
51 | assetsRoot: path.resolve(__dirname, '../dist'),
52 | assetsSubDirectory: 'static',
53 | assetsPublicPath: './',
54 |
55 | /**
56 | * Source Maps
57 | */
58 |
59 | productionSourceMap: false,
60 | // https://webpack.js.org/configuration/devtool/#production
61 | devtool: '#source-map',
62 |
63 | // Gzip off by default as many popular static hosts such as
64 | // Surge or Netlify already gzip all static assets for you.
65 | // Before setting to `true`, make sure to:
66 | // npm install --save-dev compression-webpack-plugin
67 | productionGzip: false,
68 | productionGzipExtensions: ['js', 'css'],
69 |
70 | // Run the build command with an extra argument to
71 | // View the bundle analyzer report after build finishes:
72 | // `npm run build --report`
73 | // Set to `true` or `false` to always turn it on or off
74 | bundleAnalyzerReport: process.env.npm_config_report
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/examples/base/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/examples/base/config/test.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const devEnv = require('./dev.env')
4 |
5 | module.exports = merge(devEnv, {
6 | NODE_ENV: '"testing"'
7 | })
8 |
--------------------------------------------------------------------------------
/examples/base/dist/index.html:
--------------------------------------------------------------------------------
1 |
base
--------------------------------------------------------------------------------
/examples/base/dist/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/dist/static/favicon.ico
--------------------------------------------------------------------------------
/examples/base/dist/static/js/1.85e93daacd5d5ee52249.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([1],{"3sYt":function(t,n,i){"use strict";var e={props:{data:{type:Array,default:function(){return[]}}},data:function(){return{isFinishAnimation:!1}},computed:{setStyle:function(t,n){var i=this;return function(t,n){return{background:n.background,animation:i.isFinishAnimation?"none":i.$style["list-load"]+" "+(200*t+400)+"ms"}}}},methods:{animationend:function(t){t===this.data.length-1&&(this.isFinishAnimation=!0)},clickHandler:function(t,n){this.$refs.item[n].style.background="gold",this.isFinishAnimation=!0,this.$emit("item-click",t,n)}}},s={render:function(){var t=this,n=t.$createElement,i=t._self._c||n;return i("div",{class:t.$style.list},t._l(t.data,function(n,e){return i("div",{key:n.id,ref:"item",refInFor:!0,staticClass:"item",style:t.setStyle(e,n),attrs:{id:"item"+e},on:{click:function(i){return t.clickHandler(n,e)},animationend:function(n){return t.animationend(e)}}},[i("div",{staticClass:"text"},[t._v("id: "+t._s(n.id))]),t._v(" "),i("div",{staticClass:"text"},[t._v("text: "+t._s(n.text))]),t._v(" "),n.children?i("div",{staticClass:"text"},[t._v("子项长度: "+t._s(n.children.length))]):t._e()])}),0)},staticRenderFns:[]};var r=i("VU/8")(e,s,!1,function(t){this.$style=i("nGsU")},null,null);n.a=r.exports},CHg1:function(t,n,i){(n=t.exports=i("FZ+f")(!1)).push([t.i,"\n.list-1DzVi_0 .item {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n margin-bottom: 0.1rem;\n height: 1.2rem;\n border-radius: 0.06rem;\n text-align: center;\n padding: 0.1rem 0;\n}\n.list-1DzVi_0 .item .text {\n font-size: 0.2rem;\n line-height: 0.3rem;\n color: #fff;\n}\n@-webkit-keyframes list-load-34AMi_0 {\n0% {\n -webkit-transform: translateY(100px);\n transform: translateY(100px);\n}\n100% {\n -webkit-transform: translateY(0px);\n transform: translateY(0px);\n}\n}\n@keyframes list-load-34AMi_0 {\n0% {\n -webkit-transform: translateY(100px);\n transform: translateY(100px);\n}\n100% {\n -webkit-transform: translateY(0px);\n transform: translateY(0px);\n}\n}\n",""]),n.locals={list:"list-1DzVi_0","list-load":"list-load-34AMi_0"}},KvKx:function(t,n,i){(n=t.exports=i("FZ+f")(!1)).push([t.i,"\n.number-list-1XfEg_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding: 0.1rem;\n}\n",""]),n.locals={"number-list":"number-list-1XfEg_0"}},OWP9:function(t,n,i){var e=i("KvKx");"string"==typeof e&&(e=[[t.i,e,""]]),e.locals&&(t.exports=e.locals);i("rjj0")("428486ba",e,!0,{})},P31M:function(t,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var e=i("3sYt"),s=i("W+FH"),r=i("ye1K"),a=(i("YBc6"),{beforeRouteEnter:function(t,n,i){i()},components:{ColorList:e.a},mixins:[r.a],data:function(){return{numberList:[]}},methods:{pullingDownHandler:function(){this.getNumberList()},getNumberList:function(){var t=this;Object(s.e)().then(function(n){n.data&&1===n.data.code&&(t.numberList=n.data.data)})},clickHandler:function(t){this.$router.push({name:"testCaseNumberDetail",params:{numberId:t.id}})}}}),o={render:function(){var t=this.$createElement,n=this._self._c||t;return n("vi-page",[n("vi-scroll",{ref:"scroll",staticStyle:{color:"#999"},attrs:{data:this.numberList,options:this.scrollOptions,scrollEvents:this.scrollEvents},on:{"pulling-down":this.pullingDownHandler}},[n("div",{class:this.$style["number-list"]},[n("color-list",{attrs:{data:this.numberList},on:{"item-click":this.clickHandler}})],1)])],1)},staticRenderFns:[]};var l=i("VU/8")(a,o,!1,function(t){this.$style=i("OWP9")},null,null);n.default=l.exports},nGsU:function(t,n,i){var e=i("CHg1");"string"==typeof e&&(e=[[t.i,e,""]]),e.locals&&(t.exports=e.locals);i("rjj0")("09946e9e",e,!0,{})}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/2.ce5276a4acd7fba9efd3.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([2],{"3sYt":function(t,n,e){"use strict";var i={props:{data:{type:Array,default:function(){return[]}}},data:function(){return{isFinishAnimation:!1}},computed:{setStyle:function(t,n){var e=this;return function(t,n){return{background:n.background,animation:e.isFinishAnimation?"none":e.$style["list-load"]+" "+(200*t+400)+"ms"}}}},methods:{animationend:function(t){t===this.data.length-1&&(this.isFinishAnimation=!0)},clickHandler:function(t,n){this.$refs.item[n].style.background="gold",this.isFinishAnimation=!0,this.$emit("item-click",t,n)}}},s={render:function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",{class:t.$style.list},t._l(t.data,function(n,i){return e("div",{key:n.id,ref:"item",refInFor:!0,staticClass:"item",style:t.setStyle(i,n),attrs:{id:"item"+i},on:{click:function(e){return t.clickHandler(n,i)},animationend:function(n){return t.animationend(i)}}},[e("div",{staticClass:"text"},[t._v("id: "+t._s(n.id))]),t._v(" "),e("div",{staticClass:"text"},[t._v("text: "+t._s(n.text))]),t._v(" "),n.children?e("div",{staticClass:"text"},[t._v("子项长度: "+t._s(n.children.length))]):t._e()])}),0)},staticRenderFns:[]};var r=e("VU/8")(i,s,!1,function(t){this.$style=e("nGsU")},null,null);n.a=r.exports},CHg1:function(t,n,e){(n=t.exports=e("FZ+f")(!1)).push([t.i,"\n.list-1DzVi_0 .item {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n margin-bottom: 0.1rem;\n height: 1.2rem;\n border-radius: 0.06rem;\n text-align: center;\n padding: 0.1rem 0;\n}\n.list-1DzVi_0 .item .text {\n font-size: 0.2rem;\n line-height: 0.3rem;\n color: #fff;\n}\n@-webkit-keyframes list-load-34AMi_0 {\n0% {\n -webkit-transform: translateY(100px);\n transform: translateY(100px);\n}\n100% {\n -webkit-transform: translateY(0px);\n transform: translateY(0px);\n}\n}\n@keyframes list-load-34AMi_0 {\n0% {\n -webkit-transform: translateY(100px);\n transform: translateY(100px);\n}\n100% {\n -webkit-transform: translateY(0px);\n transform: translateY(0px);\n}\n}\n",""]),n.locals={list:"list-1DzVi_0","list-load":"list-load-34AMi_0"}},DKo8:function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var i=e("3sYt"),s=e("W+FH"),r=e("ye1K"),a={components:{ColorList:i.a},mixins:[r.a],data:function(){return{letterList:[]}},computed:{numberId:function(){return this.$route.params.numberId}},methods:{pullingDownHandler:function(){this.getLetterList()},getLetterList:function(){var t=this;Object(s.c)(this.numberId).then(function(n){n.data&&1===n.data.code&&(t.letterList=n.data.data)})},clickHandler:function(t){this.$router.push({name:"testCaseLetterDetail",params:{numberId:this.numberId,letterId:t.id}})}}},l={render:function(){var t=this.$createElement,n=this._self._c||t;return n("vi-page",[n("vi-scroll",{ref:"scroll",staticStyle:{color:"#999"},attrs:{data:this.letterList,options:this.scrollOptions,scrollEvents:this.scrollEvents},on:{"pulling-down":this.pullingDownHandler}},[n("div",{class:this.$style["letter-list"]},[n("color-list",{ref:"list",attrs:{data:this.letterList},on:{"item-click":this.clickHandler}})],1)])],1)},staticRenderFns:[]};var o=e("VU/8")(a,l,!1,function(t){this.$style=e("xaPO")},null,null);n.default=o.exports},k2tD:function(t,n,e){(n=t.exports=e("FZ+f")(!1)).push([t.i,"\n.letter-list-27O5N_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding: 0.1rem;\n}\n",""]),n.locals={"letter-list":"letter-list-27O5N_0"}},nGsU:function(t,n,e){var i=e("CHg1");"string"==typeof i&&(i=[[t.i,i,""]]),i.locals&&(t.exports=i.locals);e("rjj0")("09946e9e",i,!0,{})},xaPO:function(t,n,e){var i=e("k2tD");"string"==typeof i&&(i=[[t.i,i,""]]),i.locals&&(t.exports=i.locals);e("rjj0")("6f46b3e9",i,!0,{})}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/3.887f5c911af41e18021a.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([3],{I5cG:function(n,e,i){var t=i("IUp6");"string"==typeof t&&(t=[[n.i,t,""]]),t.locals&&(n.exports=t.locals);i("rjj0")("1d3b4465",t,!0,{})},IUp6:function(n,e,i){(e=n.exports=i("FZ+f")(!1)).push([n.i,"\n.config-39bPE_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding: 0.1rem;\n}\n.config-39bPE_0 .wrapper {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n}\n.config-39bPE_0 .wrapper .mode {\n font-size: 0.24rem;\n color: #41b883;\n text-align: right;\n margin-bottom: 0.2rem;\n line-height: 0.3rem;\n}\n.config-39bPE_0 .wrapper .mode .mode-placeholder {\n font-size: 0.16rem;\n vertical-align: middle;\n}\n.config-39bPE_0 .wrapper .instruction {\n font-size: 0.16rem;\n color: #41b883;\n line-height: 0.2rem;\n}\n.config-39bPE_0 .wrapper .instruction .group {\n margin-bottom: 0.15rem;\n}\n.config-39bPE_0 .sticky-footer {\n -webkit-transform: translate(0, -100%);\n transform: translate(0, -100%);\n}\n",""]),e.locals={config:"config-39bPE_0"}},fSeh:function(n,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var t=i("1Va+"),o=i("QmSG"),s={components:{Btn:i("cyB7").a},data:function(){return{isSingleMode:o.a}},computed:{mode:function(){return this.isSingleMode?"单例模式":"多例模式"}},methods:{pageTurnNumberList:function(){this.$router.push({name:"testCaseNumberList"})},change:function(n){t.a.set("isSingleMode",n),window.location.reload()}}},r={render:function(){var n=this,e=n.$createElement,i=n._self._c||e;return i("vi-page",[i("div",{class:n.$style.config},[i("div",{staticClass:"wrapper"},[i("div",{staticClass:"mode"},[i("span",{staticClass:"mode-placeholder"},[n._v("使用单例模式")]),n._v(" "),i("vi-switch",{on:{change:n.change},model:{value:n.isSingleMode,callback:function(e){n.isSingleMode=e},expression:"isSingleMode"}})],1),n._v(" "),i("div",{staticClass:"instruction"},[i("div",{staticClass:"group"},[n._v("\n 单例模式是指系统中一个路由对应的组件(缓存实例)只存在一个,\n 比如A=>B=>C=>A,系统中只会存在3个缓存实例,\n 所以路由和缓存实例(key)是一一对应的。\n ")]),n._v(" "),i("div",{staticClass:"group"},[n._v("\n 多例模式是指系统中一个路由对应的组件(缓存实例)可以存在多个,\n 比如A=>B=>C=>A,系统中会存在4个缓存实例,\n 所以路由和缓存实例(key)是一对多的关系。\n ")]),n._v(" "),i("div",{staticClass:"group"},[n._v("\n 多例模式系统性能存在浪费,\n 多例模式可以说是简单模式,\n 如果A要回跳C页面需要C页面刷新,只能push到C页面,\n 多例模式能做的事情单例模式都能实现。\n ")]),n._v(" "),i("div",{staticClass:"group"},[n._v("\n 单例模式系统的性能高,\n 有灵活的手动清除缓存的api,\n 如果A要回跳C页面需要C页面刷新,\n 可以调用api清除C页面缓存然后back到C页面(推荐)。\n 也可以直接push到C页面(但是这样浏览器会比执行back多存历史记录)\n ")])])]),n._v(" "),i("div",{staticClass:"sticky-footer"},[i("btn",{nativeOn:{click:function(e){return n.pageTurnNumberList(e)}}},[n._v(n._s(n.mode)+"尝试")])],1)])])},staticRenderFns:[]};var a=i("VU/8")(s,r,!1,function(n){this.$style=i("I5cG")},null,null);e.default=a.exports}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/4.c2ab346742beedb49c80.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([4],{"7KuP":function(t,e,n){(e=t.exports=n("FZ+f")(!1)).push([t.i,"\n.number-detail-2L-Vu_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding: 0.1rem;\n}\n.number-detail-2L-Vu_0 .wrapper {\n height: 100%;\n}\n.number-detail-2L-Vu_0 .wrapper .context {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n margin-bottom: 0.1rem;\n height: 1.2rem;\n border-radius: 0.06rem;\n text-align: center;\n padding: 0.1rem 0;\n}\n.number-detail-2L-Vu_0 .wrapper .context .text {\n font-size: 0.2rem;\n line-height: 0.3rem;\n color: #fff;\n}\n.number-detail-2L-Vu_0 .wrapper .input {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n display: block;\n margin: 0 auto;\n width: 100%;\n height: 0.4rem;\n color: #333;\n border: 0.01rem solid #ccc;\n border-radius: 0.06rem;\n margin-bottom: 0.2rem;\n padding-left: 0.1rem;\n}\n.number-detail-2L-Vu_0 .sticky-footer {\n padding-bottom: 0.2rem;\n}\n",""]),e.locals={"number-detail":"number-detail-2L-Vu_0"}},"j/sV":function(t,e,n){var r=n("7KuP");"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n("rjj0")("62319234",r,!0,{})},oEQ0:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n("W+FH"),i=n("QmSG"),a=n("cyB7"),o=n("ye1K"),s={components:{Btn:a.a},mixins:[o.a],data:function(){return{text:"",numberDetail:{},isSingleMode:i.a}},computed:{numberId:function(){return this.$route.params.numberId}},methods:{pullingDownHandler:function(){this.getNumberDetail()},getNumberDetail:function(){var t=this;Object(r.d)(this.numberId).then(function(e){e.data&&1===e.data.code&&(t.numberDetail=e.data.data,t.$refs.scroll.deblocking())})},updateNumberDetail:function(){var t=this;Object(r.g)(this.numberId,this.text).then(function(e){e.data&&1===e.data.code&&t.getNumberDetail()})},back:function(){var t=this;i.a?this.$global.confirm.show({title:"要手动删除上个页面缓存吗",text:"执行this.$routerCache.remove('/test-case/number-list')",confirmText:"确定",cancelText:"不需要",onConfirm:function(){t.$routerCache.remove("/test-case/number-list"),t.$router.back()},onCancel:function(){t.$router.back()}}):this.$router.back()},go:function(){var t=0|this.text;this.$router.go(t)},forward:function(){this.$router.forward()},pageTurnLetterList:function(){this.$router.push({name:"testCaseLetterList",params:{numberId:this.numberId}})},pageTurnNumberList:function(){this.$router.push({name:"testCaseNumberList"})},replaceNumberList:function(){this.$router.replace({name:"testCaseNumberList"})}}},u={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("vi-page",[n("vi-scroll",{ref:"scroll",staticStyle:{color:"#999"},attrs:{options:t.scrollOptions,scrollEvents:t.scrollEvents},on:{"pulling-down":t.pullingDownHandler}},[n("div",{class:t.$style["number-detail"]},[n("div",{staticClass:"wrapper"},[n("div",{staticClass:"context",style:{background:t.numberDetail.background}},[n("div",{staticClass:"text"},[t._v("id: "+t._s(t.numberDetail.id))]),t._v(" "),n("div",{staticClass:"text"},[t._v("text: "+t._s(t.numberDetail.text))]),t._v(" "),t.numberDetail.children?n("div",{staticClass:"text"},[t._v("子项长度: "+t._s(t.numberDetail.children.length))]):t._e()]),t._v(" "),n("input",{directives:[{name:"model",rawName:"v-model",value:t.text,expression:"text"}],staticClass:"input",attrs:{type:"tel",placeholder:"输入你想输入的值"},domProps:{value:t.text},on:{input:function(e){e.target.composing||(t.text=e.target.value)}}}),t._v(" "),n("btn",{staticStyle:{background:"#ff1133"},nativeOn:{click:function(e){return t.updateNumberDetail(e)}}},[t._v("修改当前列表元素的text为输入框的值")])],1),t._v(" "),n("div",{staticClass:"sticky-footer"},[n("btn",{nativeOn:{click:function(e){return t.back(e)}}},[t._v("返回上一页(back)")]),t._v(" "),n("btn",{nativeOn:{click:function(e){return t.forward(e)}}},[t._v("前进一页(forward)")]),t._v(" "),n("btn",{nativeOn:{click:function(e){return t.go(e)}}},[t._v("导航到输入框的值的页数(go)")]),t._v(" "),n("btn",{nativeOn:{click:function(e){return t.pageTurnNumberList(e)}}},[t._v("自动更新并跳转到上一个列表页(push)")]),t._v(" "),n("btn",{nativeOn:{click:function(e){return t.replaceNumberList(e)}}},[t._v("自动更新并替换到上一个的列表页(replace)")]),t._v(" "),n("btn",{nativeOn:{click:function(e){return t.pageTurnLetterList(e)}}},[t._v("跳转到子项列表页(push)")])],1)])])],1)},staticRenderFns:[]};var c=n("VU/8")(s,u,!1,function(t){this.$style=n("j/sV")},null,null);e.default=c.exports}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/5.56ad8f276acac6f40f5b.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([5],{"8m0p":function(t,e,n){var r=n("Gknd");"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n("rjj0")("69bfd816",r,!0,{})},Gknd:function(t,e,n){(e=t.exports=n("FZ+f")(!1)).push([t.i,"\n.letter-detail-3kCC6_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding: 0.1rem;\n}\n.letter-detail-3kCC6_0 .wrapper {\n height: 100%;\n}\n.letter-detail-3kCC6_0 .wrapper .context {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n margin-bottom: 0.1rem;\n height: 1.2rem;\n border-radius: 0.06rem;\n text-align: center;\n padding: 0.1rem 0;\n}\n.letter-detail-3kCC6_0 .wrapper .context .text {\n font-size: 0.2rem;\n line-height: 0.3rem;\n color: #fff;\n}\n.letter-detail-3kCC6_0 .wrapper .input {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n display: block;\n margin: 0 auto;\n width: 100%;\n height: 0.4rem;\n color: #333;\n border: 0.01rem solid #ccc;\n border-radius: 0.06rem;\n margin-bottom: 0.2rem;\n padding-left: 0.1rem;\n}\n.letter-detail-3kCC6_0 .sticky-footer {\n padding-bottom: 0.2rem;\n}\n",""]),e.locals={"letter-detail":"letter-detail-3kCC6_0"}},KuRI:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n("W+FH"),i=n("cyB7"),a=n("QmSG"),o=n("ye1K"),l={components:{Btn:i.a},mixins:[o.a],data:function(){return{text:"",letterDetail:{},isSingleMode:a.a}},computed:{numberId:function(){return this.$route.params.numberId},letterId:function(){return this.$route.params.letterId}},methods:{pullingDownHandler:function(){this.getLetterDetail()},getLetterDetail:function(){var t=this;Object(r.b)(this.numberId,this.letterId).then(function(e){e.data&&1===e.data.code&&(t.letterDetail=e.data.data,t.$refs.scroll.deblocking())})},updateLetterDetail:function(){var t=this;Object(r.f)(this.numberId,this.letterId,this.text).then(function(e){e.data&&1===e.data.code&&t.getLetterDetail()})},deleteLetterDetail:function(){var t=this;Object(r.a)(this.numberId,this.letterId).then(function(e){e.data&&1===e.data.code&&(t.$routerCache.removeBackUntil({name:"mainEnter"}),t.$router.back())})},back:function(){this.$router.back()},removeCacheBack:function(){this.$routerCache.remove({name:"testCaseLetterList"}),this.$router.back()},pageTurnLetterList:function(){this.$router.push({name:"testCaseLetterList"})}}},s={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("vi-page",[n("vi-scroll",{ref:"scroll",staticStyle:{color:"#999"},attrs:{options:t.scrollOptions,scrollEvents:t.scrollEvents},on:{"pulling-down":t.pullingDownHandler}},[n("div",{class:t.$style["letter-detail"]},[n("div",{staticClass:"wrapper"},[n("div",{staticClass:"context",style:{background:t.letterDetail.background}},[n("div",{staticClass:"text"},[t._v("id: "+t._s(t.letterDetail.id))]),t._v(" "),n("div",{staticClass:"text"},[t._v("text: "+t._s(t.letterDetail.text))])]),t._v(" "),n("input",{directives:[{name:"model",rawName:"v-model",value:t.text,expression:"text"}],staticClass:"input",attrs:{placeholder:"输入你想修改的text值"},domProps:{value:t.text},on:{input:function(e){e.target.composing||(t.text=e.target.value)}}}),t._v(" "),n("btn",{staticStyle:{background:"#ff1133"},nativeOn:{click:function(e){return t.updateLetterDetail(e)}}},[t._v("修改当前列表元素的text为输入框的值")]),t._v(" "),t.isSingleMode?n("btn",{staticStyle:{background:"#ff1133"},nativeOn:{click:function(e){return t.deleteLetterDetail(e)}}},[t._v("删除并返回")]):t._e()],1),t._v(" "),n("div",{staticClass:"sticky-footer"},[n("btn",{nativeOn:{click:function(e){return t.back(e)}}},[t._v("返回")]),t._v(" "),t.isSingleMode?n("btn",{nativeOn:{click:function(e){return t.removeCacheBack(e)}}},[t._v("销毁上个列表页的缓存并返回")]):t._e(),t._v(" "),t.isSingleMode?t._e():n("btn",{nativeOn:{click:function(e){return t.pageTurnLetterList(e)}}},[t._v("push到新的列表页")])],1)])])],1)},staticRenderFns:[]};var c=n("VU/8")(l,s,!1,function(t){this.$style=n("8m0p")},null,null);e.default=c.exports}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/6.1a7fe521fe0160fd7f46.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([6],{"2dQG":function(n,e,i){(e=n.exports=i("FZ+f")(!1)).push([n.i,'\n.enter-2YjIi_0 {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: 100%;\n padding-top: 0.8rem;\n}\n.enter-2YjIi_0 .package {\n text-align: center;\n margin-bottom: 0.6rem;\n}\n.enter-2YjIi_0 .package .package-icon {\n font-size: 0.6rem;\n margin-bottom: 0.1rem;\n}\n.enter-2YjIi_0 .package .package-version {\n font-size: 0.14rem;\n color: #999;\n}\n.enter-2YjIi_0 .link {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n padding-left: 0.15rem;\n margin-bottom: 0.15rem;\n border-top: 0.01rem solid #eee;\n border-bottom: 0.01rem solid #eee;\n background: #fff;\n}\n.enter-2YjIi_0 .link a {\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n position: relative;\n line-height: 0.44rem;\n font-size: 0.16rem;\n color: #333;\n border-bottom: 0.01rem solid #eee;\n}\n.enter-2YjIi_0 .link a .link-icon {\n font-size: 0.26rem;\n margin-right: 0.1rem;\n}\n.enter-2YjIi_0 .link a:last-child {\n border-bottom: none;\n}\n.enter-2YjIi_0 .link a:after {\n position: absolute;\n content: "";\n height: 0.06rem;\n width: 0.06rem;\n border-width: 0.02rem 0.02rem 0 0;\n border-color: #c8c8cd;\n border-style: solid;\n -webkit-transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);\n transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);\n top: -0.02rem;\n top: 50%;\n margin-top: -0.04rem;\n right: 0.15rem;\n}\n.enter-2YjIi_0 .version {\n font-size: 0.12rem;\n color: #999;\n text-align: center;\n line-height: 0.2rem;\n}\n',""]),e.locals={enter:"enter-2YjIi_0"}},C8Wp:function(n,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var t=i("7+uW"),r=i("/ocq"),o=i("YBc6"),s={data:function(){return{VueVersion:t.a.version,VueRouterVersion:r.a.version,version:o.a.version}},methods:{pageTurnConfig:function(){this.$router.push({name:"mainConfig"})}}},a={render:function(){var n=this,e=n.$createElement,i=n._self._c||e;return i("vi-page",[i("div",{class:n.$style.enter},[i("div",{staticClass:"package"},[i("div",{staticClass:"package-icon common-icon-location"}),n._v(" "),i("div",{staticClass:"package-version"},[n._v(n._s("v"+n.version))])]),n._v(" "),i("div",{staticClass:"link"},[i("a",{staticClass:"link-examples",attrs:{href:"javascript:;"},on:{click:n.pageTurnConfig}},[i("span",{staticClass:"link-icon common-icon-location"}),n._v(" "),i("span",{staticClass:"link-text"},[n._v("Live Examples")])]),n._v(" "),i("a",{staticClass:"link-github",attrs:{href:"https://github.com/kallsave/vue-router-cache"}},[i("span",{staticClass:"link-icon common-icon-github"}),n._v(" "),i("span",{staticClass:"link-text"},[n._v("Github")])])]),n._v(" "),i("div",{staticClass:"version"},[n._v(n._s("current vue version "+n.VueVersion))]),n._v(" "),i("div",{staticClass:"version"},[n._v(n._s("current vue-router version "+n.VueRouterVersion))])])])},staticRenderFns:[]};var l=i("VU/8")(s,a,!1,function(n){this.$style=i("r9sy")},null,null);e.default=l.exports},r9sy:function(n,e,i){var t=i("2dQG");"string"==typeof t&&(t=[[n.i,t,""]]),t.locals&&(n.exports=t.locals);i("rjj0")("463ad5fe",t,!0,{})}});
--------------------------------------------------------------------------------
/examples/base/dist/static/js/manifest.1b9b4c5812c25f16ba1e.js:
--------------------------------------------------------------------------------
1 | !function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,a,c){for(var f,i,u,d=0,s=[];d
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | base
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-router-cache-base-example",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "kallsave <415034609@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 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
11 | "e2e": "node test/e2e/runner.js",
12 | "test": "npm run unit && npm run e2e",
13 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
14 | "build": "node build/build.js",
15 | "svgtofont": "node build/svgtofont"
16 | },
17 | "transformModules": {
18 | "cube-ui": {
19 | "transform": "cube-ui/src/modules/${member}",
20 | "kebabCase": true
21 | }
22 | },
23 | "dependencies": {
24 | "better-scroll": "^1.15.2",
25 | "store": "^2.0.12",
26 | "vconsole": "^3.3.4",
27 | "vue": "^2.5.2",
28 | "vue-router": "^3.0.1",
29 | "vuex": "^3.1.2",
30 | "vuex-persistedstate": "^2.7.0"
31 | },
32 | "devDependencies": {
33 | "autoprefixer": "^7.1.2",
34 | "babel-core": "^6.22.1",
35 | "babel-eslint": "^8.2.1",
36 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
37 | "babel-loader": "^7.1.1",
38 | "babel-plugin-istanbul": "^4.1.1",
39 | "babel-plugin-syntax-jsx": "^6.18.0",
40 | "babel-plugin-transform-modules": "^0.1.1",
41 | "babel-plugin-transform-runtime": "^6.22.0",
42 | "babel-plugin-transform-vue-jsx": "^3.5.0",
43 | "babel-preset-env": "^1.3.2",
44 | "babel-preset-stage-2": "^6.22.0",
45 | "babel-register": "^6.22.0",
46 | "chai": "^4.1.2",
47 | "chalk": "^2.0.1",
48 | "chromedriver": "^2.27.2",
49 | "compression-webpack-plugin": "^3.0.0",
50 | "copy-webpack-plugin": "^4.0.1",
51 | "cross-env": "^5.0.1",
52 | "cross-spawn": "^5.0.1",
53 | "css-loader": "^0.28.0",
54 | "cube-ui": "^1.12.32",
55 | "eslint": "^4.15.0",
56 | "eslint-config-standard": "^10.2.1",
57 | "eslint-friendly-formatter": "^3.0.0",
58 | "eslint-loader": "^1.7.1",
59 | "eslint-plugin-import": "^2.7.0",
60 | "eslint-plugin-node": "^5.2.0",
61 | "eslint-plugin-promise": "^3.4.0",
62 | "eslint-plugin-standard": "^3.0.1",
63 | "eslint-plugin-vue": "^4.0.0",
64 | "extract-text-webpack-plugin": "^3.0.0",
65 | "file-loader": "^1.1.4",
66 | "friendly-errors-webpack-plugin": "^1.6.1",
67 | "glob-all": "^3.1.0",
68 | "html-webpack-plugin": "^2.30.1",
69 | "inject-loader": "^3.0.0",
70 | "karma": "^1.4.1",
71 | "karma-coverage": "^1.1.1",
72 | "karma-mocha": "^1.3.0",
73 | "karma-phantomjs-launcher": "^1.0.2",
74 | "karma-phantomjs-shim": "^1.4.0",
75 | "karma-sinon-chai": "^1.3.1",
76 | "karma-sourcemap-loader": "^0.3.7",
77 | "karma-spec-reporter": "0.0.31",
78 | "karma-webpack": "^2.0.2",
79 | "less": "^3.10.3",
80 | "less-loader": "^5.0.0",
81 | "mocha": "^3.2.0",
82 | "nightwatch": "^0.9.12",
83 | "node-notifier": "^5.1.2",
84 | "optimize-css-assets-webpack-plugin": "^3.2.0",
85 | "ora": "^1.2.0",
86 | "phantomjs-prebuilt": "^2.1.14",
87 | "portfinder": "^1.0.13",
88 | "postcss-import": "^11.0.0",
89 | "postcss-loader": "^2.0.8",
90 | "postcss-url": "^7.2.1",
91 | "rimraf": "^2.6.0",
92 | "selenium-server": "^3.0.1",
93 | "semver": "^5.3.0",
94 | "shelljs": "^0.7.6",
95 | "sinon": "^4.0.0",
96 | "sinon-chai": "^2.8.0",
97 | "style": "0.0.3",
98 | "style-loader": "^1.0.0",
99 | "stylus": "^0.54.7",
100 | "stylus-loader": "^3.0.2",
101 | "svgtofont": "^1.3.0",
102 | "uglifyjs-webpack-plugin": "^1.1.1",
103 | "url-loader": "^0.5.8",
104 | "vue-loader": "^13.3.0",
105 | "vue-style-loader": "^3.0.1",
106 | "vue-template-compiler": "^2.5.2",
107 | "webpack": "^3.6.0",
108 | "webpack-bundle-analyzer": "^3.6.0",
109 | "webpack-dev-server": "^2.9.1",
110 | "webpack-merge": "^4.1.0",
111 | "webpack-post-compile-plugin": "^1.0.0",
112 | "webpack-transform-modules-plugin": "^0.4.4"
113 | },
114 | "engines": {
115 | "node": ">= 6.0.0",
116 | "npm": ">= 3.0.0"
117 | },
118 | "browserslist": [
119 | "> 1%",
120 | "last 2 versions",
121 | "not ie <= 8"
122 | ]
123 | }
124 |
--------------------------------------------------------------------------------
/examples/base/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
25 |
34 |
--------------------------------------------------------------------------------
/examples/base/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/src/assets/logo.png
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "common-icon";
3 | src: url('common-icon.eot?t=1573524366163'); /* IE9*/
4 | src: url('common-icon.eot?t=1573524366163#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url("common-icon.woff2?t=1573524366163") format("woff2"),
6 | url("common-icon.woff?t=1573524366163") format("woff"),
7 | url('common-icon.ttf?t=1573524366163') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
8 | url('common-icon.svg?t=1573524366163#common-icon') format('svg'); /* iOS 4.1- */
9 | }
10 |
11 | [class^="common-icon-"], [class*=" common-icon-"] {
12 | font-family: 'common-icon' !important;
13 | font-size:16px;
14 | font-style:normal;
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | }
18 |
19 |
20 | .common-icon-github:before { content: "\ea01"; }
21 | .common-icon-location:before { content: "\ea02"; }
22 |
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/src/common/fonts/common-icon.eot
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.less:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "common-icon";
2 | src: url('common-icon.eot?t=1573524366163'); /* IE9*/
3 | src: url('common-icon.eot?t=1573524366163#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url("common-icon.woff2?t=1573524366163") format("woff2"),
5 | url("common-icon.woff?t=1573524366163") format("woff"),
6 | url('common-icon.ttf?t=1573524366163') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('common-icon.svg?t=1573524366163#common-icon') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | [class^="common-icon-"], [class*=" common-icon-"] {
11 | font-family: 'common-icon' !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | :global {
19 | .common-icon-github:before { content: "\ea01"; }
20 | .common-icon-location:before { content: "\ea02"; }
21 |
22 | }
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.symbol.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/src/common/fonts/common-icon.ttf
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/src/common/fonts/common-icon.woff
--------------------------------------------------------------------------------
/examples/base/src/common/fonts/common-icon.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/src/common/fonts/common-icon.woff2
--------------------------------------------------------------------------------
/examples/base/src/common/helpers/dom.js:
--------------------------------------------------------------------------------
1 | import { camelize } from './utils.js'
2 |
3 | export function hasClass(el, className) {
4 | const reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
5 | return reg.test(el.className)
6 | }
7 |
8 | export function addClass(el, className) {
9 | /* istanbul ignore if */
10 | if (hasClass(el, className)) {
11 | return
12 | }
13 |
14 | const newClass = el.className.split(' ')
15 | newClass.push(className)
16 | el.className = newClass.join(' ')
17 | }
18 |
19 | export function removeClass(el, className) {
20 | /* istanbul ignore if */
21 | if (!hasClass(el, className)) {
22 | return
23 | }
24 |
25 | const reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g')
26 | el.className = el.className.replace(reg, ' ')
27 | }
28 |
29 | export function hasData(el, name) {
30 | const prefix = 'data-'
31 | return el.hasAttribute(prefix + name)
32 | }
33 |
34 | export function getData(el, name) {
35 | const prefix = 'data-'
36 | return el.getAttribute(prefix + name)
37 | }
38 |
39 | export function setData(el, name, value) {
40 | const prefix = 'data-'
41 | el.setAttribute(prefix + name, value)
42 | }
43 |
44 | // getRect是获取相对父元素的位置,如果想获取相对页面的位置
45 | // 请使用getBoundingClientRect
46 | export function getRect(el) {
47 | return {
48 | top: el.offsetTop,
49 | left: el.offsetLeft,
50 | width: el.offsetWidth,
51 | height: el.offsetHeight
52 | }
53 | }
54 |
55 | const elementStyle = document.createElement('div').style
56 |
57 | const endEventListenerList = ['transitionend', 'animationend']
58 |
59 | const browserPrefix = {
60 | standard: '',
61 | webkit: 'webkit',
62 | Moz: 'Moz',
63 | O: 'O',
64 | ms: 'ms',
65 | }
66 |
67 | const endEventListenerPrefixList = {
68 | transition: {
69 | transition: 'transitionend',
70 | webkitTransition: 'webkitTransitionEnd',
71 | MozTransition: 'transitionend',
72 | OTransition: 'oTransitionEnd',
73 | msTransition: 'msTransitionEnd'
74 | },
75 | animation: {
76 | animation: 'animationend',
77 | webkitAnimation: 'webkitAnimationEnd',
78 | MozAnimation: 'animationend',
79 | OAnimation: 'oAnimationEnd',
80 | msAnimation: 'msAnimationEnd'
81 | }
82 | }
83 |
84 | export function prefixStyle(style) {
85 | let baseStyle = ''
86 | if (endEventListenerList.indexOf(style) !== -1) {
87 | baseStyle = style.replace(/end/i, '')
88 | }
89 |
90 | for (const key in browserPrefix) {
91 | if (baseStyle) {
92 | const cssPrefixStyle = browserPrefix[key] ? browserPrefix[key] + '-' + baseStyle : baseStyle
93 | const keyName = camelize(cssPrefixStyle)
94 | if (elementStyle[keyName] !== undefined) {
95 | return endEventListenerPrefixList[baseStyle][keyName]
96 | }
97 | } else {
98 | const cssPrefixStyle = browserPrefix[key] ? browserPrefix[key] + '-' + style : style
99 | const keyName = camelize(cssPrefixStyle)
100 | if (elementStyle[keyName] !== undefined) {
101 | return keyName
102 | }
103 | }
104 | }
105 | return ''
106 | }
107 |
108 | export function getMatchedTarget(e, targetClassName) {
109 | let el = e.target
110 |
111 | while (el && !hasClass(el, targetClassName)) {
112 | if (el === e.currentTarget) return null
113 | el = el.parentNode
114 | }
115 |
116 | return el
117 | }
118 |
119 | export function dispatchEvent(el, name, { type = 'Event', bubbles = true, cancelable = true } = {}) {
120 | const e = document.createEvent(type)
121 | e.initEvent(name, bubbles, cancelable)
122 | el.dispatchEvent(e)
123 | }
124 |
--------------------------------------------------------------------------------
/examples/base/src/common/helpers/utils.js:
--------------------------------------------------------------------------------
1 | const hasOwnProperty = Object.prototype.hasOwnProperty
2 |
3 | export function hasOwn(obj, key) {
4 | return hasOwnProperty.call(obj, key)
5 | }
6 |
7 | const _toString = Object.prototype.toString
8 |
9 | export function toRawType(value) {
10 | return _toString.call(value).slice(8, -1)
11 | }
12 |
13 | export function deepClone(value) {
14 | let ret
15 | const type = toRawType(value)
16 |
17 | if (type === 'Object') {
18 | ret = {}
19 | } else if (type === 'Array') {
20 | ret = []
21 | } else {
22 | return value
23 | }
24 |
25 | Object.keys(value).forEach((key) => {
26 | const copy = value[key]
27 | ret[key] = deepClone(copy)
28 | })
29 |
30 | return ret
31 | }
32 |
33 | export function deepAssign(origin, mixin) {
34 | for (const key in mixin) {
35 | if (!origin[key] || typeof origin[key] !== 'object') {
36 | origin[key] = mixin[key]
37 | } else {
38 | deepAssign(origin[key], mixin[key])
39 | }
40 | }
41 | }
42 |
43 | export function multiDeepClone(target, ...rest) {
44 | for (let i = 0; i < rest.length; i++) {
45 | const clone = deepClone(rest[i])
46 | deepAssign(target, clone)
47 | }
48 | return target
49 | }
50 |
51 | export const MOBILE_MAX_SIZE = 640
52 |
53 | export const UA = window.navigator.userAgent.toLowerCase()
54 |
55 | export let isMobile = checkIsMobile() || checkIsMobileSize()
56 |
57 | export function checkIsMobile() {
58 | return !!UA.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
59 | }
60 |
61 | export function checkIsMobileSize() {
62 | return window.innerWidth < MOBILE_MAX_SIZE
63 | }
64 |
65 | window.addEventListener('resize', () => {
66 | // basic data type in ES6 modules is strong binding
67 | isMobile = checkIsMobile() || checkIsMobileSize()
68 | }, false)
69 |
70 | export function getUrlParams(currentUrl = window.location.href) {
71 | const result = {}
72 | if (currentUrl.indexOf('?') === -1) {
73 | return result
74 | }
75 | const paramsUrl = currentUrl.replace(/.*\?/g, '')
76 | const arr = paramsUrl.match(/[^&]+?=[^&]*/g)
77 | if (arr) {
78 | for (let i = 0; i < arr.length; i++) {
79 | const reg = new RegExp(`(.+?)=(.*)`)
80 | reg.exec(arr[i])
81 | const key = decodeURIComponent(RegExp.$1)
82 | const value = decodeURIComponent(RegExp.$2)
83 | result[key] = value
84 | }
85 | }
86 | return result
87 | }
88 |
89 | export function camelize(str) {
90 | str = String(str)
91 | return str.replace(/-(\w)/g, function (m, c) {
92 | return c ? c.toUpperCase() : ''
93 | })
94 | }
95 |
96 | const DEFAULT_TIME_SLICE = 400
97 |
98 | export class Debounce {
99 | constructor(timeSlice = DEFAULT_TIME_SLICE) {
100 | this.timeSlice = timeSlice
101 | }
102 | run(func) {
103 | if (typeof func === 'function') {
104 | if (this.timer) {
105 | window.clearTimeout(this.timer)
106 | }
107 | this.timer = window.setTimeout(func, this.timeSlice)
108 | }
109 | }
110 | destroy() {
111 | window.clearTimeout(this.timer)
112 | this.timer = null
113 | this.timeSlice = null
114 | }
115 | }
116 |
117 | export class Throttle {
118 | constructor(timeSlice = DEFAULT_TIME_SLICE) {
119 | this.timeSlice = timeSlice
120 | }
121 | run(func, overload) {
122 | const currentTime = new Date().getTime()
123 | if (!this.lastTime || currentTime - this.lastTime > this.timeSlice) {
124 | this.lastTime = currentTime
125 | if (typeof func === 'function') {
126 | func()
127 | }
128 | } else {
129 | if (typeof overload === 'function') {
130 | overload()
131 | }
132 | }
133 | }
134 | destroy() {
135 | this.timeSlice = null
136 | this.lastTime = null
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/examples/base/src/common/helpers/vconsole.js:
--------------------------------------------------------------------------------
1 | import VConsole from 'vconsole'
2 |
3 | const platform = window.navigator.platform
4 |
5 | let vConsole
6 |
7 | if (!/^(Win|Mac)/i.test(platform)) {
8 | vConsole = new VConsole()
9 | }
10 |
11 | function observeProperty(obj, key, fn) {
12 | var val = obj[key]
13 | Object.defineProperty(obj, key, {
14 | enumerable: true,
15 | configurable: true,
16 | get: function () {
17 | return val
18 | },
19 | set: function (newVal) {
20 | if (val === newVal) {
21 | return
22 | }
23 | val = newVal
24 | if (typeof fn === 'function') {
25 | fn()
26 | }
27 | }
28 | })
29 | }
30 |
31 | if (vConsole) {
32 | observeProperty(vConsole, 'isInited', function () {
33 | vConsole.$dom.style.display = 'none'
34 | })
35 | }
36 |
37 | export function showVConsole() {
38 | if (vConsole) {
39 | window.setTimeout(() => {
40 | vConsole.$dom.style.display = 'block'
41 | }, 1000)
42 | } else {
43 | console.log(`%c${platform} platfrom close vconsole`, 'color:orange')
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/animation.less:
--------------------------------------------------------------------------------
1 | @keyframes popup-enter {
2 | 0% {
3 | opacity: 0;
4 | }
5 | 100% {
6 | opacity: 1;
7 | }
8 | }
9 |
10 | @keyframes popup-leave {
11 | 0% {
12 | opacity: 1;
13 | }
14 | 100% {
15 | opacity: 0;
16 | }
17 | }
18 |
19 | @keyframes popup-content-enter {
20 | 0% {
21 | transform: scale(0);
22 | }
23 | 50% {
24 | transform: scale(1.1);
25 | }
26 | 100% {
27 | transform: scale(1);
28 | }
29 | }
30 |
31 | @keyframes popup-content-leave {
32 | 0% {
33 | transform: scale(1);
34 | opacity: 1;
35 | }
36 | 100% {
37 | transform: translateY(-200px) scale(0.2);
38 | opacity: 0;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/base.less:
--------------------------------------------------------------------------------
1 | // 项目自定义的基础样式
2 | html {
3 | height: 100%;
4 | }
5 |
6 | html body {
7 | font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif;
8 | -webkit-tap-highlight-color: transparent;
9 | user-select: none;
10 | font-size: 0;
11 | position: relative;
12 | width: 100%;
13 | height: 100%;
14 | max-width: 640px;
15 | margin: 0 auto;
16 | }
17 |
18 | .clear {
19 | &:after {
20 | display: block;
21 | clear: both;
22 | content: "";
23 | height: 0;
24 | line-height: 0;
25 | *zoom: 1;
26 | }
27 | }
28 |
29 | a {
30 | -webkit-tap-highlight-color: transparent;
31 | -webkit-user-select: none;
32 | -moz-user-focus: none;
33 | -moz-user-select: none;
34 | }
35 |
36 | a,a:hover,a:active,a:visited,a:link,a:focus {
37 | -webkit-tap-highlight-color: transparent;
38 | outline:none;
39 | background: none;
40 | text-decoration: none;
41 | }
42 |
43 | textarea, input{
44 | -webkit-user-select:text;
45 | -moz-user-select:text;
46 | -ms-user-select:text;
47 | user-select:text;
48 | -webkit-tap-highlight-color: rgba(0,0,0,0);
49 | }
50 |
51 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5) {
52 | .border-1px {
53 | &:before, &:after {
54 | -webkit-transform: scaleY(0.7);
55 | transform: scaleY(0.7);
56 | }
57 | }
58 | }
59 |
60 | // dpr大于2生效
61 | @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
62 | .border-1px {
63 | &:before, &:after {
64 | -webkit-transform: scaleY(0.5);
65 | transform: scaleY(0.5);
66 | }
67 | }
68 | }
69 |
70 | // 扩大cube-picker的点击范围
71 | .cube-picker-cancel {
72 | padding: 0 24px!important;
73 | font-size: 16px!important;
74 | }
75 |
76 | .cube-picker-confirm {
77 | padding: 0 24px!important;
78 | font-size: 16px!important;
79 | }
80 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/index.less:
--------------------------------------------------------------------------------
1 | // 所有全局样式入口文件
2 | @import './reset.less';
3 | @import './base.less';
4 | @import '../fonts/common-icon.less';
5 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/mixin.less:
--------------------------------------------------------------------------------
1 | // 这文件是提供常用less变量,less函数的
2 |
3 | @easeInOut: cubic-bezier(.61,0,.44,1);
4 | @easeIn: cubic-bezier(.2,0,0,1);
5 |
6 | // 清除浮动
7 | .clear {
8 | &:after {
9 | display: block;
10 | clear: both;
11 | content: "";
12 | height: 0;
13 | line-height: 0;
14 | *zoom: 1;
15 | }
16 | }
17 |
18 | // 文字换行
19 | .no-wrap {
20 | text-overflow: ellipsis;
21 | overflow: hidden;
22 | white-space: nowrap;
23 | }
24 |
25 | // 盒子下阴影
26 | .box-shadow {
27 | box-shadow: 0 4px 10px 0 rgba(153, 153, 153, 0.14);
28 | }
29 |
30 | // 设置底部1px,配合.border-1px实现移动端准确的1px
31 | .border-top-1px(@color) {
32 | position: relative;
33 | &:after {
34 | display: block;
35 | position: absolute;
36 | content: "";
37 | top: 0;
38 | left: 0;
39 | width: 100%;
40 | border-top: 1px solid @color;
41 | }
42 | }
43 |
44 | // 设置底部1px,配合.border-1px实现移动端准确的1px
45 | .border-bottom-1px(@color) {
46 | position: relative;
47 | &:after {
48 | display: block;
49 | position: absolute;
50 | content: "";
51 | bottom: 0;
52 | left: 0;
53 | width: 100%;
54 | border-top: 1px solid @color;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/popup-transition.less:
--------------------------------------------------------------------------------
1 | // 给弹窗用的过渡动画
2 |
3 | @keyframes popup-enter {
4 | 0% {
5 | opacity: 0;
6 | }
7 | 100% {
8 | opacity: 1;
9 | }
10 | }
11 |
12 | @keyframes popup-leave {
13 | 0% {
14 | opacity: 1;
15 | }
16 | 100% {
17 | opacity: 0;
18 | }
19 | }
20 |
21 | @keyframes popup-bounce-content-enter {
22 | 0% {
23 | transform: scale(0);
24 | }
25 | 50% {
26 | transform: scale(1.1);
27 | }
28 | 100% {
29 | transform: scale(1);
30 | }
31 | }
32 |
33 | @keyframes popup-bounce-content-leave {
34 | 0% {
35 | transform: scale(1);
36 | opacity: 1;
37 | }
38 | 100% {
39 | transform: translateY(-200px) scale(0.2);
40 | opacity: 0;
41 | }
42 | }
43 |
44 | @keyframes popup-shrink-content-enter {
45 | 0% {
46 | transform: scale(1.1);
47 | }
48 | 100% {
49 | transform: scale(1);
50 | }
51 | }
52 |
53 | .popup-enter-active {
54 | animation-name: popup-enter;
55 | animation-duration: 400ms;
56 | animation-direction: normal;
57 | animation-fill-mode: forwards;
58 | .popup-bounce-content {
59 | animation-name: popup-bounce-content-enter;
60 | animation-duration: 400ms;
61 | animation-direction: normal;
62 | animation-fill-mode: forwards;
63 | }
64 | .popup-shrink-content {
65 | animation-name: popup-shrink-content-enter;
66 | animation-duration: 400ms;
67 | animation-direction: normal;
68 | animation-fill-mode: forwards;
69 | }
70 | }
71 |
72 | .popup-leave-active {
73 | animation-name: popup-leave;
74 | animation-duration: 400ms;
75 | animation-direction: normal;
76 | animation-fill-mode: forwards;
77 | .popup-bounce-content {
78 | animation-name: popup-bounce-content-leave;
79 | animation-duration: 400ms;
80 | animation-direction: normal;
81 | animation-fill-mode: forwards;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/reset.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3 | * http://cssreset.com
4 | */
5 | html, body, div, span, applet, object, iframe,
6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7 | a, abbr, acronym, address, big, cite, code,
8 | del, dfn, em, img, ins, kbd, q, s, samp,
9 | small, strike, strong, sub, sup, tt, var,
10 | b, u, i, center,
11 | dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td,
14 | article, aside, canvas, details, embed,
15 | figure, figcaption, footer, header,
16 | menu, nav, output, ruby, section, summary,
17 | time, mark, audio, video, input {
18 | margin: 0;
19 | padding: 0;
20 | border: 0;
21 | }
22 |
23 | /* HTML5 display-role reset for older browsers */
24 | article, aside, details, figcaption, figure,
25 | footer, header, menu, nav, section {
26 | display: block
27 | }
28 |
29 | // body {
30 | // line-height: 1;
31 | // }
32 |
33 | blockquote, q {
34 | quotes: none;
35 | }
36 |
37 | blockquote:before, blockquote:after,
38 | q:before, q:after {
39 | content: none;
40 | }
41 |
42 | table {
43 | border-collapse: collapse;
44 | border-spacing: 0;
45 | }
46 |
47 | a {
48 | color: #7e8c8d;
49 | text-decoration: none;
50 | }
51 |
52 | li {
53 | list-style: none;
54 | }
55 |
56 | button, input, textarea {
57 | background: none;
58 | outline: none;
59 | border: none;
60 | }
61 |
62 | input {
63 | &::-webkit-input-placeholder {
64 | color: #999;
65 | }
66 | &:-moz-placeholder{
67 | color: #999;
68 | }
69 | &::-moz-placeholder{
70 | color: #999;
71 | }
72 | &:-ms-input-placeholder{
73 | color: #999;
74 | }
75 | }
76 |
77 | input, textarea {
78 | -webkit-appearance: none;
79 | }
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/examples/base/src/common/less/router-transition.less:
--------------------------------------------------------------------------------
1 | @easeOut: cubic-bezier(0.445, 0.05, 0.55, 0.95);
2 | @easeIn: cubic-bezier(0.07, 0.59, 0.26, 0.97);
3 |
4 | .router-view {
5 | position: absolute;
6 | width: 100%;
7 | height: 100%;
8 | left: 0;
9 | top: 0;
10 | &.move-right-enter {
11 | position: fixed;
12 | z-index: 20;
13 | height: 100%;
14 | left: 100%;
15 | }
16 | &.move-right-enter-active {
17 | position: fixed;
18 | z-index: 20;
19 | height: 100%;
20 | transition: left 350ms @easeIn;
21 | }
22 | &.move-right-enter-to {
23 | position: fixed;
24 | z-index: 20;
25 | height: 100%;
26 | }
27 | &.move-right-leave {
28 | z-index: 10;
29 | }
30 | &.move-right-leave-active {
31 | z-index: 10;
32 | transition: left 330ms @easeOut;
33 | }
34 | &.move-right-leave-to {
35 | z-index: 10;
36 | left: -30%;
37 | }
38 | &.move-left-enter {
39 | z-index: 10;
40 | left: -30%;
41 | }
42 | &.move-left-enter-active {
43 | z-index: 10;
44 | transition: left 350ms @easeIn;
45 | }
46 | &.move-left-enter-to {
47 | z-index: 10;
48 | }
49 | &.move-left-leave {
50 | position: fixed;
51 | z-index: 20;
52 | height: 100%;
53 | left: 0%;
54 | }
55 | &.move-left-leave-active {
56 | position: fixed;
57 | z-index: 20;
58 | height: 100%;
59 | left: 100%;
60 | transition: left 330ms @easeOut;
61 | }
62 | &.move-left-leave-to {
63 | position: fixed;
64 | z-index: 20;
65 | height: 100%;
66 | left: 100%;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/examples/base/src/common/mixins/recover-webview.js:
--------------------------------------------------------------------------------
1 | import {
2 | hasData,
3 | } from '@/common/helpers/dom.js'
4 |
5 | export default {
6 | mounted() {
7 | this.addEventListenerFocus()
8 | this.addEventListenerBlur()
9 | },
10 | methods: {
11 | addEventListenerFocus() {
12 | this.$el.addEventListener('focus', this.cacheScrollTop, true)
13 | },
14 | addEventListenerBlur() {
15 | this.$el.addEventListener('blur', this.recoverScrollTop, true)
16 | },
17 | removeEventListenerFocus() {
18 | this.$el.removeEventListener('focus', this.cacheScrollTop, true)
19 | },
20 | removeEventListenerBlur() {
21 | this.$el.removeEventListener('blur', this.recoverScrollTop, true)
22 | },
23 | cacheScrollTop() {
24 | this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop
25 | },
26 | recoverScrollTop(event) {
27 | const target = event.target
28 | const tagNameList = ['INPUT', 'TEXTAREA']
29 | const isNoRollback = hasData(target, 'no-rollback')
30 | if (tagNameList.indexOf(target.tagName) !== -1 && !isNoRollback) {
31 | window.scrollTo({
32 | top: this.scrollTop,
33 | })
34 | }
35 | },
36 | },
37 | beforeDestroy() {
38 | this.removeEventListenerFocus()
39 | this.removeEventListenerBlur()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/base/src/common/mixins/router-transition.js:
--------------------------------------------------------------------------------
1 | import '@/common/less/router-transition.less'
2 |
3 | import { isMobile } from '@/common/helpers/utils.js'
4 |
5 | const TRANSITION_DURATION = {
6 | enter: 350,
7 | leave: 330
8 | }
9 |
10 | export default {
11 | data() {
12 | return {
13 | transitionName: '',
14 | transitionMode: '',
15 | transitionDuration: TRANSITION_DURATION
16 | }
17 | },
18 | watch: {
19 | $route: {
20 | handler(to, from) {
21 | if (!isMobile) {
22 | this.transitionName = ''
23 | this.transitionMode = ''
24 | this.transitionDuration = {
25 | enter: 0,
26 | leave: 0
27 | }
28 | } else {
29 | this.transitionDuration = TRANSITION_DURATION
30 | if (to.params.direction === 'back') {
31 | this.transitionName = 'move-left'
32 | this.transitionMode = ''
33 | } else if (to.params.direction === 'forward') {
34 | this.transitionName = 'move-right'
35 | this.transitionMode = ''
36 | } else if (to.params.direction === 'replace') {
37 | // replace
38 | this.transitionName = ''
39 | this.transitionMode = ''
40 | }
41 | }
42 | },
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/base/src/common/svg/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/base/src/common/svg/location.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/base/src/components/btn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
27 |
--------------------------------------------------------------------------------
/examples/base/src/components/color-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
id: {{item.id}}
13 |
text: {{item.text}}
14 |
子项长度: {{item.children.length}}
15 |
16 |
17 |
18 |
19 |
60 |
61 |
91 |
--------------------------------------------------------------------------------
/examples/base/src/config.js:
--------------------------------------------------------------------------------
1 | import tpLocalStorage from '@/store/cache/local-storage/index'
2 |
3 | if (tpLocalStorage.get('isSingleMode') === undefined) {
4 | tpLocalStorage.set('isSingleMode', true)
5 | }
6 |
7 | export const isSingleMode = tpLocalStorage.get('isSingleMode')
8 |
--------------------------------------------------------------------------------
/examples/base/src/main.js:
--------------------------------------------------------------------------------
1 | import { showVConsole } from '@/common/helpers/vconsole.js'
2 | import './rem.js'
3 | import '@/common/less/index.less'
4 | import Vue from 'vue'
5 | import App from './App'
6 | import router from './pages/router.js'
7 | import store from './store/index.js'
8 | import VueRouterCache from 'vue-router-cache'
9 | import ViUi from '@/plugins/vi-ui/index.js'
10 | import { isSingleMode } from '@/config.js'
11 | import { getUrlParams } from '@/common/helpers/utils.js'
12 |
13 | // ios的输入框失焦,webview无法回弹的hack
14 | import recoverWebviewMixin from '@/common/mixins/recover-webview.js'
15 |
16 | const env = process.env.NODE_ENV
17 |
18 | Vue.use(ViUi)
19 |
20 | Vue.use(VueRouterCache, {
21 | router: router,
22 | max: 10,
23 | isSingleMode: isSingleMode,
24 | isDebugger: env === 'development',
25 | directionKey: 'direction',
26 | getHistoryStack() {
27 | const str = window.sessionStorage.getItem('historyStack')
28 | return JSON.parse(str)
29 | },
30 | setHistoryStack(history) {
31 | const str = JSON.stringify(history)
32 | window.sessionStorage.setItem('historyStack', str)
33 | }
34 | })
35 |
36 | Vue.config.productionTip = false
37 | Vue.prototype.$global = {}
38 |
39 | /* eslint-disable no-new */
40 | new Vue({
41 | el: '#app',
42 | router,
43 | store,
44 | components: { App },
45 | template: '',
46 | mixins: [
47 | recoverWebviewMixin
48 | ],
49 | beforeMount() {
50 | this.createGlobalConfirmApi()
51 | this.showVConsole()
52 | },
53 | methods: {
54 | showVConsole() {
55 | const urlParams = getUrlParams()
56 | if (urlParams.hasOwnProperty('vconsole')) {
57 | showVConsole()
58 | }
59 | },
60 | createGlobalConfirmApi() {
61 | // DEV: vue-create-api $update
62 | const defaultOptions = {}
63 | this.$global.confirm = this.$createViConfirm(defaultOptions)
64 |
65 | const originShow = this.$global.confirm.show
66 |
67 | this.$global.confirm.show = (options) => {
68 | if (options) {
69 | this.$createViConfirm({
70 | ...options
71 | })
72 | }
73 | originShow()
74 | }
75 | }
76 | }
77 | })
78 |
--------------------------------------------------------------------------------
/examples/base/src/pages/main/children/config.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 使用单例模式
7 |
11 |
12 |
13 |
14 | 单例模式是指系统中一个路由对应的组件(缓存实例)只存在一个,
15 | 比如A=>B=>C=>A,系统中只会存在3个缓存实例,
16 | 所以路由和缓存实例(key)是一一对应的。
17 |
18 |
19 | 多例模式是指系统中一个路由对应的组件(缓存实例)可以存在多个,
20 | 比如A=>B=>C=>A,系统中会存在4个缓存实例,
21 | 所以路由和缓存实例(key)是一对多的关系。
22 |
23 |
24 | 多例模式系统性能存在浪费,
25 | 多例模式可以说是简单模式,
26 | 如果A要回跳C页面需要C页面刷新,只能push到C页面,
27 | 多例模式能做的事情单例模式都能实现。
28 |
29 |
30 | 单例模式系统的性能高,
31 | 有灵活的手动清除缓存的api,
32 | 如果A要回跳C页面需要C页面刷新,
33 | 可以调用api清除C页面缓存然后back到C页面(推荐)。
34 | 也可以直接push到C页面(但是这样浏览器会比执行back多存历史记录)
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
77 |
78 |
115 |
--------------------------------------------------------------------------------
/examples/base/src/pages/main/children/enter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{`v${version}`}}
7 |
8 |
18 |
{{`current vue version ${VueVersion}`}}
19 |
{{`current vue-router version ${VueRouterVersion}`}}
20 |
21 |
22 |
23 |
24 |
46 |
47 |
114 |
--------------------------------------------------------------------------------
/examples/base/src/pages/main/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
24 |
--------------------------------------------------------------------------------
/examples/base/src/pages/main/routes.js:
--------------------------------------------------------------------------------
1 | // 嵌套路由
2 | // const routes = [
3 | // {
4 | // path: '/main/',
5 | // component: () => import('./index.vue'),
6 | // children: [
7 | // {
8 | // path: '/main/enter',
9 | // name: 'mainEnter',
10 | // component: () => import('./children/enter.vue')
11 | // },
12 | // {
13 | // path: '/main/config',
14 | // name: 'mainConfig',
15 | // component: () => import('./children/config.vue')
16 | // },
17 | // ]
18 | // }
19 | // ]
20 |
21 | // 非嵌套路由
22 | const routes = [
23 | {
24 | path: '/main/enter',
25 | name: 'mainEnter',
26 | component: () => import('./children/enter.vue')
27 | },
28 | {
29 | path: '/main/config',
30 | name: 'mainConfig',
31 | component: () => import('./children/config.vue')
32 | },
33 | ]
34 |
35 | export default routes
36 |
--------------------------------------------------------------------------------
/examples/base/src/pages/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import MainRoutes from './main/routes.js'
4 | import TestCaseRoutes from './test-case/routes.js'
5 |
6 | Vue.use(Router)
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | redirect: '/main/enter',
12 | },
13 | ...MainRoutes,
14 | ...TestCaseRoutes,
15 | ]
16 |
17 | const router = new Router({
18 | mode: 'hash',
19 | routes: routes
20 | })
21 |
22 | export default router
23 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/children/letter-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
id: {{letterDetail.id}}
13 |
text: {{letterDetail.text}}
14 |
15 |
16 |
修改当前列表元素的text为输入框的值
17 |
删除并返回
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
103 |
104 |
146 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/children/letter-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
66 |
67 |
77 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/children/mixins/scroll.js:
--------------------------------------------------------------------------------
1 | import { isSingleMode } from '@/config.js'
2 |
3 | export default {
4 | activated() {
5 | if (!isSingleMode) {
6 | this.pullingDownHandler()
7 | }
8 | this.$nextTick(() => {
9 | this.$refs.scroll.refresh()
10 | })
11 | },
12 | data() {
13 | return {
14 | scrollEvents: ['scroll'],
15 | scrollOptions: {
16 | probeType: 3,
17 | click: true,
18 | pullDownRefresh: {
19 | // 阀值
20 | threshold: 80,
21 | // 滞留的位置
22 | stop: 60,
23 | txt: '更新成功',
24 | stopTime: 400
25 | },
26 | directionLockThreshold: 0,
27 | },
28 | }
29 | },
30 | mounted() {
31 | this.$refs.scroll.autoPullDownRefresh()
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/children/number-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
id: {{numberDetail.id}}
13 |
text: {{numberDetail.text}}
14 |
子项长度: {{numberDetail.children.length}}
15 |
16 |
17 |
修改当前列表元素的text为输入框的值
18 |
19 |
27 |
28 |
29 |
30 |
31 |
32 |
129 |
130 |
172 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/children/number-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
65 |
66 |
75 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
25 |
--------------------------------------------------------------------------------
/examples/base/src/pages/test-case/routes.js:
--------------------------------------------------------------------------------
1 | // 嵌套路由
2 | // const routes = [
3 | // {
4 | // path: '/test-case/',
5 | // component: () => import('./index.vue'),
6 | // children: [
7 | // {
8 | // path: '/test-case/number-list',
9 | // name: 'testCaseNumberList',
10 | // component: () => import('./children/number-list.vue')
11 | // },
12 | // {
13 | // path: '/test-case/number-detail/:numberId',
14 | // name: 'testCaseNumberDetail',
15 | // component: () => import('./children/number-detail.vue')
16 | // },
17 | // {
18 | // path: '/test-case/letter-list/:numberId',
19 | // name: 'testCaseLetterList',
20 | // component: () => import('./children/letter-list.vue')
21 | // },
22 | // {
23 | // path: '/test-case/letter-detail/:numberId/:letterId',
24 | // name: 'testCaseLetterDetail',
25 | // component: () => import('./children/letter-detail.vue')
26 | // },
27 | // ]
28 | // }
29 | // ]
30 |
31 | // 非嵌套路由
32 | const routes = [
33 | {
34 | path: '/test-case/number-list',
35 | name: 'testCaseNumberList',
36 | component: () => import('./children/number-list.vue')
37 | },
38 | {
39 | path: '/test-case/number-detail/:numberId',
40 | name: 'testCaseNumberDetail',
41 | component: () => import('./children/number-detail.vue')
42 | },
43 | {
44 | path: '/test-case/letter-list/:numberId',
45 | name: 'testCaseLetterList',
46 | component: () => import('./children/letter-list.vue')
47 | },
48 | {
49 | path: '/test-case/letter-detail/:numberId/:letterId',
50 | name: 'testCaseLetterDetail',
51 | component: () => import('./children/letter-detail.vue')
52 | },
53 | ]
54 |
55 | export default routes
56 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/helpers/bom.js:
--------------------------------------------------------------------------------
1 | const DEFAULT_INTERVAL = 100 / 6
2 |
3 | let timeStart
4 |
5 | export const requestAnimationFrame = (() => {
6 | return window.requestAnimationFrame ||
7 | window.webkitRequestAnimationFrame ||
8 | window.mozRequestAnimationFrame ||
9 | window.oRequestAnimationFrame ||
10 | window.msRequestAnimationFrame ||
11 | function (cb) {
12 | return window.setTimeout(() => {
13 | let timestamp = new Date().getTime()
14 | if (!timeStart) {
15 | timeStart = timestamp
16 | }
17 | let timeCurrent = timestamp - timeStart
18 | cb(timeCurrent)
19 | }, DEFAULT_INTERVAL)
20 | }
21 | })()
22 |
23 | export const cancelAnimationFrame = (() => {
24 | return window.cancelAnimationFrame ||
25 | window.webkitCancelAnimationFrame ||
26 | window.mozCancelAnimationFrame ||
27 | window.oCancelAnimationFrame ||
28 | function (id) {
29 | return window.clearTimeout(id)
30 | }
31 | })()
32 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/helpers/create-api.js:
--------------------------------------------------------------------------------
1 | import createAPIComponent from 'vue-create-api'
2 | // import createAPIComponent from '@/lib/vue-create-api/dist/vue-create-api.esm.js'
3 |
4 | export default function createAPI(Vue, Component, events, single) {
5 | Vue.use(createAPIComponent)
6 | const api = Vue.createAPI(Component, events, single)
7 | return api
8 | }
9 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/helpers/dom.js:
--------------------------------------------------------------------------------
1 | import { camelize } from './utils.js'
2 |
3 | export function hasClass(el, className) {
4 | const reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
5 | return reg.test(el.className)
6 | }
7 |
8 | export function addClass(el, className) {
9 | /* istanbul ignore if */
10 | if (hasClass(el, className)) {
11 | return
12 | }
13 |
14 | const newClass = el.className.split(' ')
15 | newClass.push(className)
16 | el.className = newClass.join(' ')
17 | }
18 |
19 | export function removeClass(el, className) {
20 | /* istanbul ignore if */
21 | if (!hasClass(el, className)) {
22 | return
23 | }
24 |
25 | const reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g')
26 | el.className = el.className.replace(reg, ' ')
27 | }
28 |
29 | export function getData(el, name) {
30 | const prefix = 'data-'
31 | return el.getAttribute(prefix + name)
32 | }
33 |
34 | export function setData(el, name, value) {
35 | const prefix = 'data-'
36 | el.setAttribute(prefix + name, value)
37 | }
38 |
39 | // getRect是获取相对父元素的位置,如果想获取相对页面的位置
40 | // 请使用getBoundingClientRect
41 | export function getRect(el) {
42 | return {
43 | top: el.offsetTop,
44 | left: el.offsetLeft,
45 | width: el.offsetWidth,
46 | height: el.offsetHeight
47 | }
48 | }
49 |
50 | const elementStyle = document.createElement('div').style
51 |
52 | const endEventListenerList = ['transitionend', 'animationend']
53 |
54 | const browserPrefix = {
55 | standard: '',
56 | webkit: 'webkit',
57 | Moz: 'Moz',
58 | O: 'O',
59 | ms: 'ms',
60 | }
61 |
62 | const endEventListenerPrefixList = {
63 | transition: {
64 | transition: 'transitionend',
65 | webkitTransition: 'webkitTransitionEnd',
66 | MozTransition: 'transitionend',
67 | OTransition: 'oTransitionEnd',
68 | msTransition: 'msTransitionEnd'
69 | },
70 | animation: {
71 | animation: 'animationend',
72 | webkitAnimation: 'webkitAnimationEnd',
73 | MozAnimation: 'animationend',
74 | OAnimation: 'oAnimationEnd',
75 | msAnimation: 'msAnimationEnd'
76 | }
77 | }
78 |
79 | export function prefixStyle(style) {
80 | let baseStyle = ''
81 | if (endEventListenerList.indexOf(style) !== -1) {
82 | baseStyle = style.replace(/end/i, '')
83 | }
84 |
85 | for (let key in browserPrefix) {
86 | if (baseStyle) {
87 | let cssPrefixStyle = browserPrefix[key] ? browserPrefix[key] + '-' + baseStyle : baseStyle
88 | let keyName = camelize(cssPrefixStyle)
89 | if (elementStyle[keyName] !== undefined) {
90 | return endEventListenerPrefixList[baseStyle][keyName]
91 | }
92 | } else {
93 | let cssPrefixStyle = browserPrefix[key] ? browserPrefix[key] + '-' + style : style
94 | let keyName = camelize(cssPrefixStyle)
95 | if (elementStyle[keyName] !== undefined) {
96 | return keyName
97 | }
98 | }
99 | }
100 | return ''
101 | }
102 |
103 | export function getMatchedTarget(e, targetClassName) {
104 | let el = e.target
105 |
106 | while (el && !hasClass(el, targetClassName)) {
107 | if (el === e.currentTarget) return null
108 | el = el.parentNode
109 | }
110 |
111 | return el
112 | }
113 |
114 | export function dispatchEvent(el, name, { type = 'Event', bubbles = true, cancelable = true } = {}) {
115 | const e = document.createEvent(type)
116 | e.initEvent(name, bubbles, cancelable)
117 | el.dispatchEvent(e)
118 | }
119 |
120 | // 得到transform上的rotate,其他值不准确
121 | export function getTransformAngle(dom) {
122 | const transform = prefixStyle('transform')
123 | let matrix = getComputedStyle(dom).getPropertyValue(transform)
124 | let angle = 0
125 | if (matrix && matrix !== 'none') {
126 | let values = matrix.split('(')[1].split(')')[0].split(',')
127 | let a = values[0]
128 | let b = values[1]
129 | angle = Math.round(Math.atan2(b, a) * (180 / Math.PI))
130 | }
131 | return angle
132 | }
133 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/helpers/ease.js:
--------------------------------------------------------------------------------
1 | // https://easings.net/
2 | export const EASE_IN_OUT = 'cubic-bezier(.61,0,.44,1)'
3 | export const EASE_OUT_QUART = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
4 | export const EASE_OUT_CUBIC = 'cubic-bezier(.22,.61,.36,1)'
5 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/helpers/utils.js:
--------------------------------------------------------------------------------
1 | const hasOwnProperty = Object.prototype.hasOwnProperty
2 |
3 | export function hasOwn(obj, key) {
4 | return hasOwnProperty.call(obj, key)
5 | }
6 |
7 | const _toString = Object.prototype.toString
8 |
9 | export function toRawType(value) {
10 | return _toString.call(value).slice(8, -1)
11 | }
12 |
13 | export function deepClone(value) {
14 | let ret
15 | const type = toRawType(value)
16 |
17 | if (type === 'Object') {
18 | ret = {}
19 | } else if (type === 'Array') {
20 | ret = []
21 | } else {
22 | return value
23 | }
24 |
25 | Object.keys(value).forEach((key) => {
26 | const copy = value[key]
27 | ret[key] = deepClone(copy)
28 | })
29 |
30 | return ret
31 | }
32 |
33 | export function deepAssign(origin, mixin) {
34 | for (const key in mixin) {
35 | if (!origin[key] || typeof origin[key] !== 'object') {
36 | origin[key] = mixin[key]
37 | } else {
38 | deepAssign(origin[key], mixin[key])
39 | }
40 | }
41 | }
42 |
43 | export function multiDeepClone(target, ...rest) {
44 | for (let i = 0; i < rest.length; i++) {
45 | const clone = deepClone(rest[i])
46 | deepAssign(target, clone)
47 | }
48 | return target
49 | }
50 |
51 | export function camelize(str) {
52 | str = String(str)
53 | return str.replace(/-(\w)/g, function (m, c) {
54 | return c ? c.toUpperCase() : ''
55 | })
56 | }
57 |
58 | export function pxToNum(str) {
59 | str = str + ''
60 | return Number(str.replace(/px/g, ''))
61 | }
62 |
63 | export function padPx(num) {
64 | return `${num}px`
65 | }
66 |
67 | export function styleTogglePx(style, mode = true) {
68 | const list = [
69 | 'width',
70 | 'height',
71 | 'line-height',
72 | 'font-size',
73 | 'left',
74 | 'right',
75 | 'top',
76 | 'bottom',
77 | 'margin-left',
78 | 'margin-right',
79 | 'margin-top',
80 | 'margin-bottom',
81 | 'padding-left',
82 | 'padding-right',
83 | 'padding-top',
84 | 'padding-bottom',
85 | ]
86 |
87 | const map = {}
88 |
89 | for (const key in style) {
90 | if (list.indexOf(key) !== -1) {
91 | if (mode && !isNaN(style[key] - 0)) {
92 | map[key] = padPx(style[key])
93 | } else if (!mode) {
94 | map[key] = pxToNum(style[key])
95 | }
96 | }
97 | }
98 |
99 | return multiDeepClone(style, map)
100 | }
101 |
102 | export function stylePadPx(style) {
103 | return styleTogglePx(style, true)
104 | }
105 |
106 | export function styleRemovePx(style) {
107 | return styleTogglePx(style, false)
108 | }
109 |
110 | export function assignArray() {
111 | const arr = [].concat.apply([], arguments)
112 | const ret = []
113 | for (let i = 0, len = arr.length; i < len; i++) {
114 | if (ret.indexOf(arr[i]) !== -1) {
115 | ret.push(arr[i])
116 | }
117 | }
118 | return ret
119 | }
120 |
121 | export function spliceArray() {
122 | const arr = arguments[0]
123 | const spliceList = [].concat.apply([], [].slice.call(arguments, 1))
124 | return arr.filter((item) => {
125 | return spliceList.indexOf(item) === -1
126 | })
127 | }
128 |
129 | const DEFAULT_TIME_SLICE = 400
130 |
131 | export class Debounce {
132 | constructor(timeSlice = DEFAULT_TIME_SLICE) {
133 | this.timeSlice = timeSlice
134 | }
135 | run(func) {
136 | if (typeof func === 'function') {
137 | if (this.timer) {
138 | window.clearTimeout(this.timer)
139 | }
140 | this.timer = window.setTimeout(func, this.timeSlice)
141 | }
142 | }
143 | destroy() {
144 | window.clearTimeout(this.timer)
145 | this.timer = null
146 | this.timeSlice = null
147 | }
148 | }
149 |
150 | export class Throttle {
151 | constructor(timeSlice = DEFAULT_TIME_SLICE) {
152 | this.timeSlice = timeSlice
153 | }
154 | run(func, overload) {
155 | const currentTime = new Date().getTime()
156 | if (!this.lastTime || currentTime - this.lastTime > this.timeSlice) {
157 | this.lastTime = currentTime
158 | if (typeof func === 'function') {
159 | func()
160 | }
161 | } else {
162 | if (typeof overload === 'function') {
163 | overload()
164 | }
165 | }
166 | }
167 | destroy() {
168 | this.timeSlice = null
169 | this.lastTime = null
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/mixins/duration.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | value: {
4 | type: Boolean,
5 | default: false
6 | },
7 | duration: {
8 | type: Number,
9 | default: 0
10 | },
11 | },
12 | data() {
13 | return {
14 | isVisible: this.value
15 | }
16 | },
17 | watch: {
18 | value: {
19 | handler(newVal) {
20 | if (newVal) {
21 | this.show()
22 | } else {
23 | this.hide()
24 | }
25 | }
26 | }
27 | },
28 | methods: {
29 | show() {
30 | this.isVisible = true
31 | this.clearTimer()
32 | this.$nextTick(() => {
33 | if (this.duration !== 0) {
34 | this.timer = window.setTimeout(() => {
35 | this.hide()
36 | }, this.duration)
37 | }
38 | })
39 | },
40 | hide() {
41 | this.isVisible = false
42 | this.clearTimer()
43 | },
44 | clearTimer() {
45 | window.clearTimeout(this.timer)
46 | this.timer = null
47 | }
48 | },
49 | destroyed() {
50 | this.clearTimer()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/mixins/popup.js:
--------------------------------------------------------------------------------
1 | import visibility from './visibility.js'
2 |
3 | export default {
4 | mixins: [
5 | visibility
6 | ],
7 | props: {
8 | zIndex: {
9 | type: Number,
10 | default: 100
11 | },
12 | isShowMask: {
13 | type: Boolean,
14 | default: true
15 | },
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/mixins/visibility.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | value: {
4 | type: Boolean,
5 | default: false
6 | },
7 | },
8 | data() {
9 | return {
10 | isVisible: this.value
11 | }
12 | },
13 | watch: {
14 | value: {
15 | handler(newVal) {
16 | if (newVal) {
17 | this.show()
18 | } else {
19 | this.hide()
20 | }
21 | }
22 | }
23 | },
24 | methods: {
25 | hide() {
26 | this.isVisible = false
27 | },
28 | show() {
29 | this.isVisible = true
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/base.styl:
--------------------------------------------------------------------------------
1 | body
2 | width: 100%
3 | height: 100%
4 |
5 | // dpr小于1.5的是pc端,不做缩小处理
6 | // dpr大于1.5生效,由于后面设置了dpr大于2,所以这里相当于1.5~2生效
7 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5)
8 | .vi-border-1px
9 | &:before, &:after
10 | transform: scaleY(0.7)
11 |
12 | // dpr大于2生效
13 | @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)
14 | .vi-border-1px
15 | &:before, &:after
16 | transform: scaleY(0.5)
17 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/index.styl:
--------------------------------------------------------------------------------
1 | @import './base.styl'
2 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/mixin.styl:
--------------------------------------------------------------------------------
1 | @import './var/z-index.styl'
2 |
3 | // 设置顶部1px
4 | border-top-1px($color)
5 | position: relative
6 | &:before
7 | content: ""
8 | position: absolute
9 | display: block
10 | left: 0
11 | right: 0
12 | height: 1px
13 | background: $color
14 | top: 0
15 |
16 | border-bottom-1px($color)
17 | position: relative
18 | &:before
19 | content: ""
20 | position: absolute
21 | display: block
22 | left: 0
23 | right: 0
24 | height: 1px
25 | background: $color
26 | bottom: 0
27 |
28 | border-none()
29 | &:before, &:after
30 | display: none
31 |
32 | clear()
33 | &:after
34 | content: ""
35 | display: block
36 | clear: both
37 | width: 0
38 | height: 0
39 | line-height: 0
40 | visibility: hidden
41 |
42 | // 扩展点击区域
43 | extend-click()
44 | position: relative
45 | &:before
46 | content: ""
47 | position: absolute
48 | top: -10px
49 | left: -10px
50 | right: -10px
51 | bottom: -10px
52 |
53 | // 在popup层级上加z-index
54 | z-index-popup(number)
55 | z-index: $z-index-popup + number
56 |
57 | // 盒子阴影
58 | box-shadow()
59 | 0 4px 10px 0 rgba(153, 153, 153 , 0.14)
60 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/var/color.styl:
--------------------------------------------------------------------------------
1 | $color-popup-background = rgba(0, 0, 0, 0.6)
2 | $color-popup-content-background = #efeff4
3 | $color-border = #d9d9d9
4 | $color-text = #fff
5 | $color-text-d = rgba(255, 255, 255, 0.3)
6 | $color-text-l = rgba(255, 255, 255, 0.5)
7 | $color-text-ll = rgba(255, 255, 255, 0.8)
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/var/ease.styl:
--------------------------------------------------------------------------------
1 | $ease-out-in = cubic-bezier(.61,0,.44,1)
2 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/var/font-size.styl:
--------------------------------------------------------------------------------
1 | $font-size-small-s = 10px
2 | $font-size-small = 12px
3 | $font-size-medium = 14px
4 | $font-size-medium-x = 16px
5 | $font-size-medium-xx = 18px
6 | $font-size-medium-xxx = 20px
7 | $font-size-large = 22px
8 | $font-size-large-x = 24px
9 | $font-size-large-xx = 26px
10 | $font-size-large-xxx = 28px
11 | $font-size-large-xxxx = 30px
12 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/common/stylus/var/z-index.styl:
--------------------------------------------------------------------------------
1 | $z-index-popup-content = 220
2 | // popup的层级
3 | $z-index-popup = 200
4 | $z-index-page-xx = 150
5 | $z-index-page-x = 120
6 | // 用fixed覆盖上个页面的用这个
7 | $z-index-page = 100
8 | // 通常使用
9 | $z-index-normal = 10
10 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-collapse/index.js:
--------------------------------------------------------------------------------
1 | import ViCollapse from './vi-collapse.vue'
2 | import ViCollapseTransition from './vi-collapse-transition.js'
3 | import ViCollapseTransitionGroup from './vi-collapse-transition-group.js'
4 |
5 | ViCollapse.install = function (Vue) {
6 | Vue.component(ViCollapse.name, ViCollapse)
7 | Vue.component(ViCollapseTransition.name, ViCollapseTransition)
8 | Vue.component(ViCollapseTransitionGroup.name, ViCollapseTransitionGroup)
9 | }
10 |
11 | export default ViCollapse
12 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-collapse/transition-event.js:
--------------------------------------------------------------------------------
1 | import { prefixStyle } from '../../common/helpers/dom.js'
2 |
3 | const TRANSITION = prefixStyle('transition')
4 |
5 | export function transitionEvent(duration = 200) {
6 | return {
7 | beforeEnter(el) {
8 | if (!el.dataset) {
9 | el.dataset = {}
10 | }
11 | el.dataset.oldPaddingTop = el.style.paddingTop
12 | el.dataset.oldPaddingBottom = el.style.paddingBottom
13 | el.style[TRANSITION] = `height ${duration}ms cubic-bezier(.61,0,.44,1), padding-top ${duration}ms cubic-bezier(.61,0,.44,1), padding-bottom ${duration}ms cubic-bezier(.61,0,.44,1)`
14 | el.style.height = 0
15 | el.style.paddingTop = 0
16 | el.style.paddingBottom = 0
17 | },
18 | enter(el) {
19 | el.dataset.oldOverflow = el.style.overflow
20 | if (el.scrollHeight !== 0) {
21 | el.style.height = el.scrollHeight + 'px'
22 | el.style.paddingTop = el.dataset.oldPaddingTop
23 | el.style.paddingBottom = el.dataset.oldPaddingBottom
24 | } else {
25 | el.style.height = ''
26 | el.style.paddingTop = el.dataset.oldPaddingTop
27 | el.style.paddingBottom = el.dataset.oldPaddingBottom
28 | }
29 |
30 | el.style.overflow = 'hidden'
31 | },
32 | afterEnter(el) {
33 | // for safari: remove class then reset height is necessary
34 | el.style[TRANSITION] = ''
35 | el.style.height = ''
36 | el.style.overflow = el.dataset.oldOverflow
37 | },
38 | beforeLeave(el) {
39 | if (!el.dataset) {
40 | el.dataset = {}
41 | }
42 | el.dataset.oldPaddingTop = el.style.paddingTop
43 | el.dataset.oldPaddingBottom = el.style.paddingBottom
44 | el.dataset.oldOverflow = el.style.overflow
45 |
46 | el.style.height = el.scrollHeight + 'px'
47 | el.style.overflow = 'hidden'
48 | },
49 | leave(el) {
50 | if (el.scrollHeight !== 0) {
51 | // for safari: add class after set height, or it will jump to zero height suddenly, weired
52 | el.style[TRANSITION] = `height ${duration}ms cubic-bezier(.61,0,.44,1), padding-top ${duration}ms cubic-bezier(.61,0,.44,1), padding-bottom ${duration}ms cubic-bezier(.61,0,.44,1)`
53 | el.style.height = 0
54 | el.style.paddingTop = 0
55 | el.style.paddingBottom = 0
56 | }
57 | },
58 | afterLeave(el) {
59 | // removeClass(el, 'vi-collapse-transition')
60 | el.style[TRANSITION] = ''
61 | el.style.height = ''
62 | el.style.overflow = el.dataset.oldOverflow
63 | el.style.paddingTop = el.dataset.oldPaddingTop
64 | el.style.paddingBottom = el.dataset.oldPaddingBottom
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-collapse/vi-collapse-transition-group.js:
--------------------------------------------------------------------------------
1 | import { transitionEvent } from './transition-event.js'
2 |
3 | const COMPONENT_NAME = 'vi-collapse-transition-group'
4 |
5 | export default {
6 | name: COMPONENT_NAME,
7 | functional: true,
8 | render(h, { children, data }) {
9 | let tag = data.attrs && data.attrs.tag ? data.attrs.tag : 'div'
10 | let duration = data.attrs && data.attrs.duration ? data.attrs.duration : 200
11 | let componentData = Object.assign({
12 | on: transitionEvent(duration),
13 | props: {
14 | tag: tag
15 | },
16 | }, data)
17 | return h('transition-group', componentData, children)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-collapse/vi-collapse-transition.js:
--------------------------------------------------------------------------------
1 | import { transitionEvent } from './transition-event.js'
2 |
3 | const COMPONENT_NAME = 'vi-collapse-transition'
4 |
5 | export default {
6 | name: COMPONENT_NAME,
7 | functional: true,
8 | render(h, { children, data }) {
9 | let duration = data.attrs && data.attrs.duration ? data.attrs.duration : 200
10 | let componentData = Object.assign({
11 | on: transitionEvent(duration),
12 | }, data)
13 | return h('transition', componentData, children)
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-collapse/vi-collapse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
92 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-confirm/api.js:
--------------------------------------------------------------------------------
1 | import createAPI from '../../common/helpers/create-api.js'
2 | import { EVENT_CANCEL, EVENT_CONFIRM } from './vi-confirm.vue'
3 |
4 | export default function create(Vue, Component) {
5 | createAPI(Vue, Component, [
6 | EVENT_CANCEL,
7 | EVENT_CONFIRM
8 | ], true)
9 | }
10 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-confirm/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-confirm.vue'
2 | import create from './api.js'
3 |
4 | Component.install = function (Vue) {
5 | Vue.component(Component.name, Component)
6 | create(Vue, Component)
7 | }
8 |
9 | export default Component
10 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-confirm/vi-confirm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
{{title}}
12 |
{{text}}
13 |
14 |
{{cancelText}}
15 |
{{confirmText}}
16 |
17 |
18 |
19 |
20 |
21 |
84 |
85 |
158 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-loading/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-loading.vue'
2 |
3 | Component.install = function (Vue) {
4 | Vue.component(Component.name, Component)
5 | }
6 |
7 | export default Component
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-loading/vi-loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
113 |
114 |
153 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-page/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-page.vue'
2 |
3 | Component.install = function (Vue) {
4 | Vue.component(Component.name, Component)
5 | }
6 |
7 | export default Component
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-page/vi-page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
46 |
47 |
59 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-popup/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-popup.vue'
2 |
3 | Component.install = function (Vue) {
4 | Vue.component(Component.name, Component)
5 | }
6 |
7 | export default Component
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-popup/vi-popup.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
29 |
30 |
31 |
32 |
103 |
104 |
130 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-scroll/README.md:
--------------------------------------------------------------------------------
1 | 1.状态pulldownState初始值为normal
2 | 2.没有达到下拉阀值threshold,状态仍为normal,pullDownNormalTop为0
3 | 3.达到下拉阀值,pullDownNormalTop递增下拉的距离,状态仍为normal,
4 | 松手后卡死状态,状态为locking
5 | 4.有新数据,状态为finish,stopTime秒后状态变成normal
6 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-scroll/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-scroll.vue'
2 |
3 | Component.install = function (Vue) {
4 | Vue.component(Component.name, Component)
5 | }
6 |
7 | export default Component
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-switch/index.js:
--------------------------------------------------------------------------------
1 | import Component from './vi-switch.vue'
2 |
3 | Component.install = function (Vue) {
4 | Vue.component(Component.name, Component)
5 | }
6 |
7 | export default Component
8 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/components/vi-switch/vi-switch.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
52 |
53 |
93 |
--------------------------------------------------------------------------------
/examples/base/src/plugins/vi-ui/index.js:
--------------------------------------------------------------------------------
1 | import Style from './common/stylus/index.styl'
2 | import ViPage from './components/vi-page/index.js'
3 | import ViPopup from './components/vi-popup/index.js'
4 | import ViSwitch from './components/vi-switch/index.js'
5 | import ViLoading from './components/vi-loading/index.js'
6 | import ViScroll from './components/vi-scroll/index.js'
7 | import ViCollapse from './components/vi-collapse/index.js'
8 | import ViConfirm from './components/vi-confirm/index.js'
9 |
10 | const components = [
11 | ViPage,
12 | ViPopup,
13 | ViSwitch,
14 | ViLoading,
15 | ViScroll,
16 | ViCollapse,
17 | ViConfirm,
18 | ]
19 |
20 | function install(Vue) {
21 | if (install.isInstalled) {
22 | return
23 | }
24 | install.isInstalled = true
25 | components.forEach((Component) => {
26 | Component.install(Vue)
27 | })
28 | }
29 |
30 | const ViUi = {
31 | install,
32 | version: '0.0.1',
33 | }
34 |
35 | export default ViUi
36 |
--------------------------------------------------------------------------------
/examples/base/src/rem.js:
--------------------------------------------------------------------------------
1 | import { MOBILE_MAX_SIZE } from '@/common/helpers/utils.js'
2 |
3 | const DESIGN_WIDTH = 375
4 | const REM2PX = 100
5 |
6 | function resizeHandler() {
7 | const innerWidth = window.innerWidth > MOBILE_MAX_SIZE ? MOBILE_MAX_SIZE : window.innerWidth
8 | const remFontSize = innerWidth / DESIGN_WIDTH * REM2PX
9 | document.documentElement.style.fontSize = remFontSize + 'px'
10 | }
11 |
12 | resizeHandler()
13 | window.addEventListener('resize', resizeHandler, false)
14 |
--------------------------------------------------------------------------------
/examples/base/src/store/cache/local-storage/index.js:
--------------------------------------------------------------------------------
1 | // https://github.com/marcuswestin/store.js#make-your-own-build
2 | const engine = require('store/src/store-engine')
3 | const storages = [
4 | require('store/storages/localStorage')
5 | ]
6 |
7 | const storePlugins = [
8 | require('store/plugins/defaults'),
9 | require('store/plugins/expire')
10 | ]
11 |
12 | const tpLocalStorage = engine.createStore(storages, storePlugins)
13 |
14 | export default tpLocalStorage
15 |
--------------------------------------------------------------------------------
/examples/base/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import local from './cache/local-storage/index.js'
4 | import createPersistedState from 'vuex-persistedstate'
5 |
6 | // modules 路由动画名称
7 | import routerTransition from './modules/router-transition/router-transition.js'
8 |
9 | // 不需要vuex-persistedstate做可持续化的mutations
10 | const persistedstateIgnoreMutations = [
11 | // 路由动画
12 | 'SET_ROUTER_TRANSITION_NAME',
13 | 'SET_ROUTER_TRANSITION_MODE',
14 | 'SET_ROUTER_TRANSITION_DURATION',
15 | ]
16 |
17 | // 储存一周
18 | const TIME_SLICE = 7 * 24 * 60 * 60 * 1000
19 |
20 | const VuexPlugins = [
21 | createPersistedState({
22 | key: 'vuex',
23 | filter(mutations) {
24 | // 过滤掉
25 | if (persistedstateIgnoreMutations.indexOf(mutations.type) !== -1) {
26 | return false
27 | }
28 | return true
29 | },
30 | getState(key) {
31 | return local.get(key)
32 | },
33 | setState(key, value) {
34 | local.set(key, value, new Date().getTime() + TIME_SLICE)
35 | },
36 | })
37 | ]
38 |
39 | // const debug = false
40 | const debug = process.env.NODE_ENV !== 'production'
41 |
42 | // if (debug) {
43 | // const createLogger = require('vuex/dist/logger')()
44 | // VuexPlugins.push(createLogger)
45 | // }
46 |
47 | Vue.use(Vuex)
48 |
49 | const store = new Vuex.Store({
50 | modules: {
51 | routerTransition,
52 | },
53 | strict: debug,
54 | plugins: VuexPlugins
55 | })
56 |
57 | export default store
58 |
--------------------------------------------------------------------------------
/examples/base/src/store/modules/router-transition/router-transition.js:
--------------------------------------------------------------------------------
1 | // 路由过渡动画的名字、模式、过渡时间
2 |
3 | const types = {
4 | SET_ROUTER_TRANSITION_NAME: 'SET_ROUTER_TRANSITION_NAME',
5 | SET_ROUTER_TRANSITION_MODE: 'SET_ROUTER_TRANSITION_MODE',
6 | SET_ROUTER_TRANSITION_DURATION: 'SET_ROUTER_TRANSITION_DURATION',
7 | }
8 |
9 | const DEFAULT_ROUTER_TRANSITION = {
10 | routerTransitionName: '',
11 | routerTransitionMode: '',
12 | routerTransitionDuration: {},
13 | }
14 |
15 | const routerTransition = {
16 | state: {
17 | ...DEFAULT_ROUTER_TRANSITION,
18 | },
19 | getters: {
20 | routerTransitionName: (state) => state.routerTransitionName,
21 | routerTransitionMode: (state) => state.routerTransitionMode,
22 | routerTransitionDuration: (state) => state.routerTransitionDuration,
23 | },
24 | mutations: {
25 | [types.SET_ROUTER_TRANSITION_NAME](state, routerTransitionName) {
26 | state.routerTransitionName = routerTransitionName
27 | },
28 | [types.SET_ROUTER_TRANSITION_MODE](state, routerTransitionMode) {
29 | state.routerTransitionMode = routerTransitionMode
30 | },
31 | [types.SET_ROUTER_TRANSITION_DURATION](state, routerTransitionDuration) {
32 | state.routerTransitionDuration = routerTransitionDuration
33 | },
34 | },
35 | actions: {
36 | setRouterTransition({ commit }, { routerTransitionName, routerTransitionMode, routerTransitionDuration }) {
37 | console.log(routerTransitionName)
38 | routerTransitionName && commit(types.SET_ROUTER_TRANSITION_NAME, routerTransitionName)
39 | routerTransitionMode && commit(types.SET_ROUTER_TRANSITION_MODE, routerTransitionMode)
40 | routerTransitionDuration && commit(types.SET_ROUTER_TRANSITION_DURATION, routerTransitionDuration)
41 | },
42 | clearRouterTransition({ commit }) {
43 | const {
44 | routerTransitionName,
45 | routerTransitionMode,
46 | routerTransitionDuration
47 | } = DEFAULT_ROUTER_TRANSITION
48 | commit(types.SET_ROUTER_TRANSITION_NAME, routerTransitionName)
49 | commit(types.SET_ROUTER_TRANSITION_MODE, routerTransitionMode)
50 | commit(types.SET_ROUTER_TRANSITION_DURATION, routerTransitionDuration)
51 | }
52 | }
53 | }
54 |
55 | export default routerTransition
56 |
--------------------------------------------------------------------------------
/examples/base/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/static/.gitkeep
--------------------------------------------------------------------------------
/examples/base/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kallsave/vue-router-cache/79be97049ea37c3edc21e77e697915449ea6bcf0/examples/base/static/favicon.ico
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-router-cache",
3 | "version": "1.0.6",
4 | "description": "a cache and manage vue-router with single case or multiple cases mode plugin",
5 | "main": "dist/vue-router-cache.js",
6 | "umd:main": "dist/vue-router-cache.umd.js",
7 | "module": "dist/vue-router-cache.esm.js",
8 | "keywords": [
9 | "vue",
10 | "vue-router",
11 | "cache and manage vue-router-dom",
12 | "vue-router plugin",
13 | "vue keep-alive",
14 | "vue-router-cache",
15 | "history-direction"
16 | ],
17 | "scripts": {
18 | "dev": "cross-env EXAMPLE=base TARGET=esm node scripts/dev.js",
19 | "build": "node scripts/build.js",
20 | "prod": "sh scripts/publish.sh",
21 | "test": "jest",
22 | "cypress": "cypress open"
23 | },
24 | "author": "kallsave <415034609@qq.com>",
25 | "license": "MIT",
26 | "devDependencies": {
27 | "@babel/core": "^7.8.7",
28 | "@babel/preset-env": "^7.8.7",
29 | "@rollup/plugin-alias": "^3.1.0",
30 | "@rollup/plugin-commonjs": "^11.0.2",
31 | "@rollup/plugin-node-resolve": "^7.1.1",
32 | "@rollup/plugin-replace": "^2.3.2",
33 | "cross-env": "^5.2.1",
34 | "cypress": "^4.11.0",
35 | "eslint-config-standard": "^14.1.0",
36 | "eslint-plugin-import": "^2.18.2",
37 | "eslint-plugin-node": "^10.0.0",
38 | "eslint-plugin-promise": "^4.2.1",
39 | "eslint-plugin-standard": "^4.0.1",
40 | "jest": "^24.9.0",
41 | "ncp": "^2.0.0",
42 | "rollup": "^2.1.0",
43 | "rollup-plugin-babel": "^4.3.3",
44 | "rollup-plugin-eslint": "^7.0.0",
45 | "terser": "^4.3.9",
46 | "zlib": "^1.0.5"
47 | },
48 | "directories": {
49 | "example": "examples",
50 | "test": "test"
51 | },
52 | "repository": {
53 | "type": "git",
54 | "url": "git+https://github.com/kallsave/vue-router-cache.git"
55 | },
56 | "bugs": {
57 | "url": "https://github.com/kallsave/vue-router-cache/issues"
58 | },
59 | "homepage": "https://github.com/kallsave/vue-router-cache#readme"
60 | }
61 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const rollup = require('rollup')
4 | const terser = require('terser')
5 | const zlib = require('zlib')
6 | const buildMap = require('./config')
7 |
8 | if (!fs.existsSync('dist')) {
9 | fs.mkdirSync('dist')
10 | }
11 |
12 | function write(dest, code, zip) {
13 | return new Promise((resolve, reject) => {
14 | function report(extra) {
15 | console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
16 | resolve()
17 | }
18 |
19 | fs.writeFile(dest, code, err => {
20 | if (err) return reject(err)
21 | if (zip) {
22 | zlib.gzip(code, (err, zipped) => {
23 | if (err) return reject(err)
24 | report(' (gzipped: ' + getSize(zipped) + ')')
25 | })
26 | } else {
27 | report()
28 | }
29 | })
30 | })
31 | }
32 |
33 | function getSize(code) {
34 | return (code.length / 1024).toFixed(2) + 'kb'
35 | }
36 |
37 | function logError(e) {
38 | console.log(e)
39 | }
40 |
41 | function blue(str) {
42 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
43 | }
44 |
45 | async function buildEntry() {
46 | for (const key in buildMap) {
47 | const config = buildMap[key]
48 | const output = config.output
49 | const { file, banner } = output
50 | const isMin = /(min)\.js$/.test(file)
51 | await (() => {
52 | return new Promise((resolve) => {
53 | rollup.rollup({
54 | input: config.input,
55 | plugins: config.plugins
56 | }).then((bundle) => bundle.generate(output))
57 | .then(({ output: [{ code }] }) => {
58 | if(isMin) {
59 | const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
60 | toplevel: true,
61 | output: {
62 | ascii_only: true
63 | },
64 | compress: {
65 | pure_funcs: ['makeMap']
66 | }
67 | }).code
68 | resolve()
69 | return write(file, minified, true)
70 | } else {
71 | resolve()
72 | return write(file, code)
73 | }
74 | })
75 | }).catch((err) => {
76 | console.error(err)
77 | })
78 | })()
79 | }
80 | }
81 |
82 | buildEntry()
83 |
--------------------------------------------------------------------------------
/scripts/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const node = require('@rollup/plugin-node-resolve')
3 | const cjs = require('@rollup/plugin-commonjs')
4 | const replace = require('@rollup/plugin-replace')
5 | const babel = require('rollup-plugin-babel')
6 | const eslint = require('rollup-plugin-eslint').eslint
7 |
8 | const util = require('./util')
9 | const package = require('../package.json')
10 | const author = package.author
11 | const name = package.name
12 | const apiName = util.createApiName(name)
13 | const version = package.version
14 |
15 | const banner =
16 | '/*!\n' +
17 | ` * ${name}.js v${version}\n` +
18 | ` * (c) 2019-${new Date().getFullYear()} ${author}\n` +
19 | ' * Released under the MIT License.\n' +
20 | ' */'
21 |
22 | const resolve = (p) => {
23 | return path.resolve(__dirname, '../', p)
24 | }
25 |
26 | const plugins = [
27 | replace({
28 | include: 'src/index.js',
29 | VERSION: version,
30 | }),
31 | eslint({
32 | include: [
33 | resolve('src/**/*.js')
34 | ]
35 | }),
36 | node(),
37 | cjs(),
38 | babel(),
39 | ]
40 |
41 | const input = resolve('src/index.js')
42 |
43 | const buildMap = {
44 | esm: {
45 | input,
46 | output: {
47 | file: resolve(`dist/${name}.esm.js`),
48 | format: 'esm',
49 | banner: banner
50 | },
51 | plugins,
52 | },
53 | umd: {
54 | input,
55 | output: {
56 | file: resolve(`dist/${name}.umd.js`),
57 | format: 'umd',
58 | name: apiName,
59 | banner: banner
60 | },
61 | plugins,
62 | },
63 | main: {
64 | input,
65 | output: {
66 | file: resolve(`dist/${name}.js`),
67 | format: 'umd',
68 | name: apiName,
69 | banner: banner
70 | },
71 | plugins,
72 | },
73 | min: {
74 | input,
75 | output: {
76 | file: resolve(`dist/${name}.min.js`),
77 | format: 'umd',
78 | name: apiName,
79 | banner: banner
80 | },
81 | plugins,
82 | }
83 | }
84 |
85 | module.exports = buildMap
86 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const rollup = require('rollup')
3 | const copy = require('ncp').ncp
4 | const buildMap = require('./config')
5 | const exec = require('child_process').exec
6 | const name = require('../package.json').name
7 |
8 | const EXAMPLE = process.env.EXAMPLE
9 | const TARGET = process.env.TARGET
10 |
11 | async function buildEntry() {
12 | const build = buildMap[TARGET]
13 | await (() => {
14 | return new Promise((resolve) => {
15 | const watcher = rollup.watch(build)
16 | watcher.on('event', event => {
17 | console.log(event.code)
18 | if (event.code === 'END') {
19 | copy('dist', `examples/${EXAMPLE}/src/plugins/${name}/`, function (err) {
20 | console.log(err)
21 | })
22 | resolve()
23 | }
24 | })
25 | })
26 | })()
27 | await (() => {
28 | return new Promise((resolve) => {
29 | const cmd = `cd examples/${EXAMPLE} && npm run dev`
30 | const childProcess = exec(cmd, function (err, stdout, stderr) {
31 | if (err) {
32 | console.log('err:', err)
33 | return
34 | }
35 | console.log(`start examples ${EXAMPLE}`)
36 | })
37 |
38 | childProcess.stdout.on('data', (data) => {
39 | console.log(data)
40 | })
41 | resolve()
42 | })
43 | })()
44 | }
45 |
46 | buildEntry()
47 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | npm run test
4 | npm run build
5 | cd examples/base
6 | npm run build
7 |
--------------------------------------------------------------------------------
/scripts/util.js:
--------------------------------------------------------------------------------
1 | exports.getIPAddress = () => {
2 | let interfaces = require('os').networkInterfaces()
3 | for (let devName in interfaces) {
4 | let iface = interfaces[devName]
5 | for (let i = 0; i < iface.length; i++) {
6 | let alias = iface[i]
7 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
8 | return alias.address;
9 | }
10 | }
11 | }
12 | }
13 |
14 | exports.camelize = (str) => {
15 | str = String(str)
16 | return str.replace(/-(\w)/g, function (m, c) {
17 | return c ? c.toUpperCase() : ''
18 | })
19 | }
20 |
21 | exports.createApiName = (str) => {
22 | str = str.replace(str[0], str[0].toUpperCase())
23 | return exports.camelize(str)
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/router-cache.js:
--------------------------------------------------------------------------------
1 | import { globalCache, globalStack } from '../store/index'
2 | import config from '../config/index'
3 |
4 | const routerCache = {
5 | resolveKeyFromRoute(route) {
6 | return route.name ? route.name : route.path
7 | },
8 | resolveKeyFromLocation(location) {
9 | const router = config.router
10 | const route = router.resolve(location).route
11 | return this.resolveKeyFromRoute(route)
12 | },
13 | removeGlobalCacheFromItem(removeItem) {
14 | for (let i = 0; i < globalCache.length; i++) {
15 | const globalCacheItem = globalCache[i]
16 | const cache = globalCacheItem.cache
17 | if (cache[removeItem]) {
18 | const componentInstance = cache[removeItem].componentInstance
19 | if (componentInstance) {
20 | componentInstance.$destroy()
21 | delete cache[removeItem]
22 | }
23 | }
24 | }
25 | },
26 | removeGlobalCacheFromList(removeList) {
27 | for (let i = 0; i < removeList.length; i++) {
28 | const removeItem = removeList[i]
29 | this.removeGlobalCacheFromItem(removeItem)
30 | }
31 | },
32 | shift() {
33 | const removeItem = globalStack.shift()
34 | if (removeItem) {
35 | this.removeGlobalCacheFromItem(removeItem)
36 | }
37 | },
38 | _remove(key) {
39 | const removeList = globalStack.remove(key)
40 | if (removeList.length) {
41 | this.removeGlobalCacheFromList(removeList)
42 | }
43 | },
44 | remove() {
45 | for (let i = 0; i < arguments.length; i++) {
46 | const item = arguments[i]
47 | const key = this.resolveKeyFromLocation(item)
48 | this._remove(key)
49 | }
50 | },
51 | _removeBackUntil(key) {
52 | const removeList = globalStack.removeBackUntil(key)
53 | if (removeList.length) {
54 | this.removeGlobalCacheFromList(removeList)
55 | }
56 | },
57 | removeBackUntil(location) {
58 | const key = this.resolveKeyFromLocation(location)
59 | this._removeBackUntil(key)
60 | },
61 | _removeBackInclue(key) {
62 | const removeList = globalStack.removeBackInclue(key)
63 | if (removeList.length) {
64 | this.removeGlobalCacheFromList(removeList)
65 | }
66 | },
67 | removeBackInclue(location) {
68 | const key = this.resolveKeyFromLocation(location)
69 | this._removeBackInclue(key)
70 | },
71 | removeBackByIndex(index) {
72 | const removeList = globalStack.removeBackByIndex(index)
73 | if (removeList.length) {
74 | this.removeGlobalCacheFromList(removeList)
75 | }
76 | },
77 | _removeExclude() {
78 | const removeList = globalStack.removeExclude(...arguments)
79 | if (removeList.length) {
80 | this.removeGlobalCacheFromList(removeList)
81 | }
82 | },
83 | removeExclude() {
84 | const excludeList = []
85 | for (let i = 0; i < arguments.length; i++) {
86 | const item = arguments[i]
87 | const key = this.resolveKeyFromLocation(item)
88 | excludeList.push(key)
89 | }
90 | this._removeExclude(...excludeList)
91 | },
92 | removeAll() {
93 | const removeList = globalStack.removeAll()
94 | if (removeList.length) {
95 | this.removeGlobalCacheFromList(removeList)
96 | }
97 | },
98 | getStore() {
99 | return {
100 | cache: globalCache,
101 | stack: globalStack.getStore()
102 | }
103 | },
104 | has(location) {
105 | const key = this.resolveKeyFromLocation(location)
106 | return globalStack.has(key)
107 | },
108 | skip() {
109 | this._isSkip = true
110 | }
111 | }
112 |
113 | export default routerCache
114 |
--------------------------------------------------------------------------------
/src/components/router-cache.js:
--------------------------------------------------------------------------------
1 | import { MapStack } from '../util/stack'
2 | import { BACK } from '../history/history-direction-name'
3 | import { globalCache, globalStack, globalMultiKeyMap } from '../store/index'
4 | import routerCache from '../api/router-cache'
5 | import config from '../config/index'
6 | import { log } from '../util/log'
7 |
8 | function isDef(v) {
9 | return v !== undefined && v !== null
10 | }
11 |
12 | function isAsyncPlaceholder(node) {
13 | return node.isComment && node.asyncFactory
14 | }
15 |
16 | function getFirstComponentChild(children) {
17 | if (Array.isArray(children)) {
18 | for (let i = 0; i < children.length; i++) {
19 | const c = children[i]
20 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
21 | return c
22 | }
23 | }
24 | }
25 | }
26 |
27 | function showUsingCacheKey(config, key) {
28 | if (config.isDebugger) {
29 | log(`using cache key: ${key}`)
30 | }
31 | }
32 |
33 | function showAllCacheKeys(config, globalStack) {
34 | if (config.isDebugger) {
35 | log(`all cache keys: ${JSON.stringify(globalStack.getStore())}`)
36 | }
37 | }
38 |
39 | const COMPONENT_NAME = 'router-cache'
40 |
41 | export default {
42 | name: COMPONENT_NAME,
43 | abstract: true,
44 | created() {
45 | this.cache = Object.create(null)
46 | globalCache.push({
47 | cache: this.cache,
48 | })
49 | },
50 | render() {
51 | const slot = this.$slots.default
52 | const vnode = getFirstComponentChild(slot)
53 | const rawChild = vnode || (slot && slot[0])
54 |
55 | let key
56 | let parent = this.$parent
57 | let depth = 0
58 | let inactive = false
59 | while (parent && parent._routerRoot !== parent) {
60 | const vnodeData = parent.$vnode && parent.$vnode.data
61 | if (vnodeData) {
62 | if (vnodeData.routerView) {
63 | depth++
64 | }
65 | if (parent._inactive) {
66 | inactive = true
67 | }
68 | }
69 | parent = parent.$parent
70 | }
71 |
72 | const matched = this.$route.matched[depth]
73 | if (vnode && matched) {
74 | if (routerCache._isSkip) {
75 | routerCache._isSkip = false
76 | showAllCacheKeys(config, globalStack)
77 | return rawChild
78 | }
79 |
80 | if (config.isSingleMode) {
81 | key = routerCache.resolveKeyFromRoute(matched)
82 | } else {
83 | const baseKey = routerCache.resolveKeyFromRoute(matched)
84 | if (!globalMultiKeyMap[baseKey]) {
85 | globalMultiKeyMap[baseKey] = new MapStack()
86 | }
87 | const lastKey = globalMultiKeyMap[baseKey].getByIndex(0)
88 | if (this.$route.params[config.directionKey] !== BACK || !lastKey) {
89 | key = `${baseKey}_${globalMultiKeyMap[baseKey].getSize()}`
90 | globalMultiKeyMap[baseKey].unshift(key)
91 | } else {
92 | key = lastKey
93 | }
94 | }
95 | if (this.cache[key]) {
96 | if (inactive) {
97 | vnode.componentInstance = this.oldComponentInstance
98 | } else {
99 | vnode.componentInstance = this.cache[key].componentInstance
100 | }
101 | showUsingCacheKey(config, key)
102 | } else {
103 | if (!globalStack.checkFull()) {
104 | if (!inactive) {
105 | this.cache[key] = vnode
106 | this.oldComponentInstance = vnode.componentInstance
107 | }
108 | } else {
109 | const lastKey = globalStack.getFooter()
110 | routerCache._remove(lastKey)
111 | if (!inactive) {
112 | this.cache[key] = vnode
113 | this.oldComponentInstance = vnode.componentInstance
114 | }
115 | }
116 | }
117 | globalStack.unshift(key)
118 | vnode.data.routerCache = true
119 | vnode.data.keepAlive = true
120 | }
121 | showAllCacheKeys(config, globalStack)
122 | return rawChild
123 | },
124 | beforeDestroy() {
125 | for (const key in this.cache) {
126 | routerCache._remove(key)
127 | }
128 | let index
129 | for (let i = 0; i < globalCache.length; i++) {
130 | if (this.cache === globalCache[i]) {
131 | index = i
132 | break
133 | }
134 | }
135 | globalCache.splice(index, 1)
136 | },
137 | }
138 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | const noop = function () { }
2 |
3 | const config = {
4 | max: Infinity,
5 | directionKey: 'direction',
6 | isSingleMode: true,
7 | routerMode: 'hash',
8 | isDebugger: false,
9 | getHistoryStack: noop,
10 | setHistoryStack: noop,
11 | }
12 |
13 | export default config
14 |
--------------------------------------------------------------------------------
/src/history/history-direction-name.js:
--------------------------------------------------------------------------------
1 | export const BACK = 'back'
2 | export const FORWARD = 'forward'
3 | export const REPLACE = 'replace'
4 | export const NONE = ''
5 |
--------------------------------------------------------------------------------
/src/history/history-stack.js:
--------------------------------------------------------------------------------
1 | import { Stack } from '../util/stack'
2 | import { defineReactive } from '../util/lang'
3 | import config from '../config/index'
4 |
5 | const historyStack = new Stack()
6 |
7 | defineReactive(config, 'getHistoryStack', (newVal) => {
8 | const list = newVal()
9 | if (!list) {
10 | return
11 | }
12 | const length = list.length
13 | for (let i = length - 1; i > -1; i--) {
14 | const item = list[i]
15 | historyStack.unshift(item)
16 | }
17 | })
18 |
19 | export default historyStack
20 |
--------------------------------------------------------------------------------
/src/history/history-state-event.js:
--------------------------------------------------------------------------------
1 | import config from '../config/index'
2 | import Events from '../util/events'
3 | import historyStack from './history-stack'
4 | import { BACK, FORWARD } from './history-direction-name'
5 |
6 | const historyStateEvent = new Events()
7 |
8 | function historyChange() {
9 | if (historyStack.getByIndex(1) === window.location.href) {
10 | historyStateEvent.emit(BACK)
11 | } else {
12 | historyStateEvent.emit(FORWARD)
13 | }
14 | }
15 |
16 | switch (config.routerMode) {
17 | case 'hash': {
18 | window.addEventListener('hashchange', historyChange)
19 |
20 | break
21 | }
22 |
23 | case 'history': {
24 | window.addEventListener('popstate', historyChange)
25 |
26 | break
27 | }
28 | }
29 |
30 | export default historyStateEvent
31 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import config from './config/index'
2 | import routerCache from './api/router-cache'
3 | import Component from './components/router-cache'
4 | import routerMiddle from './router-middle/index'
5 | import { isInt } from './util/lang'
6 | import { error } from './util/log'
7 |
8 | function install(Vue, options = {}) {
9 | if (install.installed) {
10 | return
11 | }
12 | install.installed = true
13 |
14 | if (!options.router) {
15 | error('parameter router is required')
16 | return
17 | }
18 |
19 | if (!isInt(options.max)) {
20 | error('parameter max must be an integer')
21 | return
22 | }
23 |
24 | Object.assign(config, options)
25 | Vue.prototype.$routerCache = routerCache
26 | Vue.component(Component.name, Component)
27 | routerMiddle(Vue, config)
28 | }
29 |
30 | const VuerouterCache = {
31 | install,
32 | routerCache,
33 | version: 'VERSION'
34 | }
35 |
36 | export default VuerouterCache
37 |
--------------------------------------------------------------------------------
/src/router-middle/index.js:
--------------------------------------------------------------------------------
1 | import historyStack from '../history/history-stack'
2 | import historyStateEvent from '../history/history-state-event'
3 | import {
4 | BACK,
5 | FORWARD,
6 | REPLACE,
7 | NONE
8 | } from '../history/history-direction-name'
9 | import { defineReactive } from '../util/lang'
10 | import routerCache from '../api/router-cache'
11 | import config from '../config/index'
12 | import { globalMultiKeyMap } from '../store/index'
13 |
14 | let direction = NONE
15 |
16 | historyStateEvent.on(BACK, () => {
17 | direction = BACK
18 | historyStack.shift()
19 | config.setHistoryStack(historyStack.getStore())
20 | const route = config.router.history.current
21 | if (config.isSingleMode) {
22 | const key = routerCache.resolveKeyFromRoute(route)
23 | routerCache._remove(key)
24 | } else {
25 | const baseKey = routerCache.resolveKeyFromRoute(route)
26 | if (globalMultiKeyMap[baseKey]) {
27 | const key = globalMultiKeyMap[baseKey].shift()
28 | routerCache._remove(key)
29 | }
30 | }
31 | })
32 |
33 | historyStateEvent.on(FORWARD, () => {
34 | direction = FORWARD
35 | })
36 |
37 | const routerMiddle = (Vue, config) => {
38 | const router = config.router
39 | const directionKey = config.directionKey
40 |
41 | const originPush = router.push.bind(router)
42 | const originReplace = router.replace.bind(router)
43 | const originGo = router.go.bind(router)
44 |
45 | router.push = (location, onComplete, onAbort) => {
46 | direction = FORWARD
47 | if (config.isSingleMode && routerCache.has(location)) {
48 | routerCache.removeBackInclue(location)
49 | }
50 | originPush(location, onComplete, onAbort)
51 | }
52 |
53 | router.replace = (location, onComplete, onAbort) => {
54 | direction = REPLACE
55 | historyStack.shift()
56 | config.setHistoryStack(historyStack.getStore())
57 | routerCache.shift()
58 | if (config.isSingleMode && routerCache.has(location)) {
59 | routerCache.removeBackInclue(location)
60 | }
61 | originReplace(location, onComplete, onAbort)
62 | }
63 |
64 | router.go = (n) => {
65 | if (n > 0) {
66 | direction = FORWARD
67 | } else if (n < 0) {
68 | direction = BACK
69 | historyStack.removeBackByIndex(-n)
70 | config.setHistoryStack(historyStack.getStore())
71 | routerCache.removeBackByIndex(-n)
72 | }
73 | originGo(n)
74 | }
75 |
76 | router.beforeEach((to, from, next) => {
77 | // let hashchange I/0 event trigger callback before next
78 | window.setTimeout(() => {
79 | to.params[directionKey] = direction
80 | next()
81 | }, 16)
82 | })
83 |
84 | defineReactive(router.history, 'current', () => {
85 | Vue.nextTick(() => {
86 | const href = document.URL
87 | if (direction !== BACK && historyStack.getHeader() !== href) {
88 | historyStack.unshift(href)
89 | config.setHistoryStack(historyStack.getStore())
90 | }
91 | direction = FORWARD
92 | })
93 | })
94 | }
95 |
96 | export default routerMiddle
97 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import config from '../config/index'
2 | import { MapStack } from '../util/stack'
3 | import { defineReactive } from '../util/lang'
4 |
5 | export const globalStack = new MapStack(config.max)
6 |
7 | export const globalCache = []
8 |
9 | export const globalMultiKeyMap = Object.create(null)
10 |
11 | defineReactive(config, 'max', (newVal) => {
12 | globalStack.updateSize(newVal)
13 | })
14 |
--------------------------------------------------------------------------------
/src/util/env.js:
--------------------------------------------------------------------------------
1 | export const inBrowser = typeof window !== 'undefined'
2 |
--------------------------------------------------------------------------------
/src/util/events.js:
--------------------------------------------------------------------------------
1 | class Events {
2 | constructor() {
3 | this.map = {}
4 | }
5 | on(name, fn) {
6 | if (this.map[name]) {
7 | this.map[name].push(fn)
8 | return
9 | }
10 | this.map[name] = [fn]
11 | }
12 | emit(name, ...args) {
13 | if (this.map[name]) {
14 | this.map[name].forEach((fn) => {
15 | fn(...args)
16 | })
17 | }
18 | }
19 | }
20 |
21 | export default Events
22 |
--------------------------------------------------------------------------------
/src/util/lang.js:
--------------------------------------------------------------------------------
1 | export function isInt(n) {
2 | if (n === Infinity) {
3 | return true
4 | }
5 | return typeof n === 'number' && n > 0 && (n | 0) === n
6 | }
7 |
8 | export function defineReactive(data, key, fn) {
9 | let val = data[key]
10 | Object.defineProperty(data, key, {
11 | enumerable: true,
12 | configurable: true,
13 | get() {
14 | return val
15 | },
16 | set(newVal) {
17 | if (newVal === val) {
18 | return
19 | }
20 | const oldVal = val
21 | val = newVal
22 | typeof fn === 'function' && fn(newVal, oldVal)
23 | }
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/src/util/log.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export function warn(text) {
4 | console.warn(`%cwarn: ${text}`, 'color: orange')
5 | }
6 |
7 | export function error(text) {
8 | console.error(`%cerror: ${text}`, 'color: orange')
9 | }
10 |
11 | export function log(text) {
12 | console.log(`%c${text}`, 'color: orange')
13 | }
14 |
--------------------------------------------------------------------------------
/src/util/stack.js:
--------------------------------------------------------------------------------
1 | export class Stack {
2 | constructor(max = Infinity) {
3 | this.max = max
4 | this.init()
5 | }
6 | init() {
7 | this.list = []
8 | }
9 | _unshift(item) {
10 | this.list.unshift(item)
11 | if (this.list.length > this.max) {
12 | return this.list.pop()
13 | }
14 | return null
15 | }
16 | unshift() {
17 | const removeList = []
18 | for (let i = 0; i < arguments.length; i++) {
19 | const item = arguments[i]
20 | const removeItem = this._unshift(item)
21 | if (removeItem) {
22 | removeList.push(removeItem)
23 | }
24 | }
25 | return removeList
26 | }
27 | shift() {
28 | if (this.list.length) {
29 | return this.list.shift()
30 | }
31 | return null
32 | }
33 | _remove(item) {
34 | const index = this.list.indexOf(item)
35 | if (index !== -1) {
36 | return this.list.splice(index, 1)[0]
37 | }
38 | return null
39 | }
40 | remove() {
41 | const removeList = []
42 | for (let i = 0; i < arguments.length; i++) {
43 | const item = arguments[i]
44 | const removeItem = this._remove(item)
45 | if (removeItem) {
46 | removeList.push(removeItem)
47 | }
48 | }
49 | return removeList
50 | }
51 | removeByIndex(index) {
52 | if (this.list[index]) {
53 | return this.list.splice(index, 1)[0]
54 | }
55 | return null
56 | }
57 | removeBackUntil(item) {
58 | const index = this.list.indexOf(item)
59 | if (index !== -1) {
60 | return this.list.splice(0, index)
61 | }
62 | return this.list.splice(0)
63 | }
64 | removeBackInclue(item) {
65 | const index = this.list.indexOf(item)
66 | if (index !== -1) {
67 | return this.list.splice(0, index + 1)
68 | }
69 | return this.list.splice(0)
70 | }
71 | removeBackByIndex(index) {
72 | if (index <= this.list.length - 1) {
73 | return this.list.splice(0, index)
74 | }
75 | return this.list.splice(0)
76 | }
77 | removeExclude() {
78 | const removeList = []
79 | for (let i = 0; i < this.list.length; i++) {
80 | const item = this.list[i]
81 | if (Array.prototype.indexOf.call(arguments, item) === -1) {
82 | const remove = this.list.splice(i, 1)[0]
83 | Array.prototype.push.call(removeList, remove)
84 | i--
85 | }
86 | }
87 | return removeList
88 | }
89 | removeAll() {
90 | return this.list.splice(0)
91 | }
92 | replace(item) {
93 | const removeItem = this.shift()
94 | if (removeItem) {
95 | this._unshift(item)
96 | return removeItem
97 | }
98 | return null
99 | }
100 | getHeader() {
101 | return this.list[0]
102 | }
103 | getByIndex(index) {
104 | return this.list[index]
105 | }
106 | getFooter() {
107 | return this.list[this.list.length - 1]
108 | }
109 | getSize() {
110 | return this.list.length
111 | }
112 | getStore() {
113 | return this.list
114 | }
115 | getMax() {
116 | return this.max
117 | }
118 | has(item) {
119 | return this.list.indexOf(item) !== -1
120 | }
121 | updateSize(max) {
122 | this.max = max
123 | }
124 | checkFull() {
125 | return this.max === this.list.length
126 | }
127 | }
128 |
129 | export class MapStack extends Stack {
130 | _unshift(item) {
131 | const index = this.list.indexOf(item)
132 | if (index !== -1) {
133 | this.list.splice(index, 1)
134 | }
135 | this.list.unshift(item)
136 | if (this.list.length > this.max) {
137 | return this.list.pop()
138 | }
139 | return null
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/test/util/stack.test.js:
--------------------------------------------------------------------------------
1 | import { Stack, MapStack } from '../../src/util/stack'
2 |
3 | describe('test Stack unshift', () => {
4 | const stack = new Stack(4)
5 | stack.unshift(1, 2, 3, 1)
6 | it('stack exec unshift', () => {
7 | expect(stack.getStore()).toEqual([1, 3, 2, 1])
8 | })
9 | it('stack is full and exec unshift', () => {
10 | const removeList = stack.unshift(4)
11 | expect(removeList).toEqual([1])
12 | expect(stack.getStore()).toEqual([4, 1, 3, 2])
13 | })
14 | })
15 |
16 | describe('test Stack shift', () => {
17 | const stack = new Stack()
18 | stack.unshift(1)
19 | it('stack exec shift', () => {
20 | const removeItem = stack.shift()
21 | expect(removeItem).toBe(1)
22 | expect(stack.getStore()).toEqual([])
23 | })
24 | it('stack is empty and exec shift', () => {
25 | const removeItem = stack.shift()
26 | expect(removeItem).toBe(null)
27 | expect(stack.getStore()).toEqual([])
28 | })
29 | })
30 |
31 | describe('test Stack remove', () => {
32 | const stack = new Stack()
33 | stack.unshift(1, 2, 3, 4)
34 | it('stack exec remove', () => {
35 | const removeList = stack.remove(2, 4)
36 | expect(removeList).toEqual([2, 4])
37 | expect(stack.getStore()).toEqual([3, 1])
38 | })
39 | it('stack not found item and exec remove', () => {
40 | const removeList = stack.remove(100)
41 | expect(removeList).toEqual([])
42 | expect(stack.getStore()).toEqual([3, 1])
43 | })
44 | })
45 |
46 | describe('test Stack removeByIndex', () => {
47 | const stack = new Stack()
48 | stack.unshift(1, 2, 3, 4, 5)
49 | it('stack exec removeByIndex', () => {
50 | const removeItem = stack.removeByIndex(2)
51 | expect(removeItem).toBe(3)
52 | expect(stack.getStore()).toEqual([5, 4, 2, 1])
53 | })
54 | it('stack not found index and exec removeByIndex', () => {
55 | const removeItem = stack.removeByIndex(100)
56 | expect(removeItem).toBe(null)
57 | expect(stack.getStore()).toEqual([5, 4, 2, 1])
58 | })
59 | })
60 |
61 | describe('test Stack removeBackUntil', () => {
62 | const stack = new Stack()
63 | stack.unshift(1, 2, 3, 4, 5)
64 | it('stack exec removeBackUntil', () => {
65 | const removeList = stack.removeBackUntil(2)
66 | expect(removeList).toEqual([5, 4, 3])
67 | expect(stack.getStore()).toEqual([2, 1])
68 | })
69 | it('stack not found index', () => {
70 | const removeList = stack.removeBackUntil(0)
71 | expect(removeList).toEqual([2, 1])
72 | expect(stack.getStore()).toEqual([])
73 | })
74 | })
75 |
76 | describe('test Stack removeExclude', () => {
77 | const stack = new Stack()
78 | stack.unshift(1, 2, 3, 4, 5)
79 | it('stack exec removeExclude', () => {
80 | const removeList = stack.removeExclude(1, 5)
81 | expect(removeList).toEqual([4, 3, 2])
82 | expect(stack.getStore()).toEqual([5, 1])
83 | })
84 | it('stack not found item and exec removeExclude', () => {
85 | const removeList = stack.removeExclude(0)
86 | expect(removeList).toEqual([5, 1])
87 | expect(stack.getStore()).toEqual([])
88 | })
89 | })
90 |
91 | describe('test Stack removeBackByIndex', () => {
92 | const stack = new Stack()
93 | stack.unshift(1, 2, 3, 4, 5)
94 | it('stack exec removeBackByIndex', () => {
95 | const removeList = stack.removeBackByIndex(3)
96 | expect(removeList).toEqual([5, 4, 3])
97 | expect(stack.getStore()).toEqual([2, 1])
98 | })
99 | it('stack not found index and exec removeBackByIndex', () => {
100 | const removeList = stack.removeBackByIndex(100)
101 | expect(removeList).toEqual([2, 1])
102 | expect(stack.getStore()).toEqual([])
103 | })
104 | })
105 |
106 | describe('test Stack removeBackInclue', () => {
107 | const stack = new Stack()
108 | stack.unshift(1, 2, 3, 4, 5)
109 | it('stack exec removeBackInclue', () => {
110 | const removeList = stack.removeBackInclue(3)
111 | expect(removeList).toEqual([5, 4, 3])
112 | expect(stack.getStore()).toEqual([2, 1])
113 | })
114 | it('stack not found item exec removeBackInclue', () => {
115 | const removeList = stack.removeBackInclue(100)
116 | expect(removeList).toEqual([2, 1])
117 | expect(stack.getStore()).toEqual([])
118 | })
119 | })
120 |
121 | describe('test Stack removeAll', () => {
122 | const stack = new Stack()
123 | stack.unshift(1, 2, 3)
124 | it('stack exec removeAll', () => {
125 | const removeList = stack.removeAll()
126 | expect(removeList).toEqual([3, 2, 1])
127 | expect(stack.getStore()).toEqual([])
128 | })
129 | })
130 |
131 | describe('test Stack replace', () => {
132 | const stack = new Stack()
133 | stack.unshift(1, 2, 3)
134 | it('stack exec replace', () => {
135 | const removeItem = stack.replace(4)
136 | expect(removeItem).toEqual(3)
137 | expect(stack.getStore()).toEqual([4, 2, 1])
138 | })
139 | })
140 |
141 | describe('test MapStack unshift', () => {
142 | it('mapStack exec unshift', () => {
143 | const mapStack = new MapStack()
144 | const removeList = mapStack.unshift(1, 2, 3, 1, 4)
145 | expect(mapStack.getStore()).toEqual([4, 1, 3, 2])
146 | })
147 | })
148 |
--------------------------------------------------------------------------------