基于Vue+Node.js實(shí)現(xiàn)埋點(diǎn)功能全流程

要實(shí)現(xiàn)用戶訪問量統(tǒng)計(jì)和模塊點(diǎn)擊統(tǒng)計(jì)功能,可以采用以下方案:
一、前端(Vue)實(shí)現(xiàn)
1. 安裝必要的依賴
npm install axios --save # 用于發(fā)送請(qǐng)求 npm install js-cookie --save # 用于識(shí)別用戶
2. 創(chuàng)建埋點(diǎn)工具類 (tracker.js)
import axios from 'axios';
import Cookies from 'js-cookie';
const Tracker = {
// 初始化用戶ID(如果不存在)
initUserId() {
let userId = Cookies.get('user_id');
if (!userId) {
userId = 'user_' + Math.random().toString(36).substr(2, 9);
Cookies.set('user_id', userId, { expires: 365 });
}
return userId;
},
// 發(fā)送頁面訪問埋點(diǎn)
trackPageView() {
const userId = this.initUserId();
const data = {
userId: userId,
eventType: 'pageview',
pageUrl: window.location.href,
timestamp: new Date().toISOString()
};
this.sendData(data);
},
// 發(fā)送模塊點(diǎn)擊埋點(diǎn)
trackModuleClick(moduleName) {
const userId = this.initUserId();
const data = {
userId: userId,
eventType: 'module_click',
moduleName: moduleName,
timestamp: new Date().toISOString()
};
this.sendData(data);
},
// 發(fā)送數(shù)據(jù)到后端
sendData(data) {
axios.post('/api/track', data)
.catch(error => {
console.error('Tracking error:', error);
});
}
};
export default Tracker;
3. 在Vue應(yīng)用中使用
全局埋點(diǎn) (main.js)
import Tracker from './utils/tracker';
import router from './router';
// 頁面訪問埋點(diǎn)
router.afterEach((to, from) => {
Tracker.trackPageView();
});
// 掛載到Vue原型,方便組件內(nèi)使用
Vue.prototype.$tracker = Tracker;
組件內(nèi)模塊點(diǎn)擊埋點(diǎn)
<template>
<div>
<button @click="handleClick('module1')">模塊1</button>
<button @click="handleClick('module2')">模塊2</button>
</div>
</template>
<script>
export default {
methods: {
handleClick(moduleName) {
// 業(yè)務(wù)邏輯...
// 埋點(diǎn)
this.$tracker.trackModuleClick(moduleName);
}
}
}
</script>
二、后端(Node.js)實(shí)現(xiàn)
1. 創(chuàng)建數(shù)據(jù)庫(kù)表
假設(shè)使用MySQL:
CREATE TABLE tracking_events ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255), event_type VARCHAR(50), page_url VARCHAR(500), module_name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE daily_stats ( id INT AUTO_INCREMENT PRIMARY KEY, date DATE UNIQUE, total_visits INT DEFAULT 0, unique_visitors INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE module_stats ( id INT AUTO_INCREMENT PRIMARY KEY, module_name VARCHAR(100) UNIQUE, click_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
2. Node.js 后端接口 (Express示例)
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql2/promise');
const app = express();
// 數(shù)據(jù)庫(kù)連接配置
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'tracking_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
app.use(bodyParser.json());
// 埋點(diǎn)數(shù)據(jù)接收接口
app.post('/api/track', async (req, res) => {
try {
const { userId, eventType, pageUrl, moduleName } = req.body;
// 1. 記錄原始事件
await pool.query(
'INSERT INTO tracking_events (user_id, event_type, page_url, module_name) VALUES (?, ?, ?, ?)',
[userId, eventType, pageUrl, moduleName]
);
// 2. 如果是頁面訪問,更新每日統(tǒng)計(jì)
if (eventType === 'pageview') {
await updateDailyStats(userId);
}
// 3. 如果是模塊點(diǎn)擊,更新模塊統(tǒng)計(jì)
if (eventType === 'module_click' && moduleName) {
await updateModuleStats(moduleName);
}
res.status(200).send('OK');
} catch (error) {
console.error('Tracking error:', error);
res.status(500).send('Internal Server Error');
}
});
// 更新每日統(tǒng)計(jì)
async function updateDailyStats(userId) {
const today = new Date().toISOString().split('T')[0];
// 檢查今天是否已有記錄
const [rows] = await pool.query('SELECT * FROM daily_stats WHERE date = ?', [today]);
if (rows.length === 0) {
// 新的一天,創(chuàng)建新記錄
await pool.query(
'INSERT INTO daily_stats (date, total_visits, unique_visitors) VALUES (?, 1, 1)',
[today]
);
} else {
// 更新現(xiàn)有記錄
// 檢查用戶今天是否已經(jīng)訪問過
const [visits] = await pool.query(
'SELECT COUNT(DISTINCT user_id) as user_visited FROM tracking_events ' +
'WHERE event_type = "pageview" AND DATE(created_at) = ? AND user_id = ?',
[today, userId]
);
const isNewVisitor = visits[0].user_visited === 0;
await pool.query(
'UPDATE daily_stats SET total_visits = total_visits + 1, ' +
'unique_visitors = unique_visitors + ? WHERE date = ?',
[isNewVisitor ? 1 : 0, today]
);
}
}
// 更新模塊統(tǒng)計(jì)
async function updateModuleStats(moduleName) {
await pool.query(
'INSERT INTO module_stats (module_name, click_count) VALUES (?, 1) ' +
'ON DUPLICATE KEY UPDATE click_count = click_count + 1',
[moduleName]
);
}
// 獲取統(tǒng)計(jì)數(shù)據(jù)的接口
app.get('/api/stats', async (req, res) => {
try {
// 總訪問量
const [totalVisits] = await pool.query('SELECT SUM(total_visits) as total FROM daily_stats');
// 今日訪問量
const today = new Date().toISOString().split('T')[0];
const [todayStats] = await pool.query('SELECT * FROM daily_stats WHERE date = ?', [today]);
// 熱門模塊
const [popularModules] = await pool.query(
'SELECT module_name, click_count FROM module_stats ORDER BY click_count DESC LIMIT 5'
);
res.json({
totalVisits: totalVisits[0].total || 0,
todayVisits: todayStats[0] ? todayStats[0].total_visits : 0,
todayUniqueVisitors: todayStats[0] ? todayStats[0].unique_visitors : 0,
popularModules: popularModules
});
} catch (error) {
console.error('Error fetching stats:', error);
res.status(500).send('Internal Server Error');
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
三、數(shù)據(jù)可視化
可以創(chuàng)建一個(gè)管理后臺(tái)頁面來展示這些統(tǒng)計(jì)數(shù)據(jù):
<template>
<div class="stats-dashboard">
<h1>網(wǎng)站訪問統(tǒng)計(jì)</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>總訪問量</h3>
<p class="stat-value">{{ stats.totalVisits }}</p>
</div>
<div class="stat-card">
<h3>今日訪問量</h3>
<p class="stat-value">{{ stats.todayVisits }}</p>
</div>
<div class="stat-card">
<h3>今日獨(dú)立訪客</h3>
<p class="stat-value">{{ stats.todayUniqueVisitors }}</p>
</div>
</div>
<div class="popular-modules">
<h2>熱門模塊</h2>
<ul>
<li v-for="(module, index) in stats.popularModules" :key="index">
{{ module.module_name }}: {{ module.click_count }} 次點(diǎn)擊
</li>
</ul>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
stats: {
totalVisits: 0,
todayVisits: 0,
todayUniqueVisitors: 0,
popularModules: []
}
};
},
mounted() {
this.fetchStats();
},
methods: {
async fetchStats() {
try {
const response = await axios.get('/api/stats');
this.stats = response.data;
} catch (error) {
console.error('Error fetching stats:', error);
}
}
}
};
</script>
<style>
.stats-dashboard {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin: 10px 0 0;
}
.popular-modules ul {
list-style: none;
padding: 0;
}
.popular-modules li {
padding: 10px;
background: #f0f0f0;
margin-bottom: 5px;
border-radius: 4px;
}
</style>
四、優(yōu)化建議
性能優(yōu)化:
- 前端可以使用節(jié)流(throttle)或防抖(debounce)技術(shù)減少高頻點(diǎn)擊的埋點(diǎn)請(qǐng)求
- 后端可以考慮使用批量插入代替單條插入
數(shù)據(jù)準(zhǔn)確性:
- 使用更可靠的用戶識(shí)別方式,如結(jié)合IP、設(shè)備指紋等
- 考慮使用Web Beacon API在頁面卸載時(shí)發(fā)送數(shù)據(jù)
擴(kuò)展性:
- 可以添加更多事件類型(如停留時(shí)長(zhǎng)、滾動(dòng)深度等)
- 可以按時(shí)間段(小時(shí)/天/周)分析訪問模式
隱私合規(guī):
- 添加用戶同意機(jī)制(如GDPR合規(guī))
- 提供隱私政策說明數(shù)據(jù)收集用途
這個(gè)方案提供了完整的從前端埋點(diǎn)到后端存儲(chǔ)再到數(shù)據(jù)展示的全流程實(shí)現(xiàn),你可以根據(jù)實(shí)際需求進(jìn)行調(diào)整和擴(kuò)展。
以上就是基于Vue+Node.js實(shí)現(xiàn)埋點(diǎn)功能全流程的詳細(xì)內(nèi)容,更多關(guān)于Vue+Node.js埋點(diǎn)功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談vue自定義全局組件并通過全局方法 Vue.use() 使用該組件
本篇文章主要介紹了vue自定義全局組件并通過全局方法 Vue.use() 使用該組件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Vue2.5學(xué)習(xí)筆記之如何在項(xiàng)目中使用和配置Vue
這篇文章主要介紹了Vue2.5學(xué)習(xí)筆記之如何在項(xiàng)目中使用和配置Vue的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09
Vue中嵌入可浮動(dòng)的第三方網(wǎng)頁窗口的示例詳解
本文介紹了在Vue2項(xiàng)目中嵌入可浮動(dòng)的第三方網(wǎng)頁窗口的實(shí)現(xiàn)方法,包括使用iframe、div+script和dialog元素等方式,并提供了一個(gè)實(shí)戰(zhàn)Demo,展示了如何在Vue組件中動(dòng)態(tài)加載和控制浮窗的顯示,需要的朋友可以參考下2025-02-02
vue實(shí)現(xiàn)的上拉加載更多數(shù)據(jù)/分頁功能示例
這篇文章主要介紹了vue實(shí)現(xiàn)的上拉加載更多數(shù)據(jù)/分頁功能,涉及基于vue的事件響應(yīng)、數(shù)據(jù)交互等相關(guān)操作技巧,需要的朋友可以參考下2019-05-05
vue 設(shè)置 input 為不可以編輯的實(shí)現(xiàn)方法
今天小編就為大家分享一篇vue 設(shè)置 input 為不可以編輯的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09

