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 推荐系统的完整实现方法,包括:

  1. 推荐系统架构设计:了解了推荐系统的前端组件、后端服务、数据层、算法层等核心组成部分

  2. 推荐算法实现:掌握了协同过滤、内容推荐、混合推荐等核心算法的实现方法

  3. 用户画像构建:学会了用户行为分析、用户特征提取、用户分群、用户偏好建模等技巧

  4. 内容匹配策略:掌握了内容特征提取、相似度计算、标签匹配、语义匹配等方法

  5. 推荐系统优化:应用了冷启动处理、多样性优化、实时性优化、准确性优化等策略

  6. 推荐系统评估:学会了推荐系统的评估方法和指标

推荐系统是现代应用的核心功能之一,它不仅可以提高用户体验,还能增加用户粘性和转化率。在实际项目中,你可以根据具体需求对本教程中的实现进行扩展和优化,构建更加完善的推荐系统。

学习目标

  • 掌握 uni-app 推荐系统的完整实现方法
  • 理解推荐系统的架构设计和数据流程
  • 学会使用推荐算法和用户画像技术
  • 掌握推荐系统的优化策略和评估方法
  • 实现个性化推荐、相关推荐、热门推荐等功能
  • 构建高效、准确、用户友好的推荐系统

通过本教程的学习,你已经具备了开发高质量推荐系统的能力,可以在实际项目中灵活应用这些知识,为用户提供更好的个性化推荐体验。

« 上一篇 uni-app 搜索系统 下一篇 » uni-app 订单系统