鸿蒙OS&UniApp制作动态筛选功能的列表组件(鸿蒙系统适配版)#三方框架 #Uniapp

发布于:2025-05-19 ⋅ 阅读:(16) ⋅ 点赞:(0)

使用UniApp制作动态筛选功能的列表组件(鸿蒙系统适配版)

前言

随着移动应用的普及,用户对应用内容检索和筛选的需求也越来越高。在开发跨平台应用时,动态筛选功能已成为提升用户体验的重要组成部分。本文将详细介绍如何使用UniApp开发一个功能完善的动态筛选列表组件,并重点关注如何适配鸿蒙系统,确保在华为设备上获得良好的用户体验。

需求分析

在日常应用场景中,常见的动态筛选功能包括:

  1. 多条件组合筛选(如价格、分类、评分等)
  2. 筛选条件的动态加载
  3. 筛选结果的实时更新
  4. 筛选历史记录保存
  5. 鸿蒙系统上的特殊适配

一个优秀的筛选组件应该具备以下特点:易用性强、响应速度快、视觉反馈清晰、适配多平台(特别是鸿蒙系统)。

技术选型

我们将使用以下技术栈:

  • UniApp作为跨平台开发框架
  • Vue3 + TypeScript提供响应式编程体验
  • uView UI组件库辅助界面开发
  • Vuex管理筛选状态
  • 针对鸿蒙系统的特殊API调用

组件设计与实现

1. 基础结构设计

首先,我们设计组件的基础结构:

<template>
  <view class="filter-container">
    <!-- 筛选条件区域 -->
    <view class="filter-header">
      <view 
        v-for="(item, index) in filterOptions" 
        :key="index"
        class="filter-tab"
        :class="{'active': currentTab === index}"
        @click="switchTab(index)"
      >
        <text>{{ item.name }}</text>
        <text class="icon" :class="{'icon-up': item.isOpen, 'icon-down': !item.isOpen}"></text>
      </view>
    </view>
    
    <!-- 筛选面板 -->
    <view class="filter-panel" v-if="showPanel">
      <component 
        :is="activeComponent" 
        :options="activeOptions"
        @confirm="confirmFilter"
        @reset="resetFilter"
      ></component>
    </view>
    
    <!-- 列表区域 -->
    <view class="list-container">
      <view 
        v-for="(item, index) in filteredList" 
        :key="index"
        class="list-item harmony-list-item"
      >
        <text class="item-title">{{ item.title }}</text>
        <text class="item-desc">{{ item.description }}</text>
      </view>
      
      <!-- 空数据提示 -->
      <view class="empty-tip" v-if="filteredList.length === 0">
        <text>暂无符合条件的数据</text>
      </view>
    </view>
  </view>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, computed, onMounted } from 'vue';
import CategoryFilter from './filter-components/CategoryFilter.vue';
import PriceFilter from './filter-components/PriceFilter.vue';
import SortFilter from './filter-components/SortFilter.vue';
import { isHarmonyOS, adaptToHarmonyOS } from '@/utils/platform';

