185 lines
3.9 KiB
Vue
Raw Normal View History

2025-08-28 17:29:25 +08:00
<!-- components/uni-app-tag/uni-app-tag.vue -->
<template>
<view class="uni-app-tag-container">
<!-- 匹配的标签 -->
<template
v-for="(item, index) in matchedItems"
:key="getValue(item)"
>
<!-- 普通 spandefault 类型 + class -->
<span
v-if="isDefaultTag(item)"
class="uni-app-tag-span"
:class="item.elTagClass"
>
{{ getText(item) }}&nbsp;
</span>
<!-- 自定义样式 view 模拟 tag -->
<view
v-else
class="uni-app-tag"
:class="[
`uni-app-tag--${getUniType(item.elTagType)}`,
item.elTagClass
]"
>
{{ getText(item) }}
</view>
</template>
<!-- 未匹配的值 -->
<text v-if="unmatch && showValue" class="uni-app-tag-unmatch">
{{ unmatchText }}
</text>
</view>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
/* 选项数组 */
options: {
type: Array,
default: () => []
},
/* 当前值String | Number | Array */
value: [String, Number, Array],
/* 是否显示未匹配值 */
showValue: {
type: Boolean,
default: true
},
/* 字符串分隔符(仅 value 为字符串时生效) */
separator: {
type: String,
default: ','
},
/* 字段映射:值字段名 */
valueField: {
type: String,
default: 'dictValue'
},
/* 字段映射:文本字段名 */
textField: {
type: String,
default: 'dictLabel'
}
})
/* 工具函数:统一取出值 / 文本 */
const getValue = (opt) => String(opt[props.valueField])
const getText = (opt) => opt[props.textField] ?? ''
/* 1. 统一转成字符串数组 */
const values = computed(() => {
if (props.value == null || props.value === '') return []
return Array.isArray(props.value)
? props.value.map(String)
: String(props.value).split(props.separator).map(v => v.trim())
})
/* 2. 匹配到的选项 */
const matchedItems = computed(() =>
values.value
.map(val => props.options.find(opt => getValue(opt) === val))
.filter(Boolean)
)
/* 3. 未匹配的值 */
const unmatchArray = computed(() => {
if (!values.value.length || !props.options.length) return []
return values.value.filter(
val => !props.options.some(opt => getValue(opt) === val)
)
})
const unmatch = computed(() => unmatchArray.value.length > 0)
const unmatchText = computed(() => unmatchArray.value.join(' '))
/* 4. 判断是否为默认 tagdefault 且无自定义 class */
const isDefaultTag = (item) => {
const type = item.elTagType || 'default'
const cls = item.elTagClass || ''
return (type === 'default' || !type) && !cls
}
/* 5. el-tag type -> uni 类名映射 */
const getUniType = (type) => {
const map = {
primary: 'primary',
success: 'success',
warning: 'warning',
danger: 'error',
info: 'info',
default: 'default'
}
return map[type] || 'default'
}
</script>
<style scoped>
.uni-app-tag-container {
display: flex;
flex-wrap: wrap;
align-items: center;
font-size: 14px;
}
.uni-app-tag-span {
margin-right: 8px;
white-space: nowrap;
}
.uni-app-tag {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
margin-right: 8px;
white-space: nowrap;
}
.uni-app-tag--primary {
background-color: #d0e3ff;
color: #0066ff;
border: 1px solid #99ccff;
}
.uni-app-tag--success {
background-color: #dcf5e7;
color: #00aa55;
border: 1px solid #88ccaa;
}
.uni-app-tag--warning {
background-color: #fff0d9;
color: #cc8800;
border: 1px solid #ffcc88;
}
.uni-app-tag--error {
background-color: #ffd9dc;
color: #ff3344;
border: 1px solid #ff99aa;
}
.uni-app-tag--info {
background-color: #e6e6e6;
color: #666666;
border: 1px solid #cccccc;
}
.uni-app-tag--default {
background-color: #f4f4f4;
color: #333;
border: 1px solid #ddd;
}
.uni-app-tag-unmatch {
margin-left: 4px;
color: #999;
font-size: 12px;
}
</style>