├── static ├── .gitkeep ├── css │ ├── fonts │ │ └── icons.ttf │ └── reset.css └── js │ └── adjustPage.js ├── README.md ├── .eslintignore ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── img └── calendar.png ├── src ├── assets │ └── logo.png ├── router │ └── index.js ├── main.js ├── App.vue ├── components │ └── page │ │ ├── index.vue │ │ └── calendarpage.vue └── common │ └── vhomed.js ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── .eslintrc.js └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # calendar 2 | 基于vue的移动端日历组件(酒店预订场景) 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/common/vhomed.js 4 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /img/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjwt/calendar/HEAD/img/calendar.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjwt/calendar/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /static/css/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjwt/calendar/HEAD/static/css/fonts/icons.ttf -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import calendarpage from '../components/page/calendarpage.vue'; 4 | import index from '../components/page/index.vue'; 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | component: index 12 | }, 13 | { 14 | path: '/calendarpage', 15 | component: calendarpage 16 | } 17 | ] 18 | }); 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 酒店住宿 6 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue'; 4 | import App from './App'; 5 | import router from './router'; 6 | import fastclick from 'fastclick'; 7 | import vhomed from './common/vhomed.js'; 8 | import axios from 'axios'; 9 | 10 | Vue.use(vhomed); 11 | Vue.config.productionTip = false; 12 | // 消除手机端点击300ms延迟 13 | fastclick.attach(document.body); 14 | Vue.prototype.$ajax = axios; 15 | 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: '#app', 19 | router, 20 | template: '', 21 | components: {App} 22 | }); 23 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | } 14 | } 15 | ], 16 | "stage-2" 17 | ], 18 | "plugins": [ 19 | "transform-runtime" 20 | ], 21 | "env": { 22 | "test": { 23 | "presets": [ 24 | "env", 25 | "stage-2" 26 | ], 27 | "plugins": [ 28 | "istanbul" 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | 'rules': { 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 21 | 'semi': ['error', 'always'], 22 | 'indent': ["warn", 4], 23 | 'space-before-function-paren': 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 49 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: './', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 80, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { 31 | '/api': { 32 | target: 'http://www.sojson.com', 33 | changeOrigin: true, 34 | pathRewrite: { 35 | '^/api': '' 36 | } 37 | } 38 | }, 39 | // CSS Sourcemaps off by default because relative paths are "buggy" 40 | // with this option, according to the CSS-Loader README 41 | // (https://github.com/webpack/css-loader#sourcemaps) 42 | // In our experience, they generally work as expected, 43 | // just be aware of this issue when enabling this option. 44 | cssSourceMap: false 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /static/css/reset.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video, input { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font-weight: normal; 23 | vertical-align: baseline; 24 | } 25 | 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, menu, nav, section { 29 | display: block; 30 | } 31 | 32 | body { 33 | line-height: 1; 34 | } 35 | 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: none; 43 | } 44 | 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | 50 | /* custom */ 51 | a { 52 | color: #7e8c8d; 53 | text-decoration: none; 54 | -webkit-backface-visibility: hidden; 55 | } 56 | 57 | li { 58 | list-style: none; 59 | } 60 | 61 | ::-webkit-scrollbar { 62 | width: 5px; 63 | height: 5px; 64 | } 65 | 66 | ::-webkit-scrollbar-track-piece { 67 | background-color: rgba(0, 0, 0, 0.2); 68 | -webkit-border-radius: 6px; 69 | } 70 | 71 | ::-webkit-scrollbar-thumb:vertical { 72 | height: 5px; 73 | background-color: rgba(125, 125, 125, 0.7); 74 | -webkit-border-radius: 6px; 75 | } 76 | 77 | ::-webkit-scrollbar-thumb:horizontal { 78 | width: 5px; 79 | background-color: rgba(125, 125, 125, 0.7); 80 | -webkit-border-radius: 6px; 81 | } 82 | 83 | html, body { 84 | width: 100%; 85 | } 86 | 87 | body { 88 | -webkit-text-size-adjust: none; 89 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 90 | } 91 | 92 | @font-face { 93 | font-family: 'iconfont'; 94 | src: url('./fonts/icons.ttf') format('truetype'); /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ 95 | } 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calendar", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "jinwt <380793594@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "better-scroll": "^0.4.0", 15 | "fastclick": "^1.0.6", 16 | "flatpickr": "^3.0.6", 17 | "vue": "^2.3.3", 18 | "vue-router": "^2.6.0" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^7.1.2", 22 | "axios": "^0.18.0", 23 | "babel-core": "^6.22.1", 24 | "babel-eslint": "^7.1.1", 25 | "babel-loader": "^7.1.1", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-preset-env": "^1.3.2", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "babel-register": "^6.22.0", 30 | "chalk": "^2.0.1", 31 | "connect-history-api-fallback": "^1.3.0", 32 | "copy-webpack-plugin": "^4.0.1", 33 | "css-loader": "^0.28.0", 34 | "cssnano": "^3.10.0", 35 | "eslint": "^3.19.0", 36 | "eslint-config-standard": "^6.2.1", 37 | "eslint-friendly-formatter": "^3.0.0", 38 | "eslint-loader": "^1.7.1", 39 | "eslint-plugin-html": "^3.0.0", 40 | "eslint-plugin-promise": "^3.4.0", 41 | "eslint-plugin-standard": "^2.0.1", 42 | "eventsource-polyfill": "^0.9.6", 43 | "express": "^4.14.1", 44 | "extract-text-webpack-plugin": "^2.0.0", 45 | "file-loader": "^0.11.1", 46 | "flatpickr": "^3.0.6", 47 | "friendly-errors-webpack-plugin": "^1.1.3", 48 | "html-webpack-plugin": "^2.28.0", 49 | "http-proxy-middleware": "^0.17.3", 50 | "node-sass": "^4.5.3", 51 | "opn": "^5.1.0", 52 | "optimize-css-assets-webpack-plugin": "^2.0.0", 53 | "ora": "^1.2.0", 54 | "rimraf": "^2.6.0", 55 | "sass-loader": "^6.0.6", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "style-loader": "^0.18.2", 59 | "url-loader": "^0.5.8", 60 | "vue-loader": "^12.1.0", 61 | "vue-style-loader": "^3.0.1", 62 | "vue-template-compiler": "^2.3.3", 63 | "webpack": "^2.6.1", 64 | "webpack-bundle-analyzer": "^2.2.1", 65 | "webpack-dev-middleware": "^1.10.0", 66 | "webpack-hot-middleware": "^2.18.0", 67 | "webpack-merge": "^4.1.0" 68 | }, 69 | "engines": { 70 | "node": ">= 4.0.0", 71 | "npm": ">= 3.0.0" 72 | }, 73 | "browserslist": [ 74 | "> 1%", 75 | "last 2 versions", 76 | "not ie <= 8" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /static/js/adjustPage.js: -------------------------------------------------------------------------------- 1 | // 考虑到有些应用只要用到页面适配不需要用到homed.js中的方法,古单独提出此部分 2 | (function (doc, win) { 3 | var docEl = doc.documentElement; 4 | 5 | // 一物理像素在不同屏幕的显示效果不一样。要根据devicePixelRatio来修改meta标签的scale 6 | (function () { 7 | var dpr = scale = '', 8 | isIPhone = win.navigator.appVersion.match(/iphone/gi), 9 | devicePixelRatio = win.devicePixelRatio; 10 | 11 | if (isIPhone) { 12 | // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 13 | if (devicePixelRatio >= 3) { 14 | dpr = 3; 15 | } else if (devicePixelRatio >= 2) { 16 | dpr = 2; 17 | } else { 18 | dpr = 1; 19 | } 20 | } else { // 其他设备下,仍旧使用1倍的方案 21 | dpr = 1; 22 | } 23 | scale = 1 / dpr; 24 | 25 | var metaEl = ""; 26 | metaEl = doc.createElement('meta'); 27 | metaEl.setAttribute('name', 'viewport'); 28 | metaEl.setAttribute('content', 'width=device-width,initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); 29 | if (docEl.firstElementChild) { 30 | docEl.firstElementChild.appendChild(metaEl); 31 | } else { 32 | var wrap = doc.createElement('div'); 33 | wrap.appendChild(metaEl); 34 | doc.write(wrap.innerHTML); 35 | } 36 | })(); 37 | 38 | var isMobile = navigator.userAgent.toLowerCase().match(/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile|micromessenger/i); 39 | resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', 40 | clientWidth = docEl.clientWidth, // 此处放在外层防止input唤起页面重新计算font-size 41 | clientHeight = docEl.clientHeight, 42 | min = Math.min(clientWidth, clientHeight), 43 | recalc = function () { 44 | if (!!isMobile) { 45 | // 移动端按640标准(以iphone5标准开发,iphone5上1rem == 100px)设置字体 46 | var fontSize = Math.floor(min / 64) * 10; 47 | docEl.style.fontSize = fontSize + 'px'; 48 | } else { // 此处为兼容pc端手动调整的,不影响手机上效果(pc上1px任然为1px) 49 | docEl.style.fontSize = '100px'; 50 | } 51 | // 页面横竖屏切换时让页面向上滚动到顶部,解决fixed定位浮动bug 52 | window.scrollTo(0, 0); 53 | }; 54 | 55 | // 如果浏览器不支持addEventListener方法,终止运行 56 | if (!doc.addEventListener) return; 57 | win.addEventListener(resizeEvt, recalc, false); // 页面调试(横竖屏)时重新设置字体 58 | doc.addEventListener('DOMContentLoaded', recalc, false); // 页面dom结构加载完设置字体 59 | })(document, window); 60 | -------------------------------------------------------------------------------- /src/components/page/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 39 | 40 | 92 | -------------------------------------------------------------------------------- /src/common/vhomed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by 38079 on 2017/8/17. 3 | */ 4 | let vhomed = { 5 | }; 6 | vhomed.install = function (Vue, options) { 7 | // 调整页面 8 | (function (doc, win) { 9 | let docEl = doc.documentElement; 10 | 11 | // 一物理像素在不同屏幕的显示效果不一样。要根据devicePixelRatio来修改meta标签的scale 12 | (function () { 13 | let dpr = 1; 14 | let scale = 1; 15 | let isIPhone = win.navigator.appVersion.match(/iphone/gi); 16 | let devicePixelRatio = win.devicePixelRatio; 17 | 18 | if (isIPhone) { 19 | // iOS下,对于2和3的屏,用2、3倍的方案,其余的用1倍方案 20 | if (devicePixelRatio >= 3) { 21 | dpr = 3; 22 | } else if (devicePixelRatio >= 2) { 23 | dpr = 2; 24 | } 25 | } 26 | scale = 1 / dpr; 27 | 28 | let metaEl = ''; 29 | metaEl = doc.createElement('meta'); 30 | metaEl.setAttribute('name', 'viewport'); 31 | metaEl.setAttribute('content', 'width=device-width,initial-scale=' + scale + ',user-scalable=no'); 32 | if (docEl.firstElementChild) { 33 | docEl.firstElementChild.appendChild(metaEl); 34 | } else { 35 | let wrap = doc.createElement('div'); 36 | wrap.appendChild(metaEl); 37 | doc.write(wrap.innerHTML); 38 | } 39 | let divElBasement = doc.createElement('div'); 40 | divElBasement.setAttribute('id', 'basement'); 41 | let bodyEl = doc.body; 42 | bodyEl.appendChild(divElBasement); 43 | })(); 44 | 45 | let isMobile = navigator.userAgent.match(/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile|micromessenger/i); 46 | let resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'; 47 | let clientWidth = docEl.clientWidth; // 此处放在外层防止input唤起页面重新计算font-size 48 | let clientHeight = docEl.clientHeight; 49 | let min = Math.min(clientWidth, clientHeight); 50 | let recalc = function () { 51 | /* eslint-enable */ 52 | if (isMobile) { 53 | // 移动端按750标准(以iphone5标准开发,iphone5上1rem == 100px)设置字体 54 | let fontSize = Math.floor(min / 75) * 10; 55 | docEl.style.fontSize = fontSize + 'px'; 56 | } else { // 此处为兼容pc端手动调整的,不影响手机上效果(pc上1px任然为1px) 57 | docEl.style.fontSize = '100px'; 58 | } 59 | // 页面横竖屏切换时让页面向上滚动到顶部,解决fixed定位浮动bug 60 | window.scrollTo(0, 0); 61 | }; 62 | 63 | // 如果浏览器不支持addEventListener方法,终止运行 64 | if (!doc.addEventListener) return; 65 | win.addEventListener(resizeEvt, recalc, false); // 页面调试(横竖屏)时重新设置字体 66 | doc.addEventListener('DOMContentLoaded', recalc, false); // 页面dom结构加载完设置字体 67 | })(document, window); 68 | Vue.prototype.$util = { 69 | // 禁用缩放手势 70 | forbidScal: function () { 71 | document.addEventListener('touchstart', function (event) { 72 | if (event.touches.length > 1) { 73 | event.preventDefault(); 74 | } 75 | }); 76 | let lastTouchEnd = 0; 77 | document.addEventListener('touchend', function (event) { 78 | let now = (new Date()).getTime(); 79 | if (now - lastTouchEnd <= 300) { 80 | event.preventDefault(); 81 | } 82 | lastTouchEnd = now; 83 | }, false); 84 | } 85 | }; 86 | }; 87 | export default vhomed; 88 | 89 | -------------------------------------------------------------------------------- /src/components/page/calendarpage.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 228 | 229 | 330 | --------------------------------------------------------------------------------