使用UniApp制作动态筛选功能的列表组件(鸿蒙系统适配版)
前言
随着移动应用的普及,用户对应用内容检索和筛选的需求也越来越高。在开发跨平台应用时,动态筛选功能已成为提升用户体验的重要组成部分。本文将详细介绍如何使用UniApp开发一个功能完善的动态筛选列表组件,并重点关注如何适配鸿蒙系统,确保在华为设备上获得良好的用户体验。
需求分析
在日常应用场景中,常见的动态筛选功能包括:
- 多条件组合筛选(如价格、分类、评分等)
- 筛选条件的动态加载
- 筛选结果的实时更新
- 筛选历史记录保存
- 鸿蒙系统上的特殊适配
一个优秀的筛选组件应该具备以下特点:易用性强、响应速度快、视觉反馈清晰、适配多平台(特别是鸿蒙系统)。
技术选型
我们将使用以下技术栈:
- 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. 鸿蒙系统适配要点
在开发过程中,我们需要特别关注鸿蒙系统的适配问题:
- 系统检测:通过
isHarmonyOS()
函数检测当前运行环境是否为鸿蒙系统 - UI适配:针对鸿蒙系统的UI特点(如圆角大小、渐变风格等)进行样式调整
- 字体适配:鸿蒙系统推荐使用HarmonyOS Sans字体
- 动画优化:针对鸿蒙系统的渲染引擎特点进行动画性能优化
示例中的.harmony-list-item
样式类展示了如何为鸿蒙系统添加特定样式:
.harmony-list-item {
/* 鸿蒙系统特有样式 */
border-radius: 16rpx;
background: linear-gradient(to right, #f8f8f8, #fff);
}
3. 性能优化
为了确保组件在各平台(尤其是鸿蒙系统)上的流畅运行,我们采取了以下优化措施:
- 虚拟列表:当数据量大时,可以使用虚拟列表技术,只渲染可视区域的内容
- 懒加载:筛选条件组件采用懒加载方式,按需渲染
- 数据缓存:对筛选结果进行缓存,避免重复计算
- 防抖处理:对筛选操作添加防抖处理,避免频繁触发
实际应用案例
电商商品列表筛选
在电商应用中,可以使用该组件实现商品的多维度筛选,如根据分类、价格、销量等条件筛选商品。
资讯内容筛选
在新闻资讯类应用中,可以使用该组件实现内容的分类筛选,如按照时间、类别、关键词等条件筛选文章。
鸿蒙生态应用
特别是在针对华为鸿蒙生态开发的应用中,该组件可以很好地适配鸿蒙系统的UI风格,提供流畅的用户体验。
总结
本文详细介绍了如何使用UniApp开发一个功能完善的动态筛选列表组件,并重点关注了鸿蒙系统的适配问题。通过合理的组件设计和性能优化,我们可以开发出用户体验良好的筛选功能,满足各种业务场景的需求。
在实际开发中,还可以根据具体业务需求对组件进行扩展和定制,比如添加更多的筛选维度、优化筛选条件的展示方式、增强数据加载性能等。希望本文对你在UniApp开发中实现动态筛选功能有所帮助。