Long winded words

As we all know, making C-end products requires extremely high user experience. At the same time, whether the product implementation is in place and whether the return on investment is proportional becomes the key factor of whether a team has rewards (this is one of the real demands of the most practical software team “hahaha”). Although we will do a lot of user research, collect receipts and so on in the early stage to clarify which are the basic needs of users and which are the excited needs of users, we still need a specific and quantifiable data support to evaluate whether a certain business meets the real needs of users and serve as a reference for the later iteration of the product.

Then based on the actual situation above, for the entire product line, it is very necessary to do a burial point business collection. Of course, there are more significance to do a buried point, and I will not discuss it here.

The following will be a comprehensive analysis of the implementation process of the scheme, and also provide a simple reasonable reusable scheme output. Interested guests please stay at ~ ✌

The current status quo

  • At present, the products of the three business lines are managed separately for the front-end projects. There will also be a steady increase in demand from the existing businesses, and new product lines will be added later.
  • The main use of[email protected]Version, but there is one item that is adopted3.0Version development.
  • The project has been developed and iterated for 8 months, and the main product business code has exceeded 170,000 lines.

The overall analysis

  • There is no ready-made solution for docking with the third party that can not only meet the current demand but also meet the key elements of strong expansibility. At the same time, the collection of private information cannot be left to the third party.
  • There is not enough code to sell accurately.
  • PV type data collection can be temporarily handed over to a certain degree of statistics, this kind of large and general data only the operation will pay attention to, buried point for user analysis of smaller granularity.
  • Burying point as far as possible does not affect the specific business function and code, or even absolutely does not affect.

Actual demand

  • Collect granularity to button level users.
  • Page entry time, residence time, that is to getenterTimeandleaveTime.
  • Have to be able topageUsage amount, some pressButton to makeDosage, a userUsing trajectoryOne,Period of timeButton usage, userThe browserBasic information, the background has the best front-end page clicksvisualizationRestore.

Transform requirements into data

The following is a summary of the data that needs to be collected

UserAgent // Browser information, including device information, resolution, etc

Path // The page can be accessed using either a router or a location.href

PageInfo // Page information EnterTime and LeaveTime

Userinfo // Userinfo Personal info of the current logged-in user

EventData // Operate event information event type, operation DOM node, operation time, node name (HTML node name), node text

How does code work?

  • Without affecting the existing business, it is necessary to pull out into the common class.
  • User information and other extensibility information that may be logged later should be externally available.
  • Currently using open source UI framework, the project as a wholebuttonNode isbuttonLabels (parts that are not can be changed).
  • Data recording and sending, sending can not be too frequent, need a queue to record data, set a threshold, reach the threshold for sending and emptying.

A rough prototype of the code

The basic structure

Class Monitor {constructor() {} /** * @description * ExtentData is used to pass in business-based data information. * Router is a Vue-Router object, which can be passed in either through init or directly in the current class module. * config * @param {*} {extentData = null, router = null, Config = {memberOf Monitor */ init({extentData = null, router = null, config = {}}) {//TODO // Let {VPT} = config this. VPT = VPT? vpt : This.vpt this.uaHandler() // this.EventCallback.bind (this.vpt this.uaHandler()) = this.eventCallback.bind() // key document.addEventListener('click', this.eventHandler, true) } }


// import Monitor from './ Monitor 'const Monitor = new Monitor() Monitor. Init ({router: import Monitor from './ Monitor' const Monitor = new Monitor() Monitor. Router, extentData: {userInfo: {userName:" 13666666666", account:"13666666666"}})

Data that the Monitor class should have

This.limitNodeType = ['button'] // User browser information this.uainfo = {} // This property is used to hold the anonymous function returned by bind this.eventHandler = null. This property is used to hold the anonymous function returned by bind this.eventHandler = null }

Buried functional structure

Browser Information Record

Here can be customized to get more information, later if the first screen loading, network speed detection and other data also have collection requirements can also be added.

/** * @Description * @MemberOf Monitor */ uaHandler() {this.uaInfo = {userAgent: navigator.userAgent, dpiWidth: window.screen.width, dpiHeight: window.screen.height } }

Record the page dimension information

Because ultimately upload data is an integration of an array of objects, and individual data it is follow the page, the page may be within the scope of threshold access repeatedly, so need a guid identifies the current page “to ensure uniqueness, guid generated as follows, generally within the threshold value to guarantee uniqueness, there is no need to get too complicated.

/** * @description generates a GUID, * @Returns {*} * @MemberOf Monitor */ guid() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8 return v.toString(16) }) }

