Files
EmailBill/Web/src/components/Common/ClassifySelector.vue

261 lines
5.4 KiB
Vue
Raw Normal View History

2026-01-08 14:41:50 +08:00
<template>
<div class="classify-selector">
<div class="classify-buttons">
<!-- 全选按钮 (仅多选模式) -->
<van-button
v-if="multiple && showAll"
:type="isAllSelected ? 'primary' : 'default'"
size="small"
class="classify-btn all-btn"
@click="toggleAll"
>
{{ isAllSelected ? '取消全选' : '全选' }}
</van-button>
<!-- 新增按钮 -->
<van-button
v-if="showAdd"
type="success"
size="small"
class="classify-btn"
@click="openAddDialog"
>
+ 新增
</van-button>
<!-- 分类项按钮 -->
<van-button
v-for="item in displayOptions"
:key="item.id || item.text"
:type="isSelected(item) ? 'primary' : 'default'"
size="small"
class="classify-btn"
@click="toggleItem(item)"
>
{{ item.text }}
</van-button>
<!-- 清空按钮 -->
<van-button
v-if="showClear && hasSelection"
type="warning"
size="small"
class="classify-btn"
@click="clear"
>
清空
</van-button>
</div>
<!-- 新增分类对话框 -->
<AddClassifyDialog
ref="addClassifyDialogRef"
@confirm="handleAddConfirm"
/>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { showToast } from 'vant'
import { getCategoryList, createCategory } from '@/api/transactionCategory'
import AddClassifyDialog from './AddClassifyDialog.vue'
const props = defineProps({
modelValue: {
type: [String, Array],
default: ''
},
options: {
type: Array,
default: null
},
type: {
type: [Number, String],
default: null
},
multiple: {
type: Boolean,
default: false
},
showAdd: {
type: Boolean,
default: true
},
showClear: {
type: Boolean,
default: true
},
showAll: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'add', 'change'])
const innerOptions = ref([])
const addClassifyDialogRef = ref()
const displayOptions = computed(() => {
2026-01-16 11:15:44 +08:00
if (props.options) {
return props.options
}
2026-01-08 14:41:50 +08:00
return innerOptions.value
})
const fetchOptions = async () => {
2026-01-16 11:15:44 +08:00
if (props.options) {
return
}
2026-01-08 14:41:50 +08:00
try {
const response = await getCategoryList(props.type)
if (response.success) {
2026-01-16 11:15:44 +08:00
innerOptions.value = (response.data || []).map((item) => ({
2026-01-08 14:41:50 +08:00
text: item.name,
value: item.name,
id: item.id
}))
}
} catch (error) {
console.error('ClassifySelector 加载分类失败:', error)
}
}
// 打开新增对话框
const openAddDialog = () => {
addClassifyDialogRef.value?.open()
}
// 处理新增确认
const handleAddConfirm = async (categoryName) => {
try {
// 调用API创建分类
const response = await createCategory({
name: categoryName,
type: props.type
})
2026-01-16 11:15:44 +08:00
2026-01-08 14:41:50 +08:00
if (response.success) {
showToast('分类创建成功')
// 刷新列表
await fetchOptions()
2026-01-16 11:15:44 +08:00
2026-01-08 14:41:50 +08:00
// 如果是单选模式,且当前没有选值或就是为了新增,则自动选中
if (!props.multiple) {
emit('update:modelValue', categoryName)
emit('change', categoryName)
}
} else {
showToast(response.message || '创建分类失败')
}
} catch (error) {
console.error('ClassifySelector 创建分类出错:', error)
showToast('创建分类失败')
}
}
2026-01-16 11:15:44 +08:00
watch(
() => props.type,
() => {
fetchOptions()
}
)
2026-01-08 14:41:50 +08:00
onMounted(() => {
fetchOptions()
})
// 公开刷新方法
defineExpose({
refresh: fetchOptions
})
// 是否选中
const isSelected = (item) => {
if (props.multiple) {
return Array.isArray(props.modelValue) && props.modelValue.includes(item.text)
}
return props.modelValue === item.text
}
// 是否全部选中
const isAllSelected = computed(() => {
2026-01-16 11:15:44 +08:00
if (!props.multiple || displayOptions.value.length === 0) {
return false
}
return displayOptions.value.every((item) => props.modelValue.includes(item.text))
2026-01-08 14:41:50 +08:00
})
// 是否有任何选中
const hasSelection = computed(() => {
if (props.multiple) {
return Array.isArray(props.modelValue) && props.modelValue.length > 0
}
return !!props.modelValue
})
// 切换选中状态
const toggleItem = (item) => {
if (props.multiple) {
const newValue = Array.isArray(props.modelValue) ? [...props.modelValue] : []
const index = newValue.indexOf(item.text)
if (index > -1) {
newValue.splice(index, 1)
} else {
newValue.push(item.text)
}
emit('update:modelValue', newValue)
emit('change', newValue)
} else {
const newValue = props.modelValue === item.text ? '' : item.text
emit('update:modelValue', newValue)
emit('change', newValue)
}
}
// 切换全选
const toggleAll = () => {
2026-01-16 11:15:44 +08:00
if (!props.multiple) {
return
}
2026-01-08 14:41:50 +08:00
if (isAllSelected.value) {
emit('update:modelValue', [])
emit('change', [])
} else {
2026-01-16 11:15:44 +08:00
const allValues = displayOptions.value.map((item) => item.text)
2026-01-08 14:41:50 +08:00
emit('update:modelValue', allValues)
emit('change', allValues)
}
}
// 清空
const clear = () => {
const newValue = props.multiple ? [] : ''
emit('update:modelValue', newValue)
emit('change', newValue)
}
</script>
<style scoped>
.classify-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 16px;
}
.classify-btn {
flex: 0 0 auto;
min-width: 70px;
border-radius: 16px;
}
.all-btn {
font-weight: bold;
border-style: dashed;
}
</style>