export default defineComponent({
  components: {
    CategoryFilter,
    PriceFilter,
    SortFilter
  },
  setup() {
    // 是否为鸿蒙系统
    const isHarmony = ref(false);
    
    // 筛选选项定义
    const filterOptions = reactive([
      { name: '分类', type: 'category', isOpen: false },
      { name: '价格', type: 'price', isOpen: false },
      { name: '排序', type: 'sort', isOpen: false },
    ]);
    
    // 当前选中的标签
    const currentTab = ref(-1);
    
    // 是否显示筛选面板
    const showPanel = computed(() => currentTab.value >= 0);
    
    // 筛选条件
    const filterConditions = reactive({
      category: [],
      price: { min: 0, max: 9999 },
      sort: 'default'
    });
    
    // 原始数据列表
    const originalList = ref([]);
    
    // 获取筛选后的列表
    const filteredList = computed(() => {
      return originalList.value.filter((item: any) => {
        // 分类筛选
        if (filterConditions.category.length > 0 && 
            !filterConditions.category.includes(item.category)) {
          return false;
        }
        
        // 价格筛选
        if (item.price < filterConditions.price.min || 
            item.price > filterConditions.price.max) {
          return false;
        }
        
        return true;
      }).sort((a: any, b: any) => {
        // 排序逻辑
        if (filterConditions.sort === 'price-asc') {
          return a.price - b.price;
        } else if (filterConditions.sort === 'price-desc') {
          return b.price - a.price;
        }
        return 0;
      });
    });
    
    // 当前激活的筛选组件
    const activeComponent = computed(() => {
      if (currentTab.value < 0) return null;
      const type = filterOptions[currentTab.value].type;
      return type.charAt(0).toUpperCase() + type.slice(1) + 'Filter';
    });
    
    // 当前筛选组件的选项
    const activeOptions = computed(() => {
      if (currentTab.value < 0) return {};
      const type = filterOptions[currentTab.value].type;
      return filterConditions[type];
    });
    
    // 切换筛选标签
    const switchTab = (index: number) => {
      if (currentTab.value === index) {
        currentTab.value = -1;
        filterOptions[index].isOpen = false;
      } else {
        // 关闭之前打开的标签
        if (currentTab.value >= 0) {
          filterOptions[currentTab.value].isOpen = false;
        }
        currentTab.value = index;
        filterOptions[index].isOpen = true;
      }
    };
    
    // 确认筛选
    const confirmFilter = (data: any) => {
      const type = filterOptions[currentTab.value].type;
      filterConditions[type] = data;
      currentTab.value = -1;
      filterOptions[currentTab.value].isOpen = false;
      
      // 保存筛选历史
      saveFilterHistory();
    };
    
    // 重置筛选
    const resetFilter = () => {
      const type = filterOptions[currentTab.value].type;
      
      if (type === 'category') {
        filterConditions.category = [];
      } else if (type === 'price') {
        filterConditions.price = { min: 0, max: 9999 };
      } else if (type === 'sort') {
        filterConditions.sort = 'default';
      }
    };
    
    // 保存筛选历史
    const saveFilterHistory = () => {
      uni.setStorageSync('filter_history', JSON.stringify(filterConditions));
    };
    
    // 获取筛选历史
    const getFilterHistory = () => {
      try {
        const history = uni.getStorageSync('filter_history');
        if (history) {
          const parsedHistory = JSON.parse(history);
          Object.assign(filterConditions, parsedHistory);
        }
      } catch (e) {
        console.error('获取筛选历史失败', e);
      }
    };
    
    // 获取列表数据
    const fetchListData = () => {
      // 这里模拟数据请求
      setTimeout(() => {
        originalList.value = [
          { id: 1, title: '商品1', description: '这是商品1的描述', category: 'food', price: 129 },
          { id: 2, title: '商品2', description: '这是商品2的描述', category: 'food', price: 59 },
          { id: 3, title: '商品3', description: '这是商品3的描述', category: 'cloth', price: 199 },
          { id: 4, title: '商品4', description: '这是商品4的描述', category: 'electronic', price: 1299 },
          { id: 5, title: '商品5', description: '这是商品5的描述', category: 'electronic', price: 899 },
        ];
      }, 500);
    };
    
    onMounted(() => {
      // 检测是否为鸿蒙系统
      isHarmony.value = isHarmonyOS();
      
      // 鸿蒙系统适配
      if (isHarmony.value) {
        adaptToHarmonyOS();
      }
      
      // 获取数据
      fetchListData();
      
      // 获取历史筛选条件
      getFilterHistory();
    });
    
    return {
      filterOptions,
      currentTab,
      showPanel,
      filteredList,
      activeComponent,
      activeOptions,
      switchTab,
      confirmFilter,
      resetFilter,
      isHarmony
    };
  }
});
</script>

