132 lines
3.0 KiB
Plaintext
132 lines
3.0 KiB
Plaintext
<template>
|
|
<view class="segmented-control">
|
|
<view
|
|
v-for="(item, index) in options"
|
|
:key="index"
|
|
class="segment-item"
|
|
:class="{ 'active': selectedIndex === index }"
|
|
@click="handleSelect(index)"
|
|
>
|
|
<text class="segment-text" :class="{ 'active-text': selectedIndex === index }">
|
|
{{ displayLabel(item.label) }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { translateText } from "@/utils/i18n";
|
|
|
|
interface SegmentOption {
|
|
label: string
|
|
value: string | number
|
|
}
|
|
|
|
interface Props {
|
|
options: SegmentOption[]
|
|
modelValue?: string | number
|
|
defaultIndex?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
defaultIndex: 0
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string | number]
|
|
'change': [value: string | number, index: number]
|
|
}>()
|
|
|
|
const selectedIndex = ref<number>(props.defaultIndex)
|
|
|
|
// 监听外部传入的 modelValue 变化
|
|
watch(() => props.modelValue, (newValue) => {
|
|
if (newValue !== undefined) {
|
|
const index = props.options.findIndex(option => option.value === newValue)
|
|
if (index !== -1) {
|
|
selectedIndex.value = index
|
|
}
|
|
}
|
|
}, { immediate: true })
|
|
|
|
const handleSelect = (index: number) => {
|
|
selectedIndex.value = index
|
|
const selectedOption = props.options[index]
|
|
emit('update:modelValue', selectedOption.value)
|
|
emit('change', selectedOption.value, index)
|
|
}
|
|
|
|
/**
|
|
* 统一兼容 key 和旧的字面量文案,减少调用方迁移成本。
|
|
*/
|
|
const displayLabel = (label: string): string => {
|
|
return translateText(label)
|
|
}
|
|
|
|
// 初始化时发送默认值
|
|
onMounted(() => {
|
|
if (props.modelValue === undefined && props.options.length > 0) {
|
|
const defaultOption = props.options[props.defaultIndex]
|
|
emit('update:modelValue', defaultOption.value)
|
|
emit('change', defaultOption.value, props.defaultIndex)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.segmented-control {
|
|
display: flex;
|
|
flex-direction: row;
|
|
background-color: #f5f5f5;
|
|
border-radius: 12rpx;
|
|
padding: 6rpx;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.segment-item {
|
|
flex: 1;
|
|
padding: 16rpx 24rpx;
|
|
text-align: center;
|
|
border-radius: 8rpx;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
z-index: 2;
|
|
cursor: pointer;
|
|
margin-right: 8rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
&:last-child {
|
|
margin-right: 0;
|
|
}
|
|
|
|
&:hover {
|
|
background-color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
&.active {
|
|
background-color: #ffffff;
|
|
z-index: 3;
|
|
}
|
|
}
|
|
|
|
.segment-text {
|
|
text-align: center;
|
|
font-size: 28rpx;
|
|
color: #666666;
|
|
font-weight: 400;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
line-height: 44rpx;
|
|
z-index: 4;
|
|
|
|
&.active-text {
|
|
color: #5147FF;
|
|
font-weight: 600;
|
|
text-shadow: 0 1rpx 2rpx rgba(81, 71, 255, 0.1);
|
|
}
|
|
}
|
|
</style> |