├── .gitignore ├── spa-angular ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── hero.ts │ │ ├── app.component.ts │ │ ├── messages │ │ │ ├── messages.component.html │ │ │ ├── messages.component.ts │ │ │ ├── messages.component.css │ │ │ └── messages.component.spec.ts │ │ ├── dashboard │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.ts │ │ │ ├── dashboard.component.spec.ts │ │ │ └── dashboard.component.css │ │ ├── message.service.ts │ │ ├── hero-detail │ │ │ ├── hero-detail.component.html │ │ │ ├── hero-detail.component.css │ │ │ ├── hero-detail.component.spec.ts │ │ │ └── hero-detail.component.ts │ │ ├── hero-search │ │ │ ├── hero-search.component.html │ │ │ ├── hero-search.component.spec.ts │ │ │ ├── hero-search.component.css │ │ │ └── hero-search.component.ts │ │ ├── hero.service.spec.ts │ │ ├── message.service.spec.ts │ │ ├── in-memory-data.service.spec.ts │ │ ├── mock-heroes.ts │ │ ├── heroes │ │ │ ├── heroes.component.html │ │ │ ├── heroes.component.spec.ts │ │ │ ├── heroes.component.ts │ │ │ └── heroes.component.css │ │ ├── app.component.css │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── in-memory-data.service.ts │ │ ├── app.component.spec.ts │ │ ├── app.module.ts │ │ └── hero.service.ts │ ├── favicon.ico │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── browserslist │ ├── index.html │ ├── styles.css │ ├── test.ts │ ├── karma.conf.js │ ├── main.ts │ └── polyfills.ts ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.e2e.json │ └── protractor.conf.js ├── .editorconfig ├── tsconfig.json ├── .gitignore ├── README.md ├── package.json ├── tslint.json └── angular.json ├── spa-react ├── .env ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── App.css │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── App.js │ ├── logo.svg │ └── serviceWorker.js ├── config │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── env.js │ └── webpackDevServer.config.js ├── .gitignore ├── scripts │ ├── test.js │ ├── start.js │ └── build.js ├── README.md └── package.json ├── src ├── config │ ├── config.dev.js │ ├── config.prod.js │ ├── config.test.js │ ├── index.js │ └── config.default.js ├── common │ ├── listener.js │ ├── interval.js │ ├── ws.js │ ├── ajax.js │ └── util.js ├── index.js ├── core │ ├── listener │ │ ├── beforeUnload.js │ │ ├── popState.js │ │ ├── visibilityChange.js │ │ ├── pushState.js │ │ ├── onload.js │ │ ├── message.js │ │ └── click.js │ ├── sender.js │ ├── params.js │ ├── interval │ │ ├── heatMap.js │ │ └── phoneList.js │ └── index.js ├── iframe.html └── index.html ├── tech-stack.png ├── dist ├── vue │ ├── favicon.ico │ ├── img │ │ └── logo.82b9c7a5.png │ ├── index.html │ ├── css │ │ └── app.e775cd24.css │ └── js │ │ └── app.3d9f652a.js ├── react │ ├── favicon.ico │ ├── manifest.json │ ├── static │ │ ├── css │ │ │ ├── main.28732bcc.chunk.css │ │ │ └── main.28732bcc.chunk.css.map │ │ └── js │ │ │ ├── runtime~main.4a686d48.js │ │ │ ├── main.80af4b44.chunk.js │ │ │ └── runtime~main.4a686d48.js.map │ ├── precache-manifest.1135b6be65e6d9e26e2ec6ede87ff50d.js │ ├── asset-manifest.json │ ├── service-worker.js │ └── index.html ├── angular │ ├── favicon.ico │ ├── index.html │ ├── runtime.js.map │ └── runtime.js ├── iframe.html └── index-sdk.html ├── spa-vue ├── babel.config.js ├── vue.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── routes.js │ ├── components │ │ ├── HelloWorld.vue │ │ ├── Bar.vue │ │ └── Foo.vue │ ├── main.js │ └── App.vue ├── .gitignore ├── README.md └── package.json ├── test ├── config.test.js └── util.test.js ├── server ├── view │ ├── heatMap.ejs │ ├── common │ │ ├── footer.ejs │ │ ├── header.ejs │ │ └── demo.ejs │ ├── phoneList.ejs │ ├── demoA.ejs │ ├── demoB.ejs │ ├── demoC.ejs │ └── index.ejs ├── ws.js ├── index.js └── router.js ├── config ├── webpack.dev.js ├── webpack.prod.js └── webpack.common.js ├── nginx.conf ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules -------------------------------------------------------------------------------- /spa-angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spa-react/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /src/config/config.dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/config/config.prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/config/config.test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /tech-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/tech-stack.png -------------------------------------------------------------------------------- /dist/vue/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/dist/vue/favicon.ico -------------------------------------------------------------------------------- /dist/react/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/dist/react/favicon.ico -------------------------------------------------------------------------------- /spa-angular/src/app/hero.ts: -------------------------------------------------------------------------------- 1 | export class Hero { 2 | id: number; 3 | name: string; 4 | } -------------------------------------------------------------------------------- /spa-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /dist/angular/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/dist/angular/favicon.ico -------------------------------------------------------------------------------- /spa-vue/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: 'vue', 3 | outputDir: '../dist/vue' 4 | } -------------------------------------------------------------------------------- /spa-angular/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/spa-angular/src/favicon.ico -------------------------------------------------------------------------------- /spa-vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/spa-vue/public/favicon.ico -------------------------------------------------------------------------------- /spa-vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/spa-vue/src/assets/logo.png -------------------------------------------------------------------------------- /dist/vue/img/logo.82b9c7a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/dist/vue/img/logo.82b9c7a5.png -------------------------------------------------------------------------------- /spa-angular/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /spa-react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drafish/dc-sdk-js/HEAD/spa-react/public/favicon.ico -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.assign(require('./config.default'), require(`./config.${process.env.NODE_ENV}`)) 2 | -------------------------------------------------------------------------------- /spa-react/src/App.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | width: 200px; 3 | float: left; 4 | } 5 | 6 | .container { 7 | width: 70%; 8 | float: left; 9 | } 10 | -------------------------------------------------------------------------------- /spa-vue/src/routes.js: -------------------------------------------------------------------------------- 1 | import Foo from './components/Foo' 2 | import Bar from './components/Bar' 3 | 4 | const routes = [ 5 | { path: '/foo', component: Foo }, 6 | { path: '/bar', component: Bar } 7 | ] 8 | 9 | export default routes -------------------------------------------------------------------------------- /spa-angular/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /spa-angular/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spa-angular/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /spa-angular/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'Tour of Heroes'; 10 | } 11 | -------------------------------------------------------------------------------- /spa-angular/src/app/messages/messages.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Messages

