uni-app 推荐系统
核心知识点
1. 推荐系统架构
- 前端组件:推荐列表、推荐卡片、个性化设置
- 后端服务:推荐 API、算法引擎、数据处理
- 数据层:用户行为数据、内容数据、特征数据
- 算法层:协同过滤、内容推荐、混合推荐
2. 推荐算法基础
- 协同过滤:基于用户的协同过滤、基于物品的协同过滤
- 内容推荐:基于内容特征的推荐、基于标签的推荐
- 混合推荐:结合多种算法的混合推荐策略
- 实时推荐:基于用户实时行为的推荐
3. 用户画像构建
- 用户行为分析:浏览、点击、收藏、购买等行为分析
- 用户特征提取:年龄、性别、兴趣偏好等特征
- 用户分群:根据行为和特征对用户进行分群
- 用户偏好建模:构建用户偏好模型
4. 内容匹配策略
- 内容特征提取:标题、标签、分类、描述等特征
- 相似度计算:内容间相似度计算方法
- 标签匹配:基于标签的内容匹配
- 语义匹配:基于语义理解的内容匹配
5. 推荐系统优化
- 冷启动问题:新用户、新内容的推荐策略
- 多样性优化:提高推荐结果的多样性
- 实时性优化:提高推荐的实时响应速度
- 准确性优化:提高推荐的准确性和相关性
实用案例
实现个性化推荐功能
1. 推荐列表组件
<template>
<view class="recommendation-list">
<view class="list-header">
<text class="title">{{ title }}</text>
<text class="refresh-btn" @click="refreshRecommendations">换一批</text>
</view>
<view v-if="loading" class="loading-state">
<uni-icons type="spinner" size="30" color="#007AFF" animation="spin" />
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="recommendations.length > 0" class="recommendation-grid">
<view
v-for="(item, index) in recommendations"
:key="index"
class="recommendation-card"
@click="handleItemClick(item)"
>
<view class="card-image">
<image :src="item.image" mode="aspectFill" />
</view>
<view class="card-content">
<text class="card-title">{{ item.title }}</text>
<text class="card-desc">{{ item.description }}</text>
<view class="card-meta">
<text class="card-tag">{{ item.category }}</text>
<text class="card-score">{{ item.score }}分</text>
</view>
</view>
</view>
</view>
<view v-else class="empty-state">
<uni-icons type="info" size="60" color="#CCCCCC" />
<text class="empty-text">暂无推荐内容</text>
</view>
</view>
</template>
<script>
import recommendationApi from '@/api/recommendation';
export default {
props: {
title: {
type: String,
default: '为你推荐'
},
type: {
type: String,
default: 'personalized'
},
limit: {
type: Number,
default: 6
}
},
data() {
return {
recommendations: [],
loading: false,
page: 1
};
},
onLoad() {
this.loadRecommendations();
},
methods: {
loadRecommendations() {
this.loading = true;
recommendationApi.getRecommendations({
type: this.type,
limit: this.limit,
page: this.page
}).then(res => {
if (res.success) {
this.recommendations = res.data;
}
}).catch(error => {
console.error('加载推荐内容失败:', error);
}).finally(() => {
this.loading = false;
});
},
refreshRecommendations() {
this.page++;
this.loadRecommendations();
},
handleItemClick(item) {
// 记录点击行为
this.trackClick(item.id);
// 跳转到详情页
uni.navigateTo({ url: `/pages/detail?id=${item.id}` });
},
trackClick(itemId) {
// 发送点击事件到后端
recommendationApi.trackEvent({
eventType: 'click',
itemId: itemId,
timestamp: new Date().toISOString()
});
}
}
};
</script>
<style scoped>
.recommendation-list {
padding: 20rpx;
background-color: #F5F5F5;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.refresh-btn {
font-size: 24rpx;
color: #007AFF;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
.recommendation-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.recommendation-card {
background-color: #FFFFFF;
border-radius: 10rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.card-image {
width: 100%;
height: 200rpx;
overflow: hidden;
}
.card-image image {
width: 100%;
height: 100%;
}
.card-content {
padding: 15rpx;
}
.card-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.card-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
margin-bottom: 15rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-tag {
font-size: 22rpx;
color: #999;
background-color: #F0F0F0;
padding: 4rpx 12rpx;
border-radius: 12rpx;
}
.card-score {
font-size: 22rpx;
color: #FF6600;
font-weight: bold;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
background-color: #FFFFFF;
border-radius: 10rpx;
}
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
</style>2. 推荐 API 服务
// api/recommendation.js
import request from './request';
export default {
// 获取推荐内容
async getRecommendations(params = {}) {
return request({
url: '/api/recommendation',
method: 'GET',
params: {
type: 'personalized',
limit: 10,
page: 1,
...params
}
});
},
// 获取相关推荐
async getRelatedItems(itemId, params = {}) {
return request({
url: `/api/recommendation/related/${itemId}`,
method: 'GET',
params: {
limit: 6,
...params
}
});
},
// 获取热门推荐
async getHotRecommendations(params = {}) {
return request({
url: '/api/recommendation/hot',
method: 'GET',
params: {
limit: 10,
...params
}
});
},
// 获取新内容推荐
async getNewRecommendations(params = {}) {
return request({
url: '/api/recommendation/new',
method: 'GET',
params: {
limit: 10,
...params
}
});
},
// 记录用户行为
async trackEvent(eventData) {
return request({
url: '/api/recommendation/track',
method: 'POST',
data: eventData
});
},
// 更新用户偏好
async updateUserPreference(preferences) {
return request({
url: '/api/recommendation/preference',
method: 'POST',
data: preferences
});
},
// 获取用户推荐设置
async getUserSettings() {
return request({
url: '/api/recommendation/settings',
method: 'GET'
});
},
// 更新用户推荐设置
async updateUserSettings(settings) {
return request({
url: '/api/recommendation/settings',
method: 'POST',
data: settings
});
}
};3. 推荐设置页面
<template>
<view class="recommendation-settings">
<view class="settings-header">
<text class="title">推荐设置</text>
</view>
<view class="settings-section">
<text class="section-title">兴趣偏好</text>
<view class="interest-tags">
<view
v-for="(tag, index) in interestTags"
:key="index"
:class="['interest-tag', { active: selectedInterests.includes(tag) }]"
@click="toggleInterest(tag)"
>
{{ tag }}
</view>
</view>
</view>
<view class="settings-section">
<text class="section-title">推荐类型</text>
<view class="recommendation-types">
<uni-checkbox-group v-model="selectedTypes">
<label
v-for="(type, index) in recommendationTypes"
:key="index"
class="type-item"
>
<uni-checkbox :value="type.value" />
<text class="type-label">{{ type.label }}</text>
</label>
</uni-checkbox-group>
</view>
</view>
<view class="settings-section">
<text class="section-title">推荐频率</text>
<uni-slider
v-model="recommendationFrequency"
:min="1"
:max="5"
:step="1"
show-value
/>
<text class="frequency-hint">
{{ frequencyOptions[recommendationFrequency - 1] }}
</text>
</view>
<view class="settings-section">
<text class="section-title">隐私设置</text>
<uni-switch
v-model="personalizedRecommendation"
active-color="#007AFF"
@change="handlePersonalizedChange"
/>
<text class="switch-label">开启个性化推荐</text>
</view>
<button class="save-btn" @click="saveSettings">保存设置</button>
</view>
</template>
<script>
export default {
data() {
return {
interestTags: [
'技术', '娱乐', '体育', '财经', '教育',
'健康', '旅游', '美食', '时尚', '科技'
],
selectedInterests: [],
recommendationTypes: [
{ label: '热门推荐', value: 'hot' },
{ label: '最新推荐', value: 'new' },
{ label: '相关推荐', value: 'related' },
{ label: '个性化推荐', value: 'personalized' }
],
selectedTypes: ['hot', 'new', 'personalized'],
recommendationFrequency: 3,
frequencyOptions: [
'低频率', '较低频率', '中等频率', '较高频率', '高频率'
],
personalizedRecommendation: true
};
},
onLoad() {
this.loadSettings();
},
methods: {
loadSettings() {
// 实际项目中应该从后端获取用户设置
// 这里使用模拟数据
this.selectedInterests = ['技术', '科技', '教育'];
this.selectedTypes = ['hot', 'new', 'personalized'];
this.recommendationFrequency = 3;
this.personalizedRecommendation = true;
},
toggleInterest(tag) {
const index = this.selectedInterests.indexOf(tag);
if (index > -1) {
this.selectedInterests.splice(index, 1);
} else {
this.selectedInterests.push(tag);
}
},
handlePersonalizedChange(value) {
this.personalizedRecommendation = value;
},
saveSettings() {
const settings = {
interests: this.selectedInterests,
types: this.selectedTypes,
frequency: this.recommendationFrequency,
personalized: this.personalizedRecommendation
};
// 实际项目中应该调用后端 API 保存设置
console.log('保存推荐设置:', settings);
uni.showToast({
title: '设置保存成功',
icon: 'success'
});
}
}
};
</script>
<style scoped>
.recommendation-settings {
padding: 20rpx;
min-height: 100vh;
background-color: #F5F5F5;
}
.settings-header {
margin-bottom: 30rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.settings-section {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.interest-tags {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
}
.interest-tag {
padding: 15rpx 25rpx;
background-color: #F5F5F5;
border-radius: 25rpx;
font-size: 26rpx;
color: #666;
}
.interest-tag.active {
background-color: #E3F2FD;
color: #007AFF;
}
.recommendation-types {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.type-item {
display: flex;
align-items: center;
gap: 15rpx;
}
.type-label {
font-size: 26rpx;
color: #333;
}
.frequency-hint {
margin-top: 10rpx;
font-size: 24rpx;
color: #666;
}
.switch-label {
margin-left: 15rpx;
font-size: 26rpx;
color: #333;
}
.save-btn {
margin-top: 40rpx;
background-color: #007AFF;
color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
}
</style>4. 推荐算法模拟
// utils/recommendation.js
// 模拟推荐算法
class RecommendationEngine {
constructor() {
// 模拟内容数据
this.contents = [
{
id: 1,
title: 'uni-app 开发入门教程',
category: '技术',
tags: ['uni-app', '前端', '教程'],
score: 9.2,
image: 'https://example.com/images/1.jpg'
},
{
id: 2,
title: 'Vue.js 组件开发实战',
category: '技术',
tags: ['Vue.js', '组件', '前端'],
score: 8.8,
image: 'https://example.com/images/2.jpg'
},
{
id: 3,
title: 'React Native vs uni-app 对比分析',
category: '技术',
tags: ['React Native', 'uni-app', '对比'],
score: 9.0,
image: 'https://example.com/images/3.jpg'
},
{
id: 4,
title: '前端性能优化技巧',
category: '技术',
tags: ['前端', '性能优化', '技巧'],
score: 8.5,
image: 'https://example.com/images/4.jpg'
},
{
id: 5,
title: 'TypeScript 入门指南',
category: '技术',
tags: ['TypeScript', '前端', '入门'],
score: 8.7,
image: 'https://example.com/images/5.jpg'
},
{
id: 6,
title: '移动应用 UI 设计原则',
category: '设计',
tags: ['UI 设计', '移动应用', '原则'],
score: 8.9,
image: 'https://example.com/images/6.jpg'
}
];
// 模拟用户行为数据
this.userBehavior = {
clicks: [1, 2, 3],
favorites: [1, 3],
views: [1, 2, 3, 4, 5]
};
}
// 基于用户行为的推荐
getPersonalizedRecommendations(limit = 6) {
// 1. 分析用户行为
const clickedIds = this.userBehavior.clicks;
const favoritedIds = this.userBehavior.favorites;
// 2. 提取用户偏好标签
const preferredTags = this.extractPreferredTags();
// 3. 计算内容相似度
const recommendations = this.contents.map(content => {
const similarity = this.calculateSimilarity(content, preferredTags);
return {
...content,
similarity
};
});
// 4. 排序并返回结果
return recommendations
.filter(content => !clickedIds.includes(content.id)) // 排除已点击的
.sort((a, b) => b.similarity - a.similarity)
.slice(0, limit);
}
// 提取用户偏好标签
extractPreferredTags() {
const clickedContents = this.contents.filter(content =>
this.userBehavior.clicks.includes(content.id)
);
const tags = {};
clickedContents.forEach(content => {
content.tags.forEach(tag => {
tags[tag] = (tags[tag] || 0) + 1;
});
});
// 按出现次数排序
return Object.entries(tags)
.sort((a, b) => b[1] - a[1])
.map(([tag]) => tag);
}
// 计算内容相似度
calculateSimilarity(content, preferredTags) {
let similarity = 0;
// 标签匹配度
content.tags.forEach(tag => {
const index = preferredTags.indexOf(tag);
if (index !== -1) {
// 位置越靠前,权重越高
similarity += (preferredTags.length - index) / preferredTags.length;
}
});
// 评分权重
similarity += content.score / 10;
return similarity;
}
// 获取相关推荐
getRelatedItems(itemId, limit = 6) {
const targetContent = this.contents.find(content => content.id === itemId);
if (!targetContent) return [];
const recommendations = this.contents.map(content => {
if (content.id === itemId) return null;
const similarity = this.calculateSimilarity(content, targetContent.tags);
return {
...content,
similarity
};
}).filter(Boolean);
return recommendations
.sort((a, b) => b.similarity - a.similarity)
.slice(0, limit);
}
// 获取热门推荐
getHotRecommendations(limit = 6) {
return this.contents
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
// 记录用户行为
trackEvent(eventType, itemId) {
if (!this.userBehavior[eventType]) {
this.userBehavior[eventType] = [];
}
if (!this.userBehavior[eventType].includes(itemId)) {
this.userBehavior[eventType].push(itemId);
}
}
}
export default new RecommendationEngine();实用技巧
1. 冷启动策略
- 新用户冷启动:基于热门内容、默认兴趣标签推荐
- 新内容冷启动:基于内容特征、相似内容推荐
- 渐进式推荐:逐步优化推荐结果
- 用户引导:引导用户选择兴趣标签
2. 推荐系统评估
- 准确率评估:推荐结果的准确率
- 召回率评估:推荐结果的召回率
- 点击率评估:推荐结果的点击率
- 用户满意度评估:用户对推荐的满意度
3. 实时推荐优化
- 行为实时捕获:实时捕获用户行为
- 推荐实时更新:实时更新推荐结果
- 缓存策略:合理使用缓存提高响应速度
- 异步处理:使用异步处理提高系统性能
4. 多样性与新颖性
- 多样性优化:引入多样性因子
- 新颖性优化:推荐新鲜内容
- 平衡策略:平衡准确性与多样性
- 探索与利用:平衡探索新内容与利用已知偏好
5. 推荐系统架构优化
- 微服务架构:将推荐系统拆分为微服务
- 缓存层:使用缓存减少计算开销
- 批处理与流处理:结合批处理和流处理
- 可扩展性:设计可扩展的推荐系统架构
总结
通过本教程的学习,你已经掌握了 uni-app 推荐系统的完整实现方法,包括:
推荐系统架构设计:了解了推荐系统的前端组件、后端服务、数据层、算法层等核心组成部分
推荐算法实现:掌握了协同过滤、内容推荐、混合推荐等核心算法的实现方法
用户画像构建:学会了用户行为分析、用户特征提取、用户分群、用户偏好建模等技巧
内容匹配策略:掌握了内容特征提取、相似度计算、标签匹配、语义匹配等方法
推荐系统优化:应用了冷启动处理、多样性优化、实时性优化、准确性优化等策略
推荐系统评估:学会了推荐系统的评估方法和指标
推荐系统是现代应用的核心功能之一,它不仅可以提高用户体验,还能增加用户粘性和转化率。在实际项目中,你可以根据具体需求对本教程中的实现进行扩展和优化,构建更加完善的推荐系统。
学习目标
- 掌握 uni-app 推荐系统的完整实现方法
- 理解推荐系统的架构设计和数据流程
- 学会使用推荐算法和用户画像技术
- 掌握推荐系统的优化策略和评估方法
- 实现个性化推荐、相关推荐、热门推荐等功能
- 构建高效、准确、用户友好的推荐系统
通过本教程的学习,你已经具备了开发高质量推荐系统的能力,可以在实际项目中灵活应用这些知识,为用户提供更好的个性化推荐体验。