├── static └── .gitkeep ├── .eslintignore ├── .npminstall.done ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── src ├── components │ ├── ba.vue │ └── ba.js ├── directives │ ├── auto-pageview.js │ ├── utils.js │ ├── track-pageview.js │ └── track-event.js ├── index.js └── install.js ├── .editorconfig ├── .postcssrc.js ├── .gitignore ├── .npmignore ├── index.html ├── .babelrc ├── test └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── runner.js │ └── nightwatch.conf.js ├── .eslintrc.js ├── package.json ├── README.md └── dist └── index.js /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.npminstall.done: -------------------------------------------------------------------------------- 1 | Thu Jul 13 2017 19:55:37 GMT+0800 (CST) -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /src/components/ba.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /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 = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/e2e/reports 7 | selenium-debug.log 8 | 9 | # Editor directories and files 10 | .idea 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/e2e/reports 7 | selenium-debug.log 8 | 9 | # Editor directories and files 10 | .idea 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-ba 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/directives/auto-pageview.js: -------------------------------------------------------------------------------- 1 | import ba from '../index' 2 | import { notChanged } from './utils' 3 | export default function (el, binding) { 4 | if (notChanged(binding)) return 5 | 6 | let args = [] 7 | if (binding.value === false || binding.value === 'false') args.push(false) 8 | else args.push(true) 9 | ba.setAutoPageview(...args) 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ba.js: -------------------------------------------------------------------------------- 1 | import ba from '../index' 2 | 3 | export default { 4 | name: 'ba', 5 | props: { 6 | events: { 7 | type: Array, 8 | default: () => [] 9 | }, 10 | pageviews: { 11 | type: Array, 12 | default: () => [] 13 | } 14 | }, 15 | mounted () { 16 | this.pageviews.forEach((pv) => { 17 | let args = null 18 | if (typeof pv === 'string') { 19 | args = pv.split(',') 20 | args.forEach((arg, i) => (args[i] = arg.trim())) 21 | } else if (typeof pv === 'object') { 22 | args = pv 23 | } 24 | ba.trackPageview(...args) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/directives/utils.js: -------------------------------------------------------------------------------- 1 | import deepEqual from 'deep-equal' 2 | 3 | /** 4 | * if the binding value is equal to oldeValue 5 | */ 6 | export function notChanged (binding) { 7 | if (binding.oldValue !== undefined) { 8 | if (typeof binding.value === 'object') { 9 | return deepEqual(binding.value, binding.oldValue) 10 | } else { 11 | return binding.value === binding.oldValue 12 | } 13 | } else { 14 | return false 15 | } 16 | } 17 | 18 | /** 19 | * if the binding value is empty 20 | */ 21 | export function isEmpty (binding) { 22 | return binding.value === '' || binding.value === undefined || binding.value === null 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/directives/track-pageview.js: -------------------------------------------------------------------------------- 1 | import ba from '../index' 2 | import { notChanged, isEmpty } from './utils' 3 | 4 | export let watch = [] 5 | 6 | const trackPageview = { 7 | bind (el, binding) { 8 | let index = watch.findIndex(element => element === el) 9 | let isWatched = index !== -1 10 | if (el.style.display === 'none') { 11 | if (!isWatched) watch.push(el) 12 | return 13 | } else { 14 | if (isWatched) watch.splice(index, 1) 15 | } 16 | 17 | if (!isWatched && (notChanged(binding) || isEmpty(binding))) return 18 | 19 | let args = [] 20 | 21 | if (typeof binding.value === 'object') { 22 | let value = binding.value 23 | if (value.pageURL) args.push(value.pageURL) 24 | } else if (typeof binding.value === 'string' && binding.value) { 25 | args = binding.value.split(',') 26 | args.forEach((arg, i) => (arg[i] = arg.trim())) 27 | } 28 | ba.trackPageview(...args) 29 | }, 30 | unbind (el, binding) { 31 | let index = watch.findIndex(element => element === el) 32 | if (index !== -1) watch.splice(index, 1) 33 | } 34 | } 35 | 36 | trackPageview.update = trackPageview.bind 37 | 38 | export default trackPageview 39 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/directives/track-event.js: -------------------------------------------------------------------------------- 1 | import ba from '../index' 2 | import { notChanged, isEmpty } from './utils' 3 | 4 | export default function (el, binding, vnode) { 5 | if (notChanged(binding) || isEmpty(binding)) { 6 | return 7 | } 8 | 9 | let args = [] 10 | let events = Object.keys(binding.modifiers).map(modifier => { 11 | if (binding.modifiers[modifier]) { 12 | return modifier 13 | } 14 | }) 15 | 16 | if (typeof binding.value === 'object') { 17 | let value = binding.value 18 | if (value.category) args.push(value.category) 19 | if (value.action) args.push(value.action) 20 | if (value.opt_label) args.push(value.opt_label) 21 | if (value.opt_value) args.push(value.opt_value) 22 | } else if (typeof binding.value === 'string') { 23 | args = binding.value.split(',') 24 | args.forEach((arg, i) => (args[i] = arg.trim())) 25 | } 26 | 27 | if (!events.length) events.push('click') // default listen click 28 | 29 | events.forEach((eventValue) => { 30 | const customTag = 'custom' 31 | let [event, custom] = eventValue.split(':') 32 | if (custom === customTag) { 33 | vnode.componentInstance.$on(event, () => ba.trackEvent(...args), false) 34 | } else { 35 | el.addEventListener(event, () => ba.trackEvent(...args), false) 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /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: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import install from './install' 2 | 3 | const deferred = {} 4 | deferred.promise = new Promise((resolve, reject) => { 5 | deferred.resolve = resolve 6 | deferred.reject = reject 7 | }) 8 | const methods = [ 9 | 'trackPageview', 10 | 'trackEvent', 11 | 'setCustomVar' 12 | ] 13 | 14 | const ba = { 15 | /** 16 | * internal user only 17 | */ 18 | _cache: [], 19 | /** 20 | * internal user only, resolve the promise 21 | */ 22 | _resolve () { 23 | deferred.resolve() 24 | }, 25 | /** 26 | * internal user only, reject the promise 27 | */ 28 | _reject () { 29 | deferred.reject() 30 | }, 31 | 32 | /** 33 | * push the args into _czc, or _cache if the script is not loaded yet 34 | */ 35 | _push (...args) { 36 | this.debug(args) 37 | if (window._hmt) { 38 | window._hmt.push(...args) 39 | } else { 40 | this._cache.push(...args) 41 | } 42 | }, 43 | /** 44 | * general method to create baidu analystics apis 45 | */ 46 | _createMethod (method) { 47 | return (...args) => { 48 | this._push([`_${method}`, ...args]) 49 | } 50 | }, 51 | 52 | /** 53 | * debug 54 | */ 55 | debug () {}, 56 | /** 57 | * the plugins is ready when the script is loaded 58 | */ 59 | ready () { 60 | return deferred.promise 61 | }, 62 | /** 63 | * install function 64 | */ 65 | 66 | install, 67 | /** 68 | * patch up to create new api 69 | */ 70 | patch (method) { 71 | this[method] = this._createMethod(method) 72 | } 73 | } 74 | 75 | // uweb apis 76 | methods.forEach((method) => (ba[method] = ba._createMethod(method))) 77 | 78 | if (window.Vue) { 79 | window.ba = ba 80 | } 81 | 82 | export default ba 83 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | import autoPageview from './directives/auto-pageview' 2 | import trackEvent from '././directives/track-event' 3 | import trackPageview from '././directives/track-pageview' 4 | export default function install (Vue, options) { 5 | if (this.install.installed) return 6 | 7 | if (options.debug) { 8 | this.debug = console.log 9 | } else { 10 | this.debug = () => {} 11 | } 12 | 13 | let siteId = null 14 | 15 | if (typeof options === 'object') { 16 | siteId = options.siteId 17 | if (options.autoPageview !== false) { 18 | options.autoPageview = true 19 | } 20 | } else { 21 | siteId = options 22 | } 23 | 24 | if (!siteId) { 25 | return console.error(' siteId is missing') 26 | } 27 | 28 | this.install.installed = true 29 | // insert baidu analystics scripts 30 | const script = document.createElement('script') 31 | const src = `https://hm.baidu.com/hm.js?` + siteId 32 | const realSrc = options.src || src 33 | script.innerHTML = 'var _hmt = _hmt || []; (function(){var hm = document.createElement(\'script\');hm.src="' + 34 | realSrc + 35 | '";var s = document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})()' 36 | script.onload = () => { 37 | // if the global object is exist, resolve the promise, otherwise reject it 38 | if (window._hmt) { 39 | this._resolve() 40 | } else { 41 | console.error('loading ba statistics script failed, please check src and siteId') 42 | return this._reject() 43 | } 44 | this._cache.forEach((cache) => { 45 | window._hmt.push(cache) 46 | }) 47 | this._cache = [] 48 | } 49 | document.body.appendChild(script) 50 | Object.defineProperty(Vue.prototype, '$ba', { 51 | get: () => this 52 | }) 53 | 54 | Vue.directive('auto-pageview', autoPageview) 55 | Vue.directive('track-event', trackEvent) 56 | Vue.directive('track-pageview', trackPageview) 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-ba", 3 | "version": "1.2.7", 4 | "description": "baidu analystics vue module", 5 | "author": "minlingchao", 6 | "main": "dist/index", 7 | "license": "MIT", 8 | "keywords": [ 9 | "statistics", 10 | "vue-plugin", 11 | "baidu analystics", 12 | "" 13 | ], 14 | "private": false, 15 | "scripts": { 16 | "dev": "node build/dev-server.js", 17 | "start": "node build/dev-server.js", 18 | "build": "node build/build.js", 19 | "e2e": "node test/e2e/runner.js", 20 | "test": "npm run e2e", 21 | "lint": "eslint --ext .js,.vue src test/e2e/specs", 22 | "release": "npm run build && npm publish" 23 | }, 24 | "dependencies": { 25 | "vue": "^2.3.3", 26 | "deep-equal": "^1.0.1" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^6.7.2", 30 | "babel-core": "^6.22.1", 31 | "babel-eslint": "^7.1.1", 32 | "babel-loader": "^6.2.10", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "babel-register": "^6.22.0", 37 | "chalk": "^1.1.3", 38 | "connect-history-api-fallback": "^1.3.0", 39 | "copy-webpack-plugin": "^4.0.1", 40 | "css-loader": "^0.28.0", 41 | "eslint": "^3.19.0", 42 | "eslint-friendly-formatter": "^2.0.7", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-html": "^3.0.0", 45 | "eslint-config-standard": "^6.2.1", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^2.0.1", 48 | "eventsource-polyfill": "^0.9.6", 49 | "express": "^4.14.1", 50 | "extract-text-webpack-plugin": "^2.0.0", 51 | "file-loader": "^0.11.1", 52 | "friendly-errors-webpack-plugin": "^1.1.3", 53 | "html-webpack-plugin": "^2.28.0", 54 | "http-proxy-middleware": "^0.17.3", 55 | "webpack-bundle-analyzer": "^3.6.1", 56 | "chromedriver": "^2.27.2", 57 | "cross-spawn": "^5.0.1", 58 | "nightwatch": "^0.9.12", 59 | "selenium-server": "^3.0.1", 60 | "semver": "^5.3.0", 61 | "shelljs": "^0.7.6", 62 | "opn": "^4.0.2", 63 | "optimize-css-assets-webpack-plugin": "^1.3.0", 64 | "ora": "^1.2.0", 65 | "rimraf": "^2.6.0", 66 | "url-loader": "^0.5.8", 67 | "vue-loader": "^12.1.0", 68 | "vue-style-loader": "^3.0.1", 69 | "vue-template-compiler": "^2.3.3", 70 | "webpack": "^2.6.1", 71 | "webpack-dev-middleware": "^1.10.0", 72 | "webpack-hot-middleware": "^2.18.0", 73 | "webpack-merge": "^4.1.0" 74 | }, 75 | "engines": { 76 | "node": ">= 4.0.0", 77 | "npm": ">= 3.0.0" 78 | }, 79 | "browserslist": [ 80 | "> 1%", 81 | "last 2 versions", 82 | "not ie <= 8" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-ba 2 | 3 | > vuejs 百度统计埋点插件 4 | > 5 | > 项目参考:https://github.com/raychenfj/vue-uweb 6 | 7 | ## 项目地址 8 | 9 | https://github.com/minlingchao1/vue-ba 10 | 11 | ## 1. 安装 12 | 13 | ``` 14 | npm install vue-ba --save 15 | 16 | ``` 17 | 直接在页面中引用 18 | ``` 19 |