4 | 6 |
{{message}}
7 | 8 |
-------------------------------------------------------------------------------- /spa-react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /test/config.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | 3 | describe('config test', function () { 4 | it('domain should be www.test.com', function () { 5 | process.env.NODE_ENV = 'test' 6 | var config = require('../src/config') 7 | expect(config.domain).to.be.equal('www.test.com') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /spa-angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /spa-vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /spa-angular/src/app/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |

Top Heroes

2 |
3 | 5 |
6 |

{{hero.name}}

7 |
8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /spa-angular/src/app/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class MessageService { 7 | messages: string[] = []; 8 | 9 | add(message: string) { 10 | this.messages.push(message); 11 | } 12 | 13 | clear() { 14 | this.messages = []; 15 | } 16 | } -------------------------------------------------------------------------------- /spa-angular/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/config/config.default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | serverUrl: 'http://localhost:8081/api', 3 | wsUrl: 'ws://localhost:8081', 4 | channelCode: 'default channelCode', 5 | token: 'default token', 6 | heatmapUrls: ["http://localhost:8081/heatMap*"], 7 | heatMapDelay: 20000, 8 | phoneListDelay: 20000, 9 | iframe: 'http://localhost:8080/iframe.html' 10 | } 11 | -------------------------------------------------------------------------------- /dist/react/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /spa-angular/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getTitleText()).toEqual('Welcome to spa-angular!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /spa-angular/src/app/hero-detail/hero-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{hero.name | uppercase}} Details

3 |
id: {{hero.id}}
4 |
5 | 8 |
9 | 10 | 11 |
-------------------------------------------------------------------------------- /spa-react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /server/view/heatMap.ejs: -------------------------------------------------------------------------------- 1 | <%- include('common/header', {title: title}); %> 2 |

点击 P 标签

3 | 4 |
点击 DIV 标签
5 | 6 |
7 |

这适用于测试热力图模糊匹配的锚点列表

8 |

part1

9 |

part2

10 |

part3

11 |

part4

12 |

part5

13 | 14 |
15 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /spa-angular/src/app/hero-search/hero-search.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Hero Search

3 | 4 | 5 | 6 | 13 |
-------------------------------------------------------------------------------- /spa-react/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /spa-angular/src/app/hero.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { HeroService } from './hero.service'; 4 | 5 | describe('HeroService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: HeroService = TestBed.get(HeroService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /spa-angular/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spa-angular/src/app/message.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MessageService } from './message.service'; 4 | 5 | describe('MessageService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: MessageService = TestBed.get(MessageService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /spa-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /dist/react/static/css/main.28732bcc.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.sidebar{width:200px;float:left}.container{width:70%;float:left} 2 | /*# sourceMappingURL=main.28732bcc.chunk.css.map */ -------------------------------------------------------------------------------- /spa-angular/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /spa-angular/src/app/in-memory-data.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { InMemoryDataService } from './in-memory-data.service'; 4 | 5 | describe('InMemoryDataService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: InMemoryDataService = TestBed.get(InMemoryDataService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /spa-angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SpaAngular 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spa-react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const common = require('./webpack.common.js') 4 | 5 | module.exports = common.map(config => merge(config, { 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | devServer: { 9 | contentBase: '../dist' 10 | }, 11 | plugins: [ 12 | new webpack.DefinePlugin({ 13 | 'process.env.NODE_ENV': JSON.stringify('dev') 14 | }) 15 | ] 16 | })) 17 | -------------------------------------------------------------------------------- /spa-angular/src/app/messages/messages.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MessageService } from '../message.service'; 3 | 4 | @Component({ 5 | selector: 'app-messages', 6 | templateUrl: './messages.component.html', 7 | styleUrls: ['./messages.component.css'] 8 | }) 9 | export class MessagesComponent implements OnInit { 10 | 11 | constructor(public messageService: MessageService) {} 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /spa-angular/src/app/mock-heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from './hero'; 2 | 3 | export const HEROES: Hero[] = [ 4 | { id: 11, name: 'Mr. Nice' }, 5 | { id: 12, name: 'Narco' }, 6 | { id: 13, name: 'Bombasto' }, 7 | { id: 14, name: 'Celeritas' }, 8 | { id: 15, name: 'Magneta' }, 9 | { id: 16, name: 'RubberMan' }, 10 | { id: 17, name: 'Dynama' }, 11 | { id: 18, name: 'Dr IQ' }, 12 | { id: 19, name: 'Magma' }, 13 | { id: 20, name: 'Tornado' } 14 | ]; -------------------------------------------------------------------------------- /server/view/common/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /spa-vue/README.md: -------------------------------------------------------------------------------- 1 | # spa 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /spa-angular/src/styles.css: -------------------------------------------------------------------------------- 1 | /* Application-wide Styles */ 2 | h1 { 3 | color: #369; 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-size: 250%; 6 | } 7 | h2, h3 { 8 | color: #444; 9 | font-family: Arial, Helvetica, sans-serif; 10 | font-weight: lighter; 11 | } 12 | body { 13 | margin: 2em; 14 | } 15 | body, input[type="text"], button { 16 | color: #888; 17 | font-family: Cambria, Georgia; 18 | } 19 | /* everywhere else */ 20 | * { 21 | font-family: Arial, Helvetica, sans-serif; 22 | } -------------------------------------------------------------------------------- /spa-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/ws.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws') 2 | 3 | function initWs (server) { 4 | const ws = new WebSocket.Server({server}) 5 | ws.on('connection', function connection(socket) { 6 | console.log('server: receive connection.'); 7 | 8 | socket.on('message', function incoming(message) { 9 | console.log('server: received: %s', message); 10 | socket.send(JSON.stringify({ 11 | "code": "200", 12 | "message": "", 13 | "body": {} 14 | })); 15 | }); 16 | }); 17 | } 18 | 19 | module.exports = initWs 20 | -------------------------------------------------------------------------------- /src/common/listener.js: -------------------------------------------------------------------------------- 1 | import { addEvent, removeEvent } from './util' 2 | 3 | export default class Listener { 4 | constructor (target, type) { 5 | this.target = target 6 | this.type = type 7 | } 8 | 9 | handler () { 10 | console.log('Listener handler') 11 | } 12 | 13 | add () { 14 | addEvent(this.target, this.type, this.handler.bind(this)) 15 | console.log(`${this.type} event added`) 16 | } 17 | 18 | remove () { 19 | removeEvent(this.target, this.type, this.handler) 20 | console.log(`${this.type} event removed`) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spa-vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /spa-vue/src/components/Bar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 19 | 35 | -------------------------------------------------------------------------------- /spa-vue/src/components/Foo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 19 | 35 | -------------------------------------------------------------------------------- /spa-angular/src/app/heroes/heroes.component.html: -------------------------------------------------------------------------------- 1 |

My Heroes

2 | 3 |
4 | 7 | 8 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /spa-vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | spa 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dist/react/precache-manifest.1135b6be65e6d9e26e2ec6ede87ff50d.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "4a686d48d5a089750c49", 4 | "url": "./static/js/runtime~main.4a686d48.js" 5 | }, 6 | { 7 | "revision": "80af4b44e22152e069b6", 8 | "url": "./static/js/main.80af4b44.chunk.js" 9 | }, 10 | { 11 | "revision": "7b3bf74e5a9fb5246a94", 12 | "url": "./static/js/1.7b3bf74e.chunk.js" 13 | }, 14 | { 15 | "revision": "80af4b44e22152e069b6", 16 | "url": "./static/css/main.28732bcc.chunk.css" 17 | }, 18 | { 19 | "revision": "95a7a3cc3b00abafab856c572a709038", 20 | "url": "./index.html" 21 | } 22 | ]; -------------------------------------------------------------------------------- /test/util.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | var util = require('../src/common/util.js') 3 | 4 | describe('util test', function () { 5 | it('random string length should be 32', function () { 6 | expect(util.random().length).to.be.equal(32) 7 | }) 8 | 9 | it('uuid string length should be 46', function () { 10 | expect(util.uuid().length).to.be.equal(46) 11 | }) 12 | 13 | var arr = ['aa', 'bb', 'cc'] 14 | it('index of cc should be 3', function () { 15 | expect(util.indexOf(arr, 'cc')).to.be.equal(2) 16 | }) 17 | 18 | it('index of dd should be -1', function () { 19 | expect(util.indexOf(arr, 'dd')).to.be.equal(-1) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // 低版本浏览器支持,最低支持到IE8 2 | require('core-js/features/object/define-property') 3 | require('core-js/features/object/create') 4 | require('core-js/features/object/assign') 5 | require('core-js/features/array/for-each') 6 | require('core-js/features/array/index-of') 7 | // require('core-js/features/array/map') 8 | require('core-js/features/function/bind') 9 | require('core-js/features/promise') 10 | 11 | const DataCF = require('./core').default 12 | 13 | let dataCF = new DataCF() 14 | 15 | dataCF.start() 16 | 17 | window.sdk = { 18 | storeUserId: dataCF.storeUserId.bind(dataCF), 19 | dispatch: dataCF.dispatch.bind(dataCF), 20 | getDeviceId: dataCF.getDeviceId 21 | } 22 | -------------------------------------------------------------------------------- /spa-angular/src/app/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Hero } from '../hero'; 3 | import { HeroService } from '../hero.service'; 4 | 5 | @Component({ 6 | selector: 'app-dashboard', 7 | templateUrl: './dashboard.component.html', 8 | styleUrls: [ './dashboard.component.css' ] 9 | }) 10 | export class DashboardComponent implements OnInit { 11 | heroes: Hero[] = []; 12 | 13 | constructor(private heroService: HeroService) { } 14 | 15 | ngOnInit() { 16 | this.getHeroes(); 17 | } 18 | 19 | getHeroes(): void { 20 | this.heroService.getHeroes() 21 | .subscribe(heroes => this.heroes = heroes.slice(1, 5)); 22 | } 23 | } -------------------------------------------------------------------------------- /spa-angular/src/app/hero-detail/hero-detail.component.css: -------------------------------------------------------------------------------- 1 | /* HeroDetailComponent's private CSS styles */ 2 | label { 3 | display: inline-block; 4 | width: 3em; 5 | margin: .5em 0; 6 | color: #607D8B; 7 | font-weight: bold; 8 | } 9 | input { 10 | height: 2em; 11 | font-size: 1em; 12 | padding-left: .4em; 13 | } 14 | button { 15 | margin-top: 20px; 16 | font-family: Arial; 17 | background-color: #eee; 18 | border: none; 19 | padding: 5px 10px; 20 | border-radius: 4px; 21 | cursor: pointer; 22 | cursor: hand; 23 | } 24 | button:hover { 25 | background-color: #cfd8dc; 26 | } 27 | button:disabled { 28 | background-color: #eee; 29 | color: #ccc; 30 | cursor: auto; 31 | } -------------------------------------------------------------------------------- /spa-angular/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | /* AppComponent's private CSS styles */ 2 | h1 { 3 | font-size: 1.2em; 4 | color: #999; 5 | margin-bottom: 0; 6 | } 7 | h2 { 8 | font-size: 2em; 9 | margin-top: 0; 10 | padding-top: 0; 11 | } 12 | nav a { 13 | padding: 5px 10px; 14 | text-decoration: none; 15 | margin-top: 10px; 16 | display: inline-block; 17 | background-color: #eee; 18 | border-radius: 4px; 19 | } 20 | nav a:visited, a:link { 21 | color: #607d8b; 22 | } 23 | nav a:hover { 24 | color: #039be5; 25 | background-color: #cfd8dc; 26 | } 27 | nav a.active { 28 | color: #039be5; 29 | } 30 | .sidebar { 31 | width: 200px; 32 | float: left; 33 | } 34 | .container { 35 | width: 70%; 36 | float: left; 37 | } -------------------------------------------------------------------------------- /dist/vue/index.html: -------------------------------------------------------------------------------- 1 | spa
-------------------------------------------------------------------------------- /src/common/interval.js: -------------------------------------------------------------------------------- 1 | export default class Interval { 2 | constructor (delay, type) { 3 | this.delay = delay 4 | this.id = null 5 | this.startTime = null 6 | this.type = type 7 | } 8 | 9 | code () { 10 | console.log('Interval code') 11 | } 12 | 13 | set () { 14 | this.id = setInterval(this.code.bind(this), this.delay) 15 | this.startTime = new Date().getTime() 16 | console.log(`${this.type} interval setted`) 17 | } 18 | 19 | clear () { 20 | if (this.id !== null) { 21 | clearInterval(this.id) 22 | this.id = null 23 | console.log(`${this.type} interval cleared`) 24 | } else { 25 | console.log(`${this.type} interval is not setted`) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spa-angular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /dist/vue/css/app.e775cd24.css: -------------------------------------------------------------------------------- 1 | h3[data-v-0e629148]{margin:40px 0 0}ul[data-v-0e629148]{list-style-type:none;padding:0}li[data-v-0e629148]{display:inline-block;margin:0 10px}a[data-v-0e629148]{color:#42b983}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}.sidebar{width:200px;float:left}.container{width:70%;float:left}h3[data-v-b67f45e8]{margin:40px 0 0}ul[data-v-b67f45e8]{list-style-type:none;padding:0}li[data-v-b67f45e8]{display:inline-block;margin:0 10px}a[data-v-b67f45e8]{color:#42b983}h3[data-v-d9cd1302]{margin:40px 0 0}ul[data-v-d9cd1302]{list-style-type:none;padding:0}li[data-v-d9cd1302]{display:inline-block;margin:0 10px}a[data-v-d9cd1302]{color:#42b983} -------------------------------------------------------------------------------- /spa-angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /dist/angular/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SpaAngular 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spa-angular/src/app/messages/messages.component.css: -------------------------------------------------------------------------------- 1 | /* MessagesComponent's private CSS styles */ 2 | h2 { 3 | color: red; 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-weight: lighter; 6 | } 7 | body { 8 | margin: 2em; 9 | } 10 | body, input[text], button { 11 | color: crimson; 12 | font-family: Cambria, Georgia; 13 | } 14 | 15 | button.clear { 16 | font-family: Arial; 17 | background-color: #eee; 18 | border: none; 19 | padding: 5px 10px; 20 | border-radius: 4px; 21 | cursor: pointer; 22 | cursor: hand; 23 | } 24 | button:hover { 25 | background-color: #cfd8dc; 26 | } 27 | button:disabled { 28 | background-color: #eee; 29 | color: #aaa; 30 | cursor: auto; 31 | } 32 | button.clear { 33 | color: #888; 34 | margin-bottom: 12px; 35 | } -------------------------------------------------------------------------------- /spa-angular/src/app/heroes/heroes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeroesComponent } from './heroes.component'; 4 | 5 | describe('HeroesComponent', () => { 6 | let component: HeroesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /dist/react/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "./static/css/main.28732bcc.chunk.css", 3 | "main.js": "./static/js/main.80af4b44.chunk.js", 4 | "main.js.map": "./static/js/main.80af4b44.chunk.js.map", 5 | "static/js/1.7b3bf74e.chunk.js": "./static/js/1.7b3bf74e.chunk.js", 6 | "static/js/1.7b3bf74e.chunk.js.map": "./static/js/1.7b3bf74e.chunk.js.map", 7 | "runtime~main.js": "./static/js/runtime~main.4a686d48.js", 8 | "runtime~main.js.map": "./static/js/runtime~main.4a686d48.js.map", 9 | "static/css/main.28732bcc.chunk.css.map": "./static/css/main.28732bcc.chunk.css.map", 10 | "index.html": "./index.html", 11 | "precache-manifest.1135b6be65e6d9e26e2ec6ede87ff50d.js": "./precache-manifest.1135b6be65e6d9e26e2ec6ede87ff50d.js", 12 | "service-worker.js": "./service-worker.js" 13 | } -------------------------------------------------------------------------------- /spa-angular/src/app/messages/messages.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MessagesComponent } from './messages.component'; 4 | 5 | describe('MessagesComponent', () => { 6 | let component: MessagesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MessagesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MessagesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /spa-angular/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /spa-angular/src/app/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DashboardComponent } from './dashboard.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: DashboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DashboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DashboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /spa-angular/src/app/hero-detail/hero-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeroDetailComponent } from './hero-detail.component'; 4 | 5 | describe('HeroDetailComponent', () => { 6 | let component: HeroDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /spa-angular/src/app/hero-search/hero-search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeroSearchComponent } from './hero-search.component'; 4 | 5 | describe('HeroSearchComponent', () => { 6 | let component: HeroSearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroSearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroSearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /server/view/phoneList.ejs: -------------------------------------------------------------------------------- 1 | <%- include('common/header', {title: title}); %> 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
手机号采集测试
100200300
400500600
133333333331444444444415555555555
177777777771888888888818888888888
17777777777000000000666666666618888888888
30 |
31 | 34 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /spa-angular/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { DashboardComponent } from './dashboard/dashboard.component'; 5 | import { HeroesComponent } from './heroes/heroes.component'; 6 | import { HeroDetailComponent } from './hero-detail/hero-detail.component'; 7 | 8 | const routes: Routes = [ 9 | // { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, 10 | { path: 'dashboard', component: DashboardComponent }, 11 | { path: 'detail/:id', component: HeroDetailComponent }, 12 | { path: 'heroes', component: HeroesComponent } 13 | ]; 14 | 15 | @NgModule({ 16 | imports: [ RouterModule.forRoot(routes, { useHash: true }) ], 17 | exports: [ RouterModule ] 18 | }) 19 | export class AppRoutingModule {} -------------------------------------------------------------------------------- /spa-angular/src/app/hero-search/hero-search.component.css: -------------------------------------------------------------------------------- 1 | /* HeroSearch private styles */ 2 | .search-result li { 3 | border-bottom: 1px solid gray; 4 | border-left: 1px solid gray; 5 | border-right: 1px solid gray; 6 | width: 195px; 7 | height: 16px; 8 | padding: 5px; 9 | background-color: white; 10 | cursor: pointer; 11 | list-style-type: none; 12 | } 13 | 14 | .search-result li:hover { 15 | background-color: #607D8B; 16 | } 17 | 18 | .search-result li a { 19 | color: #888; 20 | display: block; 21 | text-decoration: none; 22 | } 23 | 24 | .search-result li a:hover { 25 | color: white; 26 | } 27 | .search-result li a:active { 28 | color: white; 29 | } 30 | #search-box { 31 | width: 200px; 32 | height: 20px; 33 | } 34 | 35 | 36 | ul.search-result { 37 | margin-top: 0; 38 | padding-left: 0; 39 | } -------------------------------------------------------------------------------- /src/core/listener/beforeUnload.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | 3 | export default class BeforeUnload extends Listener { 4 | constructor (ctx) { 5 | super(window, 'beforeunload') 6 | 7 | this.ctx = ctx 8 | this.sender = ctx.sender 9 | this.params = ctx.params 10 | } 11 | 12 | async handler () { 13 | console.log(`${this.type} event trigged`) 14 | 15 | let unloadEvent = this.params.getEvent({ 16 | type: 'unload', 17 | }) 18 | 19 | await this.ctx.heatMap.code() 20 | 21 | this.params.getHeader() 22 | await this.sender.send({ 23 | header: this.params.header, 24 | device: this.params.device, 25 | event: unloadEvent 26 | }) 27 | 28 | this.ctx.heatMap.clear() 29 | this.ctx.phoneList.clear() 30 | this.ctx.message.remove() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/core/listener/popState.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | import { uuid } from '../../common/util' 3 | 4 | export default class PopState extends Listener { 5 | constructor (ctx) { 6 | super(window, 'popstate') 7 | 8 | this.ctx = ctx 9 | this.sender = ctx.sender 10 | this.params = ctx.params 11 | 12 | } 13 | 14 | async handler (event) { 15 | if (event.type === 'popstate') { 16 | console.log(`popstate event trigged`) 17 | console.log('popstate event: ', event) 18 | } 19 | 20 | let hashchangeEvent = this.params.getEvent({ 21 | type: 'hashchange', 22 | }) 23 | 24 | this.params.getHeader() 25 | await this.sender.send({ 26 | header: this.params.header, 27 | device: this.params.device, 28 | event: hashchangeEvent 29 | }) 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/core/sender.js: -------------------------------------------------------------------------------- 1 | import { isSupportWs } from '../common/util' 2 | import Ws from '../common/ws' 3 | import Ajax from '../common/ajax' 4 | 5 | export default class Sender { 6 | constructor (options) { 7 | this.serverUrl = options.serverUrl 8 | this.wsUrl = options.wsUrl 9 | } 10 | 11 | initWs () { 12 | if (isSupportWs()) { 13 | this.ws = new Ws({ url: this.wsUrl }) 14 | this.ws.open() 15 | } 16 | } 17 | 18 | closeWs () { 19 | this.ws && this.ws.close() 20 | } 21 | 22 | async send (params) { 23 | // if (this.ws && this.ws.isOpen()) { 24 | // this.ws.send(JSON.stringify(params)) 25 | // } else { 26 | let ajax = new Ajax({ 27 | url: this.serverUrl, 28 | method: 'POST' 29 | }) 30 | 31 | let res = await ajax.send(params) 32 | 33 | return res 34 | // } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const views = require('koa-views') 3 | const bodyParser = require('koa-bodyparser'); 4 | const static = require('koa-static') 5 | const path = require('path') 6 | const app = new Koa() 7 | const router = require('./router') 8 | const initWs = require('./ws') 9 | 10 | app.use(static(path.join(__dirname, '../dist'))) 11 | console.log(path.join(__dirname, '../dist')) 12 | app.use(bodyParser({ 13 | detectJSON: function (ctx) { 14 | return ctx.path === '/api'; 15 | } 16 | })); 17 | app.use(views(path.join(__dirname, './view'), { 18 | extension: 'ejs' 19 | })) 20 | app.use(router.routes()) 21 | 22 | const server = require('http').Server(app.callback()) 23 | initWs(server) 24 | 25 | let port = process.env.PORT || 8081 26 | console.log(port) 27 | server.listen(port, function () { 28 | console.log(`server listen at port ${port}`) 29 | }) 30 | -------------------------------------------------------------------------------- /spa-angular/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /spa-react/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `module.exports = { 14 | __esModule: true, 15 | default: ${assetFilename}, 16 | ReactComponent: (props) => ({ 17 | $$typeof: Symbol.for('react.element'), 18 | type: 'svg', 19 | ref: null, 20 | key: null, 21 | props: Object.assign({}, props, { 22 | children: ${assetFilename} 23 | }) 24 | }), 25 | };`; 26 | } 27 | 28 | return `module.exports = ${assetFilename};`; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /spa-angular/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 32 |
33 |

{{title}}

34 | 38 | 39 | 40 |
-------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 4 | const common = require('./webpack.common.js') 5 | 6 | module.exports = common.map(config => merge(config, { 7 | mode: 'production', 8 | devtool: 'source-map', 9 | optimization: { 10 | minimizer: [ 11 | new UglifyJsPlugin({ 12 | sourceMap: true, 13 | uglifyOptions: { 14 | // properties -- 用.来重写属性引用,例如foo["bar"] → foo.bar 15 | // 但我实测发现传false和true结果都一样,估计是新版本作废了这个字段吧,有时间我研究下源码 16 | // compress: { properties: false }, 17 | ie8: true, 18 | // mangle默认就是true,会混淆变量名,有时候会想看下没混淆变量名的代码,所以留着这个注释 19 | // mangle: true 20 | } 21 | }) 22 | ] 23 | // minimize: false 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 28 | }) 29 | ] 30 | })) 31 | -------------------------------------------------------------------------------- /dist/react/static/css/main.28732bcc.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["D:/workspace/project/DataCF/spa-react/src/D:/workspace/project/DataCF/spa-react/src/index.css","D:/workspace/project/DataCF/spa-react/src/D:/workspace/project/DataCF/spa-react/src/App.css"],"names":[],"mappings":"AAAA,KACE,SAAU,AACV,UAAW,AACX,oIAEa,AACb,mCAAoC,AACpC,iCAAmC,CACpC,AAED,KACE,uEACY,CACb,ACbD,SACE,YAAa,AACb,UAAY,CACb,AAED,WACE,UAAW,AACX,UAAY,CACb","file":"main.28732bcc.chunk.css","sourcesContent":["body {\r\n margin: 0;\r\n padding: 0;\r\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\r\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\r\n sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\r\n monospace;\r\n}\r\n",".sidebar {\r\n width: 200px;\r\n float: left;\r\n}\r\n\r\n.container {\r\n width: 70%;\r\n float: left;\r\n}\r\n"]} -------------------------------------------------------------------------------- /src/common/ws.js: -------------------------------------------------------------------------------- 1 | export default class Ws { 2 | constructor (options) { 3 | this.socket = null 4 | this.url = options.url 5 | } 6 | 7 | open () { 8 | const socket = new WebSocket(this.url) 9 | 10 | socket.onopen = function (event) { 11 | console.log('Connection open ...') 12 | } 13 | 14 | socket.onmessage = function (event) { 15 | console.log('Received Message: ' + event.data) 16 | } 17 | 18 | socket.onclose = function (event) { 19 | console.log('Connection closed.') 20 | } 21 | 22 | socket.onerror = function (event) { 23 | console.log('Connection error.') 24 | } 25 | 26 | this.socket = socket 27 | } 28 | 29 | send (data) { 30 | this.socket.send(data) 31 | } 32 | 33 | close () { 34 | this.socket && this.socket.close && this.socket.close() 35 | } 36 | 37 | isOpen () { 38 | if (this.socket && this.socket.readyState === WebSocket.OPEN) { 39 | return true 40 | } else { 41 | this.open() 42 | return false 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/view/common/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= title %> 7 | 9 | 20 | 21 | 22 | 23 | 24 | 37 |
-------------------------------------------------------------------------------- /server/view/demoA.ejs: -------------------------------------------------------------------------------- 1 | <%- include('common/header', {title: title}); %> 2 |

demo A页面

3 |

demo A页面

4 | 7 |
8 |

下面是用于测试demoA自定义事件的按钮

9 | 10 | 11 |
12 | <%- include('common/demo'); %> 13 | 35 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /server/view/demoB.ejs: -------------------------------------------------------------------------------- 1 | <%- include('common/header', {title: title}); %> 2 |

demo B页面

3 |

demo B页面

4 | 7 |
8 |

下面是用于测试demoB自定义事件的按钮

9 | 10 | 11 |
12 | <%- include('common/demo'); %> 13 | 35 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /server/view/demoC.ejs: -------------------------------------------------------------------------------- 1 | <%- include('common/header', {title: title}); %> 2 |

demo C页面

3 |

demo C页面

4 | 7 |
8 |

下面是用于测试demoC自定义事件的按钮

9 | 10 | 11 |
12 | <%- include('common/demo'); %> 13 | 35 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /spa-angular/src/app/heroes/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Hero } from '../hero'; 4 | import { HeroService } from '../hero.service'; 5 | 6 | @Component({ 7 | selector: 'app-heroes', 8 | templateUrl: './heroes.component.html', 9 | styleUrls: ['./heroes.component.css'] 10 | }) 11 | export class HeroesComponent implements OnInit { 12 | heroes: Hero[]; 13 | 14 | constructor(private heroService: HeroService) { } 15 | 16 | ngOnInit() { 17 | this.getHeroes(); 18 | } 19 | 20 | getHeroes(): void { 21 | this.heroService.getHeroes() 22 | .subscribe(heroes => this.heroes = heroes); 23 | } 24 | 25 | add(name: string): void { 26 | name = name.trim(); 27 | if (!name) { return; } 28 | this.heroService.addHero({ name } as Hero) 29 | .subscribe(hero => { 30 | this.heroes.push(hero); 31 | }); 32 | } 33 | 34 | delete(hero: Hero): void { 35 | this.heroes = this.heroes.filter(h => h !== hero); 36 | this.heroService.deleteHero(hero).subscribe(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /spa-angular/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /spa-vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import App from './App.vue' 4 | import routes from './routes' 5 | 6 | Vue.config.productionTip = false 7 | 8 | Vue.use(VueRouter) 9 | 10 | const router = new VueRouter({ 11 | routes 12 | }) 13 | 14 | new Vue({ 15 | render: h => h(App), 16 | router 17 | }).$mount('#app') 18 | 19 | let domainUrl = 'localhost:8080' 20 | // let domainUrl = 'testcollect.fengdai.org'; // 测试环境是 'testcollect.fengdai.org',生产环境是 'collect.trc.com',开发人员上线时候记得修改此配置,可根据自己的情况手动更改或者通过打包命令更改. 21 | let collect = document.createElement('script'); 22 | collect.type = 'text/javascript' 23 | collect.src = `//${domainUrl}${sessionStorage.getItem('dcp_version') === 'V2.0.0' ? '/index_2.0.0.js' : '/index.js'}`; 24 | let s = document.getElementsByTagName('script')[0]; 25 | s.parentNode.insertBefore(collect, s); 26 | window._XT = window._XT || []; //定义信息配置对象 27 | window._XT.push(['Target', 'div']); //无埋点行为采集 28 | window._XT.push(['auth', '93c55350e179a3676c905723e112b440']); //处于安全性考虑的传参 29 | // 用户自定义收集字段,现在传的是接入方的渠道码 30 | window._XT.userConfig = { 31 | dcpChannelCode: 've3dC41i' 32 | } 33 | -------------------------------------------------------------------------------- /spa-angular/README.md: -------------------------------------------------------------------------------- 1 | # SpaAngular 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /src/core/listener/visibilityChange.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | 3 | export default class VisibilityChange extends Listener { 4 | constructor (ctx) { 5 | super(document, 'visibilitychange') 6 | 7 | this.ctx = ctx 8 | } 9 | 10 | handler () { 11 | console.log(`${this.type} event trigged`) 12 | var isHidden = document.hidden 13 | if (isHidden) { 14 | this.ctx.heatMap.clear() 15 | this.ctx.phoneList.clear() 16 | } else { 17 | this.ctx.heatMap.set() 18 | this.ctx.phoneList.set() 19 | } 20 | } 21 | 22 | add () { 23 | if (typeof document['hidden'] === 'boolean') { 24 | console.log('visibilitychange event added') 25 | document.addEventListener('visibilitychange', this.handler.bind(this)) 26 | } else { 27 | console.log('该浏览器不支持页面可见性API') 28 | } 29 | } 30 | 31 | remove () { 32 | if (typeof document['hidden'] === 'boolean') { 33 | console.log('visibilitychange event removed') 34 | document.removeEventListener('visibilitychange', this.handler) 35 | } else { 36 | console.log('该浏览器不支持页面可见性API') 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spa-angular/src/app/hero-detail/hero-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Location } from '@angular/common'; 4 | 5 | import { Hero } from '../hero'; 6 | import { HeroService } from '../hero.service'; 7 | 8 | @Component({ 9 | selector: 'app-hero-detail', 10 | templateUrl: './hero-detail.component.html', 11 | styleUrls: [ './hero-detail.component.css' ] 12 | }) 13 | export class HeroDetailComponent implements OnInit { 14 | @Input() hero: Hero; 15 | 16 | constructor( 17 | private route: ActivatedRoute, 18 | private heroService: HeroService, 19 | private location: Location 20 | ) {} 21 | 22 | ngOnInit(): void { 23 | this.getHero(); 24 | } 25 | 26 | getHero(): void { 27 | const id = +this.route.snapshot.paramMap.get('id'); 28 | this.heroService.getHero(id) 29 | .subscribe(hero => this.hero = hero); 30 | } 31 | 32 | goBack(): void { 33 | this.location.back(); 34 | } 35 | 36 | save(): void { 37 | this.heroService.updateHero(this.hero) 38 | .subscribe(() => this.goBack()); 39 | } 40 | } -------------------------------------------------------------------------------- /spa-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "vue": "^2.5.21", 12 | "vue-router": "^3.0.2" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "^3.3.0", 16 | "@vue/cli-plugin-eslint": "^3.3.0", 17 | "@vue/cli-service": "^3.3.0", 18 | "babel-eslint": "^10.0.1", 19 | "eslint": "^5.8.0", 20 | "eslint-plugin-vue": "^5.0.0", 21 | "vue-template-compiler": "^2.5.21" 22 | }, 23 | "eslintConfig": { 24 | "root": true, 25 | "env": { 26 | "node": true 27 | }, 28 | "extends": [ 29 | "plugin:vue/essential", 30 | "eslint:recommended" 31 | ], 32 | "rules": { 33 | "no-console": 0 34 | }, 35 | "parserOptions": { 36 | "parser": "babel-eslint" 37 | } 38 | }, 39 | "postcss": { 40 | "plugins": { 41 | "autoprefixer": {} 42 | } 43 | }, 44 | "browserslist": [ 45 | "> 1%", 46 | "last 2 versions", 47 | "not ie <= 8" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /dist/react/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "./precache-manifest.1135b6be65e6d9e26e2ec6ede87ff50d.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("./index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /spa-angular/src/app/in-memory-data.service.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | import { Hero } from './hero'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class InMemoryDataService implements InMemoryDbService { 9 | createDb() { 10 | const heroes = [ 11 | { id: 11, name: 'Mr. Nice' }, 12 | { id: 12, name: 'Narco' }, 13 | { id: 13, name: 'Bombasto' }, 14 | { id: 14, name: 'Celeritas' }, 15 | { id: 15, name: 'Magneta' }, 16 | { id: 16, name: 'RubberMan' }, 17 | { id: 17, name: 'Dynama' }, 18 | { id: 18, name: 'Dr IQ' }, 19 | { id: 19, name: 'Magma' }, 20 | { id: 20, name: 'Tornado' } 21 | ]; 22 | return {heroes}; 23 | } 24 | 25 | // Overrides the genId method to ensure that a hero always has an id. 26 | // If the heroes array is empty, 27 | // the method below returns the initial number (11). 28 | // if the heroes array is not empty, the method below returns the highest 29 | // hero id + 1. 30 | genId(heroes: Hero[]): number { 31 | return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11; 32 | } 33 | } -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | 15 | include /etc/nginx/mime.types; 16 | default_type application/octet-stream; 17 | 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | access_log /var/log/nginx/access.log main; 23 | 24 | sendfile on; 25 | #tcp_nopush on; 26 | 27 | keepalive_timeout 65; 28 | 29 | #gzip on; 30 | 31 | server { 32 | listen 80; 33 | server_name collect.datacf.com; 34 | 35 | 36 | location / { 37 | root /workspace/project/DataCF/dist; 38 | index index.html index.htm; 39 | } 40 | 41 | 42 | } 43 | 44 | server { 45 | listen 80; 46 | server_name collectiframe.datacf.com; 47 | 48 | 49 | location / { 50 | root /workspace/project/DataCF/dist; 51 | index deviceId.html deviceId.htm; 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /spa-angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | 14 | 15 | declare global { 16 | interface Window { _XT: any; } 17 | } 18 | let domainUrl = 'localhost:8080' 19 | // let domainUrl = 'testcollect.fengdai.org'; // 测试环境是 'testcollect.fengdai.org',生产环境是 'collect.trc.com',开发人员上线时候记得修改此配置,可根据自己的情况手动更改或者通过打包命令更改. 20 | let collect = document.createElement('script'); 21 | collect.type = 'text/javascript' 22 | collect.src = `//${domainUrl}${sessionStorage.getItem('dcp_version') === 'V2.0.0' ? '/index_2.0.0.js' : '/index.js'}`; 23 | let s = document.getElementsByTagName('script')[0]; 24 | s.parentNode.insertBefore(collect, s); 25 | window._XT = window._XT || []; //定义信息配置对象 26 | window._XT.push(['Target', 'div']); //无埋点行为采集 27 | window._XT.push(['auth', '93c55350e179a3676c905723e112b440']); //处于安全性考虑的传参 28 | // 用户自定义收集字段,现在传的是接入方的渠道码 29 | window._XT.userConfig = { 30 | dcpChannelCode: 've3dC41i' 31 | } -------------------------------------------------------------------------------- /spa-react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | let domainUrl = 'localhost:8080' 15 | // let domainUrl = 'testcollect.fengdai.org'; // 测试环境是 'testcollect.fengdai.org',生产环境是 'collect.trc.com',开发人员上线时候记得修改此配置,可根据自己的情况手动更改或者通过打包命令更改. 16 | let collect = document.createElement('script'); 17 | collect.type = 'text/javascript' 18 | collect.src = `//${domainUrl}${sessionStorage.getItem('dcp_version') === 'V2.0.0' ? '/index_2.0.0.js' : '/index.js'}`; 19 | let s = document.getElementsByTagName('script')[0]; 20 | s.parentNode.insertBefore(collect, s); 21 | window._XT = window._XT || []; //定义信息配置对象 22 | window._XT.push(['Target', 'div']); //无埋点行为采集 23 | window._XT.push(['auth', '93c55350e179a3676c905723e112b440']); //处于安全性考虑的传参 24 | // 用户自定义收集字段,现在传的是接入方的渠道码 25 | window._XT.userConfig = { 26 | dcpChannelCode: 've3dC41i' 27 | } -------------------------------------------------------------------------------- /spa-angular/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'spa-angular'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('spa-angular'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to spa-angular!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /spa-angular/src/app/dashboard/dashboard.component.css: -------------------------------------------------------------------------------- 1 | /* DashboardComponent's private CSS styles */ 2 | [class*='col-'] { 3 | float: left; 4 | padding-right: 20px; 5 | padding-bottom: 20px; 6 | } 7 | [class*='col-']:last-of-type { 8 | padding-right: 0; 9 | } 10 | a { 11 | text-decoration: none; 12 | } 13 | *, *:after, *:before { 14 | -webkit-box-sizing: border-box; 15 | -moz-box-sizing: border-box; 16 | box-sizing: border-box; 17 | } 18 | h3 { 19 | text-align: center; 20 | margin-bottom: 0; 21 | } 22 | h4 { 23 | position: relative; 24 | } 25 | .grid { 26 | margin: 0; 27 | } 28 | .col-1-4 { 29 | width: 25%; 30 | } 31 | .module { 32 | padding: 20px; 33 | text-align: center; 34 | color: #eee; 35 | max-height: 120px; 36 | min-width: 120px; 37 | background-color: #607d8b; 38 | border-radius: 2px; 39 | } 40 | .module:hover { 41 | background-color: #eee; 42 | cursor: pointer; 43 | color: #607d8b; 44 | } 45 | .grid-pad { 46 | padding: 10px 0; 47 | } 48 | .grid-pad > [class*='col-']:last-of-type { 49 | padding-right: 20px; 50 | } 51 | @media (max-width: 600px) { 52 | .module { 53 | font-size: 10px; 54 | max-height: 75px; } 55 | } 56 | @media (max-width: 1024px) { 57 | .grid { 58 | margin: 0; 59 | } 60 | .module { 61 | min-width: 60px; 62 | } 63 | } -------------------------------------------------------------------------------- /spa-angular/src/app/hero-search/hero-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Observable, Subject } from 'rxjs'; 4 | 5 | import { 6 | debounceTime, distinctUntilChanged, switchMap 7 | } from 'rxjs/operators'; 8 | 9 | import { Hero } from '../hero'; 10 | import { HeroService } from '../hero.service'; 11 | 12 | @Component({ 13 | selector: 'app-hero-search', 14 | templateUrl: './hero-search.component.html', 15 | styleUrls: [ './hero-search.component.css' ] 16 | }) 17 | export class HeroSearchComponent implements OnInit { 18 | heroes$: Observable; 19 | private searchTerms = new Subject(); 20 | 21 | constructor(private heroService: HeroService) {} 22 | 23 | // Push a search term into the observable stream. 24 | search(term: string): void { 25 | this.searchTerms.next(term); 26 | } 27 | 28 | ngOnInit(): void { 29 | this.heroes$ = this.searchTerms.pipe( 30 | // wait 300ms after each keystroke before considering the term 31 | debounceTime(300), 32 | 33 | // ignore new term if same as previous term 34 | distinctUntilChanged(), 35 | 36 | // switch to new search observable each time the term changes 37 | switchMap((term: string) => this.heroService.searchHeroes(term)), 38 | ); 39 | } 40 | } -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = [{ 5 | entry: { 6 | 'index': path.resolve(__dirname, '../src/index.js'), 7 | }, 8 | plugins: [ 9 | new HtmlWebpackPlugin({ 10 | filename: 'index-sdk.html', 11 | template: path.resolve(__dirname, '../src/index.html'), 12 | inject: false 13 | }), 14 | new HtmlWebpackPlugin({ 15 | filename: 'iframe.html', 16 | template: path.resolve(__dirname, '../src/iframe.html'), 17 | inject: false 18 | }) 19 | ], 20 | output: { 21 | filename: '[name].js', 22 | path: path.resolve(__dirname, '../dist'), 23 | publicPath: '/' 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | exclude: /(node_modules|bower_components)/, 30 | use: { 31 | loader: 'babel-loader', 32 | options: { 33 | presets: [ 34 | '@babel/preset-env' 35 | ], 36 | plugins: [ 37 | [ 38 | '@babel/plugin-transform-runtime' 39 | ], 40 | [ 41 | '@babel/plugin-transform-modules-commonjs' 42 | ] 43 | ] 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | }] 50 | -------------------------------------------------------------------------------- /src/core/listener/pushState.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | 3 | export default class PushState extends Listener { 4 | constructor (ctx) { 5 | super(window, 'pushstate') 6 | 7 | this.ctx = ctx 8 | this.sender = ctx.sender 9 | } 10 | 11 | handler (event) { 12 | console.log(`pushstate event trigged`) 13 | console.log('pushstate event: ', event) 14 | this.ctx.popState.handler.call(this.ctx.popState, event) 15 | } 16 | 17 | add () { 18 | let handler = this.handler.bind(this) 19 | let replaceState = history.replaceState 20 | if (replaceState) { 21 | this.replaceState = replaceState 22 | history.replaceState = function () { 23 | handler({state: arguments[0], param: arguments[1], url: arguments[2], type: 'pushstate'}) 24 | return replaceState.apply(history, arguments) 25 | } 26 | } 27 | 28 | let pushState = history.pushState 29 | if (pushState) { 30 | this.pushState = pushState 31 | history.pushState = function () { 32 | handler({state: arguments[0], param: arguments[1], url: arguments[2], type: 'pushstate'}) 33 | return pushState.apply(history, arguments) 34 | } 35 | } 36 | } 37 | 38 | remove () { 39 | history.replaceState = this.replaceState 40 | history.pushState = this.pushState 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/common/ajax.js: -------------------------------------------------------------------------------- 1 | import { IEVersion } from './util' 2 | 3 | export default class Ajax { 4 | constructor (options) { 5 | let ieVersion = IEVersion() 6 | 7 | let xhr 8 | if (window.XMLHttpRequest) { 9 | xhr = new XMLHttpRequest() 10 | 11 | // XDomainRequest可以支持到IE8以下的浏览器,但我懒得测了,所以干脆写死了,IE8以下都不管了 12 | if ([8, 9].indexOf(ieVersion) > -1) { 13 | console.log('use XDomainRequest') 14 | xhr = new XDomainRequest() 15 | } 16 | } else { 17 | xhr = new ActiveXObject('Microsoft.XMLHTTP') 18 | } 19 | 20 | xhr.open(options.method, options.url) 21 | 22 | // 显示关闭withCredentials,这样就不会发送cookie 23 | xhr.withCredentials = false 24 | 25 | this.xhr = xhr 26 | this.ieVersion = ieVersion 27 | } 28 | 29 | send (data) { 30 | if (typeof data === 'object') { 31 | data = JSON.stringify(data) 32 | } else { 33 | data = null 34 | } 35 | 36 | let xhr = this.xhr 37 | return new Promise((resolve) => { 38 | xhr.send(data) 39 | 40 | if ([8, 9].indexOf(this.ieVersion) > -1) { 41 | xhr.onload = function () { 42 | resolve(JSON.parse(xhr.responseText)) 43 | } 44 | } else { 45 | xhr.onreadystatechange = function () { 46 | if (xhr.readyState === 4 && xhr.status === 200) { 47 | resolve(JSON.parse(xhr.responseText)) 48 | } 49 | } 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dist/react/static/js/runtime~main.4a686d48.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c 2 | 3 | 4 |

点击 P 标签

5 | 6 |
点击 DIV 标签
7 | 8 |
9 |

跳转链接

10 | 跳转至demo A页面 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
sdk 暴露了3个接口供业务方调用,分别是存储 userId ,自定义事件触发, http 请求错误事件触发
23 | 24 |
25 | 26 |
27 | 52 | <%- include('common/footer'); %> -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const router = new Router() 3 | 4 | router.all('/api', (ctx, next) => { 5 | ctx.set('Access-Control-Allow-Origin', '*') 6 | ctx.set('Access-Control-Allow-Methods', '*') 7 | ctx.set('Access-Control-Allow-Headers', '*') 8 | if (ctx.request.method == "OPTIONS") { 9 | ctx.response.status = 200 10 | } 11 | next() 12 | }) 13 | 14 | router.post('/api', (ctx, next) => { 15 | console.log(JSON.stringify(ctx.request.body)) 16 | ctx.body = JSON.stringify({ 17 | "code": "200", 18 | "message": "", 19 | "body": {} 20 | }) 21 | }) 22 | 23 | router.get('/', async (ctx, next) => { 24 | let title = 'index' 25 | await ctx.render('index.ejs', { 26 | title, 27 | }) 28 | }) 29 | 30 | router.get('/demoA', async (ctx, next) => { 31 | let title = 'demoA' 32 | await ctx.render('demoA.ejs', { 33 | title, 34 | }) 35 | }) 36 | 37 | router.get('/demoB', async (ctx, next) => { 38 | let title = 'demoB' 39 | await ctx.render('demoB.ejs', { 40 | title, 41 | }) 42 | }) 43 | 44 | router.get('/demoC', async (ctx, next) => { 45 | let title = 'demoC' 46 | await ctx.render('demoC.ejs', { 47 | title, 48 | }) 49 | }) 50 | 51 | router.get('/phoneList', async (ctx, next) => { 52 | let title = '手机号采集' 53 | await ctx.render('phoneList.ejs', { 54 | title, 55 | }) 56 | }) 57 | 58 | router.get('/heatMap', async (ctx, next) => { 59 | let title = '热力图' 60 | await ctx.render('heatMap.ejs', { 61 | title, 62 | }) 63 | }) 64 | 65 | module.exports = router 66 | -------------------------------------------------------------------------------- /src/core/interval/heatMap.js: -------------------------------------------------------------------------------- 1 | import Interval from '../../common/interval' 2 | 3 | export default class HeatMap extends Interval { 4 | constructor (ctx) { 5 | super(ctx.config.heatMapDelay, 'heatMap') 6 | 7 | this.ctx = ctx 8 | this.heats = [] 9 | this.sender = ctx.sender 10 | this.params = ctx.params 11 | this.heatmapUrls = ctx.config.heatmapUrls 12 | } 13 | 14 | async code () { 15 | console.log('heats.length: ', this.heats.length) 16 | if (this.heats.length > 0) { 17 | let heatMapEvent = this.params.getEvent({ 18 | type: 'heatmap', 19 | data: this.heats 20 | }) 21 | this.params.getHeader() 22 | await this.sender.send({ 23 | device: this.params.device, 24 | header: this.params.header, 25 | event: heatMapEvent 26 | }) 27 | this.heats = [] 28 | } 29 | } 30 | 31 | set () { 32 | this.isCollect() && super.set() 33 | } 34 | 35 | // 是否采集 36 | isCollect () { 37 | let hasSign = false 38 | for (var key in this.heatmapUrls) { 39 | let _len = this.heatmapUrls[key].length 40 | if (this.heatmapUrls[key][_len - 1] === '*') { 41 | let _currentKey = this.heatmapUrls[key].substring(0, _len - 1) 42 | if (document.URL === _currentKey || document.URL.indexOf(_currentKey + '#') !== -1) { 43 | hasSign = true 44 | break 45 | } 46 | } else if (document.URL === this.heatmapUrls[key]) { 47 | hasSign = true 48 | break 49 | } 50 | } 51 | 52 | return hasSign 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spa-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa-angular", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~7.2.0", 15 | "@angular/common": "~7.2.0", 16 | "@angular/compiler": "~7.2.0", 17 | "@angular/core": "~7.2.0", 18 | "@angular/forms": "~7.2.0", 19 | "@angular/platform-browser": "~7.2.0", 20 | "@angular/platform-browser-dynamic": "~7.2.0", 21 | "@angular/router": "~7.2.0", 22 | "angular-in-memory-web-api": "^0.8.0", 23 | "core-js": "^2.5.4", 24 | "rxjs": "~6.3.3", 25 | "tslib": "^1.9.0", 26 | "zone.js": "~0.8.26" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.12.0", 30 | "@angular/cli": "~7.2.0", 31 | "@angular/compiler-cli": "~7.2.0", 32 | "@angular/language-service": "~7.2.0", 33 | "@types/node": "~8.9.4", 34 | "@types/jasmine": "~2.8.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "codelyzer": "~4.5.0", 37 | "jasmine-core": "~2.99.1", 38 | "jasmine-spec-reporter": "~4.2.1", 39 | "karma": "~3.1.1", 40 | "karma-chrome-launcher": "~2.2.0", 41 | "karma-coverage-istanbul-reporter": "~2.0.1", 42 | "karma-jasmine": "~1.1.2", 43 | "karma-jasmine-html-reporter": "^0.2.2", 44 | "protractor": "~5.4.0", 45 | "ts-node": "~7.0.0", 46 | "tslint": "~5.11.0", 47 | "typescript": "~3.2.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spa-react/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--coverage') === -1 && 45 | argv.indexOf('--watchAll') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/core/interval/phoneList.js: -------------------------------------------------------------------------------- 1 | import Interval from '../../common/interval' 2 | 3 | export default class PhoneList extends Interval { 4 | constructor (ctx) { 5 | super(ctx.config.phoneListDelay, 'phoneList') 6 | 7 | this.ctx = ctx 8 | this.sender = ctx.sender 9 | this.params = ctx.params 10 | this.phoneLists = [] 11 | } 12 | 13 | async code () { 14 | let phoneList = [] 15 | 16 | let numbers = document.body.innerText.match(/[0-9]+/g) || [] 17 | for (let i = 0; i < numbers.length; i++) { 18 | if (phoneList.indexOf(numbers[i]) === -1 && /^1[34578]\d{9}$/.test(numbers[i])) { 19 | phoneList.push(numbers[i]) 20 | } 21 | } 22 | 23 | let inputs = document.getElementsByTagName('input') || [] 24 | for (let j = 0; j < inputs.length; j++) { 25 | if (phoneList.indexOf(inputs[j]._value) === -1 && /^1[34578]\d{9}$/.test(inputs[j]._value)) { 26 | phoneList.push(inputs[j]._value) 27 | } 28 | } 29 | 30 | if (phoneList.length > 0) { 31 | console.log('采集到phone list') 32 | let phoneListStr = phoneList.join(',') 33 | if (this.phoneLists.indexOf(phoneListStr) === -1) { 34 | console.log('new phone list') 35 | this.phoneLists.push(phoneListStr) 36 | 37 | let phoneListEvent = this.params.getEvent({ 38 | type: 'phonelist', 39 | data: phoneList 40 | }) 41 | this.params.getHeader() 42 | await this.sender.send({ 43 | device: this.params.device, 44 | header: this.params.header, 45 | event: phoneListEvent 46 | }) 47 | } 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /spa-angular/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { InMemoryDataService } from './in-memory-data.service'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | 11 | import { AppComponent } from './app.component'; 12 | import { DashboardComponent } from './dashboard/dashboard.component'; 13 | import { HeroDetailComponent } from './hero-detail/hero-detail.component'; 14 | import { HeroesComponent } from './heroes/heroes.component'; 15 | import { HeroSearchComponent } from './hero-search/hero-search.component'; 16 | import { MessagesComponent } from './messages/messages.component'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | BrowserModule, 21 | FormsModule, 22 | AppRoutingModule, 23 | HttpClientModule, 24 | 25 | // The HttpClientInMemoryWebApiModule module intercepts HTTP requests 26 | // and returns simulated server responses. 27 | // Remove it when a real server is ready to receive requests. 28 | HttpClientInMemoryWebApiModule.forRoot( 29 | InMemoryDataService, { dataEncapsulation: false } 30 | ) 31 | ], 32 | declarations: [ 33 | AppComponent, 34 | DashboardComponent, 35 | HeroesComponent, 36 | HeroDetailComponent, 37 | MessagesComponent, 38 | HeroSearchComponent 39 | ], 40 | bootstrap: [ AppComponent ] 41 | }) 42 | export class AppModule { } -------------------------------------------------------------------------------- /spa-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/core/listener/onload.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | import BeforeUnload from './beforeUnload' 3 | import Click from './click' 4 | import Message from './message' 5 | import PopState from './popState' 6 | import PushState from './pushState' 7 | import VisibilityChange from './visibilityChange' 8 | import HeatMap from '../interval/heatMap' 9 | import PhoneList from '../interval/phoneList' 10 | 11 | export default class Onload extends Listener { 12 | constructor (ctx) { 13 | super(document, 'DOMContentLoaded') 14 | 15 | this.ctx = ctx 16 | this.params = ctx.params 17 | this.sender = ctx.sender 18 | this.config = ctx.config 19 | } 20 | 21 | handler () { 22 | 23 | this.ctx.loadedEvent = this.params.getEvent({ 24 | type: 'loaded' 25 | }) 26 | 27 | this.sender.initWs() 28 | Object.assign(this.ctx, { 29 | heatMap: new HeatMap(this.ctx), 30 | phoneList: new PhoneList(this.ctx), 31 | beforeUnload: new BeforeUnload(this.ctx), 32 | popState: new PopState(this.ctx), 33 | pushState: new PushState(this.ctx), 34 | click: new Click(this.ctx), 35 | visibilityChange: new VisibilityChange(this.ctx) 36 | }) 37 | let message = new Message(this.ctx) 38 | this.ctx.message = message 39 | 40 | message.add() 41 | 42 | console.log('collect start') 43 | } 44 | 45 | add () { 46 | if (document.readyState === 'loading') { 47 | if (document.addEventListener) { 48 | super.add() 49 | } else { 50 | document.attachEvent('onreadystatechange', () => { 51 | if (document.readyState === 'interactive') { 52 | this.handler() 53 | } 54 | }) 55 | } 56 | } else { 57 | console.log('DOMContentLoaded event already trigged') 58 | this.handler() 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /dist/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Title 8 | 65 | 66 | 67 | 68 | 获取设备ID 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Title 8 | 65 | 66 | 67 | 68 | 获取设备ID 69 | 70 | 71 | -------------------------------------------------------------------------------- /dist/react/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /server/view/common/demo.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | First name:
5 | 6 |
7 | Last name:
8 | 9 |

10 | 11 |
12 |
13 |
14 |
15 | 16 |
17 | 18 |
19 |

这是为了凑字数的段落

20 |

这是为了凑字数的段落

21 |

这是为了凑字数的段落

22 |

这是为了凑字数的段落

23 |

这是为了凑字数的段落

24 |

这是为了凑字数的段落

25 |

这是为了凑字数的段落

26 |

这是为了凑字数的段落

27 |

这是为了凑字数的段落

28 |

这是为了凑字数的段落

29 |

这是为了凑字数的段落

30 |

这是为了凑字数的段落

31 |

这是为了凑字数的段落

32 |

这是为了凑字数的段落

33 |

这是为了凑字数的段落

34 |

这是为了凑字数的段落

35 |

这是为了凑字数的段落

36 |

这是为了凑字数的段落

37 |

这是为了凑字数的段落

38 |

这是为了凑字数的段落

39 |
40 |
41 |

这是为了凑字数的段落

42 |

这是为了凑字数的段落

43 |

这是为了凑字数的段落

44 |

这是为了凑字数的段落

45 |

这是为了凑字数的段落

46 |

这是为了凑字数的段落

47 |

这是为了凑字数的段落

48 |

这是为了凑字数的段落

49 |

这是为了凑字数的段落

50 |

这是为了凑字数的段落

51 |

这是为了凑字数的段落

52 |

这是为了凑字数的段落

53 |

这是为了凑字数的段落

54 |

这是为了凑字数的段落

55 |

这是为了凑字数的段落

56 |

这是为了凑字数的段落

57 |

这是为了凑字数的段落

58 |

这是为了凑字数的段落

59 |

这是为了凑字数的段落

60 |

这是为了凑字数的段落

61 |
62 |
63 | 65 | 67 |
-------------------------------------------------------------------------------- /src/core/listener/message.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | 3 | export default class Message extends Listener { 4 | constructor (ctx) { 5 | super(window, 'message') 6 | 7 | this.ctx = ctx 8 | this.sender = ctx.sender 9 | this.params = ctx.params 10 | this.config = ctx.config 11 | 12 | let iframe 13 | let iframeSrc = this.config.iframe 14 | try { 15 | iframe = document.createElement('iframe') 16 | iframe.id = 'frame' 17 | iframe.name = 'frame' 18 | iframe.src = iframeSrc 19 | iframe.style.display = 'none' 20 | } catch (e) { 21 | iframe = document.createElement('') 22 | } 23 | document.body.appendChild(iframe) 24 | let iframeData = { 25 | type: 'loaded' 26 | } 27 | iframe.onload = function () { 28 | iframe.contentWindow.postMessage(JSON.stringify(iframeData), '*') 29 | } 30 | 31 | this.iframe = iframe 32 | } 33 | 34 | async handler (event) { 35 | // console.log(`${this.type} event trigged`) 36 | 37 | let _eventData = event.data && typeof event.data === 'string' && JSON.parse(event.data) 38 | 39 | // console.log('iframe data: ', _eventData) 40 | 41 | // 考虑到接入方可能也会用到postMessage,所以这里做一层判断 42 | switch (_eventData.type) { 43 | case 'deviceId': 44 | 45 | localStorage.setItem('deviceId', _eventData.deviceId) 46 | 47 | this.ctx.click.add() 48 | this.ctx.visibilityChange.add() 49 | this.ctx.beforeUnload.add() 50 | this.ctx.popState.add() 51 | this.ctx.pushState.add() 52 | 53 | let device = this.params.device 54 | let header = this.params.getHeader() 55 | 56 | await this.sender.send({ 57 | device, 58 | header, 59 | event: this.ctx.loadedEvent 60 | }) 61 | 62 | this.ctx.heatMap.set() 63 | this.ctx.phoneList.set() 64 | break 65 | default: 66 | break 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spa-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 64 | 65 | 84 | -------------------------------------------------------------------------------- /spa-react/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import React from "react"; 3 | import { HashRouter as Router, Route, Link } from "react-router-dom"; 4 | 5 | const App = () => ( 6 |
7 | 38 |
39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 |
48 |
49 |
50 | ); 51 | 52 | const Home = () =>

Home

; 53 | const About = () =>

About

; 54 | const Topic = ({ match }) =>

Requested Param: {match.params.id}

; 55 | const Topics = ({ match }) => ( 56 |
57 |

Topics

58 | 59 |
    60 |
  • 61 | Components 62 |
  • 63 |
  • 64 | Props v. State 65 |
  • 66 |
67 | 68 | 69 |

Please select a topic.

} 73 | /> 74 |
75 | ); 76 | const Header = () => ( 77 |
    78 |
  • 79 | Home 80 |
  • 81 |
  • 82 | About 83 |
  • 84 |
  • 85 | Topics 86 |
  • 87 |
88 | ); 89 | 90 | export default App; -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import Sender from './sender' 2 | import Params from './params' 3 | import Onload from './listener/onload' 4 | import config from '../config' 5 | 6 | export default class DataCF { 7 | constructor () { 8 | 9 | if (typeof _XT === 'undefined') throw TypeError('必须定义全局配置变量 _XT,配置指定的请求Url。示例: let _XT = []') 10 | 11 | let ctx = {} 12 | 13 | // 解析 配置项 14 | for (let i in _XT) { 15 | switch (_XT[i][0]) { 16 | case 'token': 17 | config.token = _XT[i][1] 18 | break 19 | case 'channelCode': 20 | config.channelCode = _XT[i][1] 21 | break 22 | case 'serverUrl': 23 | config.serverUrl = _XT[i][1] 24 | break 25 | case 'wsUrl': 26 | config.wsUrl = _XT[i][1] 27 | break 28 | default: 29 | break 30 | } 31 | } 32 | 33 | let params = new Params({token: config.token, channelCode: config.channelCode}) 34 | params.setDevice() 35 | 36 | let sender = new Sender({serverUrl: config.serverUrl, wsUrl: config.wsUrl}) 37 | 38 | ctx.params = params 39 | ctx.sender = sender 40 | ctx.config = config 41 | 42 | this.ctx = ctx 43 | } 44 | 45 | async start () { 46 | let onload = new Onload(this.ctx) 47 | this.ctx.onload = onload 48 | 49 | onload.add() 50 | } 51 | 52 | storeUserId (_userId) { 53 | sessionStorage.setItem('userId', _userId) 54 | } 55 | 56 | // 采集自定义事件类型 57 | /** 58 | * @method dispatch 采集自动以事件 59 | * @parame eType 参数,触发的动作(点击还是滚动),如 click 60 | * @parame element 触发的对象信息,如果不采集,传 false 61 | * @parame extraInfo 定制化的事件额外对象参数,sdk 提供格式 62 | **/ 63 | dispatch (eType, element, extraInfo) { 64 | let device = this.ctx.params.device 65 | let header = this.ctx.params.getHeader() 66 | let customEvent = this.ctx.params.getEvent({ 67 | type: eType, 68 | data: extraInfo 69 | }) 70 | 71 | if (element) { 72 | let target = element 73 | customEvent.pageElement = '{nodeName:' + target.nodeName + ',title:' + target.title + ',text:' + target.innerHTML + '}' 74 | } 75 | 76 | this.ctx.sender.send({ 77 | device, 78 | header, 79 | event: [customEvent] 80 | }) 81 | } 82 | 83 | getDeviceId () { 84 | return localStorage.getItem('deviceId') 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dc-sdk-js", 3 | "version": "1.0.0", 4 | "description": "一个基于浏览器环境的数据采集SDK", 5 | "scripts": { 6 | "dev": "webpack-dev-server --open --config config/webpack.dev.js", 7 | "build": "cross-env NODE_ENV='prod' webpack --config config/webpack.prod.js", 8 | "build:test": "cross-env NODE_ENV='test' webpack --config config/webpack.prod.js", 9 | "test": "mocha --require @babel/register", 10 | "standard": "standard --fix config/**/*.js src/**/*.js test/**/*.js", 11 | "server": "cross-env PORT='8081' node server/index.js", 12 | "server:sdk": "cross-env PORT='8080' node server/index.js", 13 | "server:mon": "nodemon server/index.js" 14 | }, 15 | "standard": { 16 | "env": [ 17 | "mocha", 18 | "browser" 19 | ], 20 | "globals": [ 21 | "ActiveXObject", 22 | "XDomainRequest", 23 | "_XT" 24 | ] 25 | }, 26 | "babel": { 27 | "presets": [ 28 | "@babel/preset-env" 29 | ] 30 | }, 31 | "sideEffects": false, 32 | "dependencies": { 33 | "ejs": "^2.6.1", 34 | "koa": "^2.6.2", 35 | "koa-bodyparser": "^4.2.1", 36 | "koa-router": "^7.4.0", 37 | "koa-static": "^5.0.0", 38 | "koa-views": "^6.1.5", 39 | "lodash": "^4.17.11", 40 | "nodemon": "^1.18.10", 41 | "ws": "^6.1.2" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.2.2", 45 | "@babel/plugin-transform-runtime": "^7.2.0", 46 | "@babel/preset-env": "^7.1.0", 47 | "@babel/register": "^7.0.0", 48 | "@babel/runtime": "^7.3.4", 49 | "babel-loader": "^8.0.4", 50 | "chai": "^4.2.0", 51 | "core-js": "^3.0.1", 52 | "cross-env": "^5.2.0", 53 | "html-webpack-plugin": "^3.2.0", 54 | "mocha": "^5.2.0", 55 | "standard": "^12.0.1", 56 | "uglifyjs-webpack-plugin": "^2.0.1", 57 | "webpack": "^4.20.2", 58 | "webpack-cli": "^3.1.2", 59 | "webpack-dev-server": "^3.1.9", 60 | "webpack-merge": "^4.1.4" 61 | }, 62 | "repository": { 63 | "type": "git", 64 | "url": "git+https://github.com/xtTech/dc-sdk-js.git" 65 | }, 66 | "author": "drafish", 67 | "contributors": [ 68 | { 69 | "name": "muzishuiji", 70 | "email": "2510909248@qq.com" 71 | } 72 | ], 73 | "company": "xtTech", 74 | "license": "MIT", 75 | "bugs": { 76 | "url": "https://github.com/xtTech/dc-sdk-js/issues" 77 | }, 78 | "homepage": "https://github.com/xtTech/dc-sdk-js#readme" 79 | } 80 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SDK测试问题记录 6 | 7 | 8 | 9 | 10 | 11 | 12 |

点击 P 标签

13 | 14 |
点击 DIV 标签
15 | 16 |
17 |

跳转链接

18 | 跳转至demo A页面 19 |
20 | 21 |
22 |

这适用于测试热力图模糊匹配的锚点列表

23 |

part1

24 |

part2

25 |

part3

26 |

part4

27 |

part5

28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
sdk 暴露了3个接口供业务方调用,分别是存储 userId ,自定义事件触发, http 请求错误事件触发
40 | 41 |
42 | 43 |
44 | 45 | 87 | -------------------------------------------------------------------------------- /dist/index-sdk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SDK测试问题记录 6 | 7 | 8 | 9 | 10 | 11 | 12 |

点击 P 标签

13 | 14 |
点击 DIV 标签
15 | 16 |
17 |

跳转链接

18 | 跳转至demo A页面 19 |
20 | 21 |
22 |

这适用于测试热力图模糊匹配的锚点列表

23 |

part1

24 |

part2

25 |

part3

26 |

part4

27 |

part5

28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
sdk 暴露了3个接口供业务方调用,分别是存储 userId ,自定义事件触发, http 请求错误事件触发
40 | 41 |
42 | 43 |
44 | 45 | 87 | -------------------------------------------------------------------------------- /src/core/listener/click.js: -------------------------------------------------------------------------------- 1 | import Listener from '../../common/listener' 2 | import { getAllNodesPath } from '../../common/util' 3 | 4 | export default class Click extends Listener { 5 | constructor (ctx) { 6 | super(document, 'click') 7 | 8 | this.ctx = ctx 9 | } 10 | 11 | handler (event) { 12 | console.log(`${this.type} event trigged`) 13 | var e = window.event || event 14 | var _target = e.target || e.srcElement 15 | if (_target) { 16 | var _label = '' 17 | if (_target.innerText.length > 10) { 18 | _label = '' 19 | } else { 20 | _label = _target.innerText.replace(/[\s\r\n↵]/g, '') || _target.title || _target.name 21 | } 22 | var _path = ''; var _nodeName = ''; var _width; var _height 23 | if (_target.parentNode.innerText === _label) { 24 | _nodeName = _target.parentNode.nodeName.toLowerCase() 25 | _path = getAllNodesPath(_target.parentNode) 26 | _width = Math.round((e.offsetX / _target.parentNode.offsetWidth) * 100) 27 | _height = Math.round((e.offsetY / _target.parentNode.offsetHeight) * 100) 28 | } else { 29 | _nodeName = _target.nodeName.toLowerCase() 30 | if (_nodeName === 'img' && !_label) { 31 | _label = '图片' + (+new Date()) 32 | } 33 | if (_nodeName === 'canvas' && !_label) { 34 | _label = 'canvas图表' + (+new Date()) 35 | } 36 | if (_nodeName === 'body') { 37 | _label = 'body' 38 | } 39 | _path = getAllNodesPath(_target) 40 | _width = parseInt((e.offsetX / _target.offsetWidth) * 100) 41 | _height = parseInt((e.offsetY / _target.offsetHeight) * 100) 42 | } 43 | this.ctx.heatMap.heats.push( 44 | { 45 | x: _width, 46 | y: _height, 47 | nodeName: _nodeName, 48 | sign: _path.join('/'), 49 | label: _label || _path.reverse().join('/') 50 | } 51 | ) 52 | } 53 | } 54 | 55 | add () { 56 | if (typeof document.addEventListener === 'function') { 57 | console.log('click event added') 58 | document.addEventListener('click', this.handler.bind(this), true) 59 | } else { 60 | console.log('该浏览器不支持addEventListener') 61 | } 62 | } 63 | 64 | remove () { 65 | if (typeof document.addEventListener === 'function') { 66 | console.log('click event removed') 67 | document.removeEventListener('click', this.handler.bind(this), true) 68 | } else { 69 | console.log('该浏览器不支持addEventListener') 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spa-react/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /spa-react/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right