<style>
.filter-container {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.filter-header {
  display: flex;
  height: 88rpx;
  border-bottom: 1rpx solid #eee;
  background-color: #fff;
}

.filter-tab {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 28rpx;
  color: #333;
}

.filter-tab.active {
  color: #007aff;
}

.icon {
  margin-left: 10rpx;
  font-size: 24rpx;
}

.filter-panel {
  border-bottom: 1rpx solid #eee;
  background-color: #fff;
}

.list-container {
  padding: 20rpx;
}

.list-item {
  margin-bottom: 20rpx;
  padding: 20rpx;
  background-color: #fff;
  border-radius: 8rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}

.harmony-list-item {
  /* 鸿蒙系统特有样式 */
  border-radius: 16rpx;
  background: linear-gradient(to right, #f8f8f8, #fff);
}

.item-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.item-desc {
  font-size: 28rpx;
  color: #666;
}

.empty-tip {
  display: flex;
  justify-content: center;
  padding: 100rpx 0;
  color: #999;
}
</style>

2. 分类筛选子组件

<template>
  <view class="category-filter">
    <view class="filter-options">
      <view 
        v-for="(item, index) in categories" 
        :key="index"
        class="category-item"
        :class="{'selected': selected.includes(item.value)}"
        @click="toggleSelect(item.value)"
      >
        <text>{{ item.label }}</text>
      </view>
    </view>
    
    <view class="filter-actions">
      <button class="btn-reset" @click="reset">重置</button>
      <button class="btn-confirm" @click="confirm">确定</button>
    </view>
  </view>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  props: {
    options: {
      type: Array,
      default: () => []
    }
  },
  emits: ['confirm', 'reset'],
  setup(props, { emit }) {
    // 分类选项
    const categories = [
      { label: '食品', value: 'food' },
      { label: '服装', value: 'cloth' },
      { label: '电子产品', value: 'electronic' },
      { label: '家居', value: 'household' },
      { label: '美妆', value: 'beauty' }
    ];
    
    // 已选择的分类
    const selected = ref(props.options || []);
    
    // 切换选择
    const toggleSelect = (value: string) => {
      const index = selected.value.indexOf(value);
      if (index > -1) {
        selected.value.splice(index, 1);
      } else {
        selected.value.push(value);
      }
    };
    
    // 确认选择
    const confirm = () => {
      emit('confirm', [...selected.value]);
    };
    
    // 重置选择
    const reset = () => {
      selected.value = [];
      emit('reset');
    };
    
    return {
      categories,
      selected,
      toggleSelect,
      confirm,
      reset
    };
  }
});
</script>

<style>
.category-filter {
  padding: 20rpx;
}

.filter-options {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 30rpx;
}

.category-item {
  width: 30%;
  height: 80rpx;
  margin: 10rpx 1.66%;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  font-size: 28rpx;
}

.category-item.selected {
  background-color: #e1f0ff;
  border-color: #007aff;
  color: #007aff;
}

.filter-actions {
  display: flex;
  justify-content: space-between;
  padding: 20rpx 0;
}

.btn-reset, .btn-confirm {
  width: 45%;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  border-radius: 8rpx;
  font-size: 28rpx;
}

.btn-reset {
  background-color: #f5f5f5;
  color: #666;
}

.btn-confirm {
  background-color: #007aff;
  color: #fff;
}
</style>

3. 鸿蒙系统适配工具函数

// utils/platform.ts

/**
 * 检测当前环境是否为鸿蒙系统
 */
export function isHarmonyOS(): boolean {
  // #ifdef APP-PLUS
  const systemInfo = uni.getSystemInfoSync();
  const systemName = systemInfo.osName || '';
  const systemVersion = systemInfo.osVersion || '';
  
  // 鸿蒙系统识别
  return systemName.toLowerCase().includes('harmony') || 
         (systemName === 'android' && systemVersion.includes('harmony'));
  // #endif
  
  return false;
}

/**
 * 鸿蒙系统适配操作
 */
export function adaptToHarmonyOS(): void {
  // #ifdef APP-PLUS
  try {
    // 调整状态栏
    plus.navigator.setStatusBarStyle('dark');
    
    // 适配鸿蒙特有API
    if (plus.os.name === 'Android' && plus.device.vendor === 'HUAWEI') {
      // 这里可以添加针对华为设备的特殊处理
      console.log('正在运行于华为设备,进行鸿蒙系统适配');
      
      // 示例:自定义字体适配
      // 鸿蒙系统使用HarmonyOS Sans字体
      const fontFamily = plus.os.version.includes('harmony') ? 
                         'HarmonyOS_Sans' : 'sans-serif';
      
      // 可以通过CSS变量设置全局字体
      document.documentElement.style.setProperty('--app-font-family', fontFamily);
    }
  } catch (e) {
    console.error('鸿蒙系统适配失败', e);
  }
  // #endif
}