Reaching the threshold requires the existing data to be uploaded and cleared. The uploaded data should be handled externally, so a get() method should be exposed externally to get the data and clear() to clear the data

To get the data

** * @description * @returns {*} Array[] * @memberof Monitor */ get() {return this.pagedataquene}

Empty data

/** * @description to clear queue * @memberOf Monitor */ clear() {//TODO this.pageDataQuene = []}

Of course, we can also use the internal method of uploading data, that is, we need to introduce the interface request method, and give this function to Monitor. Here, we only need to listen to the queue internally at the same time, that is, if we can have the VPTHandler method

/** * @description threshold listen, Reach the threshold is sending data * @ memberof Monitor * / vptHandler () {if (this. PageDataQuene. Length > = this. VPT) {enclosing sendData publishes the event () / / This is used for data uploading service. After uploading, the data is cleared and can be processed according to the actual situation.

Record basic information about page access

Router.afterEach (async (to) => {// leave listener this.updateEaveTime () // The threshold listens for this.vpthHandler () // The unique identifier of the current action page this.currentQueneId = this.guid() let initPageData = [ { id: this.currentQueneId, path: To. path, // Because the current project directory structure is highly consistent with the menu route naming, the path can be used for recording uaInfo: this.uaInfo, pageInfo: {entryTime: This.getTime () // Note that there is only enterTime, and that the time to updateLeaveTime is given to the method "UpdateEaveTime" on 👆...extentData, eventData: [] / / the event log empty}] this. PageDataQuene = this. PageDataQuene. Concat (initPageData) / / will be appended to the current page data operation queue})

Off page listening

This is the key to completing the closed loop of a page’s data

/ * * * * @ @ leave time to update the description page memberof Monitor * / updateLeaveTime () {let index = this. PageDataQuene. FindIndex ((el) = >  el.id == this.currentQueneId ) if (index >= 0) { this.pageDataQuene[index].pageInfo.leaveTime = this.getTime() } }

Monitoring of events

Listening for events requires a relay. For EventCallback, there should be forwarding for different event types of methods. For now, of course, only click events are considered.

/** * @Description * @Param {*} e * @MemberOf Monitor */ EventCallback (e) {if (e.Type == 'Click ') {@MemberOf Monitor */ EventCallback (e) { this.clickEventHandler(e) } }

Click on Event Listening

As mentioned above, since the whole project adopts a unified UI framework and almost all buttons are standard Button labels, the following data can be obtained according to DOM nodes for recording

const { innerText, localName, formAction, type } =

/** * @description page Click event collection * @Param {*} ele event node * @MemberOf Monitor */ ClickEventHandler (ele) {const {innerText, localName, formAction, type } = ele.target let isEv = this.limitNodeType.includes(localName) if (isEv) { let eventData = [ { innerText, localName, formAction, eleType: type, eventType: 'click', clickTime: This.getTime ()}] // Find the EventData in the current page, Be supplemented the current operation in the let index = this. PageDataQuene. FindIndex ((el) = > el. Id = = this. CurrentQueneId) if (index > = 0) { this.pageDataQuene[index].eventData = this.pageDataQuene[ index ].eventData.concat(eventData) } } }

Listening Event Destruction

We do not need to record user behavior for a specific scenario. Monitor listening events can be destroyed to avoid excessive performance consumption

/ / destruction to monitor events destroy () {document. The removeEventListener (' click ', enclosing eventHandler, true)}

Results output

At this point, we can get the data with the following structure and upload it according to the timing set by the threshold value. To decide

Summary results

The above overall scheme analyzes the requirements according to the actual situation. From the overall analysis to the implementation of the code, combining with the overall characteristics of the project, it also takes into account the low-cost development and access to meet the later scalability, and even the idea can meet the needs of other single-page frameworks.

Such similar needs may not be noticed by the products when the team size is small, and some products without similar experience have no way to start and do not know how to collect and display them. Therefore, it is often the project manager who directly puts forward the requirements to the development without specific prototype output. At this time, the development needs to think about the problem from various dimensions, and turn this “one-sentence requirement” into an executable code scheme and apply it to the work.

Some parts of the code involved in this article may not be considered thoroughly, and there may be many other views and discussions on the point of view of the problem analysis. More original intention is to share this method with many peers (certainly not the best, hahaha), welcome passing XDM drop me at any time.

Don’t be stingy about leaving your footprints 👇