Showing preview only (375K chars total). Download the full file or copy to clipboard to get everything.
Repository: NewDadaFE/vue-impression
Branch: master
Commit: 6ff2b38f2a07
Files: 204
Total size: 327.8 KB
Directory structure:
gitextract_vku1hzea/
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .scss-lint.yml
├── README.md
├── _config.yml
├── gulpfile.js
├── index.html
├── package.json
├── src/
│ ├── scripts/
│ │ ├── components/
│ │ │ ├── Alert.vue
│ │ │ ├── BackToTop.vue
│ │ │ ├── Badge.vue
│ │ │ ├── Button.vue
│ │ │ ├── Card.vue
│ │ │ ├── CardBody.vue
│ │ │ ├── Cell.vue
│ │ │ ├── Checkbox.vue
│ │ │ ├── CheckboxGroup.vue
│ │ │ ├── DatePicker/
│ │ │ │ ├── DateRange.vue
│ │ │ │ ├── DateTable.vue
│ │ │ │ ├── Picker.vue
│ │ │ │ └── index.js
│ │ │ ├── Drawer.vue
│ │ │ ├── DrawerItem.vue
│ │ │ ├── Flex.vue
│ │ │ ├── FlexItem.vue
│ │ │ ├── Group.vue
│ │ │ ├── GroupTitle.vue
│ │ │ ├── HRule.vue
│ │ │ ├── Icon.vue
│ │ │ ├── InlineSelector.vue
│ │ │ ├── InlineSelectorOption.vue
│ │ │ ├── InputNumber.vue
│ │ │ ├── InputText.vue
│ │ │ ├── InputTextarea.vue
│ │ │ ├── Loading.vue
│ │ │ ├── Loadmore.vue
│ │ │ ├── Mask.vue
│ │ │ ├── Media.vue
│ │ │ ├── MediaBody.vue
│ │ │ ├── MediaObject.vue
│ │ │ ├── Navbar.vue
│ │ │ ├── Navigation.vue
│ │ │ ├── NavigationItem.vue
│ │ │ ├── Picker.vue
│ │ │ ├── PickerOption.vue
│ │ │ ├── Progressbar.vue
│ │ │ ├── Progressbars.vue
│ │ │ ├── Radio.vue
│ │ │ ├── RadioGroup.vue
│ │ │ ├── Searchbar.vue
│ │ │ ├── SearchbarBtn.vue
│ │ │ ├── SearchbarPlaceholder.vue
│ │ │ ├── SegmentedControl.vue
│ │ │ ├── SegmentedControlItem.vue
│ │ │ ├── Selector.vue
│ │ │ ├── SelectorOption.vue
│ │ │ ├── Sidelip.vue
│ │ │ ├── SlideUp.vue
│ │ │ ├── SlideUpBody.vue
│ │ │ ├── SlideUpHeader.vue
│ │ │ ├── Stepbar.vue
│ │ │ ├── StepbarItem.vue
│ │ │ ├── Sticky.vue
│ │ │ ├── Swipe.vue
│ │ │ ├── SwipeItem.vue
│ │ │ ├── Tabbar.vue
│ │ │ ├── TabbarItem.vue
│ │ │ ├── Tag.vue
│ │ │ ├── Timeline.vue
│ │ │ ├── TimelineItem.vue
│ │ │ ├── Tip.vue
│ │ │ ├── Toast.vue
│ │ │ ├── Toggle.vue
│ │ │ └── index.js
│ │ ├── containers/
│ │ │ ├── layout.vue
│ │ │ └── menubar.vue
│ │ ├── directives/
│ │ │ └── disfavor.js
│ │ ├── index.js
│ │ ├── mixins/
│ │ │ ├── emitter.js
│ │ │ ├── select.js
│ │ │ ├── selectOption.js
│ │ │ ├── sync.js
│ │ │ ├── tab.js
│ │ │ └── tabItem.js
│ │ ├── router.js
│ │ ├── routes.json
│ │ ├── utils/
│ │ │ ├── alert.js
│ │ │ ├── cssPrefix.js
│ │ │ ├── date.js
│ │ │ ├── dateUtil.js
│ │ │ ├── dom.js
│ │ │ ├── draggable.js
│ │ │ ├── easing.js
│ │ │ ├── loading.js
│ │ │ ├── toast.js
│ │ │ ├── translate.js
│ │ │ └── type.js
│ │ └── views/
│ │ ├── alert.vue
│ │ ├── back-to-top.vue
│ │ ├── badge.vue
│ │ ├── button.vue
│ │ ├── card.vue
│ │ ├── cell.vue
│ │ ├── checkbox.vue
│ │ ├── confirm.vue
│ │ ├── datepicker.vue
│ │ ├── drawer.vue
│ │ ├── flex.vue
│ │ ├── hrule.vue
│ │ ├── icon.vue
│ │ ├── index.vue
│ │ ├── inline-selecor.vue
│ │ ├── input-number.vue
│ │ ├── input-text.vue
│ │ ├── input-textarea.vue
│ │ ├── loading.vue
│ │ ├── media.vue
│ │ ├── navbar.vue
│ │ ├── navigation.vue
│ │ ├── picker.vue
│ │ ├── progressbar.vue
│ │ ├── pull-down.vue
│ │ ├── pull-up.vue
│ │ ├── radio.vue
│ │ ├── searchbar.vue
│ │ ├── segmented-control.vue
│ │ ├── selector.vue
│ │ ├── sideslip.vue
│ │ ├── slideup.vue
│ │ ├── stepbar.vue
│ │ ├── sticky.vue
│ │ ├── swipe.vue
│ │ ├── tabbar.vue
│ │ ├── tag.vue
│ │ ├── timeline.vue
│ │ ├── tip.vue
│ │ ├── toast.vue
│ │ └── toggle.vue
│ └── styles/
│ ├── animate.scss
│ ├── index.scss
│ ├── mixins/
│ │ ├── border.scss
│ │ ├── button.scss
│ │ ├── media.scss
│ │ ├── navbar.scss
│ │ ├── progressbar.scss
│ │ ├── tag.scss
│ │ └── text.scss
│ ├── mixins.scss
│ ├── modules/
│ │ ├── alert.scss
│ │ ├── back-to-top.scss
│ │ ├── badge.scss
│ │ ├── button.scss
│ │ ├── card.scss
│ │ ├── cell.scss
│ │ ├── checkbox.scss
│ │ ├── date-picker.scss
│ │ ├── drawer.scss
│ │ ├── group.scss
│ │ ├── hrule.scss
│ │ ├── inline-selector.scss
│ │ ├── input-number.scss
│ │ ├── input-text.scss
│ │ ├── input-textarea.scss
│ │ ├── loading.scss
│ │ ├── loadmore.scss
│ │ ├── mask.scss
│ │ ├── media.scss
│ │ ├── navbar.scss
│ │ ├── navigation.scss
│ │ ├── picker.scss
│ │ ├── progressbar.scss
│ │ ├── searchbar.scss
│ │ ├── segmented-control.scss
│ │ ├── selector.scss
│ │ ├── sidelip.scss
│ │ ├── slideup.scss
│ │ ├── stepbar.scss
│ │ ├── sticky.scss
│ │ ├── swipe.scss
│ │ ├── tabbar.scss
│ │ ├── tag.scss
│ │ ├── timeline.scss
│ │ ├── tip.scss
│ │ ├── toast.scss
│ │ └── toggle.scss
│ ├── modules.scss
│ ├── utils/
│ │ ├── background.scss
│ │ ├── border.scss
│ │ ├── clearfix.scss
│ │ ├── display.scss
│ │ ├── flex.scss
│ │ ├── img.scss
│ │ ├── reboot.scss
│ │ ├── spacing.scss
│ │ └── text.scss
│ ├── utils.scss
│ └── variables.scss
├── webpack.dev.config.js
├── webpack.prebuilt.config.js
└── webpack.prod.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["vue", ["es2015", { "modules": false }], "stage-1"],
"comments": false,
"plugins": [
"transform-runtime",
"transform-object-assign"
]
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{json,yml}]
trim_trailing_whitespace = false
================================================
FILE: .eslintignore
================================================
build.js
webpack.*.js
================================================
FILE: .eslintrc.js
================================================
module.exports = {
extends: 'vue-impression',
rules: {
'import/no-extraneous-dependencies': 0,
}
};
================================================
FILE: .gitignore
================================================
/node_modules
/site/build
/site/node_modules
/site/.sass-cache
/site/src/scripts/components/impression
/site/src/styles/impression
/build
/cache
*.log
/.idea
/.sass-cache
scss-lint-report.xml
/lib
.DS_Store
/dist
================================================
FILE: .npmignore
================================================
/build
/site
/.sass-cache
.DS_Store
Makefile
.babelrc
.editorconfig
.eslintignore
.eslintrc.js
.scss-lint.yml
index.todo
/src
webpack.*.js
*.log
_config.yml
build.js
gulpfile.js
index.html
================================================
FILE: .scss-lint.yml
================================================
scss_files: "src/styles/**/*.scss"
exclude: ['src/styles/font-awesome/**', 'src/styles/modules/_normalize.scss', 'src/styles/modules/_reboot.scss']
plugin_directories: ['.scss-linters']
# List of gem names to load custom linters from (make sure they are already
# installed)
plugin_gems: []
# Default severity of all linters.
severity: warning
linters:
BangFormat:
enabled: true
space_before_bang: true
space_after_bang: false
BemDepth:
enabled: false
max_elements: 1
BorderZero:
enabled: true
convention: zero # or `none`
ChainedClasses:
enabled: false
ColorKeyword:
enabled: true
ColorVariable:
enabled: true
Comment:
enabled: true
style: silent
DebugStatement:
enabled: true
DeclarationOrder:
enabled: false
DisableLinterReason:
enabled: false
DuplicateProperty:
enabled: true
ElsePlacement:
enabled: true
style: same_line # or 'new_line'
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: true
EmptyRule:
enabled: true
ExtendDirective:
enabled: false
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: short # or 'long'
HexNotation:
enabled: true
style: lowercase # or 'uppercase'
HexValidation:
enabled: true
IdSelector:
enabled: true
ImportantRule:
enabled: false
ImportPath:
enabled: true
leading_underscore: false
filename_extension: false
Indentation:
enabled: true
allow_non_nested_indentation: false
character: space # or 'tab'
width: 4
LeadingZero:
enabled: true
style: exclude_zero # or 'include_zero'
MergeableSelector:
enabled: true
force_nesting: true
NameFormat:
enabled: true
allow_leading_underscore: true
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
NestingDepth:
enabled: true
max_depth: 6
ignore_parent_selectors: false
PlaceholderInExtend:
enabled: false
PrivateNamingConvention:
enabled: false
prefix: _
PropertyCount:
enabled: false
include_nested: false
max_properties: 10
PropertySortOrder:
enabled: false
ignore_unspecified: false
min_properties: 2
separate_groups: false
PropertySpelling:
enabled: true
extra_properties: []
disabled_properties: []
PropertyUnits:
enabled: true
global: [
'ch', 'em', 'ex', 'rem', # Font-relative lengths
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
'deg', 'grad', 'rad', 'turn', # Angle
'ms', 's', # Duration
'Hz', 'kHz', # Frequency
'dpi', 'dpcm', 'dppx', # Resolution
'%'] # Other
properties: {}
PseudoElement:
enabled: true
QualifyingElement:
enabled: false
allow_element_with_attribute: false
allow_element_with_class: false
allow_element_with_id: false
SelectorDepth:
enabled: true
max_depth: 4
SelectorFormat:
enabled: true
convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
Shorthand:
enabled: true
allowed_shorthands: [1, 2, 3, 4]
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: true
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space'
SpaceAfterPropertyColon:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
SpaceAfterPropertyName:
enabled: true
SpaceAfterVariableColon:
enabled: false
style: one_space # or 'no_space', 'at_least_one_space' or 'one_space_or_newline'
SpaceAfterVariableName:
enabled: true
SpaceAroundOperator:
enabled: true
style: one_space # or 'at_least_one_space', or 'no_space'
SpaceBeforeBrace:
enabled: true
style: space # or 'new_line'
allow_single_line_padding: false
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: single_quotes # or double_quotes
TrailingSemicolon:
enabled: true
TrailingWhitespace:
enabled: true
TrailingZero:
enabled: false
TransitionAll:
enabled: false
UnnecessaryMantissa:
enabled: true
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: true
UrlQuotes:
enabled: true
VariableForProperty:
enabled: false
properties: []
VendorPrefix:
enabled: true
identifier_list: base
additional_identifiers: []
excluded_identifiers: []
ZeroUnit:
enabled: true
Compass::*:
enabled: false
================================================
FILE: README.md
================================================
# vue-impression
A Vue.js 2.0 UI elements for mobile.
## Demo
https://newdadafe.github.io/impression_vue/#/button
## Installation
```sh
yarn add vue-impression
```
## Usage
styles:
```scss
@import '~vue-impression/dist/styles/index.scss';
```
scripts:
```js
import Vue from 'vue';
import VueImpression from 'vue-impression';
Vue.use(VueImpression);
```
tree-shaking:
```sh
yarn add babel-plugin-transform-imports -D
```
```json
{
"plugins": [
[
"transform-imports",
{
"vue-impression": {
"transform": "vue-impression/dist/scripts/components/${member}",
"preventFullImport": true
}
}
]
]
}
```
## Example
```html
<btn theme="primary" size="sm" @click="doSomething">按钮</btn>
```
## Components
- [x] Button
- [x] Group
- [x] GroupTitle
- [x] Cell
- [x] Flex
- [x] FlexItem
- [x] Icon
- [x] Navbar
- [x] Navigation
- [x] Tabbar
- [x] Drawer
- [x] Loading
- [x] Alert
- [x] Toast
- [x] Radio
- [x] RadioGroup
- [x] Checkbox
- [x] CheckboxGroup
- [x] Toggle(Switch)
- [x] InputNumber
- [x] InputText
- [x] InputArea
- [x] Selector
- [x] Tag
- [x] Badge
- [x] Tip
- [x] HRule(Hr)
- [x] InlineSelector
- [x] Swipe
- [x] SlideUp
- [x] SegmentedControl
- [x] Media
- [x] Card
- [x] Picker
- [x] DatePicker
- [x] Search
- [x] BackToTop
- [x] Pull down
- [x] Pull up
- [x] Sideslip
- [x] Progressbar
- [x] Stepbar
- [x] Timeline
- [x] Sticky
## Quick start
[generator-vue-impression](https://github.com/NewDadaFE/generator/tree/master/packages/generator-vue-impression)
================================================
FILE: _config.yml
================================================
theme: jekyll-theme-cayman
================================================
FILE: gulpfile.js
================================================
const fs = require('fs-extra');
const gulp = require('gulp');
const minimist = require('minimist');
const plugin = require('gulp-load-plugins')();
const replaceExt = require('replace-ext');
const through = require('through2');
const { createDefaultCompiler, assemble } = require('@vue/component-compiler');
const options = minimist(process.argv.slice(2));
process.env.NODE_ENV = options.env || 'production';
const clean = () => Promise.all([fs.emptyDir('dist'), fs.emptyDir('build')]);
const copyHTML = () => fs.copy('index.html', 'build/index.html');
const copyImage = () => fs.copy('src/images', 'build/images');
const style = () => fs.copy('src/styles', 'dist/styles');
const sass = () => {
return gulp
.src('src/styles/index.scss')
.pipe(plugin.sass().on('error', plugin.sass.logError))
.pipe(plugin.autoprefixer({ browsers: ['last 30 version', '> 90%'] }))
.pipe(plugin.cssmin())
.pipe(gulp.dest('build/styles'));
};
const script = () => {
const directory = ['components', 'directives', 'mixins', 'utils'];
const compiler = createDefaultCompiler();
return gulp
.src(`src/scripts/?(${directory.join('|')})/**/*.{js,vue}`)
.pipe(
through.obj((file, encoding, callback) => {
if(file.extname === '.vue') {
const result = compiler.compileToDescriptor(
file.basename,
file.contents.toString()
);
const output = assemble(compiler, file.basename, result);
file.contents = new Buffer(output.code);
file.path = replaceExt(file.path, '.js');
}
callback(null, file);
})
)
.pipe(plugin.babel())
.pipe(gulp.dest('dist/scripts'));
};
const watch = () => gulp.watch('src/styles/**/*.scss', sass);
const build = gulp.series(clean, gulp.parallel(copyHTML, copyImage, sass));
const release = gulp.series(clean, gulp.parallel(style, script));
module.exports = { watch, build, release };
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue-impression</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico">
<link rel="stylesheet" href="styles/index.css">
<link rel="stylesheet" href="https://fe.imdada.cn/font-awesome/4.7.0/index.css">
</head>
<body>
<div id="app"></div>
<script src="scripts/app.js"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "vue-impression",
"version": "0.21.13",
"description": "A Vue.js 2.0 UI elements for mobile.",
"sass": "dist/styles/index.scss",
"main": "dist/scripts/components/index.js",
"module": "dist/scripts/components/index.js",
"scripts": {
"start": "gulp build && webpack -p --config webpack.prebuilt.config.js",
"poststart": "webpack-dev-server --config webpack.dev.config.js --colors --port 9008",
"build:site": "gulp build && webpack -p --config webpack.prod.config.js",
"scsslint": "scss-lint src/styles/**/*.scss",
"eslint": "eslint ./src/scripts/",
"build": "npm run eslint && gulp release"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NewDadaFE/vue-impression.git"
},
"keywords": [
"Vue 2.0",
"UI"
],
"author": "NewDadaFE",
"license": "MIT",
"bugs": {
"url": "https://github.com/NewDadaFE/vue-impression/issues"
},
"homepage": "https://github.com/NewDadaFE/vue-impression#readme",
"devDependencies": {
"@vue/component-compiler": "^3.6.0",
"ajv": "5.5.2",
"autoprefixer": "^6.5.3",
"babel-cli": "^6.24.1",
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-helper-vue-jsx-merge-props": "^2.0.2",
"babel-loader": "^6.2.8",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-plugin-transform-vue-jsx": "^3.2.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-stage-1": "^6.24.1",
"babel-preset-vue": "^0.1.0",
"babel-register": "^6.18.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"css-loader": "^0.26.0",
"eslint": "^3.10.2",
"eslint-config-vue-impression": "2.1.6",
"eslint-friendly-formatter": "^2.0.6",
"eslint-import-resolver-webpack": "^0.7.0",
"eslint-loader": "^1.6.1",
"eslint-plugin-import": "^2.2.0",
"extract-text-webpack-plugin": "2.1.2",
"file-loader": "^0.9.0",
"fs-extra": "^7.0.0",
"gulp": "^4.0.0",
"gulp-autoprefixer": "^3.1.1",
"gulp-babel": "7.0.1",
"gulp-cssmin": "^0.1.7",
"gulp-imagemin": "^3.1.1",
"gulp-load-plugins": "^1.5.0",
"gulp-minify-css": "^1.2.4",
"gulp-rename": "^1.2.2",
"gulp-sass": "4.0.1",
"html-webpack-plugin": "^2.24.1",
"json-loader": "^0.5.4",
"minimist": "^1.2.0",
"postcss": "6.0.23",
"replace-ext": "^1.0.0",
"through2": "^2.0.3",
"url-loader": "^0.5.7",
"vue-html-loader": "^1.2.3",
"vue-loader": "^10.0.0",
"vue-router": "^2.1.1",
"vue-style-loader": "^1.0.0",
"vue-template-compiler": "^2.1.10",
"webpack": "2.7.0",
"webpack-dashboard": "^0.2.0",
"webpack-dev-middleware": "^1.8.4",
"webpack-dev-server": "^1.16.2",
"webpack-hot-middleware": "^2.13.2",
"webpack-merge": "^0.17.0"
},
"dependencies": {
"babel-runtime": "^6.26.0",
"vue": "^2.1.10"
}
}
================================================
FILE: src/scripts/components/Alert.vue
================================================
<template>
<div>
<transition name="zoom"
@after-leave="afterLeave">
<div
class="alert"
v-show="currentValue"
@click.self="maskClosable && (currentValue = false)"
@touchmove.prevent.stop>
<div class="alert-modal">
<div class="alert-title" v-html="title">
</div>
<div class="alert-message" v-html="message"></div>
<div class="alert-footer" :class="{'alert-footer-vertical': vertical}">
<div
v-for="btn in btns"
class="alert-btn"
@click="clickHandle(btn.click)"
v-html="btn.text">
</div>
</div>
</div>
</div>
</transition>
<mask-layer :clickable="false" />
</div>
</template>
<script>
export default {
name: 'alert',
props: {
// 标题
title: {
type: String,
default: '确认',
},
// 内容
message: String,
// 按钮垂直摆放
vertical: {
type: Boolean,
default: false,
},
// 自动关闭
autoClose: {
type: Boolean,
default: true,
},
// 点击mask是否可以关闭
maskClosable: {
type: Boolean,
default: false,
},
// 按钮组
btns: {
type: Array,
default() {
return [{
text: '确定',
}];
},
},
},
data() {
return {
currentValue: false,
};
},
methods: {
// 显示
show() {
this.currentValue = true;
},
// 按钮点击
clickHandle(callback) {
callback && callback();
this.autoClose && (this.currentValue = false);
},
hide() {
this.autoClose && (this.currentValue = false);
},
// 移除dom
/* global document:true */
afterLeave() {
document.body.removeChild(this.$el);
},
},
};
</script>
================================================
FILE: src/scripts/components/BackToTop.vue
================================================
<template>
<div
class="back-to-top"
:class="{'active': active}"
@click.prevent.stop="scrollToTopHandle">
<icon class="back-to-top-icon" name="arrow-up" />
</div>
</template>
<script>
import { easeInOutCubic } from '../utils/easing';
import { getScrollContainer } from '../utils/dom';
export default {
name: 'back-to-top',
data() {
return {
active: false,
};
},
methods: {
/* global window:true document:true requestAnimationFrame:true */
scrollToTopHandle() {
let scrollTop = this.getScrollTop(),
startTime = Date.now();
let frameFunc = () => {
let timestamp = Date.now(),
time = timestamp - startTime;
this.setScrollTop(easeInOutCubic(time, scrollTop, 0, 450));
if(time < 450) {
requestAnimationFrame(frameFunc);
}
};
requestAnimationFrame(frameFunc);
this.active = false;
},
getScrollTop() {
return this.scrollTargetEl === document
? window.pageYOffset : this.scrollTargetEl.scrollTop;
},
setScrollTop(val) {
if(this.scrollTargetEl === document) {
document.body.scrollTop = val;
document.documentElement.scrollTop = val;
return;
}
this.scrollTargetEl.scrollTop = val;
},
touchStartHandle() {
this._offsetY = this.scrollTargetEl === document
? window.pageYOffset : this.scrollTargetEl.scrollTop;
},
touchEndHandle() {
let scrollTop = this.scrollTargetEl === document
? window.pageYOffset : this.scrollTargetEl.scrollTop;
// 显示
if(scrollTop < this._offsetY && (this._offsetY - scrollTop > 60)) {
this.active = true;
} else if(Math.abs(this._offsetY - scrollTop) > 60) {
// 隐藏
this.active = false;
}
this._offsetY = scrollTop;
},
},
mounted() {
this.scrollTargetEl = getScrollContainer(this.$el);
this.scrollTargetEl.addEventListener('touchstart', this.touchStartHandle);
this.scrollTargetEl.addEventListener('touchend', this.touchEndHandle);
},
};
</script>
================================================
FILE: src/scripts/components/Badge.vue
================================================
<template>
<div
@click="$emit('click')"
class="badge"
:class="{'badge-gap': $slots.default}">
<div class="badge-addon" :class="`bg-${theme}`">{{content}}</div>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'badge',
props: {
// 内容
content: {},
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'secondary', 'success', 'warning', 'danger'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Button.vue
================================================
<template>
<button
:type="type"
@click="clickHandle"
class="btn"
:class="{
[`btn-${theme}`]: theme,
[`btn-${size}`]: size,
[`btn-${theme}-outline`]: hollow,
['btn-block']: block,
['btn-loading']: loading,
[`border-${shape}`]: shape,
}"
:disabled="disabled || loading">
<slot></slot>
<loading
size="sm"
v-if="loading"
:theme="theme === 'default' ? 'primary' : null" />
</button>
</template>
<script>
// Button
export default {
name: 'btn',
props: {
block: Boolean,
hollow: Boolean,
disabled: Boolean,
// 加载中
loading: Boolean,
// 类型
type: {
type: String,
default: 'button',
validator(value) {
return ['button', 'submit', 'reset'].indexOf(value) > -1;
},
},
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'secondary', 'default'].indexOf(value) > -1;
},
},
// 尺寸
size: {
type: String,
validator(value) {
return ['sm', 'lg'].indexOf(value) > -1;
},
},
// 形状
shape: {
type: String,
validator(value) {
return ['pill'].indexOf(value) > -1;
},
},
},
methods: {
clickHandle(event) {
this.$emit('click', event);
},
},
};
</script>
================================================
FILE: src/scripts/components/Card.vue
================================================
<template>
<div class="card" @click="$emit('click')">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'card',
};
</script>
================================================
FILE: src/scripts/components/CardBody.vue
================================================
<template>
<div class="card-body" @click="$emit('click')">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'card-body',
};
</script>
================================================
FILE: src/scripts/components/Cell.vue
================================================
<template>
<router-link
v-if="!disabled && to"
:to="to"
class="cell cell-link"
:class="{
'cell-disabled': disabled,
'cell-no-gap': !hasGap,
}">
<div class="cell-header" v-if="$slots.header">
<slot name="header"></slot>
</div>
<span class="cell-body">
<slot></slot>
</span>
<div class="cell-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
<i v-if="hasArrow" class="fa fa-angle-right cell-arrow" />
</router-link>
<a
v-else
:href="!disabled && href"
@click="!disabled && $emit('click')"
class="cell"
:class="{
'cell-link': clickable,
'cell-disabled': disabled,
'cell-no-gap': !hasGap,
}" >
<div class="cell-header" v-if="$slots.header">
<slot name="header"></slot>
</div>
<span class="cell-body">
<slot></slot>
</span>
<div class="cell-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
<i v-if="hasArrow" class="fa fa-angle-right cell-arrow" />
</a>
</template>
<script>
export default {
name: 'cell',
props: {
to: [String, Object],
href: String,
disabled: Boolean,
hasGap: {
type: Boolean,
default: true,
},
hasArrow: {
type: Boolean,
default() {
// 有href和to时默认为true
return !!(this.href || this.to);
},
},
},
computed: {
clickable() {
return !this.disabled && (this.href || this._events.click);
},
},
};
</script>
================================================
FILE: src/scripts/components/Checkbox.vue
================================================
<template>
<label
class="checkbox"
:class="'checkbox-' + type">
<input
type="checkbox"
class="checkbox-input"
:disabled="disabled || $parent.disabled"
v-model="currentValue">
<span class="checkbox-addon">
<i class="fa fa-check" />
</span>
<span class="checkbox-label">
<slot></slot>
</span>
</label>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'checkbox',
mixins: [Sync],
props: {
type: {
type: String,
default: 'square',
validator(value) {
return ['square', 'circle'].indexOf(value) > -1;
},
},
},
data() {
let currentValue;
if(this.isGroupChildComponent) {
currentValue = this.$parent.currentValue &&
this.$parent.currentValue.indexOf(this.value) > -1;
} else {
currentValue = this.value;
}
return { currentValue };
},
watch: {
currentValue(val) {
if(this.isGroupChildComponent) {
this.$parent.$emit('optionChecked', this.value);
}
this.$emit('input', val);
},
},
beforeCreate() {
// 是否CheckboxGroup下的子组件
this.isGroupChildComponent = this.$parent.$options._componentTag === 'checkbox-group';
},
};
</script>
================================================
FILE: src/scripts/components/CheckboxGroup.vue
================================================
<template>
<div class="checkbox-group">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'checkbox-group',
mixins: [Sync],
data() {
return {
currentValue: this.value || [],
};
},
methods: {
optionCheckedHandle(option) {
this.currentValue = this.currentValue || [];
const index = this.currentValue.indexOf(option);
if(index === -1) {
this.currentValue.push(option);
} else {
this.currentValue.splice(index, 1);
}
},
},
created() {
this.$on('optionChecked', this.optionCheckedHandle);
},
};
</script>
================================================
FILE: src/scripts/components/DatePicker/DateRange.vue
================================================
<template>
<div
v-show="visible"
class="date-range-picker">
<div class="header">
<div class="header-cnt">
<btn
@click="prevMonth"
class="btn btn-left">
<icon name="angle-left" size="lg"/>
</btn>
<btn
@click="nextMonth"
class="btn btn-right">
<icon name="angle-right" size="lg"/>
</btn>
<div>{{ dateLabel }}</div>
</div>
</div>
<date-table
selection-mode="range"
:date="date"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
:first-day-of-week="firstDayOfWeek"
@pick="handleRangePick">
</date-table>
</div>
</template>
<script type="text/babel">
import DateTable from './DateTable';
import Btn from '../Button';
import Icon from '../Icon';
import {
formatDate,
isDate,
prevMonth,
nextMonth,
} from '../../utils/date';
const DayMilliseconds = 24 * 60 * 60 * 1000;
export default {
computed: {
dateLabel() {
/*eslint-disable*/
return this.date.getFullYear() + '年' + (this.date.getMonth() + 1) + '月';
/*eslint-disable*/
},
},
data() {
return {
popperClass: '',
defaultTime: null,
minDate: '',
maxDate: '',
date: new Date(),
rangeState: {
endDate: null,
selecting: false,
row: null,
column: null,
},
visible: '',
firstDayOfWeek: 1,
dateDisable: false,
prevPickedDisableDays: 0,
nextPickedDisableDays: 0,
};
},
methods: {
disabledDate(time) {
if(this.dateDisable) {
if(this.maxDate) {
return false;
}
if(this.minDate) {
let prePickedDisabled = time.getTime()
<=
(new Date(this.minDate).getTime() - this.prevPickedDisableDays * DayMilliseconds);
let nextPickedDisabled = time.getTime()
>=
(new Date(this.minDate).getTime() + this.nextPickedDisableDays * DayMilliseconds);
return prePickedDisabled || nextPickedDisabled;
}
}
return false;
},
handleChangeRange(val) {
this.minDate = val.minDate;
this.maxDate = val.maxDate;
this.rangeState = val.rangeState;
},
handleRangePick(val, close = true) {
const minDate = val.minDate;
const maxDate = val.maxDate;
if(this.maxDate === maxDate && this.minDate === minDate) {
return;
}
this.onPick && this.onPick(val, formatDate);
this.maxDate = maxDate;
this.minDate = minDate;
// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
setTimeout(() => {
this.maxDate = maxDate;
this.minDate = minDate;
}, 10);
if(!close) {
return;
}
this.handleConfirm();
},
prevMonth() {
this.date = prevMonth(this.date);
},
nextMonth() {
this.date = nextMonth(this.date);
},
handleConfirm() {
this.$emit('pick', [this.minDate, this.maxDate]);
},
isValidValue(value) {
return Array.isArray(value) &&
value && value[0] && value[1] &&
isDate(value[0]) && isDate(value[1]) &&
value[0].getTime() <= value[1].getTime() && (
typeof this.disabledDate === 'function'
? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
: true
);
},
},
components: { DateTable, Btn, Icon },
};
</script>
================================================
FILE: src/scripts/components/DatePicker/DateTable.vue
================================================
<template>
<table
cellspacing="0"
cellpadding="0"
class="date-table"
@click="handleClick"
@mousemove="handleMouseMove">
<tbody>
<tr>
<th v-if="showWeekNumber"></th>
<th v-for="week in WEEKS">{{week}}</th>
</tr>
<tr
class="date-table__row"
v-for="row in rows">
<td
v-for="cell in row"
:class="getCellClasses(cell)">
<div>
<span>
{{ cell.type==='today' ? '今' : cell.text }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script>
import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, nextDate } from '../../utils/date';
import { hasClass } from '../../utils/dom';
const WEEKS = ['日', '一', '二', '三', '四', '五', '六'];
const clearHours = function(time) {
const cloneDate = new Date(time);
cloneDate.setHours(0, 0, 0, 0);
return cloneDate.getTime();
};
export default {
name: 'date-table',
props: {
// 一周的第一天
firstDayOfWeek: {
default: 7,
type: Number,
validator: val => val >= 1 && val <= 7,
},
value: {},
date: {},
selectionMode: {
type: String,
default: 'day',
},
// 是否展示周
showWeekNumber: {
type: Boolean,
default: false,
},
// 禁选日期
disabledDate: {},
selectedDate: {
type: Array,
},
minDate: {},
maxDate: {},
// 日期区间
rangeState: {
default() {
return {
endDate: null,
selecting: false,
row: null,
column: null,
};
},
},
},
computed: {
offsetDay() {
const week = this.firstDayOfWeek;
// 周日为界限,左右偏移的天数,3217654 例如周一就是 -1,目的是调整前两行日期的位置
return week > 3 ? 7 - week : -week;
},
WEEKS() {
const week = this.firstDayOfWeek;
return WEEKS.concat(WEEKS).slice(week, week + 7);
},
year() {
return this.date.getFullYear();
},
month() {
return this.date.getMonth();
},
startDate() {
return getStartDateOfMonth(this.year, this.month);
},
rows() {
// TODO: refactory rows / getCellClasses
const date = new Date(this.year, this.month, 1);
let day = getFirstDayOfMonth(date); // day of first day
const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth());
const dateCountOfLastMonth = getDayCountOfMonth(
date.getFullYear(),
(date.getMonth() === 0 ? 11 : date.getMonth() - 1));
day = (day === 0 ? 7 : day);
const offset = this.offsetDay;
const rows = this.tableRows;
let count = 1;
let firstDayPosition;
const startDate = this.startDate;
const disabledDate = this.disabledDate;
const selectedDate = this.selectedDate || this.value;
const now = clearHours(new Date());
for (let i = 0; i < 6; i++) {
const row = rows[i];
if(this.showWeekNumber) {
if(!row[0]) {
row[0] = { type: 'week', text: getWeekNumber(nextDate(startDate, i * 7 + 1)) };
}
}
for (let j = 0; j < 7; j++) {
let cell = row[this.showWeekNumber ? j + 1 : j];
if(!cell) {
cell = { row: i, column: j, type: 'normal', inRange: false, start: false, end: false };
}
cell.type = 'normal';
const index = i * 7 + j;
const time = nextDate(startDate, index - offset).getTime();
/** 仅选择一次时 选择状态不可保持 **/
if(!this.maxDate) {
// cell.inRange = time >= clearHours(this.minDate);
cell.inRange = cell.end = cell.start = this.minDate &&
time === clearHours(this.minDate);
} else {
cell.inRange = time >= clearHours(this.minDate) &&
time <= clearHours(this.maxDate);
cell.start = this.minDate && time === clearHours(this.minDate);
cell.end = this.maxDate && time === clearHours(this.maxDate);
}
// cell.inRange = time >= clearHours(this.minDate) &&
// time <= clearHours(this.maxDate);
// cell.start = this.minDate && time === clearHours(this.minDate);
// cell.end = this.maxDate && time === clearHours(this.maxDate);
/***********************************/
const isToday = time === now;
if(isToday) {
cell.type = 'today';
}
if(i >= 0 && i <= 1) {
if(j + i * 7 >= (day + offset)) {
cell.text = count++;
if(count === 2) {
firstDayPosition = i * 7 + j;
}
} else {
cell.text = dateCountOfLastMonth - (day + offset - j % 7) + 1 + i * 7;
cell.type = 'prev-month';
}
} else if(count <= dateCountOfMonth) {
cell.text = count++;
if(count === 2) {
firstDayPosition = i * 7 + j;
}
} else {
cell.text = count++ - dateCountOfMonth;
cell.type = 'next-month';
}
let newDate = new Date(time);
cell.disabled = typeof disabledDate === 'function' && disabledDate(newDate);
/*eslint-disable*/
cell.selected = Array.isArray(selectedDate) &&
selectedDate.filter(date => date.toString() === newDate.toString())[0];
/*eslint-disable*/
this.$set(row, this.showWeekNumber ? j + 1 : j, cell);
}
}
rows.firstDayPosition = firstDayPosition;
return rows;
},
},
watch: {
'rangeState.endDate'(newVal) {
this.markRange(newVal);
},
minDate(newVal, oldVal) {
if(newVal && newVal !== oldVal) {
this.rangeState.selecting = true;
this.markRange(newVal);
} else if(!newVal) {
this.rangeState.selecting = false;
this.markRange(newVal);
} else {
this.markRange();
}
},
maxDate(newVal, oldVal) {
if(newVal && newVal !== oldVal) {
this.rangeState.selecting = false;
this.markRange(newVal);
this.$emit('pick', {
minDate: this.minDate,
maxDate: this.maxDate,
});
}
},
},
data() {
return {
tableRows: [[], [], [], [], [], []],
};
},
methods: {
getCellClasses(cell) {
const selectionMode = this.selectionMode;
let classes = [];
if((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) {
classes.push('available');
if(cell.type === 'today') {
classes.push('today');
}
} else {
classes.push(cell.type);
}
if(cell.disabled) {
classes.push('disabled');
} else if(cell.inRange) {
classes.push('in-range');
if(cell.start) {
classes.push('start-date');
}
if(cell.end) {
classes.push('end-date');
}
}
if(cell.selected) {
classes.push('selected');
}
return classes.join(' ');
},
getDateOfCell(row, column) {
const offsetFromStart = row * 7
+ (column - (this.showWeekNumber ? 1 : 0))
- this.offsetDay;
return nextDate(this.startDate, offsetFromStart);
},
markRange(maxDate) {
const startDate = this.startDate;
if(!maxDate) {
maxDate = this.maxDate;
}
const rows = this.rows;
const minDate = this.minDate;
for (let i = 0, k = rows.length; i < k; i++) {
const row = rows[i];
for (let j = 0, l = row.length; j < l; j++) {
/*eslint-disable*/
if(this.showWeekNumber && j === 0) continue;
/*eslint-disable*/
const cell = row[j];
const index = i * 7 + j + (this.showWeekNumber ? -1 : 0);
const time = nextDate(startDate, index - this.offsetDay).getTime();
if(maxDate && maxDate < minDate) {
cell.inRange = minDate &&
time >= clearHours(maxDate) &&
time <= clearHours(minDate);
cell.start = maxDate && time === clearHours(maxDate.getTime());
cell.end = minDate && time === clearHours(minDate.getTime());
} else {
cell.inRange = minDate &&
time >= clearHours(minDate) &&
time <= clearHours(maxDate);
cell.start = minDate && time === clearHours(minDate.getTime());
cell.end = maxDate && time === clearHours(maxDate.getTime());
}
}
}
},
handleMouseMove(event) {
if(!this.rangeState.selecting) {
return;
}
this.$emit('changerange', {
minDate: this.minDate,
maxDate: this.maxDate,
rangeState: this.rangeState,
});
let target = event.target;
if(target.tagName === 'SPAN') {
target = target.parentNode.parentNode;
}
if(target.tagName === 'DIV') {
target = target.parentNode;
}
if(target.tagName !== 'TD') return;
else if(hasClass(target, 'disabled')) return;
const column = target.cellIndex;
const row = target.parentNode.rowIndex - 1;
const { row: oldRow, column: oldColumn } = this.rangeState;
if(oldRow !== row || oldColumn !== column) {
this.rangeState.row = row;
this.rangeState.column = column;
this.rangeState.endDate = this.getDateOfCell(row, column);
}
},
handleClick(event) {
let target = event.target;
if(target.tagName === 'SPAN') {
target = target.parentNode.parentNode;
}
if(target.tagName === 'DIV') {
target = target.parentNode;
}
if(target.tagName !== 'TD') return;
if(hasClass(target, 'disabled')) {
return;
}
const selectionMode = this.selectionMode;
let year = Number(this.year);
let month = Number(this.month);
const cellIndex = target.cellIndex;
const rowIndex = target.parentNode.rowIndex;
const cell = this.rows[rowIndex - 1][cellIndex];
const text = cell.text;
const className = target.className;
const newDate = new Date(year, month, 1);
if(className.indexOf('prev') !== -1) {
if(month === 0) {
year -= 1;
month = 11;
} else {
month -= 1;
}
newDate.setFullYear(year);
newDate.setMonth(month);
} else if(className.indexOf('next') !== -1) {
if(month === 11) {
year += 1;
month = 0;
} else {
month += 1;
}
newDate.setFullYear(year);
newDate.setMonth(month);
}
newDate.setDate(parseInt(text, 10));
if(this.selectionMode === 'range') {
if(this.minDate && this.maxDate) {
// 最小,最大日期独有
const minDate = new Date(newDate.getTime());
const maxDate = null;
this.$emit('pick', { minDate, maxDate }, false);
this.rangeState.selecting = true;
this.markRange(this.minDate);
this.$nextTick(() => {
this.handleMouseMove(event);
});
} else if(this.minDate && !this.maxDate) {
// 有最小日期 无最大日期
if(newDate >= this.minDate) {
const maxDate = new Date(newDate.getTime());
this.rangeState.selecting = false;
this.$emit('pick', {
minDate: this.minDate,
maxDate,
});
} else {
const minDate = new Date(newDate.getTime());
this.rangeState.selecting = false;
this.$emit('pick', { minDate, maxDate: this.minDate });
}
} else if(!this.minDate) {
// 无最小日期
const minDate = new Date(newDate.getTime());
this.$emit('pick', { minDate, maxDate: this.maxDate }, false);
this.rangeState.selecting = true;
this.markRange(this.minDate);
}
} else if(selectionMode === 'day') {
this.$emit('pick', newDate);
}
},
},
};
</script>
================================================
FILE: src/scripts/components/DatePicker/Picker.vue
================================================
<template>
<div
class="date-picker"
:class="{
[`date-picker-${size}`]: size,
[`date-picker-${theme}`]: theme,
}">
</div>
</template>
<script>
import Vue from 'vue';
// import { formatDate } from '../../utils/date';
import Emitter from '../../mixins/emitter';
export default {
mixins: [Emitter],
props: {
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'default'].indexOf(value) > -1;
},
},
// 尺寸
size: {
type: String,
default: 'base',
validator(value) {
return ['base', 'sm', 'lg'].indexOf(value) > -1;
},
},
// 是否开启日期不可选
dateDisable: {
type: Boolean,
default: false,
},
// 相较于当前时间,往前不可选择的天数
prevPickedDisableDays: {
type: Number,
default: 0,
},
// 相较于当前时间,往后不可选择的天数
nextPickedDisableDays: {
type: Number,
default: 0,
},
value: {},
// 是否总是展示日期组件
pickerAlwaysShow: {
type: Boolean,
default: true,
},
onPick: {},
},
data() {
return {
pickerVisible: false,
};
},
watch: {
pickerVisible(val) {
if(val) {
this.showPicker();
} else {
this.hidePicker();
}
},
value: {
immediate: true,
handler(val) {
if(this.picker) {
this.picker.value = val;
this.picker.selectedDate = Array.isArray(val) ? val : [];
}
},
},
},
computed: {
selectionMode() {
// todo
// if(this.type === 'week') {
// return 'week';
// } else if(this.type === 'month') {
// return 'month';
// } else if(this.type === 'year') {
// return 'year';
// } else if(this.type === 'dates') {
// return 'dates';
// }
//
// return 'day';
return this.type;
},
// todo
pickerSize() {
return this.size;
},
},
created() {
// 默认展示日期选择框
this.pickerVisible = true;
},
methods: {
hidePicker() {
if(this.picker) {
this.picker.resetView && this.picker.resetView();
this.pickerVisible = this.picker.visible = false;
}
},
showPicker() {
if(this.$isServer) return;
if(!this.picker) {
this.mountPicker();
}
this.pickerVisible = this.picker.visible = true;
this.picker.value = this.value;
this.picker.resetView && this.picker.resetView();
this.$nextTick(() => {
this.picker.adjustSpinners && this.picker.adjustSpinners();
});
},
mountPicker() {
this.picker = new Vue(this.view).$mount();
this.picker.selectedDate = Array.isArray(this.value) && this.value || [];
this.$watch('format', format => {
this.picker.format = format;
});
this.picker.dateDisable = this.dateDisable;
this.picker.prevPickedDisableDays = this.prevPickedDisableDays;
this.picker.nextPickedDisableDays = this.nextPickedDisableDays;
this.$el.appendChild(this.picker.$el);
this.picker.resetView && this.picker.resetView();
this.picker.onPick = this.onPick;
this.picker.$on('dodestroy', this.doDestroy);
this.picker.$on('pick', (date = '', visible = this.pickerAlwaysShow) => {
this.pickerVisible = this.picker.visible = visible;
this.picker.resetView && this.picker.resetView();
});
this.picker.$on('select-range', (start, end, pos) => {
if(this.refInput.length === 0) return;
if(!pos || pos === 'min') {
this.refInput[0].setSelectionRange(start, end);
this.refInput[0].focus();
} else if(pos === 'max') {
this.refInput[1].setSelectionRange(start, end);
this.refInput[1].focus();
}
});
},
unmountPicker() {
if(this.picker) {
this.picker.$destroy();
this.picker.$off();
this.picker.$el.parentNode.removeChild(this.picker.$el);
}
},
},
};
</script>
================================================
FILE: src/scripts/components/DatePicker/index.js
================================================
import Picker from './Picker';
import DateRangeView from './DateRange';
const getView = function(type) {
if(type === 'daterange') {
return DateRangeView;
}
// other type view
return null;
};
const DatePicker = {
mixins: [Picker],
name: 'date-picker',
props: {
type: {
type: String,
default: 'daterange',
},
},
watch: {
type(type) {
if(this.picker) {
this.unmountPicker();
this.view = getView(type);
this.mountPicker();
} else {
this.view = getView(type);
}
},
},
created() {
this.view = getView(this.type);
},
};
export default DatePicker;
================================================
FILE: src/scripts/components/Drawer.vue
================================================
<template>
<div class="drawer">
<slot></slot>
</div>
</template>
<script>
import Tab from '../mixins/tab';
export default {
name: 'drawer',
mixins: [Tab],
methods: {
reset() {
this.currentActiveKey = undefined;
},
},
};
</script>
================================================
FILE: src/scripts/components/DrawerItem.vue
================================================
<template>
<div
class="drawer-item"
:class="{
'active': $parent.currentActiveKey !== undefined && $parent.currentActiveKey === currentEventKey,
disabled: disabled || $parent.disabled,
}"
@click="clickHandle">
<slot></slot>
<icon name="caret-down" class="drawer-item-icon"></icon>
</div>
</template>
<script>
import TabItem from '../mixins/tabItem';
export default {
name: 'drawer-item',
mixins: [TabItem],
};
</script>
================================================
FILE: src/scripts/components/Flex.vue
================================================
<template>
<div
class="flex"
:class="[
alignClass,
{[`flex-justify-${justify}`]: justify},
{'flex-vertical': direction === 'column'}
]">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'flex',
props: {
// 方向
direction: {
type: String,
default: 'row',
validator(value) {
return ['row', 'column'].indexOf(value) > -1;
},
},
// 排版
justify: {
type: String,
validator(value) {
return ['start', 'end', 'center', 'between', 'around'].indexOf(value) > -1;
},
},
// 对齐
align: {
type: String,
validator(value) {
return ['top', 'bottom', 'middle'].indexOf(value) > -1;
},
},
},
computed: {
alignClass() {
let align = this.align;
if(this.direction === 'row' && !align) {
align = 'middle';
}
return `flex-align-${align}`;
},
},
};
</script>
================================================
FILE: src/scripts/components/FlexItem.vue
================================================
<template>
<div
@click="$emit('click')"
class="flex-item"
:style="{ flex }">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'flex-item',
props: {
flex: {
type: [Number, String],
default: 1,
},
},
};
</script>
================================================
FILE: src/scripts/components/Group.vue
================================================
<template>
<div class="group">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'group',
};
</script>
================================================
FILE: src/scripts/components/GroupTitle.vue
================================================
<template>
<div class="group-title">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'group-title',
};
</script>
================================================
FILE: src/scripts/components/HRule.vue
================================================
<template>
<hr class="hr" :class="{
[`border-${type}`]: type,
[`border-${theme}`]: theme,
}">
</template>
<script>
export default {
name: 'hrule',
props: {
type: {
type: String,
validator(value) {
return ['dashed', 'dotted'].indexOf(value) > -1;
},
},
theme: {
type: String,
validator(value) {
return ['primary', 'secondary', 'warning', 'success', 'danger'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Icon.vue
================================================
<template>
<i
@click="$emit('click')"
class="fa"
:class="[
`fa-${name}`,
{
[`fa-${size}`]: size,
'gap-left': left,
'gap-right': right,
}
]" />
</template>
<script>
export default {
name: 'icon',
props: {
left: Boolean,
right: Boolean,
name: {
type: String,
required: true,
},
size: {
type: String,
validator(value) {
return ['lg', '2x', '3x', '4x', '5x'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/InlineSelector.vue
================================================
<template>
<div class="inline-selector">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
import Select from '../mixins/select';
export default {
name: 'inline-selector',
mixins: [Sync, Select],
props: {
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'success', 'warning', 'danger'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/InlineSelectorOption.vue
================================================
<template>
<tag
class="inline-selector-option"
:class="{
disabled: disabled || $parent.disabled,
}"
hollow
size="sm"
@click="clickHandle"
:theme="isActive() ? $parent.theme : 'default'">
<slot></slot>
</tag>
</template>
<script>
import SelectOption from '../mixins/selectOption';
export default {
name: 'inline-selector-option',
mixins: [SelectOption],
};
</script>
================================================
FILE: src/scripts/components/InputNumber.vue
================================================
<template>
<div class="input-number" :class="{'input-number-disabled': disabled}">
<a
class="input-number-minus"
:class="{'disabled': currentValue <= min}"
@click="minusHandle">-</a>
<input
type="number"
v-model="currentValue"
:disabled="disabled"
class="input-number-input">
<a
class="input-number-plus"
:class="{'disabled': currentValue >= max}"
@click="plusHandle">
+
</a>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'input-number',
mixins: [Sync],
props: {
value: {
type: Number,
default: 0,
},
max: {
type: Number,
default: Infinity,
},
min: {
type: Number,
default: -Infinity,
},
step: {
type: Number,
default: 1,
},
},
methods: {
// 减
minusHandle() {
if(this.disabled || this.currentValue <= this.min) return;
this.currentValue -= this.step;
},
// 加
plusHandle() {
if(this.disabled || this.currentValue >= this.max) return;
this.currentValue += this.step;
},
},
};
</script>
================================================
FILE: src/scripts/components/InputText.vue
================================================
<template>
<div class="input-text" v-disfavor="blur">
<input
type="number"
v-if="type === 'number'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="tel"
v-else-if="type === 'tel'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="date"
v-else-if="type === 'date'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="datetime"
v-else-if="type === 'datetime'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="time"
v-else-if="type === 'time'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="password"
v-else-if="type === 'password'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="email"
v-else-if="type === 'email'"
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<input
type="text"
v-else
ref="input"
v-model="currentValue"
:disabled="currentDisabled"
:placeholder="placeholder"
@focus="focus = true"
class="input-text-input">
<icon
v-show="clearable && focus && !!currentValue"
class="input-text-clear"
name="times-circle"
@click="clearHandle"
size="lg" />
<icon
v-if="state"
:name="getStateIcon()"
:class="getStateClass()"
size="lg" />
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'input-text',
mixins: [Sync],
props: {
clearable: Boolean,
placeholder: String,
type: {
type: String,
default: 'text',
validator(value) {
return ['text', 'number', 'tel', 'date', 'datetime', 'time', 'password', 'email'].indexOf(value) > -1;
},
},
state: {
type: String,
validator(value) {
return ['success', 'warning', 'error'].indexOf(value) > -1;
},
},
},
data() {
return {
focus: false,
currentDisabled: this.disabled || this.$parent.disabled,
};
},
methods: {
blur() {
this.focus = false;
},
clearHandle() {
if(!this.currentDisabled) {
this.currentValue = '';
this.$refs.input.focus();
}
},
getStateClass() {
return {
success: 'text-success',
warning: 'text-warning',
error: 'text-danger',
}[this.state];
},
getStateIcon() {
return {
success: 'check',
warning: 'exclamation-triangle',
error: 'exclamation-circle',
}[this.state];
},
},
};
</script>
================================================
FILE: src/scripts/components/InputTextarea.vue
================================================
<template>
<div class="textarea">
<textarea
v-model="currentValue"
:disabled="disabled"
:placeholder="placeholder"
class="textarea-input"
:rows="rows"></textarea>
<div class="textarea-counter" v-if="countable">{{currentValue.length}}/{{maxLength}}</div>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'input-textarea',
mixins: [Sync],
props: {
placeholder: String,
countable: Boolean,
maxLength: {
type: Number,
default: 200,
},
rows: {
type: Number,
default: 4,
},
},
data() {
return {
stage: 0,
};
},
watch: {
currentValue(val) {
if(val.length > this.maxLength) {
this.currentValue = val.substring(0, this.maxLength);
}
},
},
};
</script>
================================================
FILE: src/scripts/components/Loading.vue
================================================
<template>
<svg
class="loading"
:class="{
[`loading-${size}`]: size,
[`loading-${theme}`]: theme,
}"
viewBox="25 25 50 50">
<circle class="loading-path" cx="50" cy="50" r="23" fill="none"/>
</svg>
</template>
<script>
export default {
name: 'loading',
props: {
size: {
type: String,
validator(value) {
return ['sm', 'lg'].indexOf(value) > -1;
},
},
theme: {
type: String,
validator(value) {
return ['primary', 'secondary'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Loadmore.vue
================================================
<template>
<div class="loadmore">
<div
class="loadmore-content"
:class="{ dropped: topDropped || bottomDropped }"
:style="{ transform: `translate3d(0, ${translate}px, 0)` }">
<slot name="top">
<div class="loadmore-hint loadmore-hint-top" v-if="topMethod">
<loading v-if="showLoading && topStatus === 'loading'"></loading>
<span class="loadmore-text">{{ topText }}</span>
</div>
</slot>
<slot></slot>
<slot name="bottom">
<div class="loadmore-hint loadmore-hint-bottom" v-if="bottomMethod">
<loading v-if="showLoading && bottomStatus === 'loading'" ></loading>
<span class="loadmore-text">{{ bottomText }}</span>
</div>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'loadmore',
props: {
topPullText: {
type: String,
default: 'pull down to refresh',
},
topDropText: {
type: String,
default: 'release to load more',
},
topLoadingText: {
type: String,
default: 'loading...',
},
topDistance: {
type: Number,
default: 70,
},
topMethod: {
type: Function,
},
topCancelledMethod: {
type: Function,
},
topPulledMethod: {
type: Function,
},
topAllLoaded: {
type: Boolean,
default: false,
},
bottomPullText: {
type: String,
default: 'pull up to refresh',
},
bottomDropText: {
type: String,
default: 'release to load more',
},
bottomLoadingText: {
type: String,
default: 'loading...',
},
bottomDistance: {
type: Number,
default: 70,
},
bottomMethod: {
type: Function,
},
bottomAllLoaded: {
type: Boolean,
default: false,
},
showLoading: {
type: Boolean,
default: false,
},
},
data() {
return {
topStatus: '',
topText: '',
topDropped: false,
topPulled: false,
bottomStatus: '',
bottomText: '',
bottomDropped: false,
bottomReached: false,
scrollElement: null,
startY: 0,
startScrollTop: 0,
currentY: 0,
direction: '',
translate: 0,
};
},
watch: {
topStatus(status) {
switch(status) {
case 'pull':
this.topText = this.topPullText;
break;
case 'drop':
this.topText = this.topDropText;
break;
case 'loading':
this.topText = this.topLoadingText;
break;
default:
break;
}
this.$emit('topStatusChanged', status);
},
bottomStatus(status) {
switch(status) {
case 'pull':
this.bottomText = this.bottomPullText;
break;
case 'drop':
this.bottomText = this.bottomDropText;
break;
case 'loading':
this.bottomText = this.bottomLoadingText;
break;
default:
break;
}
this.$emit('bottomStatusChanged', status);
},
},
methods: {
getScrollElement(element) {
let currentNode = element,
overflowY = '';
while(
currentNode &&
currentNode.tagName !== 'HTML' &&
currentNode.tagName !== 'BODY' &&
currentNode.nodeType === 1
) {
overflowY = document.defaultView.getComputedStyle(currentNode).overflowY;
if(overflowY === 'scroll' || overflowY === 'auto') return currentNode;
currentNode = currentNode.parentNode;
}
return window;
},
getScrollTop(element) {
if(element === window) {
return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
}
return element.scrollTop;
},
handleTouchStart(event) {
this.startY = event.touches[0].clientY;
this.startScrollTop = this.getScrollTop(this.scrollElement);
this.bottomReached = false;
if(this.topStatus !== 'loading') {
this.topStatus = 'pull';
this.topDropped = false;
this.topPulled = false;
}
if(this.bottomStatus !== 'loading') {
this.bottomStatus = 'pull';
this.bottomDropped = false;
}
},
handleTouchMove(event) {
// outside element
const rect = this.$el.getBoundingClientRect();
if(this.startY < rect.top && this.startY > rect.bottom) return;
// hand moved
let distance = 0;
this.currentY = event.touches[0].clientY;
// avoid accident
distance = (this.currentY - this.startY) / 2;
// detect direction
this.direction = distance > 0 ? 'down' : 'up';
// pull down
if(
typeof this.topMethod === 'function' &&
this.topStatus !== 'loading' &&
this.direction === 'down' &&
this.getScrollTop(this.scrollElement) === 0 &&
!this.topAllLoaded
) {
event.preventDefault();
event.stopPropagation();
this.translate = distance - this.startScrollTop;
if(this.translate < 0) this.translate = 0;
this.topStatus = this.translate >= this.topDistance ? 'drop' : 'pull';
if(!this.topPulled) {
this.topPulled = true;
this.topPulledMethod && this.topPulledMethod();
}
}
// pull up
if(this.direction === 'up') {
this.bottomReached = this.bottomReached || this.isBottomReached();
}
if(
typeof this.bottomMethod === 'function' &&
this.bottomStatus !== 'loading' &&
this.direction === 'up' &&
this.bottomReached &&
!this.bottomAllLoaded
) {
event.preventDefault();
event.stopPropagation();
this.translate = this.getScrollTop(this.scrollElement)
- this.startScrollTop
+ distance;
if(this.translate > 0) this.translate = 0;
this.bottomStatus = -this.translate >= this.bottomDistance ? 'drop' : 'pull';
}
},
handleTouchEnd() {
// pull down
if(this.direction === 'down' &&
this.getScrollTop(this.scrollElement) === 0 &&
this.translate > 0
) {
this.topDropped = true;
if(this.topStatus === 'drop') {
this.translate = 50;
this.topStatus = 'loading';
this.topMethod();
} else {
this.translate = 0;
this.topStatus = 'pull';
this.topCancelledMethod && this.topCancelledMethod();
}
}
// pull up
if(
this.direction === 'up' &&
this.bottomReached &&
this.translate < 0
) {
this.bottomDropped = true;
// reset after pull up takes effect
this.bottomReached = false;
if(this.bottomStatus === 'drop') {
this.translate = -50;
this.bottomStatus = 'loading';
this.bottomMethod();
} else {
this.translate = 0;
this.bottomStatus = 'pull';
}
}
// reset direction
this.direction = '';
},
isBottomReached() {
if(this.scrollElement === window) {
return (
document.documentElement.clientHeight + (document.body.scrollTop || window.pageYOffset)
=== document.body.scrollHeight
);
}
return (
this.scrollElement.getBoundingClientRect().bottom
>= this.$el.getBoundingClientRect().bottom
);
},
onTopLoaded() {
this.translate = 0;
setTimeout(() => {
this.topStatus = 'pull';
}, 200);
},
onBottomLoaded() {
this.bottomStatus = 'pull';
this.bottomDropped = false;
this.$nextTick(() => {
if(this.scrollElement === window) {
document.body.scrollTop += 50;
} else {
this.scrollElement.scrollTop += 50;
}
this.translate = 0;
});
},
},
mounted() {
this.topText = this.topPullText;
this.topStatus = 'pull';
this.bottomStatus = 'pull';
this.scrollElement = this.getScrollElement(this.$el);
if(
typeof this.topMethod === 'function' ||
typeof this.bottomMethod === 'function'
) {
this.$el.addEventListener('touchstart', this.handleTouchStart);
this.$el.addEventListener('touchmove', this.handleTouchMove);
this.$el.addEventListener('touchend', this.handleTouchEnd);
}
},
beforeDestroy() {
if(
typeof this.topMethod === 'function' ||
typeof this.bottomMethod === 'function'
) {
this.$el.removeEventListener('touchstart', this.handleTouchStart);
this.$el.removeEventListener('touchmove', this.handleTouchMove);
this.$el.removeEventListener('touchend', this.handleTouchEnd);
}
},
};
</script>
================================================
FILE: src/scripts/components/Mask.vue
================================================
<template>
<transition :name="transition">
<div
class="mask"
v-show="$parent.currentValue"
@click="clickable && ($parent.currentValue = false)">
</div>
</transition>
</template>
<script>
export default {
name: 'mask-layer',
props: {
transition: {
type: String,
default: 'fade',
},
clickable: {
type: Boolean,
default: true,
},
},
};
</script>
================================================
FILE: src/scripts/components/Media.vue
================================================
<template>
<div
@click="$emit('click')"
class="media"
:class="`flex-align-${align}`">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'media',
props: {
// 对齐
align: {
type: String,
default: 'top',
validator(value) {
return ['top', 'bottom', 'middle'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/MediaBody.vue
================================================
<template>
<div class="media-body">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'media-body',
};
</script>
================================================
FILE: src/scripts/components/MediaObject.vue
================================================
<template>
<div class="media-object">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'media-object',
};
</script>
================================================
FILE: src/scripts/components/Navbar.vue
================================================
<template>
<div class="navbar" :class="'navbar-' + theme">
<div class="navbar-header">
<slot></slot>
</div>
<div class="navbar-body" v-if="$slots.body">
<slot name="body"></slot>
</div>
<div class="navbar-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'navbar',
props: {
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'default'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Navigation.vue
================================================
<template>
<div class="navigation">
<slot></slot>
</div>
</template>
<script>
import Tab from '../mixins/tab';
export default {
name: 'navigation',
mixins: [Tab],
props: {
activeKey: {
default: 0,
},
},
};
</script>
================================================
FILE: src/scripts/components/NavigationItem.vue
================================================
<template>
<div
class="navigation-item"
:class="{'active': $parent.currentActiveKey === currentEventKey}"
@click="$parent.$emit('itemClick', currentEventKey)">
<slot></slot>
<div class="navigation-item-label">
{{label}}
</div>
</div>
</template>
<script>
import TabItem from '../mixins/tabItem';
export default {
name: 'navigation-item',
mixins: [TabItem],
props: {
label: String,
},
};
</script>
================================================
FILE: src/scripts/components/Picker.vue
================================================
<template>
<div
class="picker"
:class="{
[`picker-${size}`]: size,
}"
ref="picker">
<div class="picker-mask"></div>
<div class="picker-indicator"></div>
<div class="picker-list" :class="{active: dragging}" ref="pickerList">
<slot></slot>
</div>
</div>
</template>
<script>
import Sync from '../mixins/sync';
import draggable from '../utils/draggable';
import { getTranslate, setTranslate } from '../utils/translate';
export default {
name: 'picker',
mixins: [Sync],
props: {
size: {
type: String,
validator(value) {
return ['sm', 'lg'].indexOf(value) > -1;
},
},
},
data() {
return {
dragging: false,
itemHeight: 0,
pickedIndex: -1,
};
},
methods: {
// 计算拖拽区间
getDragRange() {
let { pickerList } = this.$refs;
return {
min: -1 * this.getOptionHeight() * (pickerList.children.length - 1),
max: 0,
};
},
// 获取每个Option高度
getOptionHeight() {
if(this.itemHeight) return this.itemHeight;
let style = document.body.currentStyle ||
document.defaultView.getComputedStyle(document.body, '');
this.itemHeight = 3 * parseInt(style.fontSize, 10);
return this.itemHeight;
},
// 选择Option
pickOption() {
let pickIndex = 0;
this.$children.forEach((option, index) => {
if(option.value === this.currentValue) {
pickIndex = index;
}
});
let itemHeight = this.getOptionHeight(),
translate = itemHeight * pickIndex * -1;
setTranslate(this.$refs.pickerList, null, translate);
this.pickedIndex = Math.abs(pickIndex);
},
// 事件初始化
initDrag() {
let { picker, pickerList } = this.$refs,
prevTranslateY;
draggable(picker, {
effectEl: pickerList,
onDragStart: ({ translateY }, event) => {
event.preventDefault();
prevTranslateY = translateY;
},
onDrag: ({ dragging, effectEl, translateY }, event) => {
event.preventDefault();
this.dragging = dragging;
let maxTranslateY = this.getOptionHeight() * 3;
let currentTranslateY = translateY;
let pickerHeight = this.$children.length * this.getOptionHeight();
// 拉过了
if(translateY > 0) {
let rate = (maxTranslateY - translateY) / maxTranslateY;
rate = rate >= 0 ? rate : 0.1;
currentTranslateY = prevTranslateY + rate * (translateY - prevTranslateY);
} else if(translateY < -pickerHeight) {
let rate = ((translateY - pickerHeight) - maxTranslateY) / maxTranslateY;
rate = rate >= 0 ? rate : 0.1;
currentTranslateY = prevTranslateY + rate * (translateY - prevTranslateY);
}
prevTranslateY = currentTranslateY;
setTranslate(effectEl, null, currentTranslateY);
},
onDragEnd: ({ dragging, startTimestamp, velocityTranslateY }, event) => {
event.preventDefault();
this.dragging = dragging;
let momentumRatio = 10,
itemHeight = this.getOptionHeight(),
translateY = getTranslate(pickerList).y,
duration = new Date() - startTimestamp;
let momentumTranslate;
if(duration < 300) {
momentumTranslate = translateY + (velocityTranslateY * momentumRatio);
}
// this.$nextTick(() => {
let translate;
let dragRange = this.getDragRange();
if(momentumTranslate) {
translate = Math.round(momentumTranslate / itemHeight) * itemHeight;
} else {
translate = Math.round(translateY / itemHeight) * itemHeight;
}
translate = Math.max(Math.min(translate, dragRange.max), dragRange.min);
setTranslate(pickerList, null, translate);
this.pickedIndex = Math.abs(translate / itemHeight);
// });
},
});
},
},
watch: {
pickedIndex(val) {
this.currentValue = this.$children[val].value;
},
value() {
this.pickOption();
},
},
mounted() {
this.pickOption();
this.initDrag();
},
};
</script>
================================================
FILE: src/scripts/components/PickerOption.vue
================================================
<template>
<div
class="picker-list-item"
:class="{active: $parent.currentValue === value}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'picker-option',
props: {
value: {},
},
};
</script>
================================================
FILE: src/scripts/components/Progressbar.vue
================================================
<template>
<div
class="progressbar"
:class="{
[`progressbar-${size}`]: size,
[`progressbar-${theme}`]: theme,
}">
<div class="progressbar-indicator" :style="{width: `${value * 100}%`}">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'progressbar',
props: {
value: {
type: Number,
default: 0,
required: true,
validator(value) {
return value >= 0 && value <= 1;
},
},
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'success', 'warning', 'danger'].indexOf(value) > -1;
},
},
// 尺寸
size: {
type: String,
validator(value) {
return ['sm'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Progressbars.vue
================================================
<template>
<div
class="progressbars"
:class="{
[`progressbars-${size}`]: size,
[`progressbars-${theme}`]: theme,
}">
<div class="line-bg" :style="`background-color: ${backgroundColor};`">
<div class="line-progress" :style="`width: ${this.progressWidth}; background-color: ${this.foregroundColor};`"></div>
</div>
<div
v-for="(nodeData, index) in nodeDataList"
:key="index"
:class="getNodeClass(index)"
:style="getNodeStyle(index)"
>
<div class="info">
<slot :name="`infoSlot${index}`" />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'progressbars',
data() {
return {
circleWidth: 0.8,
}
},
props: {
// 所有节点数值
nodeDataList: {
type: Array,
default: [0, 10, 50],
required: true,
validator(value) {
return value && value.length >= 2;
}
},
// 当前数值
currentData: {
type: Number,
default: 0,
required: true,
},
// 所有节点展示内容
nodeInfoList: {
type: Array,
default: null,
// validator(value) {
// if (value) {
// return value.length === this.nodeDataList.length;
// }
//
// return
// }
},
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'success', 'warning', 'danger'].indexOf(value) > -1;
},
},
// 尺寸
size: {
type: String,
default: 'sm',
validator(value) {
return ['xsm', 'sm', 'lg'].indexOf(value) > -1;
},
},
// 节点形状
nodeShape: {
type: String,
default: 'dot',
validator(value) {
return ['circle', 'dot', 'checkCircle'].indexOf(value) > -1;
},
},
// 进度条背景色
backgroundColor: {
type: String,
default: '#DEE0E8',
},
// 进度条前景色
foregroundColor: {
type: String,
default: '#1C89EA',
},
},
computed: {
nodesLength: function () {
return this.nodeDataList.length
},
// 方案一
// 进度条百分比 + N个节点宽度 = 精确的进度条长度
//
// 方案二:
// (100% - 所有节点宽度)/(所有节点数 - 1)*(所处区间头节点index + 占当前区间百分比)+
// (所处区间头节点index + 1)* 一个节点宽度 =
// 精确区间百分比 + 包含所有节点宽度 =
// 精确的进度条长度
progressWidth: function () {
/* eslint-disable */
// debugger
// 最高等级
if (this.currentDotIndex === this.nodesLength - 1) {
return '100%'
}
// 方案一:总体进度
// const widthPercent =
// // 每个区间占比
// 1 / (this.nodeDataList.length - 1) *
// (
// this.currentDotIndex +
// // 当前区间分值
// ((this.currentData - this.nodeDataList[this.currentDotIndex]) /
// // 对应区间总分值
// (this.nodeDataList[this.currentDotIndex + 1] - this.nodeDataList[this.currentDotIndex]))
// )
//
// return `calc(${widthPercent * 100}% + ${(this.currentDotIndex + 1) * this.circleWidth}em)`
// 方案二:以下是经过拆解后的计算过程
// 当前区间占比
const currentAreaPercent =
// 当前区间分值
((this.currentData - this.nodeDataList[this.currentDotIndex]) /
// 对应区间总分值
(this.nodeDataList[this.currentDotIndex + 1] - this.nodeDataList[this.currentDotIndex]))
const part1 = `(100% - ${this.nodesLength * this.circleWidth}em)`
const part2 = `${this.nodesLength - 1}`
const part3 = `${this.currentDotIndex + currentAreaPercent}`
const part4 = `${(this.currentDotIndex + 1) * this.circleWidth}em`
return `calc(${part1} / ${part2} * ${part3} + ${part4})`
},
currentDotIndex: function () {
let currentDotIndex = 0
for (let i = 0; i < this.nodeDataList.length; i++) {
if (i === (this.nodeDataList.length - 1)) {
if (this.currentData >= this.nodeDataList[i]) {
currentDotIndex = i
}
} else {
if (this.currentData >= this.nodeDataList[i] && this.currentData < this.nodeDataList[i + 1]) {
currentDotIndex = i
break
}
}
}
return currentDotIndex
},
},
methods: {
getNodeClass(index) {
let nodeClass = 'node '
if (this.nodeShape === 'dot') {
nodeClass += 'dot '
} else if (this.nodeShape === 'circle') {
nodeClass += 'circle '
} else if (this.nodeShape === 'checkCircle') {
nodeClass += 'check-circle '
}
if (index <= this.currentDotIndex) {
nodeClass += 'active '
}
if (index === this.currentDotIndex) {
nodeClass += 'current-dot '
}
return nodeClass
},
getNodeStyle(index) {
let nodeStyle = ''
if (this.nodeShape === 'dot') {
nodeStyle = 'border-color: '
} else if (this.nodeShape === 'circle') {
nodeStyle = 'background-color: '
} else if (this.nodeShape === 'checkCircle') {
// nodeStyle = 'color: '
nodeStyle = 'background-color: '
}
if (index <= this.currentDotIndex) {
nodeStyle += `${this.foregroundColor};`
} else {
nodeStyle += `${this.backgroundColor};`
}
return `${this.getCirclePosition(index)} ${nodeStyle}`
},
getCirclePosition(index) {
// (全长 - 一个节点宽度) / (节点数 - 1) * 节点索引 = 节点left定位
// return `left: calc(100%/${this.nodesLength-1}*${index} - ${this.circleWidth / (this.nodesLength -1 ) * index}em);`
return `left: calc((100% - ${this.circleWidth}em) / ${this.nodesLength - 1} * ${index});`
},
},
};
</script>
================================================
FILE: src/scripts/components/Radio.vue
================================================
<template>
<label
class="radio"
:class="'radio-' + shape">
<input
type="radio"
class="radio-input"
v-model="model"
:value="isGroupChildComponent ? value : val"
:disabled="disabled || $parent.disabled">
<span class="radio-addon">
<i v-if="shape==='default'" />
<i v-else class="fa fa-check" />
</span>
<span class="radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
export default {
name: 'radio',
props: {
shape: {
type: String,
default: 'default',
validator(value) {
return ['square', 'circle', 'default'].indexOf(value) > -1;
},
},
value: {},
val: {},
disabled: {
type: Boolean,
default: false,
},
},
computed: {
model: {
get() {
return this.isGroupChildComponent ? this.$parent.value : this.value;
},
set(val) {
if(this.isGroupChildComponent) {
this.$parent.$emit('input', val);
} else {
this.$emit('input', val);
}
},
},
},
beforeCreate() {
this.isGroupChildComponent = this.$parent.$options._componentTag === 'radio-group';
},
};
</script>
================================================
FILE: src/scripts/components/RadioGroup.vue
================================================
<template>
<div class="radio-group">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'radio-group',
mixins: [Sync],
};
</script>
================================================
FILE: src/scripts/components/Searchbar.vue
================================================
<template>
<div class="searchbar" :class="{active: focus}" v-disfavor="blur">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'searchbar',
mixins: [Sync],
props: {
clearable: {
type: Boolean,
default: true,
},
shape: {
type: String,
validator(value) {
return ['circle'].indexOf(value) > -1;
},
},
autoBlur: {
type: Boolean,
default: true,
},
},
data() {
return {
focus: false,
};
},
methods: {
blur() {
this.autoBlur && (this.focus = false);
},
},
};
</script>
================================================
FILE: src/scripts/components/SearchbarBtn.vue
================================================
<template>
<div class="searchbar-btn">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'searchbar-btn',
};
</script>
================================================
FILE: src/scripts/components/SearchbarPlaceholder.vue
================================================
<template>
<div
@click="clickHandle"
class="searchbar-input"
:class="{
'border-circle': $parent.shape === 'circle'
}">
<div
class="searchbar-input-placeholder"
:style="{overflow: !!$parent.currentValue ? 'hidden' : 'visible'}">
<slot></slot>
</div>
<input
type="text"
class="searchbar-input-field"
v-model="currentValue"
ref="input">
<icon
v-show="$parent.clearable && !!$parent.currentValue"
class="searchbar-input-clear"
name="times-circle"
@click="currentValue = ''"
size="lg" />
</div>
</template>
<script>
export default {
name: 'searchbar-placeholder',
data() {
return {
currentValue: this.$parent.value,
};
},
watch: {
currentValue(val) {
this.$parent.currentValue = val;
},
},
methods: {
clickHandle() {
this.$parent.focus = true;
setTimeout(() => {
this.$refs.input.focus();
}, 300);
},
},
};
</script>
================================================
FILE: src/scripts/components/SegmentedControl.vue
================================================
<template>
<div class="segmented-control" :class="{disabled}">
<slot></slot>
</div>
</template>
<script>
import Tab from '../mixins/tab';
export default {
name: 'segmented-control',
mixins: [Tab],
props: {
activeKey: {
default: 0,
},
},
};
</script>
================================================
FILE: src/scripts/components/SegmentedControlItem.vue
================================================
<template>
<div
class="segmented-control-item"
:class="{
'active': $parent.currentActiveKey === currentEventKey,
disabled: disabled || $parent.disabled,
}"
@click="clickHandle">
<slot></slot>
</div>
</template>
<script>
import TabItem from '../mixins/tabItem';
export default {
name: 'segmented-control-item',
mixins: [TabItem],
};
</script>
================================================
FILE: src/scripts/components/Selector.vue
================================================
<template>
<div class="selector">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
import Select from '../mixins/select';
export default {
name: 'selector',
mixins: [Sync, Select],
};
</script>
================================================
FILE: src/scripts/components/SelectorOption.vue
================================================
<template>
<div
class="cell selector-option"
:class="{
'active': isActive(),
'cell-disabled': disabled || $parent.disabled }"
@click="clickHandle">
<span slot="header">
</span>
<span class="cell-body">
<slot></slot>
</span>
<icon
:name="checkedIcon"
size="lg"
class="selector-icon" />
</div>
</template>
<script>
import SelectOption from '../mixins/selectOption';
export default {
name: 'selector-option',
mixins: [SelectOption],
};
</script>
================================================
FILE: src/scripts/components/Sidelip.vue
================================================
<template>
<div @click="$emit('click')">
<transition :name="transition">
<div v-show="currentValue" class="sidelip">
<slot></slot>
</div>
</transition>
<mask-layer :clickable="closeOnClickMask" />
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'sidelip',
mixins: [Sync],
props: {
transition: {
type: String,
default: 'slide-left',
},
closeOnClickMask: {
type: Boolean,
default: true,
},
},
watch: {
currentValue(val) {
!val && (this.$emit('close'));
},
},
};
</script>
================================================
FILE: src/scripts/components/SlideUp.vue
================================================
<template>
<div>
<transition :name="transition">
<div v-show="currentValue" class="slideup">
<slot></slot>
</div>
</transition>
<mask-layer :clickable="closeOnClickMask" />
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'slideup',
mixins: [Sync],
props: {
transition: {
type: String,
default: 'slide-up',
},
closeOnClickMask: {
type: Boolean,
default: true,
},
},
watch: {
currentValue(val) {
val ? this.$emit('show') : this.$emit('close');
},
},
};
</script>
================================================
FILE: src/scripts/components/SlideUpBody.vue
================================================
<template>
<div class="slideup-body" :class="{'no-padding': noPadding}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'slideup-body',
props: {
noPadding: Boolean,
},
};
</script>
=
================================================
FILE: src/scripts/components/SlideUpHeader.vue
================================================
<template>
<div class="slideup-header">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'slideup-header',
};
</script>
================================================
FILE: src/scripts/components/Stepbar.vue
================================================
<template>
<div class="stepbar">
<slot></slot>
</div>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'stepbar',
mixins: [Sync],
props: {
size: {
type: String,
validator(value) {
return ['sm', 'lg'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/StepbarItem.vue
================================================
<template>
<div class="stepbar-item">
<div
class="stepbar-line"
:class="{active: state === 'active' || state === 'finished' }"
v-if="!isFirstChild"></div>
<div
class="stepbar-item-addon"
:class="{[state]: state !== 'default'}">
<icon name="check" v-if="state === 'finished'"/>
{{state === 'finished' ? '' : (index + 1)}}
</div>
<div
class="stepbar-item-title"
:class="{[state]: state !== 'default'}">
<slot></slot>
</div>
<div
class="stepbar-line"
:class="{active: state === 'finished' }"
v-if="!isLastChild"></div>
</div>
</template>
<script>
export default {
name: 'stepbar-item',
props: {
description: {
type: String,
},
},
data() {
return {
isFirstChild: false,
isLastChild: false,
state: 'default',
index: 0,
};
},
mounted() {
let length = this.$parent.$children.length;
this.index = this.$parent.$children.indexOf(this);
this.index === 0 && (this.isFirstChild = true);
(this.index === length - 1) && (this.isLastChild = true);
switch(true) {
case this.index === this.$parent.currentValue:
this.state = 'active';
break;
case this.index < this.$parent.currentValue:
this.state = 'finished';
break;
default:
this.state = 'default';
}
},
};
</script>
================================================
FILE: src/scripts/components/Sticky.vue
================================================
<template>
<div>
<div :class="classes" :style="styles">
<slot></slot>
</div>
</div>
</template>
<script>
const prefixCls = 'sticky-affix';
function getScroll(target, top) {
const prop = top ? 'pageYOffset' : 'pageXOffset';
const method = top ? 'scrollTop' : 'scrollLeft';
let ret = target[prop];
if(typeof ret !== 'number') {
ret = window.document.documentElement[method];
}
return ret;
}
function getOffset(element) {
const rect = element.getBoundingClientRect();
const scrollTop = getScroll(window, true);
const scrollLeft = getScroll(window);
const docEl = window.document.body;
const clientTop = docEl.clientTop || 0;
const clientLeft = docEl.clientLeft || 0;
return {
top: rect.top + scrollTop - clientTop,
left: rect.left + scrollLeft - clientLeft,
};
}
export default {
name: 'sticky',
props: {
offsetTop: {
type: Number,
default: 0,
},
offsetBottom: {
type: Number,
},
},
data() {
return {
affix: false,
styles: {},
};
},
computed: {
offsetType() {
let type = 'top';
if(this.offsetBottom >= 0) {
type = 'bottom';
}
return type;
},
classes() {
return [
{
[`${prefixCls}`]: this.affix,
},
];
},
},
mounted() {
window.addEventListener('scroll', this.handleScroll, false);
window.addEventListener('resize', this.handleScroll, false);
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll, false);
window.removeEventListener('resize', this.handleScroll, false);
},
methods: {
handleScroll() {
const affix = this.affix;
const scrollTop = getScroll(window, true);
const elOffset = getOffset(this.$el);
const windowHeight = window.innerHeight;
const elHeight = this.$el.getElementsByTagName('div')[0].offsetHeight;
// Fixed Top
if((elOffset.top - this.offsetTop) < scrollTop && this.offsetType === 'top' && !affix) {
this.affix = true;
this.styles = {
top: `${this.offsetTop}px`,
left: `${elOffset.left}px`,
width: `${this.$el.offsetWidth}px`,
};
this.$emit('on-change', true);
} else if((elOffset.top - this.offsetTop) > scrollTop && this.offsetType === 'top' && affix) {
this.affix = false;
this.styles = null;
this.$emit('on-change', false);
}
// Fixed Bottom
if((elOffset.top + this.offsetBottom + elHeight) > (scrollTop + windowHeight) && this.offsetType === 'bottom' && !affix) {
this.affix = true;
this.styles = {
bottom: `${this.offsetBottom}px`,
left: `${elOffset.left}px`,
width: `${this.$el.offsetWidth}px`,
};
this.$emit('on-change', true);
} else if((elOffset.top + this.offsetBottom + elHeight) < (scrollTop + windowHeight) && this.offsetType === 'bottom' && affix) {
this.affix = false;
this.styles = null;
this.$emit('on-change', false);
}
},
},
};
</script>
================================================
FILE: src/scripts/components/Swipe.vue
================================================
<template>
<div class="swipe" ref="swipe">
<div class="swipe-items">
<slot></slot>
</div>
<div class="swipe-indicators" v-show="length > 1 ? dots : (dots && onlyOneDot)">
<div
v-for="index in length"
:key="index"
class="swipe-indicator"
:class="{active: index - 1 === activeIndex}"
></div>
</div>
</div>
</template>
<script>
import draggable from '../utils/draggable';
export default {
name: 'swipe',
props: {
// 自动播放
autoplay: {
type: Boolean,
default: true,
},
// 默认起始项
defaultIndex: {
type: Number,
default: 0,
},
// 是否显示指示器
dots: {
type: Boolean,
default: true,
},
// 只有一张图片时,是否显示指示器
onlyOneDot: {
type: Boolean,
default: true,
},
// 轮播间隔时间
interval: {
type: Number,
default: 3000,
},
// 轮播速度
speed: {
type: Number,
default: 300,
},
dragThreshold: {
type: Number,
default: 1 / 2,
},
dragRate: {
type: Number,
default: 0.2,
},
onDragStart: Function,
onDrag: Function,
onDragEnd: Function,
// 过渡效果
easing: {
type: String,
default: 'ease-in-out',
},
// 循环播放
cycle: {
type: Boolean,
default: true,
},
},
data() {
return {
length: 0,
dragging: false,
transitioning: false,
negative: false,
activeIndex: this.defaultIndex,
isCycleEnd: false,
};
},
methods: {
init() {
this.length = this.$children.length;
},
// 获取前一个索引
getPrevIndex() {
let prevIndex = this.activeIndex - 1;
if(!this.cycle) {
return prevIndex < 0 ? 0 : prevIndex;
}
return prevIndex < 0 ? this.length + prevIndex : prevIndex;
},
// 获取后一个索引
getNextIndex() {
const nextIndex = this.activeIndex + 1;
const length = this.length - 1;
if(!this.cycle) {
return nextIndex > length ? length : nextIndex;
}
return nextIndex > length ? nextIndex % this.length : nextIndex;
},
// 是禁止循环
isDisableCycle(nextIndex) {
if(!this.cycle && nextIndex === this.activeIndex) {
return true;
}
return false;
},
// 初始化拖拽
initDrag() {
if(this.length <= 1) {
return;
}
let { swipe } = this.$refs,
translateX,
newIndex,
dragStartTime;
draggable(swipe, {
onDragStart: () => {
// event.preventDefault();
this.onDragStart && this.onDragStart(this.activeIndex);
if(this.transitioning) return;
dragStartTime = new Date();
this.dragging = true;
this.isCycleEnd = false;
clearInterval(this.swipeInterval);
},
onDrag: (option, event) => {
event.preventDefault();
if(this.transitioning) return;
translateX = option.translateX;
this.onDrag && this.onDrag(this.activeIndex, translateX);
if(translateX === 0 || !this.$children[this.activeIndex]) return;
// 往左
if(translateX < 0) {
let nextIndex = this.getNextIndex();
// 禁止循环播放
if(this.isDisableCycle(nextIndex)) {
this.isCycleEnd = true;
return;
}
this.$children[this.activeIndex].swipeToLeft(translateX);
this.$children[nextIndex].swipeToLeft(translateX);
this.negative = false;
newIndex = nextIndex;
} else {
// 往右
let prevIndex = this.getPrevIndex();
// 禁止循环播放
if(this.isDisableCycle(prevIndex)) {
this.isCycleEnd = true;
return;
}
this.$children[this.activeIndex].swipeToRight(translateX);
this.$children[prevIndex].swipeToRight(translateX);
this.negative = true;
newIndex = prevIndex;
}
},
onDragEnd: () => {
// event.preventDefault();
if(this.transitioning) return;
this.dragging = false;
if(!this.isCycleEnd && this.$children[this.activeIndex]) {
let threshold = this.$children[this.activeIndex].width * this.dragThreshold,
rate = Math.abs(translateX) / (new Date() - dragStartTime);
if(Math.abs(translateX) >= threshold || rate > this.dragRate) {
this.activeIndex = newIndex;
} else if(this.negative) {
let prevIndex = this.getPrevIndex();
this.$children[this.activeIndex].swipeToLeft(0);
this.$children[prevIndex].swipeToLeft(0);
} else {
// 往左
let nextIndex = this.getNextIndex();
this.$children[this.activeIndex].swipeToRight(0);
this.$children[nextIndex].swipeToRight(0);
}
this.onDragEnd && this.onDragEnd(this.activeIndex);
}
setTimeout(() => {
this.play();
}, this.interval);
},
});
},
// 自动播放
play() {
if(!this.autoplay) return;
clearInterval(this.swipeInterval);
this.swipeInterval = setInterval(() => {
if(this.dragging) return;
this.activeIndex = this.getNextIndex();
}, this.interval);
},
},
watch: {
activeIndex() {
this.transitioning = true;
let nextItem = this.$children[this.activeIndex],
currentIndex = this.negative ? this.getNextIndex() : this.getPrevIndex(),
currentItem = this.$children[currentIndex];
// 重置()
// this.$children.forEach((child, activeIndex) => {
// if(activeIndex !== currentIndex) {
// child.reset(this.negative);
// }
// });
if(!this.negative) {
currentItem.swipeToLeft();
nextItem.swipeToLeft();
} else {
currentItem.swipeToRight();
nextItem.swipeToRight();
this.negative = false;
}
},
// 异步swipeItem
length() {
if(this.length) {
this.initDrag();
this.play();
}
},
},
mounted() {
this.init();
},
updated() {
this.$nextTick(() => {
this.init();
});
setTimeout(() => {
this.transitioning = false;
}, this.speed);
},
beforeDestroy() {
clearInterval(this.swipeInterval);
},
};
</script>
================================================
FILE: src/scripts/components/SwipeItem.vue
================================================
<template>
<div class="swipe-item"
@webkitTransitionEnd="resetByTranslateX()">
<slot></slot>
</div>
</template>
<script>
import { getTranslate, setTranslate } from '../utils/translate';
import { getPrefixStyle } from '../utils/cssPrefix';
export default {
name: 'swipe-item',
data() {
return {
width: 0,
translate: 0,
};
},
methods: {
// 根据位移重置
resetByTranslateX() {
this.$el.style.transition = '';
let translateX = getTranslate(this.$el).x;
translateX < 0 && setTranslate(this.$el, this.width, 0);
},
// 根据是否active重置
resetByNegative(negative) {
this.$el.style.transition = '';
if(this.index !== this.$parent.activeIndex) {
setTranslate(this.$el, negative ? -this.width : this.width, 0);
}
},
// 初始化
init() {
this.width = this.$el.offsetWidth;
this.index = this.$parent.$children.indexOf(this);
let translate = this.width;
if(this.$parent.defaultIndex === this.index) {
translate = 0;
}
setTranslate(this.$el, translate, 0);
},
// 是否在可编译范围内
isInTheLimitRange(translate, negative = false) {
let min,
max;
// 正序
if(!negative) {
min = 0;
max = this.width;
if(this.index === this.$parent.activeIndex) {
min = -this.width;
max = 0;
}
} else {
min = -this.width;
max = 0;
if(this.index === this.$parent.activeIndex) {
min = 0;
max = this.width;
}
}
return translate >= min && translate <= max;
},
// 向左滑动
swipeToLeft(translateX) {
// 轮播
if(!translateX) {
this.$el.style.transition = getPrefixStyle('transform', `${this.$parent.speed}ms ${this.$parent.easing}`);
let translate = -this.width;
if(this.index === this.$parent.activeIndex) {
translate = 0;
}
setTranslate(this.$el, translate, 0);
return;
}
// 滑动
let initTranslate = this.width;
if(this.index === this.$parent.activeIndex) {
initTranslate = 0;
}
let finalTranslateX = initTranslate + translateX;
if(this.isInTheLimitRange(finalTranslateX)) {
setTranslate(this.$el, finalTranslateX, 0);
}
},
// 向右滑动
swipeToRight(translateX) {
// 轮播
if(!translateX) {
this.$el.style.transition = getPrefixStyle('transform', `${this.$parent.speed}ms ${this.$parent.easing}`);
let translate = this.width;
if(this.index === this.$parent.activeIndex) {
translate = 0;
}
setTranslate(this.$el, translate, 0);
return;
}
// 滑动
let initTranslate = -this.width;
if(this.index === this.$parent.activeIndex) {
initTranslate = 0;
}
let finalTranslateX = initTranslate + translateX;
if(this.isInTheLimitRange(finalTranslateX, true)) {
setTranslate(this.$el, finalTranslateX, 0);
}
},
},
mounted() {
this.init();
},
};
</script>
================================================
FILE: src/scripts/components/Tabbar.vue
================================================
<template>
<div class="tabbar" :class="{disabled}">
<slot></slot>
<div class="tabbar-indicator" ref="indicator"></div>
</div>
</template>
<script>
import Tab from '../mixins/tab';
import { setTranslate } from '../utils/translate';
export default {
name: 'tabbar',
mixins: [Tab],
props: {
activeKey: {
default: 0,
},
},
methods: {
// 设置指示器宽度
setIndicatorWidth() {
this.indicatorWidth = this.$el.offsetWidth / this.$children.length;
this.$refs.indicator.style.width = `${this.indicatorWidth}px`;
},
// 初始化指示器位置
initSelectedIndicator() {
this.$children.forEach((child, index) => {
if(this.currentActiveKey === child.currentEventKey) {
let $indicator = this.$refs.indicator,
translateX = index * this.indicatorWidth;
setTranslate($indicator, translateX, 0);
}
});
},
},
watch: {
currentActiveKey() {
this.initSelectedIndicator();
},
},
mounted() {
this.setIndicatorWidth();
this.initSelectedIndicator();
},
updated() {
this.setIndicatorWidth();
this.initSelectedIndicator();
},
};
</script>
================================================
FILE: src/scripts/components/TabbarItem.vue
================================================
<template>
<div
class="tabbar-item"
:class="{
'active': $parent.currentActiveKey === currentEventKey,
disabled: disabled || $parent.disabled,
}"
@click="clickHandle">
<slot></slot>
</div>
</template>
<script>
import TabItem from '../mixins/tabItem';
export default {
name: 'tabbar-item',
mixins: [TabItem],
};
</script>
================================================
FILE: src/scripts/components/Tag.vue
================================================
<template>
<span
@click="$emit('click')"
class="tag"
:class="{
[`tag-${theme}`]: !hollow,
[`tag-outline-${theme}`]: hollow,
[`tag-${size}`]: size,
[`border-${shape}`]: shape,
}">
<slot></slot>
</span>
</template>
<script>
export default {
name: 'tag',
props: {
// 空心
hollow: Boolean,
// 尺寸
size: {
type: String,
validator(value) {
return ['sm'].indexOf(value) > -1;
},
},
// 主题
theme: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'default', 'success', 'warning', 'danger'].indexOf(value) > -1;
},
},
// 形状
shape: {
type: String,
validator(value) {
return ['pill'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Timeline.vue
================================================
<template>
<div class="timeline">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'timeline',
};
</script>
================================================
FILE: src/scripts/components/TimelineItem.vue
================================================
<template>
<div class="timeline-item" :class="{'active': index === 0}">
<div class="timeline-item-addon"></div>
<div class="timeline-item-line"></div>
<div class="timeline-item-body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'timeline-item',
data() {
return {
index: -1,
};
},
mounted() {
this.index = this.$parent.$children.indexOf(this);
},
};
</script>
================================================
FILE: src/scripts/components/Tip.vue
================================================
<template>
<div class="tip" @click="$emit('click')">
<hrule :type="type" :theme="theme" />
<span class="tip-label" :class="{[`text-${theme}`]: theme}">
<slot></slot>
</span>
<hrule :type="type" :theme="theme" />
</div>
</template>
<script>
export default {
name: 'tip',
props: {
type: {
type: String,
validator(value) {
return ['dashed', 'dotted'].indexOf(value) > -1;
},
},
theme: {
type: String,
validator(value) {
return ['primary', 'secondary', 'warning', 'success', 'danger'].indexOf(value) > -1;
},
},
},
};
</script>
================================================
FILE: src/scripts/components/Toast.vue
================================================
<template>
<div>
<transition name="toast-fade">
<div
v-show="currentValue"
class="toast"
:class="[
`toast-${position}`,
{ 'toast-lg': type }
]">
<div class="toast-icon" v-if="icon">
<icon :name="icon" />
</div>
<loading v-else-if="type === 'loading'" />
<div class="toast-message" v-html="message" />
</div>
</transition>
<mask-layer
:clickable="closeOnClickMask"
class="bg-transparent"
v-if="!!type"/>
</div>
</template>
<script>
export default {
name: 'toast',
props: {
message: String,
position: {
type: String,
default: 'bottom',
validator(value) {
return ['bottom', 'top', 'center'].indexOf(value) > -1;
},
},
type: {
type: String,
validator(value) {
return ['success', 'error', 'warning', 'loading'].indexOf(value) > -1;
},
},
closeOnClickMask: {
type: Boolean,
default: false,
},
},
data() {
return {
currentValue: false,
};
},
computed: {
icon() {
switch(this.type) {
case 'error':
return 'exclamation-circle';
case 'success':
return 'check';
case 'warning':
return 'exclamation-triangle';
default:
return null;
}
},
},
};
</script>
================================================
FILE: src/scripts/components/Toggle.vue
================================================
<template>
<label class="toggle">
<input
type="checkbox"
class="toggle-input"
:disabled="disabled"
v-model="currentValue">
<span class="toggle-addon">
<i />
</span>
</label>
</template>
<script>
import Sync from '../mixins/sync';
export default {
name: 'toggle',
mixins: [Sync],
};
</script>
================================================
FILE: src/scripts/components/index.js
================================================
import Button from './Button';
import Icon from './Icon';
import Group from './Group';
import GroupTitle from './GroupTitle';
import Cell from './Cell';
import Flex from './Flex';
import FlexItem from './FlexItem';
import Navbar from './Navbar';
import Tabbar from './Tabbar';
import TabbarItem from './TabbarItem';
import Mask from './Mask';
import Loading from './Loading';
import Checkbox from './Checkbox';
import CheckboxGroup from './CheckboxGroup';
import Radio from './Radio';
import RadioGroup from './RadioGroup';
import Toggle from './Toggle';
import InputNumber from './InputNumber';
import InputText from './InputText';
import InputTextarea from './InputTextarea';
import Selector from './Selector';
import SelectorOption from './SelectorOption';
import InlineSelector from './InlineSelector';
import InlineSelectorOption from './InlineSelectorOption';
import Navigation from './Navigation';
import NavigationItem from './NavigationItem';
import Tag from './Tag';
import Tip from './Tip';
import HRule from './HRule';
import BackToTop from './BackToTop';
import Badge from './Badge';
import Drawer from './Drawer';
import DrawerItem from './DrawerItem';
import SlideUp from './SlideUp';
import SlideUpHeader from './SlideUpHeader';
import SlideUpBody from './SlideUpBody';
import SegmentedControl from './SegmentedControl';
import SegmentedControlItem from './SegmentedControlItem';
import Sidelip from './Sidelip';
import Media from './Media';
import MediaObject from './MediaObject';
import MediaBody from './MediaBody';
import Card from './Card';
import CardBody from './CardBody';
import DatePicker from './DatePicker';
import Searchbar from './Searchbar';
import SearchbarBtn from './SearchbarBtn';
import SearchbarPlaceholder from './SearchbarPlaceholder';
import Picker from './Picker';
import PickerOption from './PickerOption';
import Loadmore from './Loadmore';
import Alert from './Alert';
import Swipe from './Swipe';
import SwipeItem from './SwipeItem';
import Progressbar from './Progressbar';
import Progressbars from './Progressbars';
import Stepbar from './Stepbar';
import StepbarItem from './StepbarItem';
import Timeline from './Timeline';
import TimelineItem from './TimelineItem';
import Sticky from './Sticky';
// global utils
import toast from '../utils/toast';
import { alert } from '../utils/alert';
import loading from '../utils/loading';
// directives
import disfavor from '../directives/disfavor';
const impression = {
Button,
Group,
GroupTitle,
Cell,
Flex,
FlexItem,
Icon,
Tag,
Tip,
HRule,
Badge,
Media,
MediaObject,
MediaBody,
Card,
CardBody,
Swipe,
SwipeItem,
Navbar,
Tabbar,
TabbarItem,
Navigation,
NavigationItem,
Drawer,
DrawerItem,
SegmentedControl,
SegmentedControlItem,
SlideUp,
SlideUpHeader,
SlideUpBody,
Sidelip,
Searchbar,
SearchbarBtn,
SearchbarPlaceholder,
Picker,
PickerOption,
Loadmore,
Mask,
Alert,
Loading,
BackToTop,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Toggle,
InputNumber,
InputText,
InputTextarea,
Selector,
SelectorOption,
InlineSelector,
InlineSelectorOption,
DatePicker,
Progressbar,
Progressbars,
Stepbar,
StepbarItem,
Timeline,
TimelineItem,
Sticky,
};
const install = Vue => {
if(install.installed) return;
// components
Object.keys(impression).forEach(key => {
Vue.component(impression[key].name, impression[key]);
});
// global component utils
Vue.$toast = Vue.prototype.$toast = toast;
Vue.$alert = Vue.prototype.$alert = alert;
Vue.$loading = Vue.prototype.$loading = loading;
// directives
Vue.directive('disfavor', disfavor);
};
if(typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export {
Button,
Group,
GroupTitle,
Cell,
Flex,
FlexItem,
Icon,
Tag,
Tip,
HRule,
Badge,
Media,
MediaObject,
MediaBody,
Card,
CardBody,
Swipe,
SwipeItem,
Navbar,
Tabbar,
TabbarItem,
Navigation,
NavigationItem,
Drawer,
DrawerItem,
SegmentedControl,
SegmentedControlItem,
SlideUp,
SlideUpHeader,
SlideUpBody,
Sidelip,
Searchbar,
SearchbarBtn,
SearchbarPlaceholder,
Picker,
PickerOption,
Loadmore,
Mask,
Alert,
Loading,
BackToTop,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Toggle,
InputNumber,
InputText,
InputTextarea,
Selector,
SelectorOption,
InlineSelector,
InlineSelectorOption,
DatePicker,
Progressbar,
Stepbar,
StepbarItem,
Timeline,
TimelineItem,
Sticky,
};
export default {
install,
...impression,
};
================================================
FILE: src/scripts/containers/layout.vue
================================================
<template>
<flex direction="column">
<navbar theme="default">
<icon name="bars" size="lg" @click="showMenubar = true" />
<h3 slot="body">{{$route.name || 'Impression'}}</h3>
</navbar>
<flex-item>
<router-view></router-view>
</flex-item>
<menubar v-model="showMenubar" />
</flex>
</template>
<script>
import menubar from './menubar';
export default {
components: {
menubar,
},
data() {
return {
showMenubar: false,
};
},
};
</script>
================================================
FILE: src/scripts/containers/menubar.vue
================================================
<template>
<sidelip v-model="currentValue">
<flex direction="column" :style="{backgroundColor: '#f0eff5'}">
<navbar theme="primary">
<h3>Impression</h3>
</navbar>
<flex-item>
<div v-for="group in groups">
<group-title v-if="group.title"><strong>{{group.title}}</strong></group-title>
<group>
<cell
v-for="cell in group.children"
:to="{path: cell.path}">
<icon size="lg" left :name="cell.icon"></icon>
{{cell.name}}
</cell>
</group>
</div>
</flex-item>
<group>
<cell>
NewDadaFE©️所有
</cell>
</group>
</flex>
</sidelip>
</template>
<script>
import routesConfig from '../routes.json';
export default {
props: {
value: {},
disabled: Boolean,
},
data() {
return {
currentValue: this.value,
groups: routesConfig,
};
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
this.$emit('input', val);
},
$route() {
this.currentValue = false;
},
},
};
</script>
================================================
FILE: src/scripts/directives/disfavor.js
================================================
import { contains } from '../utils/dom';
const disfavorContext = '@@disfavor';
// 失去焦点
export default {
bind(el, binding, vnode) {
const clickHandle = event => {
if(vnode.context && !contains(el, event.target)) {
el[disfavorContext].callback && vnode.context[el[disfavorContext].callback]();
}
};
el[disfavorContext] = {
clickHandle,
callback: binding.expression,
};
document.addEventListener('click', clickHandle);
},
unbind(el) {
let { clickHandle } = el[disfavorContext];
document.removeEventListener('click', clickHandle);
},
};
================================================
FILE: src/scripts/index.js
================================================
import Vue from 'vue';
import router from './router';
import Impression from './components';
Vue.use(Impression);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<router-view></router-view>',
});
================================================
FILE: src/scripts/mixins/emitter.js
================================================
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root,
name = parent.$options._componentTag;
while(parent && (!name || name !== componentName)) {
parent = parent.$parent;
if(parent) {
name = parent.$options._componentTag;
}
}
if(parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
},
};
================================================
FILE: src/scripts/mixins/select.js
================================================
export default {
props: {
multiple: Boolean,
},
data() {
return {
currentText: {},
};
},
methods: {
optionClickHandle(option) {
if(this.disabled || this.currentValue === option.value) return;
if(this.multiple) {
this.currentValue = this.currentValue || [];
let index = this.currentValue.indexOf(option.value);
if(index === -1) {
this.currentValue.push(option.value);
} else {
this.currentValue.splice(index, 1);
}
return;
}
this.currentText = option.$el.innerText.trim();
this.currentValue = option.value;
},
},
created() {
this.$on('optionClick', this.optionClickHandle);
},
};
================================================
FILE: src/scripts/mixins/selectOption.js
================================================
export default {
props: {
value: {},
checkedIcon: {
type: String,
default: 'check',
},
disabled: Boolean,
},
methods: {
clickHandle() {
if(this.disabled) return;
this.$parent.$emit.apply(this.$parent, ['optionClick'].concat(this));
},
isActive() {
if(this.$parent.multiple) {
return this.$parent.currentValue &&
this.$parent.currentValue.indexOf(this.value) !== -1;
}
return this.$parent.currentValue === this.value;
},
},
};
================================================
FILE: src/scripts/mixins/sync.js
================================================
import { isArray } from '../utils/type';
export default {
props: {
value: {},
disabled: Boolean,
},
data() {
return {
currentValue: this.value,
};
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
if(this.disabled) return;
if(isArray(val) && val.length === 0) {
this.currentValue = undefined;
return;
}
this.$emit('input', val);
this.$emit('change', val, this.currentText);
},
},
};
================================================
FILE: src/scripts/mixins/tab.js
================================================
export default {
props: {
activeKey: {},
disabled: Boolean,
},
data() {
return {
currentActiveKey: this.activeKey,
};
},
methods: {
itemClickHandle(val) {
if(this.currentActiveKey === val) return;
this.currentActiveKey = val;
this.$emit('change', val);
},
},
created() {
this.$on('itemClick', this.itemClickHandle);
},
};
================================================
FILE: src/scripts/mixins/tabItem.js
================================================
export default {
props: {
eventKey: {},
disabled: Boolean,
},
data() {
return {
currentEventKey: this.eventKey,
index: 0,
};
},
mounted() {
this.index = this.$parent.$children.indexOf(this);
this.eventKey === undefined && (this.currentEventKey = this.index);
},
methods: {
clickHandle() {
if(this.disabled || this.$parent.disabled) return;
this.$parent.$emit('itemClick', this.currentEventKey, this.index);
},
},
};
================================================
FILE: src/scripts/router.js
================================================
import Vue from 'vue';
import VueRouter from 'vue-router';
import routesConfig from './routes.json';
Vue.use(VueRouter);
// 提取路由
const extractRoutes = config => {
const routes = [],
children = [];
routes.push({
path: '/',
component: require('./containers/layout'),
children,
});
config.forEach(group => {
group.children.forEach(item => {
const { path, name } = item;
children.push({
path,
name,
component: require(`./views${path}`),
});
});
});
return routes;
};
const routes = extractRoutes(routesConfig);
// 创建router对象
const router = new VueRouter({
base: __dirname,
routes,
});
export default router;
================================================
FILE: src/scripts/routes.json
================================================
[
{
"title": "Base",
"children": [
{
"path": "/button",
"name": "Button",
"icon": "hand-pointer-o"
},
{
"path": "/icon",
"name": "Icon",
"icon": "flag"
},
{
"path": "/cell",
"name": "Group && Cell",
"icon": "list"
},
{
"path": "/flex",
"name": "Flex && FlexItem",
"icon": "th"
},
{
"path": "/tag",
"name": "Tag",
"icon": "tag"
},
{
"path": "/badge",
"name": "Badge",
"icon": "bell-o"
},
{
"path": "/hrule",
"name": "HRule(Hr)",
"icon": "minus"
},
{
"path": "/tip",
"name": "Tip",
"icon": "paperclip"
},
{
"path": "/card",
"name": "Card",
"icon": "file-image-o"
},
{
"path": "/media",
"name": "Media",
"icon": "id-card-o"
},
{
"path": "/swipe",
"name": "Swipe",
"icon": "newspaper-o"
},
{
"path": "/datepicker",
"name": "DatePicker",
"icon": "newspaper-o"
}
]
},
{
"title": "Layout",
"children": [
{
"path": "/navbar",
"name": "Navbar",
"icon": "window-maximize"
},
{
"path": "/tabbar",
"name": "Tabbar",
"icon": "clone"
},
{
"path": "/navigation",
"name": "Navigation",
"icon": "tablet"
},
{
"path": "/drawer",
"name": "Drawer",
"icon": "caret-square-o-down"
},
{
"path": "/segmented-control",
"name": "SegmentedControl",
"icon": "columns"
},
{
"path": "/slideup",
"name": "SlideUp",
"icon": "angle-double-up"
},
{
"path": "/sideslip",
"name": "Sideslip",
"icon": "window-restore"
},
{
"path": "/searchbar",
"name": "Searchbar",
"icon": "search"
},
{
"path": "/stepbar",
"name": "Stepbar",
"icon": "flag-checkered"
},
{
"path": "/timeline",
"name": "Timeline",
"icon": "flag-o"
},
{
"path": "/picker",
"name": "Picker",
"icon": "building-o"
},
{
"path": "/pull-down",
"name": "Pull down",
"icon": "long-arrow-down"
},
{
"path": "/pull-up",
"name": "Pull up",
"icon": "long-arrow-up"
},
{
"path": "/sticky",
"name": "Sticky Affix",
"icon": "thumb-tack"
}
]
},
{
"title": "prompt",
"children": [
{
"path": "/toast",
"name": "Toast",
"icon": "commenting-o"
},
{
"path": "/alert",
"name": "Alert",
"icon": "bullhorn"
},
{
"path": "/loading",
"name": "loading",
"icon": "spinner"
},
{
"path": "/progressbar",
"name": "Progressbar",
"icon": "battery-three-quarters"
},
{
"path": "/back-to-top",
"name": "BackToTop",
"icon": "arrow-circle-up"
}
]
},
{
"title": "form",
"children": [
{
"path": "/checkbox",
"name": "Checkbox",
"icon": "check-square"
},
{
"path": "/radio",
"name": "Radio",
"icon": "dot-circle-o"
},
{
"path": "/toggle",
"name": "Toggle(Switch)",
"icon": "toggle-on"
},
{
"path": "/input-text",
"name": "InputText",
"icon": "font"
},
{
"path": "/input-number",
"name": "InputNumber",
"icon": "plus-circle"
},
{
"path": "/input-textarea",
"name": "InputTextarea",
"icon": "file-text-o"
},
{
"path": "/selector",
"name": "Selector",
"icon": "check"
},
{
"path": "/inline-selecor",
"name": "InlineSelector",
"icon": "th-large"
}
]
}
]
================================================
FILE: src/scripts/utils/alert.js
================================================
import Vue from 'vue';
import OriginAlert from '../components/Alert';
const Alert = Vue.extend(OriginAlert);
let alertInstance;
// Alert框
export const alert = option => {
option.title = option.title || '提示';
alertInstance = new Alert({
el: document.createElement('div'),
});
document.body.appendChild(alertInstance.$el);
Object.assign(alertInstance, option);
Vue.nextTick(() => {
alertInstance.show();
});
return alertInstance;
};
alert.hide = () => {
alertInstance.hide();
};
================================================
FILE: src/scripts/utils/cssPrefix.js
================================================
let engine;
let docStyle = document.documentElement.style;
if('MozAppearance' in docStyle) {
engine = 'gecko';
} else if('WebkitAppearance' in docStyle) {
engine = 'webkit';
} else if(typeof navigator.cpuClass === 'string') {
engine = 'trident';
}
export const vendorPrefix = { trident: 'ms', gecko: 'Moz', webkit: 'Webkit' }[engine];
export const cssPrefix = { trident: '-ms-', gecko: '-moz-', webkit: '-webkit-', presto: '-o-' }[engine];
export const getPrefixStyle = (name, value) => {
let namePrefix = `${cssPrefix}${name}`;
return `${namePrefix} ${value}`;
};
================================================
FILE: src/scripts/utils/date.js
================================================
/*eslint-disable*/
import dateUtil from './dateUtil';
const weeks = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const newArray = function(start, end) {
let result = [];
for (let i = start; i <= end; i++) {
result.push(i);
}
return result;
};
export const toDate = function(date) {
return isDate(date) ? new Date(date) : null;
};
export const isDate = function(date) {
if(date === null || date === undefined) return false;
if(isNaN(new Date(date).getTime())) return false;
if(Array.isArray(date)) return false; // deal with `new Date([ new Date() ]) -> new Date()`
return true;
};
export const isDateObject = function(val) {
return val instanceof Date;
};
export const formatDate = function(date, format) {
date = toDate(date);
if(!date) return '';
return dateUtil.format(date, format || 'yyyy-MM-dd');
};
export const parseDate = function(string, format) {
return dateUtil.parse(string, format || 'yyyy-MM-dd');
};
export const getDayCountOfMonth = function(year, month) {
if(month === 3 || month === 5 || month === 8 || month === 10) {
return 30;
}
if(month === 1) {
if(year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
return 29;
} else {
return 28;
}
}
return 31;
};
export const getDayCountOfYear = function(year) {
const isLeapYear = year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
return isLeapYear ? 366 : 365;
};
export const getFirstDayOfMonth = function(date) {
const temp = new Date(date.getTime());
temp.setDate(1);
return temp.getDay();
};
// see: https://stackoverflow.com/questions/3674539/incrementing-a-date-in-javascript
// {prev, next} Date should work for Daylight Saving Time
// Adding 24 * 60 * 60 * 1000 does not work in the above scenario
export const prevDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - amount);
};
export const nextDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
};
export const getStartDateOfMonth = function(year, month) {
const result = new Date(year, month, 1);
const day = result.getDay();
if(day === 0) {
return prevDate(result, 7);
} else {
return prevDate(result, day);
}
};
export const getWeekNumber = function(src) {
if(!isDate(src)) return null;
const date = new Date(src.getTime());
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
// January 4 is always in week 1.
const week1 = new Date(date.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week 1.
// Rounding should be fine for Daylight Saving Time. Its shift should never be more than 12 hours.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
};
export const getRangeHours = function(ranges) {
const hours = [];
let disabledHours = [];
(ranges || []).forEach(range => {
const value = range.map(date => date.getHours());
disabledHours = disabledHours.concat(newArray(value[0], value[1]));
});
if (disabledHours.length) {
for (let i = 0; i < 24; i++) {
hours[i] = disabledHours.indexOf(i) === -1;
}
} else {
for (let i = 0; i < 24; i++) {
hours[i] = false;
}
}
return hours;
};
export const range = function(n) {
// see https://stackoverflow.com/questions/3746725/create-a-javascript-array-containing-1-n
return Array.apply(null, {length: n}).map((_, n) => n);
};
export const modifyDate = function(date, y, m, d) {
return new Date(y, m, d, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
};
export const modifyTime = function(date, h, m, s) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, m, s, date.getMilliseconds());
};
export const modifyWithTimeString = (date, time) => {
if(date == null || !time) {
return date;
}
time = parseDate(time, 'HH:mm:ss');
return modifyTime(date, time.getHours(), time.getMinutes(), time.getSeconds());
};
export const clearTime = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
};
export const clearMilliseconds = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
};
export const limitTimeRange = function(date, ranges, format = 'HH:mm:ss') {
// TODO: refactory a more elegant solution
if(ranges.length === 0) return date;
const normalizeDate = date => dateUtil.parse(dateUtil.format(date, format), format);
const ndate = normalizeDate(date);
const nranges = ranges.map(range => range.map(normalizeDate));
if(nranges.some(nrange => ndate >= nrange[0] && ndate <= nrange[1])) return date;
let minDate = nranges[0][0];
let maxDate = nranges[0][0];
nranges.forEach(nrange => {
minDate = new Date(Math.min(nrange[0], minDate));
maxDate = new Date(Math.max(nrange[1], minDate));
});
const ret = ndate < minDate ? minDate : maxDate;
// preserve Year/Month/Date
return modifyDate(
ret,
date.getFullYear(),
date.getMonth(),
date.getDate()
);
};
export const timeWithinRange = function(date, selectableRange, format) {
const limitedDate = limitTimeRange(date, selectableRange, format);
return limitedDate.getTime() === date.getTime();
};
export const changeYearMonthAndClampDate = function(date, year, month) {
// clamp date to the number of days in `year`, `month`
// eg: (2010-1-31, 2010, 2) => 2010-2-28
const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
export const prevMonth = function(date) {
const year = date.getFullYear();
const month = date.getMonth();
return month === 0
? changeYearMonthAndClampDate(date, year - 1, 11)
: changeYearMonthAndClampDate(date, year, month - 1);
};
export const nextMonth = function(date) {
const year = date.getFullYear();
const month = date.getMonth();
return month === 11
? changeYearMonthAndClampDate(date, year + 1, 0)
: changeYearMonthAndClampDate(date, year, month + 1);
};
export const prevYear = function(date, amount = 1) {
const year = date.getFullYear();
const month = date.getMonth();
return changeYearMonthAndClampDate(date, year - amount, month);
};
export const nextYear = function(date, amount = 1) {
const year = date.getFullYear();
const month = date.getMonth();
return changeYearMonthAndClampDate(date, year + amount, month);
};
export const extractDateFormat = function(format) {
return format
.replace(/\W?m{1,2}|\W?ZZ/g, '')
.replace(/\W?h{1,2}|\W?s{1,3}|\W?a/gi, '')
.trim();
};
export const extractTimeFormat = function(format) {
return format
.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?y{2,4}/g, '')
.trim();
};
================================================
FILE: src/scripts/utils/dateUtil.js
================================================
/* Modified from https://github.com/taylorhakes/fecha
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Taylor Hakes
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*eslint-disable*/
// 把 YYYY-MM-DD 改成了 yyyy-MM-dd
(function (main) {
'use strict';
/**
* Parse or format dates
* @class fecha
*/
var fecha = {};
var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
var twoDigits = /\d\d?/;
var threeDigits = /\d{3}/;
var fourDigits = /\d{4}/;
var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
var noop = function () {
};
function shorten(arr, sLen) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; i++) {
newArr.push(arr[i].substr(0, sLen));
}
return newArr;
}
function monthUpdate(arrName) {
return function (d, v, i18n) {
var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase());
if (~index) {
d.month = index;
}
};
}
function pad(val, len) {
val = String(val);
len = len || 2;
while (val.length < len) {
val = '0' + val;
}
return val;
}
var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var monthNamesShort = shorten(monthNames, 3);
var dayNamesShort = shorten(dayNames, 3);
fecha.i18n = {
dayNamesShort: dayNamesShort,
dayNames: dayNames,
monthNamesShort: monthNamesShort,
monthNames: monthNames,
amPm: ['am', 'pm'],
DoFn: function DoFn(D) {
return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
}
};
var formatFlags = {
D: function(dateObj) {
return dateObj.getDay();
},
DD: function(dateObj) {
return pad(dateObj.getDay());
},
Do: function(dateObj, i18n) {
return i18n.DoFn(dateObj.getDate());
},
d: function(dateObj) {
return dateObj.getDate();
},
dd: function(dateObj) {
return pad(dateObj.getDate());
},
ddd: function(dateObj, i18n) {
return i18n.dayNamesShort[dateObj.getDay()];
},
dddd: function(dateObj, i18n) {
return i18n.dayNames[dateObj.getDay()];
},
M: function(dateObj) {
return dateObj.getMonth() + 1;
},
MM: function(dateObj) {
return pad(dateObj.getMonth() + 1);
},
MMM: function(dateObj, i18n) {
return i18n.monthNamesShort[dateObj.getMonth()];
},
MMMM: function(dateObj, i18n) {
return i18n.monthNames[dateObj.getMonth()];
},
yy: function(dateObj) {
return String(dateObj.getFullYear()).substr(2);
},
yyyy: function(dateObj) {
return dateObj.getFullYear();
},
h: function(dateObj) {
return dateObj.getHours() % 12 || 12;
},
hh: function(dateObj) {
return pad(dateObj.getHours() % 12 || 12);
},
H: function(dateObj) {
return dateObj.getHours();
},
HH: function(dateObj) {
return pad(dateObj.getHours());
},
m: function(dateObj) {
return dateObj.getMinutes();
},
mm: function(dateObj) {
return pad(dateObj.getMinutes());
},
s: function(dateObj) {
return dateObj.getSeconds();
},
ss: function(dateObj) {
return pad(dateObj.getSeconds());
},
S: function(dateObj) {
return Math.round(dateObj.getMilliseconds() / 100);
},
SS: function(dateObj) {
return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
},
SSS: function(dateObj) {
return pad(dateObj.getMilliseconds(), 3);
},
a: function(dateObj, i18n) {
return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
},
A: function(dateObj, i18n) {
return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
},
ZZ: function(dateObj) {
var o = dateObj.getTimezoneOffset();
return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
}
};
var parseFlags = {
d: [twoDigits, function (d, v) {
d.day = v;
}],
M: [twoDigits, function (d, v) {
d.month = v - 1;
}],
yy: [twoDigits, function (d, v) {
var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
d.year = '' + (v > 68 ? cent - 1 : cent) + v;
}],
h: [twoDigits, function (d, v) {
d.hour = v;
}],
m: [twoDigits, function (d, v) {
d.minute = v;
}],
s: [twoDigits, function (d, v) {
d.second = v;
}],
yyyy: [fourDigits, function (d, v) {
d.year = v;
}],
S: [/\d/, function (d, v) {
d.millisecond = v * 100;
}],
SS: [/\d{2}/, function (d, v) {
d.millisecond = v * 10;
}],
SSS: [threeDigits, function (d, v) {
d.millisecond = v;
}],
D: [twoDigits, noop],
ddd: [word, noop],
MMM: [word, monthUpdate('monthNamesShort')],
MMMM: [word, monthUpdate('monthNames')],
a: [word, function (d, v, i18n) {
var val = v.toLowerCase();
if (val === i18n.amPm[0]) {
d.isPm = false;
} else if (val === i18n.amPm[1]) {
d.isPm = true;
}
}],
ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) {
var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
if (parts) {
minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
}
}]
};
parseFlags.DD = parseFlags.D;
parseFlags.dddd = parseFlags.ddd;
parseFlags.Do = parseFlags.dd = parseFlags.d;
parseFlags.mm = parseFlags.m;
parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
parseFlags.MM = parseFlags.M;
parseFlags.ss = parseFlags.s;
parseFlags.A = parseFlags.a;
// Some common format strings
fecha.masks = {
'default': 'ddd MMM dd yyyy HH:mm:ss',
shortDate: 'M/D/yy',
mediumDate: 'MMM d, yyyy',
longDate: 'MMMM d, yyyy',
fullDate: 'dddd, MMMM d, yyyy',
shortTime: 'HH:mm',
mediumTime: 'HH:mm:ss',
longTime: 'HH:mm:ss.SSS'
};
/***
* Format a date
* @method format
* @param {Date|number} dateObj
* @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
*/
fecha.format = function (dateObj, mask, i18nSettings) {
var i18n = i18nSettings || fecha.i18n;
if (typeof dateObj === 'number') {
dateObj = new Date(dateObj);
}
if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
throw new Error('Invalid Date in fecha.format');
}
mask = fecha.masks[mask] || mask || fecha.masks['default'];
return mask.replace(token, function ($0) {
return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
});
};
/**
* Parse a date string into an object, changes - into /
* @method parse
* @param {string} dateStr Date string
* @param {string} format Date parse format
* @returns {Date|boolean}
*/
fecha.parse = function (dateStr, format, i18nSettings) {
var i18n = i18nSettings || fecha.i18n;
if (typeof format !== 'string') {
throw new Error('Invalid format in fecha.parse');
}
format = fecha.masks[format] || format;
// Avoid regular expression denial of service, fail early for really long strings
// https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
if (dateStr.length > 1000) {
return false;
}
var isValid = true;
var dateInfo = {};
format.replace(token, function ($0) {
if (parseFlags[$0]) {
var info = parseFlags[$0];
var index = dateStr.search(info[0]);
if (!~index) {
isValid = false;
} else {
dateStr.replace(info[0], function (result) {
info[1](dateInfo, result, i18n);
dateStr = dateStr.substr(index + result.length);
return result;
});
}
}
return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});
if (!isValid) {
return false;
}
var today = new Date();
if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
dateInfo.hour = +dateInfo.hour + 12;
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0;
}
var date;
if (dateInfo.timezoneOffset != null) {
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
} else {
date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
}
return date;
};
/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) {
module.exports = fecha;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return fecha;
});
} else {
main.fecha = fecha;
}
})(this);
================================================
FILE: src/scripts/utils/dom.js
================================================
/**
* 判断一个元素是否为另一个的后代元素.
* @param {[Element]} ancestor [祖先元素]
* @param {[Element]} descendent [后代元素]
* @return {[Boolean]} [是否]
*/
export const contains = (ancestor, descendent) => {
if(ancestor.compareDocumentPosition) {
return ancestor === descendent || !!(ancestor.compareDocumentPosition(descendent) & 16);
}
if(ancestor.contains && descendent.nodeType === 1) {
return ancestor.contains(descendent) && ancestor !== descendent;
}
let tmpDescendent = descendent;
// 递归
while(tmpDescendent !== document) {
tmpDescendent = tmpDescendent.parentNode;
if(tmpDescendent === ancestor) return true;
}
return false;
};
/**
* 判断元素是否有滚动条.
* @param {[Element]} el [元素]
* @return {[Boolean]} [是否有滚动条]
*/
export const hasScrollbar = el => {
if(!el) return false;
return el.scrollHeight > el.offsetHeight;
};
/**
* 返回具有滚动条的祖先元素.
* @param {[Element]} el [Dom元素]
* @return {[Element]} [祖先元素]
*/
export const getScrollContainer = el => {
let tmpEl = el;
while(tmpEl !== document) {
tmpEl = tmpEl.parentNode;
if(hasScrollbar(tmpEl)) return tmpEl;
}
return document;
};
/**
* 返回布尔值.
* @param {[Element]} el [Dom元素]
* @param {[class]} cls [样式]
* @return {[Boolean]} [是否有class]
*/
export const hasClass = (el, cls) => {
if(!el || !cls) return false;
if(cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
if(el.classList) {
return el.classList.contains(cls);
}
/*eslint-disable*/
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
/*eslint-disable*/
};
================================================
FILE: src/scripts/utils/draggable.js
================================================
import { getTranslate } from './translate';
/* global document:true */
const draggable = (el, options) => {
let prevTranslateX,
prevTranslateY,
dragState = {
dragging: false,
effectEl: options.effectEl || el,
};
// start
const onTouchStartHandle = (event, sourceEvent) => {
if(dragState.dragging) return;
let translate = getTranslate(dragState.effectEl);
Object.assign(dragState, {
startTimestamp: new Date(),
pageX: event.pageX,
pageY: event.pageY,
translateX: translate.x,
translateY: translate.y,
});
document.onselectstart = () => false;
document.ondragstart = () => false;
if(options.onDragStart) options.onDragStart(dragState, sourceEvent);
};
// move
const onTouchMoveHandle = (event, sourceEvent) => {
let deltaX = event.pageX - dragState.pageX,
deltaY = event.pageY - dragState.pageY,
translateX = dragState.translateX + deltaX,
translateY = dragState.translateY + deltaY,
velocityTranslateX = translateX - prevTranslateX || translateX,
velocityTranslateY = translateY - prevTranslateY || translateY;
prevTranslateX = translateX;
prevTranslateY = translateY;
Object.assign(dragState, {
dragging: true,
velocityTranslateX,
velocityTranslateY,
});
if(options.onDrag) {
options.onDrag({
...dragState,
deltaX,
deltaY,
translateX,
translateY,
}, sourceEvent);
}
};
// end
const onTouchEndHandle = (event, sourceEvent) => {
Object.assign(dragState, {
dragging: false,
});
document.onselectstart = null;
document.ondragstart = null;
if(options.onDragEnd) options.onDragEnd(dragState, sourceEvent);
// reset
dragState = {
dragging: false,
effectEl: options.effectEl || el,
};
};
el.addEventListener('touchstart', event => onTouchStartHandle(event.changedTouches[0] || event.touches[0], event));
el.addEventListener('touchmove', event => onTouchMoveHandle(event.changedTouches[0] || event.touches[0], event));
el.addEventListener('touchend', event => onTouchEndHandle(event.changedTouches[0] || event.touches[0], event));
el.addEventListener('touchcancel', event => onTouchEndHandle(event.changedTouches[0] || event.touches[0], event));
};
export default draggable;
================================================
FILE: src/scripts/utils/easing.js
================================================
// easeInOut
export const easeInOutCubic = (time, offset, end, duration) => {
let cc = end - offset,
tempTime = time / (duration / 2);
if(tempTime < 1) {
return (cc / 2 * tempTime * tempTime * tempTime) + offset;
}
return (cc / 2 * (((tempTime -= 2) * tempTime * tempTime) + 2)) + offset;
};
================================================
FILE: src/scripts/utils/loading.js
================================================
import Vue from 'vue';
import OriginToast from '../components/Toast';
const Toast = Vue.extend(OriginToast);
let instance,
active = false;
export default {
show(message = '加载中') {
if(active) return;
/* global document:true */
if(!instance) {
instance = new Toast({
el: document.createElement('div'),
});
document.body.appendChild(instance.$el);
}
active = true;
instance.message = message;
instance.type = 'loading';
instance.position = 'center';
Vue.nextTick(() => {
instance.show();
});
},
hide() {
instance.hide();
active = false;
},
toggle(message) {
return active ? this.hide() : this.show(message);
},
};
================================================
FILE: src/scripts/utils/toast.js
================================================
import Vue from 'vue';
import OriginToast from '../components/Toast';
const Toast = Vue.extend(OriginToast);
// toast缓存池
const toastCache = {
cache: [],
active: false,
pop() {
if(this.cache.length) return this.cache.splice(0, 1)[0];
return new Toast({
el: document.createElement('div'),
});
},
push(instance) {
this.cache.push(instance);
},
toggle() {
this.active = !this.active;
},
};
Toast.prototype.show = function() {
this.currentValue = true;
toastCache.active = true;
};
Toast.prototype.hide = function() {
this.currentValue = false;
toastCache.active = false;
};
/* global document:true */
const toastUtil = (options = {}) => {
if(toastCache.active) return;
let duration = options.duration || 2000,
instance = toastCache.pop();
instance.message = typeof options === 'string' ? options : options.message;
instance.type = options.type || '';
instance.position = options.position || 'bottom';
document.body.appendChild(instance.$el);
instance.show();
instance.timer = setTimeout(() => {
instance.hide();
toastCache.push(instance);
}, duration);
};
export default toastUtil;
================================================
FILE: src/scripts/utils/translate.js
================================================
import { vendorPrefix } from './cssPrefix';
let transformProperty = `${vendorPrefix}Transform`;
// 获取位移
export const getTranslate = el => {
let result = {
x: 0,
y: 0,
};
if(el === null || el.style === null) return result;
let transform = el.style[transformProperty];
let matches = /translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*(translateZ\(0px\))?/g.exec(transform);
if(matches) {
result.x = +matches[1];
result.y = +matches[3];
}
return result;
};
// 取消位移
export const cancelTranslate = el => {
if(el === null || el.style === null) return;
let transform = el.style[transformProperty];
if(transform) {
transform = transform.replace(/translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*(translateZ\(0px\))?/g, '');
el.style[transformProperty] = transform;
}
};
// 设置位移
export const setTranslate = (el, x, y) => {
if(!el) return;
if(x === null && y === null) return;
// if(!el.style.transform && !x && !y) return;
let translate = getTranslate(el),
currentX = x,
currentY = y;
if(x === null) currentX = translate.x;
if(y === null) currentY = translate.y;
cancelTranslate(el);
el.style[transformProperty] += ` translate(${currentX ? `${currentX}px` : '0px'}, ${currentY ? `${currentY}px` : '0px'})`;
};
================================================
FILE: src/scripts/utils/type.js
================================================
// 是否数组
export const isArray = array => Object.prototype.toString.call(array) === '[object Array]';
================================================
FILE: src/scripts/views/alert.vue
================================================
<template>
<div>
<group-title>base</group-title>
<group>
<cell @click="alertClickHandle">
<icon class="text-muted" name="exclamation-triangle" left size="lg" />
alert
</cell>
<cell @click="confirmClickHandle">
<icon class="text-muted" name="question-circle-o" left size="lg" />
confirm
</cell>
</group>
<group-title>vertical buttons</group-title>
<group>
<cell @click="confirmVerticalClickHandle">
<icon class="text-muted" name="question-circle-o" left size="lg" />
confirm
</cell>
</group>
</div>
</template>
<script>
export default {
methods: {
// alert
alertClickHandle() {
this.$alert({
maskClosable: true,
btns: [{
text: '关闭',
click() {
console.log('close!!!');
},
}],
title: '<span class="text-default">提示</span>',
message: '这是Vue 2.0组件库!',
});
},
// confirm
confirmClickHandle() {
this.$alert({
btns: [{
text: '确定',
click() {
console.log('sure!!!');
},
}, {
text: '取消',
click() {
console.log('cancel!!!');
},
}],
title: '<span class="text-success">提示</span>',
message: '这是Vue 2.0组件库!',
});
},
// confirm
confirmVerticalClickHandle() {
this.$alert({
vertical: true,
btns: [{
text: '验证身份',
click() {
console.log('check!!!');
},
}, {
text: '开始使用',
click() {
console.log('start!!!');
},
}],
title: '<span class="text-success">恭喜你</span>',
message: '通过考试,你已完成初级培训!',
});
},
},
};
</script>
================================================
FILE: src/scripts/views/back-to-top.vue
================================================
<template>
<div>
<div v-for="n in 10">
<group-title><strong>group {{n}}</strong></group-title>
<group>
<cell v-for="m in 5">
{{n}}-{{m}}
</cell>
</group>
</div>
<back-to-top />
</div>
</template>
================================================
FILE: src/scripts/views/badge.vue
================================================
<template>
<div>
<group-title>base</group-title>
<group>
<cell>
<badge content="3" theme="primary" />
<badge content="3" theme="secondary" />
<badge content="3" theme="success" />
<badge content="3" theme="warning" />
</cell>
<cell>
<badge content="3" theme="primary">
<div class="bg-default" :style="{width: '50px', height: '50px'}"></div>
</badge>
<badge content="99+" theme="secondary">
<div class="bg-default" :style="{width: '50px', height: '50px'}"></div>
</badge>
<badge content="99+" theme="success">
<div class="bg-default" :style="{width: '50px', height: '50px'}"></div>
</badge>
<badge content="3" theme="warning">
<div class="bg-default" :style="{width: '50px', height: '50px'}"></div>
</badge>
</cell>
<cell>
<badge content="3" theme="primary">
<icon class="text-muted" size="3x" name="bell-o" />
</badge>
<badge content="3" theme="secondary">
<icon class="text-muted" size="3x" name="shopping-cart" />
</badge>
<badge content="3" theme="success">
<icon class="text-muted" size="3x" name="bell-o" />
</badge>
<badge content="3" theme="warning">
<icon class="text-muted" size="3x" name="shopping-cart" />
</badge>
</cell>
</group>
<group-title>dot</group-title>
<group>
<cell>
<badge theme="primary">
<icon class="text-muted" size="2x" name="bell-o" />
</badge>
<badge theme="secondary">
<icon class="text-muted" size="2x" name="bell-o" />
</badge>
<badge theme="success">
<icon class="text-muted" size="2x" name="shopping-cart" />
</badge>
<badge theme="warning">
<icon class="text-muted" size="2x" name="shopping-cart" />
</badge>
</cell>
</group>
</div>
</template>
================================================
FILE: src/scripts/views/button.vue
================================================
<template>
<div>
<group-title>theme</group-title>
<group>
<cell>
<btn theme="primary" @click="clickHandle('Vue 2.0')">primary</btn>
<btn theme="secondary">secondary</btn>
<btn theme="default">default</btn>
</cell>
</group>
<group-title>hollow</group-title>
<group>
<cell class="bg-inverse">
<btn hollow theme="primary">primary</btn>
<btn hollow theme="secondary">secondary</btn>
<btn hollow theme="default">default</btn>
</cell>
</group>
<group-title>shape="pill"</group-title>
<group>
<cell>
<btn shape="pill" theme="primary">primary</btn>
<btn shape="pill" theme="secondary">secondary</btn>
<btn shape="pill" theme="default">default</btn>
</cell>
</group>
<group-title>loading</group-title>
<group>
<cell>
<btn theme="primary" loading>loading</btn>
<btn theme="default" loading>保存中</btn>
</cell>
</group>
<group-title>disabled</group-title>
<group>
<cell>
<btn disabled theme="primary">primary</btn>
<btn disabled theme="secondary">secondary</btn>
<btn disabled theme="default">default</btn>
</cell>
</group>
<group-title>size</group-title>
<group>
<cell>
<btn size="sm" hollow theme="primary">small</btn>
<btn theme="primary">normal</btn>
<btn size="lg" theme="primary">large</btn>
</cell>
</group>
<group-title>block</group-title>
<group>
<cell>
<btn block size="lg" theme="primary">primary</btn>
</cell>
<cell>
<btn block size="lg" theme="secondary">secondary</btn>
</cell>
<cell>
<btn block size="lg" theme="default">default</btn>
</cell>
</group>
</div>
</template>
<script>
export default {
methods: {
clickHandle(message) {
this.$toast(`hello ${message}`);
},
},
};
</script>
================================================
FILE: src/scripts/views/card.vue
================================================
<template>
<div class="container">
<tip><span class="text-pure">科比系列</span></tip>
<div class="prod-container clearfix">
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe8.jpg" alt="kobe 8">
<card-body>
<p class="prod-info">
Nike耐克 Kobe8 ZK8 科比八代低帮篮球鞋 全配色合集 745334-005科八蓝色 42
</p>
<div class="text-danger">¥ 998</div>
</card-body>
</card>
</div>
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe8.jpg" alt="kobe 8">
<card-body>
<p class="prod-info">
Nike耐克 Kobe8 ZK8 科比八代低帮篮球鞋 全配色合集 745334-005科八蓝色 42
</p>
<div class="text-danger">¥ 998</div>
</card-body>
</card>
</div>
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe9.jpg" alt="kobe 10">
<card-body>
<p class="prod-info">
Nike耐克 Kobe9 ZK9 科比九代低帮篮球鞋 全配色合集 745334-005科九蓝色 42
</p>
<div class="text-danger">¥ 1080</div>
</card-body>
</card>
</div>
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe9.jpg" alt="kobe 10">
<card-body>
<p class="prod-info">
Nike耐克 Kobe9 ZK9 科比九代低帮篮球鞋 全配色合集 745334-005科九蓝色 42
</p>
<div class="text-danger">¥ 1080</div>
</card-body>
</card>
</div>
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe10.jpg" alt="kobe 10">
<card-body>
<p class="prod-info">
Nike耐克 Kobe10 ZK10 科比十代低帮篮球鞋 全配色合集 745334-005科十紫色 42
</p>
<div class="text-danger">¥ 1380</div>
</card-body>
</card>
</div>
<div class="prod-item">
<card>
<img class="img-fluid" src="../../images/kobe10.jpg" alt="kobe 10">
<card-body>
<p class="prod-info">
Nike耐克 Kobe10 ZK10 科比十代低帮篮球鞋 全配色合集 745334-005科十紫色 42
</p>
<div class="text-danger">¥ 1380</div>
</card-body>
gitextract_vku1hzea/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .scss-lint.yml ├── README.md ├── _config.yml ├── gulpfile.js ├── index.html ├── package.json ├── src/ │ ├── scripts/ │ │ ├── components/ │ │ │ ├── Alert.vue │ │ │ ├── BackToTop.vue │ │ │ ├── Badge.vue │ │ │ ├── Button.vue │ │ │ ├── Card.vue │ │ │ ├── CardBody.vue │ │ │ ├── Cell.vue │ │ │ ├── Checkbox.vue │ │ │ ├── CheckboxGroup.vue │ │ │ ├── DatePicker/ │ │ │ │ ├── DateRange.vue │ │ │ │ ├── DateTable.vue │ │ │ │ ├── Picker.vue │ │ │ │ └── index.js │ │ │ ├── Drawer.vue │ │ │ ├── DrawerItem.vue │ │ │ ├── Flex.vue │ │ │ ├── FlexItem.vue │ │ │ ├── Group.vue │ │ │ ├── GroupTitle.vue │ │ │ ├── HRule.vue │ │ │ ├── Icon.vue │ │ │ ├── InlineSelector.vue │ │ │ ├── InlineSelectorOption.vue │ │ │ ├── InputNumber.vue │ │ │ ├── InputText.vue │ │ │ ├── InputTextarea.vue │ │ │ ├── Loading.vue │ │ │ ├── Loadmore.vue │ │ │ ├── Mask.vue │ │ │ ├── Media.vue │ │ │ ├── MediaBody.vue │ │ │ ├── MediaObject.vue │ │ │ ├── Navbar.vue │ │ │ ├── Navigation.vue │ │ │ ├── NavigationItem.vue │ │ │ ├── Picker.vue │ │ │ ├── PickerOption.vue │ │ │ ├── Progressbar.vue │ │ │ ├── Progressbars.vue │ │ │ ├── Radio.vue │ │ │ ├── RadioGroup.vue │ │ │ ├── Searchbar.vue │ │ │ ├── SearchbarBtn.vue │ │ │ ├── SearchbarPlaceholder.vue │ │ │ ├── SegmentedControl.vue │ │ │ ├── SegmentedControlItem.vue │ │ │ ├── Selector.vue │ │ │ ├── SelectorOption.vue │ │ │ ├── Sidelip.vue │ │ │ ├── SlideUp.vue │ │ │ ├── SlideUpBody.vue │ │ │ ├── SlideUpHeader.vue │ │ │ ├── Stepbar.vue │ │ │ ├── StepbarItem.vue │ │ │ ├── Sticky.vue │ │ │ ├── Swipe.vue │ │ │ ├── SwipeItem.vue │ │ │ ├── Tabbar.vue │ │ │ ├── TabbarItem.vue │ │ │ ├── Tag.vue │ │ │ ├── Timeline.vue │ │ │ ├── TimelineItem.vue │ │ │ ├── Tip.vue │ │ │ ├── Toast.vue │ │ │ ├── Toggle.vue │ │ │ └── index.js │ │ ├── containers/ │ │ │ ├── layout.vue │ │ │ └── menubar.vue │ │ ├── directives/ │ │ │ └── disfavor.js │ │ ├── index.js │ │ ├── mixins/ │ │ │ ├── emitter.js │ │ │ ├── select.js │ │ │ ├── selectOption.js │ │ │ ├── sync.js │ │ │ ├── tab.js │ │ │ └── tabItem.js │ │ ├── router.js │ │ ├── routes.json │ │ ├── utils/ │ │ │ ├── alert.js │ │ │ ├── cssPrefix.js │ │ │ ├── date.js │ │ │ ├── dateUtil.js │ │ │ ├── dom.js │ │ │ ├── draggable.js │ │ │ ├── easing.js │ │ │ ├── loading.js │ │ │ ├── toast.js │ │ │ ├── translate.js │ │ │ └── type.js │ │ └── views/ │ │ ├── alert.vue │ │ ├── back-to-top.vue │ │ ├── badge.vue │ │ ├── button.vue │ │ ├── card.vue │ │ ├── cell.vue │ │ ├── checkbox.vue │ │ ├── confirm.vue │ │ ├── datepicker.vue │ │ ├── drawer.vue │ │ ├── flex.vue │ │ ├── hrule.vue │ │ ├── icon.vue │ │ ├── index.vue │ │ ├── inline-selecor.vue │ │ ├── input-number.vue │ │ ├── input-text.vue │ │ ├── input-textarea.vue │ │ ├── loading.vue │ │ ├── media.vue │ │ ├── navbar.vue │ │ ├── navigation.vue │ │ ├── picker.vue │ │ ├── progressbar.vue │ │ ├── pull-down.vue │ │ ├── pull-up.vue │ │ ├── radio.vue │ │ ├── searchbar.vue │ │ ├── segmented-control.vue │ │ ├── selector.vue │ │ ├── sideslip.vue │ │ ├── slideup.vue │ │ ├── stepbar.vue │ │ ├── sticky.vue │ │ ├── swipe.vue │ │ ├── tabbar.vue │ │ ├── tag.vue │ │ ├── timeline.vue │ │ ├── tip.vue │ │ ├── toast.vue │ │ └── toggle.vue │ └── styles/ │ ├── animate.scss │ ├── index.scss │ ├── mixins/ │ │ ├── border.scss │ │ ├── button.scss │ │ ├── media.scss │ │ ├── navbar.scss │ │ ├── progressbar.scss │ │ ├── tag.scss │ │ └── text.scss │ ├── mixins.scss │ ├── modules/ │ │ ├── alert.scss │ │ ├── back-to-top.scss │ │ ├── badge.scss │ │ ├── button.scss │ │ ├── card.scss │ │ ├── cell.scss │ │ ├── checkbox.scss │ │ ├── date-picker.scss │ │ ├── drawer.scss │ │ ├── group.scss │ │ ├── hrule.scss │ │ ├── inline-selector.scss │ │ ├── input-number.scss │ │ ├── input-text.scss │ │ ├── input-textarea.scss │ │ ├── loading.scss │ │ ├── loadmore.scss │ │ ├── mask.scss │ │ ├── media.scss │ │ ├── navbar.scss │ │ ├── navigation.scss │ │ ├── picker.scss │ │ ├── progressbar.scss │ │ ├── searchbar.scss │ │ ├── segmented-control.scss │ │ ├── selector.scss │ │ ├── sidelip.scss │ │ ├── slideup.scss │ │ ├── stepbar.scss │ │ ├── sticky.scss │ │ ├── swipe.scss │ │ ├── tabbar.scss │ │ ├── tag.scss │ │ ├── timeline.scss │ │ ├── tip.scss │ │ ├── toast.scss │ │ └── toggle.scss │ ├── modules.scss │ ├── utils/ │ │ ├── background.scss │ │ ├── border.scss │ │ ├── clearfix.scss │ │ ├── display.scss │ │ ├── flex.scss │ │ ├── img.scss │ │ ├── reboot.scss │ │ ├── spacing.scss │ │ └── text.scss │ ├── utils.scss │ └── variables.scss ├── webpack.dev.config.js ├── webpack.prebuilt.config.js └── webpack.prod.config.js
SYMBOL INDEX (28 symbols across 11 files)
FILE: src/scripts/components/DatePicker/index.js
method type (line 26) | type(type) {
method created (line 37) | created() {
FILE: src/scripts/directives/disfavor.js
method bind (line 7) | bind(el, binding, vnode) {
method unbind (line 21) | unbind(el) {
FILE: src/scripts/mixins/emitter.js
method dispatch (line 3) | dispatch(componentName, eventName, params) {
FILE: src/scripts/mixins/select.js
method data (line 5) | data() {
method optionClickHandle (line 11) | optionClickHandle(option) {
method created (line 31) | created() {
FILE: src/scripts/mixins/selectOption.js
method clickHandle (line 11) | clickHandle() {
method isActive (line 16) | isActive() {
FILE: src/scripts/mixins/sync.js
method data (line 8) | data() {
method value (line 14) | value(val) {
method currentValue (line 17) | currentValue(val) {
FILE: src/scripts/mixins/tab.js
method data (line 6) | data() {
method itemClickHandle (line 12) | itemClickHandle(val) {
method created (line 19) | created() {
FILE: src/scripts/mixins/tabItem.js
method data (line 6) | data() {
method mounted (line 12) | mounted() {
method clickHandle (line 17) | clickHandle() {
FILE: src/scripts/utils/dateUtil.js
function shorten (line 44) | function shorten(arr, sLen) {
function monthUpdate (line 52) | function monthUpdate(arrName) {
function pad (line 61) | function pad(val, len) {
FILE: src/scripts/utils/loading.js
method show (line 10) | show(message = '加载中') {
method hide (line 31) | hide() {
method toggle (line 35) | toggle(message) {
FILE: src/scripts/utils/toast.js
method pop (line 10) | pop() {
method push (line 17) | push(instance) {
method toggle (line 20) | toggle() {
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (367K chars).
[
{
"path": ".babelrc",
"chars": 182,
"preview": "{\n \"presets\": [\"vue\", [\"es2015\", { \"modules\": false }], \"stage-1\"],\n \"comments\": false,\n \"plugins\": [\n \""
},
{
"path": ".editorconfig",
"chars": 197,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".eslintignore",
"chars": 22,
"preview": "build.js\nwebpack.*.js\n"
},
{
"path": ".eslintrc.js",
"chars": 120,
"preview": "module.exports = {\n extends: 'vue-impression',\n rules: {\n 'import/no-extraneous-dependencies': 0,\n }\n};\n"
},
{
"path": ".gitignore",
"chars": 213,
"preview": "/node_modules\n/site/build\n/site/node_modules\n/site/.sass-cache\n/site/src/scripts/components/impression\n/site/src/styles/"
},
{
"path": ".npmignore",
"chars": 189,
"preview": "/build\n/site\n/.sass-cache\n.DS_Store\nMakefile\n.babelrc\n.editorconfig\n.eslintignore\n.eslintrc.js\n.scss-lint.yml\nindex.todo"
},
{
"path": ".scss-lint.yml",
"chars": 4918,
"preview": "scss_files: \"src/styles/**/*.scss\"\nexclude: ['src/styles/font-awesome/**', 'src/styles/modules/_normalize.scss', 'src/st"
},
{
"path": "README.md",
"chars": 1698,
"preview": "# vue-impression\n\nA Vue.js 2.0 UI elements for mobile.\n\n## Demo\n\nhttps://newdadafe.github.io/impression_vue/#/button\n\n##"
},
{
"path": "_config.yml",
"chars": 26,
"preview": "theme: jekyll-theme-cayman"
},
{
"path": "gulpfile.js",
"chars": 2102,
"preview": "const fs = require('fs-extra');\nconst gulp = require('gulp');\nconst minimist = require('minimist');\nconst plugin = requi"
},
{
"path": "index.html",
"chars": 589,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <title>vue-impression</title>\n <meta name=\"viewport\" con"
},
{
"path": "package.json",
"chars": 2958,
"preview": "{\n \"name\": \"vue-impression\",\n \"version\": \"0.21.13\",\n \"description\": \"A Vue.js 2.0 UI elements for mobile.\",\n \"sass\":"
},
{
"path": "src/scripts/components/Alert.vue",
"chars": 2513,
"preview": "<template>\n <div>\n <transition name=\"zoom\"\n @after-leave=\"afterLeave\">\n <div\n "
},
{
"path": "src/scripts/components/BackToTop.vue",
"chars": 2660,
"preview": "<template>\n <div\n class=\"back-to-top\"\n :class=\"{'active': active}\"\n @click.prevent.stop=\"scrollT"
},
{
"path": "src/scripts/components/Badge.vue",
"chars": 662,
"preview": "<template>\n <div\n @click=\"$emit('click')\"\n class=\"badge\"\n :class=\"{'badge-gap': $slots.default}\""
},
{
"path": "src/scripts/components/Button.vue",
"chars": 1841,
"preview": "<template>\n <button\n :type=\"type\"\n @click=\"clickHandle\"\n class=\"btn\"\n :class=\"{\n "
},
{
"path": "src/scripts/components/Card.vue",
"chars": 173,
"preview": "<template>\n <div class=\"card\" @click=\"$emit('click')\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n exp"
},
{
"path": "src/scripts/components/CardBody.vue",
"chars": 183,
"preview": "<template>\n <div class=\"card-body\" @click=\"$emit('click')\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n "
},
{
"path": "src/scripts/components/Cell.vue",
"chars": 1887,
"preview": "<template>\n <router-link\n v-if=\"!disabled && to\"\n :to=\"to\"\n class=\"cell cell-link\"\n :clas"
},
{
"path": "src/scripts/components/Checkbox.vue",
"chars": 1616,
"preview": "<template>\n <label\n class=\"checkbox\"\n :class=\"'checkbox-' + type\">\n <input\n type=\"che"
},
{
"path": "src/scripts/components/CheckboxGroup.vue",
"chars": 844,
"preview": "<template>\n <div class=\"checkbox-group\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Sync from "
},
{
"path": "src/scripts/components/DatePicker/DateRange.vue",
"chars": 4882,
"preview": "<template>\n\t<div\n\t v-show=\"visible\"\n\t class=\"date-range-picker\">\n \n <div class=\"header\">\n <div "
},
{
"path": "src/scripts/components/DatePicker/DateTable.vue",
"chars": 16584,
"preview": "<template>\n <table\n cellspacing=\"0\"\n cellpadding=\"0\"\n class=\"date-table\"\n @click=\"handleC"
},
{
"path": "src/scripts/components/DatePicker/Picker.vue",
"chars": 5462,
"preview": "<template>\n <div\n class=\"date-picker\"\n :class=\"{\n [`date-picker-${size}`]: size,\n "
},
{
"path": "src/scripts/components/DatePicker/index.js",
"chars": 768,
"preview": "import Picker from './Picker';\nimport DateRangeView from './DateRange';\n\nconst getView = function(type) {\n if(type =="
},
{
"path": "src/scripts/components/Drawer.vue",
"chars": 332,
"preview": "<template>\n <div class=\"drawer\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Tab from '../mixin"
},
{
"path": "src/scripts/components/DrawerItem.vue",
"chars": 529,
"preview": "<template>\n <div\n class=\"drawer-item\"\n :class=\"{\n 'active': $parent.currentActiveKey !== und"
},
{
"path": "src/scripts/components/Flex.vue",
"chars": 1312,
"preview": "<template>\n <div\n class=\"flex\"\n :class=\"[\n alignClass,\n {[`flex-justify-${justify"
},
{
"path": "src/scripts/components/FlexItem.vue",
"chars": 356,
"preview": "<template>\n <div\n @click=\"$emit('click')\"\n class=\"flex-item\"\n :style=\"{ flex }\">\n <slot><"
},
{
"path": "src/scripts/components/Group.vue",
"chars": 151,
"preview": "<template>\n <div class=\"group\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n n"
},
{
"path": "src/scripts/components/GroupTitle.vue",
"chars": 163,
"preview": "<template>\n <div class=\"group-title\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n "
},
{
"path": "src/scripts/components/HRule.vue",
"chars": 642,
"preview": "<template>\n <hr class=\"hr\" :class=\"{\n [`border-${type}`]: type,\n [`border-${theme}`]: theme,\n }\">\n</"
},
{
"path": "src/scripts/components/Icon.vue",
"chars": 723,
"preview": "<template>\n <i\n @click=\"$emit('click')\"\n class=\"fa\"\n :class=\"[\n `fa-${name}`,\n "
},
{
"path": "src/scripts/components/InlineSelector.vue",
"chars": 562,
"preview": "<template>\n <div class=\"inline-selector\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Sync from"
},
{
"path": "src/scripts/components/InlineSelectorOption.vue",
"chars": 480,
"preview": "<template>\n <tag\n class=\"inline-selector-option\"\n :class=\"{\n disabled: disabled || $parent.d"
},
{
"path": "src/scripts/components/InputNumber.vue",
"chars": 1520,
"preview": "<template>\n <div class=\"input-number\" :class=\"{'input-number-disabled': disabled}\">\n <a\n class=\"inp"
},
{
"path": "src/scripts/components/InputText.vue",
"chars": 4367,
"preview": "<template>\n <div class=\"input-text\" v-disfavor=\"blur\">\n <input\n type=\"number\"\n v-if=\"typ"
},
{
"path": "src/scripts/components/InputTextarea.vue",
"chars": 1094,
"preview": "<template>\n <div class=\"textarea\">\n <textarea\n v-model=\"currentValue\"\n :disabled=\"disabl"
},
{
"path": "src/scripts/components/Loading.vue",
"chars": 755,
"preview": "<template>\n <svg\n class=\"loading\"\n :class=\"{\n [`loading-${size}`]: size,\n [`loadi"
},
{
"path": "src/scripts/components/Loadmore.vue",
"chars": 11865,
"preview": "<template>\n <div class=\"loadmore\">\n <div\n class=\"loadmore-content\"\n :class=\"{ dropped: t"
},
{
"path": "src/scripts/components/Mask.vue",
"chars": 548,
"preview": "<template>\n <transition :name=\"transition\">\n <div\n class=\"mask\"\n v-show=\"$parent.current"
},
{
"path": "src/scripts/components/Media.vue",
"chars": 504,
"preview": "<template>\n <div\n @click=\"$emit('click')\"\n class=\"media\"\n :class=\"`flex-align-${align}`\">\n "
},
{
"path": "src/scripts/components/MediaBody.vue",
"chars": 161,
"preview": "<template>\n <div class=\"media-body\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n "
},
{
"path": "src/scripts/components/MediaObject.vue",
"chars": 165,
"preview": "<template>\n <div class=\"media-object\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n "
},
{
"path": "src/scripts/components/Navbar.vue",
"chars": 705,
"preview": "<template>\n <div class=\"navbar\" :class=\"'navbar-' + theme\">\n <div class=\"navbar-header\">\n <slot></s"
},
{
"path": "src/scripts/components/Navigation.vue",
"chars": 318,
"preview": "<template>\n <div class=\"navigation\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Tab from '../m"
},
{
"path": "src/scripts/components/NavigationItem.vue",
"chars": 523,
"preview": "<template>\n <div\n class=\"navigation-item\"\n :class=\"{'active': $parent.currentActiveKey === currentEvent"
},
{
"path": "src/scripts/components/Picker.vue",
"chars": 5603,
"preview": "<template>\n <div\n class=\"picker\"\n :class=\"{\n [`picker-${size}`]: size,\n }\"\n re"
},
{
"path": "src/scripts/components/PickerOption.vue",
"chars": 287,
"preview": "<template>\n <div\n class=\"picker-list-item\"\n :class=\"{active: $parent.currentValue === value}\">\n "
},
{
"path": "src/scripts/components/Progressbar.vue",
"chars": 1106,
"preview": "<template>\n <div\n class=\"progressbar\"\n :class=\"{\n [`progressbar-${size}`]: size,\n "
},
{
"path": "src/scripts/components/Progressbars.vue",
"chars": 7298,
"preview": "<template>\n <div\n class=\"progressbars\"\n :class=\"{\n [`progressbars-${size}`]: size,\n "
},
{
"path": "src/scripts/components/Radio.vue",
"chars": 1601,
"preview": "<template>\n <label\n class=\"radio\"\n :class=\"'radio-' + shape\">\n <input\n type=\"radio\"\n "
},
{
"path": "src/scripts/components/RadioGroup.vue",
"chars": 227,
"preview": "<template>\n <div class=\"radio-group\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Sync from '.."
},
{
"path": "src/scripts/components/Searchbar.vue",
"chars": 891,
"preview": "<template>\n <div class=\"searchbar\" :class=\"{active: focus}\" v-disfavor=\"blur\">\n <slot></slot>\n </div>\n</tem"
},
{
"path": "src/scripts/components/SearchbarBtn.vue",
"chars": 167,
"preview": "<template>\n <div class=\"searchbar-btn\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n "
},
{
"path": "src/scripts/components/SearchbarPlaceholder.vue",
"chars": 1277,
"preview": "<template>\n <div\n @click=\"clickHandle\"\n class=\"searchbar-input\"\n :class=\"{\n 'border-c"
},
{
"path": "src/scripts/components/SegmentedControl.vue",
"chars": 352,
"preview": "<template>\n <div class=\"segmented-control\" :class=\"{disabled}\">\n <slot></slot>\n </div>\n</template>\n\n<script"
},
{
"path": "src/scripts/components/SegmentedControlItem.vue",
"chars": 444,
"preview": "<template>\n <div\n class=\"segmented-control-item\"\n :class=\"{\n 'active': $parent.currentActive"
},
{
"path": "src/scripts/components/Selector.vue",
"chars": 272,
"preview": "<template>\n <div class=\"selector\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Sync from '../mi"
},
{
"path": "src/scripts/components/SelectorOption.vue",
"chars": 617,
"preview": "<template>\n <div\n class=\"cell selector-option\"\n :class=\"{\n 'active': isActive(),\n "
},
{
"path": "src/scripts/components/Sidelip.vue",
"chars": 794,
"preview": "<template>\n <div @click=\"$emit('click')\">\n <transition :name=\"transition\">\n <div v-show=\"currentVal"
},
{
"path": "src/scripts/components/SlideUp.vue",
"chars": 785,
"preview": "<template>\n <div>\n <transition :name=\"transition\">\n <div v-show=\"currentValue\" class=\"slideup\">\n "
},
{
"path": "src/scripts/components/SlideUpBody.vue",
"chars": 262,
"preview": "<template>\n <div class=\"slideup-body\" :class=\"{'no-padding': noPadding}\">\n <slot></slot>\n </div>\n</template"
},
{
"path": "src/scripts/components/SlideUpHeader.vue",
"chars": 169,
"preview": "<template>\n <div class=\"slideup-header\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n"
},
{
"path": "src/scripts/components/Stepbar.vue",
"chars": 427,
"preview": "<template>\n <div class=\"stepbar\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n import Sync from '../mix"
},
{
"path": "src/scripts/components/StepbarItem.vue",
"chars": 1781,
"preview": "<template>\n <div class=\"stepbar-item\">\n <div\n class=\"stepbar-line\"\n :class=\"{active: sta"
},
{
"path": "src/scripts/components/Sticky.vue",
"chars": 4007,
"preview": "<template>\n <div>\n <div :class=\"classes\" :style=\"styles\">\n <slot></slot>\n </div>\n </div>\n"
},
{
"path": "src/scripts/components/Swipe.vue",
"chars": 8989,
"preview": "<template>\n <div class=\"swipe\" ref=\"swipe\">\n <div class=\"swipe-items\">\n <slot></slot>\n </div"
},
{
"path": "src/scripts/components/SwipeItem.vue",
"chars": 4150,
"preview": "<template>\n <div class=\"swipe-item\"\n @webkitTransitionEnd=\"resetByTranslateX()\">\n <slot></slot>\n </d"
},
{
"path": "src/scripts/components/Tabbar.vue",
"chars": 1506,
"preview": "<template>\n <div class=\"tabbar\" :class=\"{disabled}\">\n <slot></slot>\n <div class=\"tabbar-indicator\" ref="
},
{
"path": "src/scripts/components/TabbarItem.vue",
"chars": 422,
"preview": "<template>\n <div\n class=\"tabbar-item\"\n :class=\"{\n 'active': $parent.currentActiveKey === cur"
},
{
"path": "src/scripts/components/Tag.vue",
"chars": 1117,
"preview": "<template>\n <span\n @click=\"$emit('click')\"\n class=\"tag\"\n :class=\"{\n [`tag-${theme}`]:"
},
{
"path": "src/scripts/components/Timeline.vue",
"chars": 157,
"preview": "<template>\n <div class=\"timeline\">\n <slot></slot>\n </div>\n</template>\n\n<script>\n export default {\n "
},
{
"path": "src/scripts/components/TimelineItem.vue",
"chars": 540,
"preview": "<template>\n <div class=\"timeline-item\" :class=\"{'active': index === 0}\">\n <div class=\"timeline-item-addon\"></d"
},
{
"path": "src/scripts/components/Tip.vue",
"chars": 793,
"preview": "<template>\n <div class=\"tip\" @click=\"$emit('click')\">\n <hrule :type=\"type\" :theme=\"theme\" />\n <span cla"
},
{
"path": "src/scripts/components/Toast.vue",
"chars": 1926,
"preview": "<template>\n <div>\n <transition name=\"toast-fade\">\n <div\n v-show=\"currentValue\"\n "
},
{
"path": "src/scripts/components/Toggle.vue",
"chars": 414,
"preview": "<template>\n <label class=\"toggle\">\n <input\n type=\"checkbox\"\n class=\"toggle-input\"\n "
},
{
"path": "src/scripts/components/index.js",
"chars": 4846,
"preview": "import Button from './Button';\nimport Icon from './Icon';\nimport Group from './Group';\nimport GroupTitle from './GroupTi"
},
{
"path": "src/scripts/containers/layout.vue",
"chars": 615,
"preview": "<template>\n <flex direction=\"column\">\n <navbar theme=\"default\">\n <icon name=\"bars\" size=\"lg\" @click"
},
{
"path": "src/scripts/containers/menubar.vue",
"chars": 1546,
"preview": "<template>\n <sidelip v-model=\"currentValue\">\n <flex direction=\"column\" :style=\"{backgroundColor: '#f0eff5'}\">\n"
},
{
"path": "src/scripts/directives/disfavor.js",
"chars": 675,
"preview": "import { contains } from '../utils/dom';\n\nconst disfavorContext = '@@disfavor';\n\n// 失去焦点\nexport default {\n bind(el, b"
},
{
"path": "src/scripts/index.js",
"chars": 231,
"preview": "import Vue from 'vue';\nimport router from './router';\nimport Impression from './components';\n\nVue.use(Impression);\n\n/* e"
},
{
"path": "src/scripts/mixins/emitter.js",
"chars": 552,
"preview": "export default {\n methods: {\n dispatch(componentName, eventName, params) {\n let parent = this.$pare"
},
{
"path": "src/scripts/mixins/select.js",
"chars": 871,
"preview": "export default {\n props: {\n multiple: Boolean,\n },\n data() {\n return {\n currentText: {"
},
{
"path": "src/scripts/mixins/selectOption.js",
"chars": 624,
"preview": "export default {\n props: {\n value: {},\n checkedIcon: {\n type: String,\n default: '"
},
{
"path": "src/scripts/mixins/sync.js",
"chars": 614,
"preview": "import { isArray } from '../utils/type';\n\nexport default {\n props: {\n value: {},\n disabled: Boolean,\n "
},
{
"path": "src/scripts/mixins/tab.js",
"chars": 459,
"preview": "export default {\n props: {\n activeKey: {},\n disabled: Boolean,\n },\n data() {\n return {\n "
},
{
"path": "src/scripts/mixins/tabItem.js",
"chars": 561,
"preview": "export default {\n props: {\n eventKey: {},\n disabled: Boolean,\n },\n data() {\n return {\n "
},
{
"path": "src/scripts/router.js",
"chars": 777,
"preview": "import Vue from 'vue';\nimport VueRouter from 'vue-router';\nimport routesConfig from './routes.json';\n\nVue.use(VueRouter)"
},
{
"path": "src/scripts/routes.json",
"chars": 5689,
"preview": "[\n {\n \"title\": \"Base\",\n \"children\": [\n {\n \"path\": \"/button\",\n "
},
{
"path": "src/scripts/utils/alert.js",
"chars": 537,
"preview": "import Vue from 'vue';\nimport OriginAlert from '../components/Alert';\n\nconst Alert = Vue.extend(OriginAlert);\nlet alertI"
},
{
"path": "src/scripts/utils/cssPrefix.js",
"chars": 589,
"preview": "let engine;\nlet docStyle = document.documentElement.style;\n\nif('MozAppearance' in docStyle) {\n engine = 'gecko';\n} el"
},
{
"path": "src/scripts/utils/date.js",
"chars": 7485,
"preview": "/*eslint-disable*/\n\nimport dateUtil from './dateUtil';\n\nconst weeks = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];"
},
{
"path": "src/scripts/utils/dateUtil.js",
"chars": 10423,
"preview": "/* Modified from https://github.com/taylorhakes/fecha\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015 Taylor Hakes\n"
},
{
"path": "src/scripts/utils/dom.js",
"chars": 1684,
"preview": "/**\n * 判断一个元素是否为另一个的后代元素.\n * @param {[Element]} ancestor [祖先元素]\n * @param {[Element]} descendent [后代元素]\n * @retu"
},
{
"path": "src/scripts/utils/draggable.js",
"chars": 2657,
"preview": "import { getTranslate } from './translate';\n\n/* global document:true */\nconst draggable = (el, options) => {\n let pre"
},
{
"path": "src/scripts/utils/easing.js",
"chars": 326,
"preview": "// easeInOut\nexport const easeInOutCubic = (time, offset, end, duration) => {\n let cc = end - offset,\n tempTim"
},
{
"path": "src/scripts/utils/loading.js",
"chars": 814,
"preview": "import Vue from 'vue';\nimport OriginToast from '../components/Toast';\n\nconst Toast = Vue.extend(OriginToast);\n\nlet insta"
},
{
"path": "src/scripts/utils/toast.js",
"chars": 1250,
"preview": "import Vue from 'vue';\nimport OriginToast from '../components/Toast';\n\nconst Toast = Vue.extend(OriginToast);\n\n// toast缓"
},
{
"path": "src/scripts/utils/translate.js",
"chars": 1379,
"preview": "import { vendorPrefix } from './cssPrefix';\n\nlet transformProperty = `${vendorPrefix}Transform`;\n\n// 获取位移\nexport const g"
},
{
"path": "src/scripts/utils/type.js",
"chars": 100,
"preview": "// 是否数组\nexport const isArray = array => Object.prototype.toString.call(array) === '[object Array]';\n"
},
{
"path": "src/scripts/views/alert.vue",
"chars": 2566,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell @click=\"alertClickHandle\""
},
{
"path": "src/scripts/views/back-to-top.vue",
"chars": 316,
"preview": "<template>\n <div>\n <div v-for=\"n in 10\">\n <group-title><strong>group {{n}}</strong></group-title>\n "
},
{
"path": "src/scripts/views/badge.vue",
"chars": 2421,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <badge c"
},
{
"path": "src/scripts/views/button.vue",
"chars": 2362,
"preview": "<template>\n <div>\n <group-title>theme</group-title>\n <group>\n <cell>\n <btn th"
},
{
"path": "src/scripts/views/card.vue",
"chars": 3727,
"preview": "<template>\n <div class=\"container\">\n <tip><span class=\"text-pure\">科比系列</span></tip>\n <div class=\"prod-c"
},
{
"path": "src/scripts/views/cell.vue",
"chars": 1867,
"preview": "<template>\n <div>\n <group-title><strong>Base</strong></group-title>\n <group>\n <cell>Home</ce"
},
{
"path": "src/scripts/views/checkbox.vue",
"chars": 3013,
"preview": "<template>\n <div>\n <group-title>Base</group-title>\n <group>\n <cell>\n <checkbo"
},
{
"path": "src/scripts/views/confirm.vue",
"chars": 719,
"preview": "<template>\n <div>\n <group-title>confirm</group-title>\n <group>\n <cell @click=\"showHandle(fal"
},
{
"path": "src/scripts/views/datepicker.vue",
"chars": 871,
"preview": "<template>\n <div class=\"date-wrapper\">\n <date-picker\n type=\"daterange\"\n size=\"base\"\n "
},
{
"path": "src/scripts/views/drawer.vue",
"chars": 988,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <drawer @change=\"changeHandle\">\n <drawer"
},
{
"path": "src/scripts/views/flex.vue",
"chars": 2191,
"preview": "<template>\n <div>\n <group-title>等比</group-title>\n <flex class=\"row\">\n <flex-item class=\"bg-p"
},
{
"path": "src/scripts/views/hrule.vue",
"chars": 760,
"preview": "<template>\n <div>\n <group-title>base(hr)</group-title>\n <group>\n <cell>\n <hru"
},
{
"path": "src/scripts/views/icon.vue",
"chars": 1085,
"preview": "<template>\n <div>\n <group-title>fontawesome</group-title>\n <group>\n <cell href=\"http://fonta"
},
{
"path": "src/scripts/views/index.vue",
"chars": 779,
"preview": "<template>\n <div>\n <div v-for=\"group in groups\">\n <group-title v-if=\"group.title\"><strong>{{group.t"
},
{
"path": "src/scripts/views/inline-selecor.vue",
"chars": 4013,
"preview": "<template>\n <div>\n <group-title>\n base\n <span class=\"pull-right\">size: {{size}}</span>\n "
},
{
"path": "src/scripts/views/input-number.vue",
"chars": 1490,
"preview": "<template>\n <div>\n <group-title>Base</group-title>\n <group>\n <cell>\n <span>de"
},
{
"path": "src/scripts/views/input-text.vue",
"chars": 1479,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <span sl"
},
{
"path": "src/scripts/views/input-textarea.vue",
"chars": 456,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell class=\"no-padding\">\n "
},
{
"path": "src/scripts/views/loading.vue",
"chars": 937,
"preview": "<template>\n <div>\n <group-title>loading</group-title>\n <group>\n <cell @click=\"showLoadingHan"
},
{
"path": "src/scripts/views/media.vue",
"chars": 1012,
"preview": "<template>\n <div>\n <group-title>Media</group-title>\n <group>\n <cell>\n <media>"
},
{
"path": "src/scripts/views/navbar.vue",
"chars": 1053,
"preview": "<template>\n <div>\n <navbar theme=\"default\">\n <router-link :to=\"{path: '/'}\">\n <icon "
},
{
"path": "src/scripts/views/navigation.vue",
"chars": 1305,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <navigation>\n <navigation-item label=\"首页"
},
{
"path": "src/scripts/views/picker.vue",
"chars": 2397,
"preview": "<template>\n <div>\n <group-title>base:{{defaultCity}}</group-title>\n <picker v-model=\"defaultCity\">\n "
},
{
"path": "src/scripts/views/progressbar.vue",
"chars": 8521,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <progres"
},
{
"path": "src/scripts/views/pull-down.vue",
"chars": 2124,
"preview": "<template>\n <div>\n <group-title>\n Pull down\n <span class=\"pull-right\">status: {{ topAllL"
},
{
"path": "src/scripts/views/pull-up.vue",
"chars": 2190,
"preview": "<template>\n <div>\n <group-title>\n Pull up\n <span class=\"pull-right\">status: {{ bottomAll"
},
{
"path": "src/scripts/views/radio.vue",
"chars": 2003,
"preview": "<template>\n <div>\n <group-title>\n Radio\n <span class=\"pull-right\">selected: {{ single }}"
},
{
"path": "src/scripts/views/searchbar.vue",
"chars": 1349,
"preview": "<template>\n <div>\n <group-title>base:{{search}}</group-title>\n <group>\n <cell>\n "
},
{
"path": "src/scripts/views/segmented-control.vue",
"chars": 1014,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell class=\"text-center\">\n "
},
{
"path": "src/scripts/views/selector.vue",
"chars": 2158,
"preview": "<template>\n <div>\n <group-title>\n base\n <span class=\"pull-right\">selected:{{single}}</sp"
},
{
"path": "src/scripts/views/sideslip.vue",
"chars": 2277,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell @click=\"toggleHandle\">sid"
},
{
"path": "src/scripts/views/slideup.vue",
"chars": 3395,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell @click=\"showBase = true\">"
},
{
"path": "src/scripts/views/stepbar.vue",
"chars": 1745,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <stepbar"
},
{
"path": "src/scripts/views/sticky.vue",
"chars": 5324,
"preview": "<template>\n <div>\n <group-title><strong>Base</strong></group-title>\n <group>\n <cell>Home</ce"
},
{
"path": "src/scripts/views/swipe.vue",
"chars": 3804,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <swipe\n :onDragStart=\"onDragStart\"\n "
},
{
"path": "src/scripts/views/tabbar.vue",
"chars": 1725,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <tabbar class=\"no-margin\" @change=\"changeHandle\">\n "
},
{
"path": "src/scripts/views/tag.vue",
"chars": 3797,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n primary\n"
},
{
"path": "src/scripts/views/timeline.vue",
"chars": 579,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <timelin"
},
{
"path": "src/scripts/views/tip.vue",
"chars": 618,
"preview": "<template>\n <div>\n <group-title>base</group-title>\n <group>\n <cell>\n <tip>查看图"
},
{
"path": "src/scripts/views/toast.vue",
"chars": 1996,
"preview": "<template>\n <div>\n <group-title>Base</group-title>\n <group>\n <cell @click=\"baseClickHandle('"
},
{
"path": "src/scripts/views/toggle.vue",
"chars": 868,
"preview": "<template>\n <div>\n <group-title>Base</group-title>\n <group>\n <cell>\n <toggle "
},
{
"path": "src/styles/animate.scss",
"chars": 1179,
"preview": "// fade\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n\n@keyframes fadeO"
},
{
"path": "src/styles/index.scss",
"chars": 96,
"preview": "@import 'variables';\n@import 'mixins';\n@import 'modules';\n@import 'utils';\n@import 'animate';\n\n\n"
},
{
"path": "src/styles/mixins/border.scss",
"chars": 785,
"preview": "// 上边框\n@mixin border-top {\n background-image: $border-image-top;\n background-size: 100% 1px;\n background-repeat"
},
{
"path": "src/styles/mixins/button.scss",
"chars": 1133,
"preview": "// Button sizes\n@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {\n padding: $padding-y $paddin"
},
{
"path": "src/styles/mixins/media.scss",
"chars": 339,
"preview": "@mixin respond-to($size) {\n $query: map-get($all-media-query-device, $size);\n\n @if not $query {\n @error 'No"
},
{
"path": "src/styles/mixins/navbar.scss",
"chars": 306,
"preview": "// Navbar variant\n@mixin navbar-variant($background-color, $color, $has-border) {\n background-color: $background-colo"
},
{
"path": "src/styles/mixins/progressbar.scss",
"chars": 130,
"preview": "@mixin progressbar-variant($background-color) {\n .progressbar-indicator {\n background-color: $background-color"
},
{
"path": "src/styles/mixins/tag.scss",
"chars": 204,
"preview": "@mixin tag-variant($background-color) {\n background-color: $background-color;\n}\n\n@mixin tag-outline-variant($border-c"
},
{
"path": "src/styles/mixins/text.scss",
"chars": 242,
"preview": "// Typography\n@mixin text-emphasis-variant($parent, $color) {\n #{$parent} {\n color: $color !important;\n }\n\n"
},
{
"path": "src/styles/mixins.scss",
"chars": 184,
"preview": "// utils\n@import 'mixins/border';\n@import 'mixins/text';\n\n@import 'mixins/button';\n@import 'mixins/navbar';\n@import 'mix"
},
{
"path": "src/styles/modules/alert.scss",
"chars": 1598,
"preview": ".alert {\n position: fixed;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n z-index: $z-index-alert;\n dis"
},
{
"path": "src/styles/modules/back-to-top.scss",
"chars": 639,
"preview": ".back-to-top {\n position: fixed;\n right: $top-right;\n bottom: -1.5 * $top-height;\n z-index: $z-index-top;\n "
},
{
"path": "src/styles/modules/badge.scss",
"chars": 1412,
"preview": ".badge {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n\n.badge-gap {\n margin-right"
},
{
"path": "src/styles/modules/button.scss",
"chars": 1687,
"preview": ".btn {\n display: inline-block;\n appearance: none;\n overflow: hidden;\n outline: 0;\n text-align: center;\n "
},
{
"path": "src/styles/modules/card.scss",
"chars": 98,
"preview": ".card {\n background-color: $card-bg-color;\n}\n\n\n.card-body {\n padding: $card-body-padding;\n}\n"
},
{
"path": "src/styles/modules/cell.scss",
"chars": 835,
"preview": ".cell {\n display: flex;\n align-items: center;\n overflow: hidden;\n text-decoration: none;\n color: $cell-co"
},
{
"path": "src/styles/modules/checkbox.scss",
"chars": 2721,
"preview": ".checkbox,\n.radio {\n user-select: none;\n display: inline-block;\n}\n\n// shape\n.checkbox-square {\n .checkbox-addon"
},
{
"path": "src/styles/modules/date-picker.scss",
"chars": 3974,
"preview": "$ColorBlue: #1287FF;\n$ColorGrey: #BCC1CC;\n\n.date-picker {\n\twidth: 18.75em;\n box-sizing: border-box;\n margin: 0;\n "
},
{
"path": "src/styles/modules/drawer.scss",
"chars": 729,
"preview": ".drawer {\n display: flex;\n color: $drawer-color;\n background-color: $drawer-bg-color;\n padding: $drawer-padd"
},
{
"path": "src/styles/modules/group.scss",
"chars": 319,
"preview": ".group {\n padding-top: 1px;\n padding-bottom: 1px;\n margin: $group-margin;\n @include border-vertical();\n b"
},
{
"path": "src/styles/modules/hrule.scss",
"chars": 189,
"preview": ".hr {\n border-width: 0;\n border-top-width: 1px;\n border-style: solid;\n border-color: $hr-color;\n transfor"
},
{
"path": "src/styles/modules/inline-selector.scss",
"chars": 285,
"preview": ".inline-selector {\n display: block;\n}\n\n.inline-selector-option {\n line-height: 1;\n padding: $inline-selector-op"
},
{
"path": "src/styles/modules/input-number.scss",
"chars": 1829,
"preview": ".input-number {\n display: inline-block;\n}\n\n.input-number-disabled {\n opacity: .6;\n\n .input-number-input {\n "
},
{
"path": "src/styles/modules/input-text.scss",
"chars": 388,
"preview": ".input-text {\n display: flex;\n align-items: center;\n}\n\n.input-text-input {\n flex: 1;\n border: 0;\n padding"
},
{
"path": "src/styles/modules/input-textarea.scss",
"chars": 416,
"preview": ".textarea-input {\n border: 0;\n width: 100%;\n resize: none;\n display: block;\n appearance: none;\n user-s"
},
{
"path": "src/styles/modules/loading.scss",
"chars": 945,
"preview": ".loading {\n width: $loading-width;\n animation: rotate $loading-animation-duration linear infinite;\n}\n\n.loading-pat"
},
{
"path": "src/styles/modules/loadmore.scss",
"chars": 484,
"preview": ".loadmore {\n overflow: hidden;\n}\n\n.loadmore-content {\n &.dropped {\n transition: $loadmore-dropped-transitio"
},
{
"path": "src/styles/modules/mask.scss",
"chars": 404,
"preview": ".mask {\n position: fixed;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n background-color: $mask-bg-color;"
},
{
"path": "src/styles/modules/media.scss",
"chars": 295,
"preview": ".media {\n display: flex;\n background-color: $media-bg-color;\n}\n\n\n.media-object {\n display: block;\n\n &:first-"
},
{
"path": "src/styles/modules/navbar.scss",
"chars": 1152,
"preview": ".navbar {\n display: flex;\n user-select: none;\n padding: $navbar-padding;\n height: $navbar-height;\n line-h"
},
{
"path": "src/styles/modules/navigation.scss",
"chars": 581,
"preview": ".navigation {\n display: flex;\n align-items: center;\n @include border-top();\n height: $navigation-height;\n "
},
{
"path": "src/styles/modules/picker.scss",
"chars": 2089,
"preview": ".picker {\n width: 100%;\n display: block;\n position: relative;\n overflow: hidden;\n background: $picker-bg-"
},
{
"path": "src/styles/modules/progressbar.scss",
"chars": 3193,
"preview": ".progressbar {\n height: $progressbar-height;\n overflow: hidden;\n background-color: $progressbar-bg-color;\n b"
},
{
"path": "src/styles/modules/searchbar.scss",
"chars": 1468,
"preview": ".searchbar {\n display: flex;\n align-items: center;\n\n &.active {\n .searchbar-input-field {\n wi"
},
{
"path": "src/styles/modules/segmented-control.scss",
"chars": 674,
"preview": ".segmented-control {\n font-size: 0;\n display: inline-block;\n border: 1px solid $segmented-control-border-color;"
},
{
"path": "src/styles/modules/selector.scss",
"chars": 434,
"preview": ".selector {\n > .selector-option:last-child {\n background-size: 0;\n }\n}\n\n.selector-option {\n transition: "
},
{
"path": "src/styles/modules/sidelip.scss",
"chars": 575,
"preview": ".sidelip {\n position: fixed;\n left: 0;\n top: 0;\n display: block;\n height: 100%;\n overflow-y: auto;\n "
},
{
"path": "src/styles/modules/slideup.scss",
"chars": 717,
"preview": ".slideup {\n position: fixed;\n left: 0;\n bottom: 0;\n width: 100%;\n background-color: $slide-bg-color;\n "
},
{
"path": "src/styles/modules/stepbar.scss",
"chars": 1648,
"preview": ".stepbar {\n display: flex;\n align-items: center;\n}\n\n.stepbar-item {\n flex: 1;\n display: flex;\n align-item"
},
{
"path": "src/styles/modules/sticky.scss",
"chars": 203,
"preview": ".sticky-affix {\n position: fixed;\n z-index: 999;\n}\n\n.demo-sticky {\n display: inline-block;\n color: #fff;\n "
},
{
"path": "src/styles/modules/swipe.scss",
"chars": 767,
"preview": ".swipe {\n overflow: hidden;\n position: relative;\n}\n\n.swipe-indicators {\n position: absolute;\n left: 50%;\n "
},
{
"path": "src/styles/modules/tabbar.scss",
"chars": 754,
"preview": ".tabbar {\n position: relative;\n display: flex;\n height: $tabbar-height;\n margin-bottom: $tabbar-margin-botto"
},
{
"path": "src/styles/modules/tag.scss",
"chars": 997,
"preview": ".tag {\n display: inline-block;\n padding: $tag-padding;\n color: $tag-color;\n text-align: center;\n white-sp"
},
{
"path": "src/styles/modules/timeline.scss",
"chars": 1891,
"preview": ".timeline {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.timeline-item {\n position: relative;\n padding"
},
{
"path": "src/styles/modules/tip.scss",
"chars": 192,
"preview": ".tip {\n display: flex;\n align-items: center;\n padding: $tip-padding;\n\n .hr {\n flex: 1;\n }\n}\n\n.tip-"
},
{
"path": "src/styles/modules/toast.scss",
"chars": 1125,
"preview": ".toast {\n position: fixed;\n text-align: center;\n color: $toast-color;\n z-index: $z-index-toast;\n max-widt"
},
{
"path": "src/styles/modules/toggle.scss",
"chars": 872,
"preview": ".toggle {\n display: inline-block;\n position: relative;\n}\n\n.toggle-input {\n display: none;\n\n &:checked + .tog"
},
{
"path": "src/styles/modules.scss",
"chars": 1038,
"preview": "// css components\n@import 'modules/button';\n@import 'modules/group';\n@import 'modules/cell';\n@import 'modules/tag';\n@imp"
},
{
"path": "src/styles/utils/background.scss",
"chars": 681,
"preview": "// backgrounds\n.bg-inverse {\n color: $gray-lighter;\n background-color: $gray-dark;\n}\n\n.bg-faded {\n background-c"
},
{
"path": "src/styles/utils/border.scss",
"chars": 594,
"preview": "// shape\n.border-circle {\n border-radius: $border-radius-circle !important;\n}\n\n.border-pill {\n border-radius: $bor"
},
{
"path": "src/styles/utils/clearfix.scss",
"chars": 101,
"preview": ".clearfix {\n &::after {\n content: '';\n display: table;\n clear: both;\n }\n}\n"
},
{
"path": "src/styles/utils/display.scss",
"chars": 137,
"preview": "\n.invisible {\n visibility: hidden !important;\n}\n\n.hidden {\n display: none !important;\n}\n\n.block {\n display: blo"
},
{
"path": "src/styles/utils/flex.scss",
"chars": 613,
"preview": ".flex {\n overflow: hidden;\n display: flex;\n}\n\n.flex-vertical {\n height: 100%;\n flex-direction: column;\n}\n\n\n."
},
{
"path": "src/styles/utils/img.scss",
"chars": 115,
"preview": ".img-circle {\n border-radius: 50%;\n}\n\n.img-fluid {\n display: block;\n max-width: 100%;\n height: auto;\n}\n"
},
{
"path": "src/styles/utils/reboot.scss",
"chars": 7176,
"preview": "// scss-lint:disable ImportantRule, QualifyingElement, DuplicateProperty\n\n// Reboot\n//\n// Global resets to common HTML e"
},
{
"path": "src/styles/utils/spacing.scss",
"chars": 177,
"preview": ".no-margin {\n margin: 0 !important;\n}\n\n.no-padding {\n padding: 0 !important;\n}\n\n.gap-left {\n margin-right: $spa"
},
{
"path": "src/styles/utils/text.scss",
"chars": 656,
"preview": "// Contextual colors\n\n@include text-emphasis-variant('.text-muted', $text-muted);\n\n@include text-emphasis-variant('.text"
},
{
"path": "src/styles/utils.scss",
"chars": 218,
"preview": "@import 'utils/reboot';\n\n@import 'utils/flex';\n@import 'utils/spacing';\n@import 'utils/background';\n@import 'utils/text'"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the NewDadaFE/vue-impression GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (327.8 KB), approximately 81.5k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.