/**
 * 针对鸿蒙系统优化动画效果
 * @param element DOM元素
 */
export function optimizeAnimationForHarmony(element: any): void {
  if (!isHarmonyOS()) return;
  
  // #ifdef APP-PLUS
  try {
    // 在鸿蒙系统上优化动画性能
    if (element && element.style) {
      element.style.setProperty('transform', 'translateZ(0)');
      element.style.setProperty('backface-visibility', 'hidden');
      element.style.setProperty('perspective', '1000px');
    }
  } catch (e) {
    console.error('动画优化失败', e);
  }
  // #endif
}

功能详解

1. 动态筛选核心逻辑

组件的核心是通过计算属性filteredList实现动态筛选。每当筛选条件变化时,该计算属性会重新计算,过滤出符合条件的数据项:

const filteredList = computed(() => {
  return originalList.value.filter((item: any) => {
    // 分类筛选
    if (filterConditions.category.length > 0 && 
        !filterConditions.category.includes(item.category)) {
      return false;
    }
    
    // 价格筛选
    if (item.price < filterConditions.price.min || 
        item.price > filterConditions.price.max) {
      return false;
    }
    
    return true;
  }).sort((a: any, b: any) => {
    // 排序逻辑
    if (filterConditions.sort === 'price-asc') {
      return a.price - b.price;
    } else if (filterConditions.sort === 'price-desc') {
      return b.price - a.price;
    }
    return 0;
  });
});

2. 鸿蒙系统适配要点

在开发过程中,我们需要特别关注鸿蒙系统的适配问题:

  1. 系统检测:通过isHarmonyOS()函数检测当前运行环境是否为鸿蒙系统
  2. UI适配:针对鸿蒙系统的UI特点(如圆角大小、渐变风格等)进行样式调整
  3. 字体适配:鸿蒙系统推荐使用HarmonyOS Sans字体
  4. 动画优化:针对鸿蒙系统的渲染引擎特点进行动画性能优化

示例中的.harmony-list-item样式类展示了如何为鸿蒙系统添加特定样式:

.harmony-list-item {
  /* 鸿蒙系统特有样式 */
  border-radius: 16rpx;
  background: linear-gradient(to right, #f8f8f8, #fff);
}

3. 性能优化

为了确保组件在各平台(尤其是鸿蒙系统)上的流畅运行,我们采取了以下优化措施:

  1. 虚拟列表:当数据量大时,可以使用虚拟列表技术,只渲染可视区域的内容
  2. 懒加载:筛选条件组件采用懒加载方式,按需渲染
  3. 数据缓存:对筛选结果进行缓存,避免重复计算
  4. 防抖处理:对筛选操作添加防抖处理,避免频繁触发

实际应用案例

电商商品列表筛选

在电商应用中,可以使用该组件实现商品的多维度筛选,如根据分类、价格、销量等条件筛选商品。

资讯内容筛选

在新闻资讯类应用中,可以使用该组件实现内容的分类筛选,如按照时间、类别、关键词等条件筛选文章。

鸿蒙生态应用

特别是在针对华为鸿蒙生态开发的应用中,该组件可以很好地适配鸿蒙系统的UI风格,提供流畅的用户体验。

总结

本文详细介绍了如何使用UniApp开发一个功能完善的动态筛选列表组件,并重点关注了鸿蒙系统的适配问题。通过合理的组件设计和性能优化,我们可以开发出用户体验良好的筛选功能,满足各种业务场景的需求。

在实际开发中,还可以根据具体业务需求对组件进行扩展和定制,比如添加更多的筛选维度、优化筛选条件的展示方式、增强数据加载性能等。希望本文对你在UniApp开发中实现动态筛选功能有所帮助。

参考资源

  1. UniApp官方文档
  2. 鸿蒙系统设计规范
  3. Vue3官方文档
  4. TypeScript官方文档