Files

127 lines
2.7 KiB
Vue

<template>
<view class="capsule-filter" :class="{ 'is-scrollable': scrollable }">
<view
v-for="(option, index) in normalizedOptions"
:key="index"
class="capsule-item"
:class="{ 'is-active': isActive(option) }"
:style="getItemStyle(option)"
@click="handleSelect(option)"
>
<text class="capsule-label">{{ getOptionLabel(option) }}</text>
</view>
</view>
</template>
<script>
import themeMixin from '@/mixins/themeMixin.js'
import { getThemeConfigFromStorage, THEME_CHANGE_EVENT } from '@/utils/theme'
export default {
name: 'CapsuleFilter',
mixins: [themeMixin],
props: {
options: {
type: Array,
required: true
},
value: {
type: String,
default: ''
},
scrollable: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'medium'
},
color: {
type: String,
default: ''
}
},
created() {
this.syncThemeConfig()
this.watchThemeChange()
},
beforeUnmount() {
if (this.themeChangeHandler) {
uni.$off(THEME_CHANGE_EVENT, this.themeChangeHandler)
}
},
computed: {
normalizedOptions() {
return this.options.map(opt => {
if (typeof opt === 'string') {
return { label: opt, value: opt }
}
return opt
})
},
activeColor() {
return this.color || this.themeConfig.primaryColor
},
sizeConfig() {
const map = {
small: { padding: '8rpx 20rpx', fontSize: '24rpx' },
medium: { padding: '12rpx 28rpx', fontSize: '28rpx' },
large: { padding: '16rpx 36rpx', fontSize: '32rpx' }
}
return map[this.size] || map.medium
}
},
methods: {
isActive(option) {
return option.value === this.value
},
getOptionLabel(option) {
return option.label
},
getItemStyle(option) {
const active = this.isActive(option)
return {
padding: this.sizeConfig.padding,
fontSize: this.sizeConfig.fontSize,
backgroundColor: active ? this.activeColor : this.themeConfig.borderColor,
color: active ? '#ffffff' : this.themeConfig.textSecondary
}
},
handleSelect(option) {
if (option.value === this.value) return
this.$emit('change', option.value)
}
}
}
</script>
<style scoped>
.capsule-filter {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.capsule-filter.is-scrollable {
flex-wrap: nowrap;
overflow-x: auto;
white-space: nowrap;
}
.capsule-filter.is-scrollable .capsule-item {
flex-shrink: 0;
}
.capsule-item {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
transition: all 0.25s ease;
}
.capsule-label {
line-height: 1.4;
}
</style>