feat: 添加选项组组件

This commit is contained in:
wsgh0202 2025-08-07 20:39:20 +08:00
parent f296bbf483
commit 5e5a2c15a5
No known key found for this signature in database
GPG Key ID: F9658C3FEDCF6979
2 changed files with 309 additions and 0 deletions

View File

@ -0,0 +1,187 @@
<template>
<OptionWidgetLayout :title="title" :is-popup="isPopup" :icon="icon">
<CollapsibleContainer :default-expanded="true" :title="title">
<RadioGroupItem
v-for="[key, itemVM] of Object.entries(itemVMs)"
:key="key"
:title="itemVM.displayName"
:default-expanded="!!itemVM.optionsFiltered"
:hide-expand-button="!itemVM.optionsFiltered"
:checked="itemVM.checked"
:group="groupName"
@change="checked => onRadioChange(itemVM, checked)"
>
<div v-if="itemVM.optionsFiltered">
<ComponentOption
v-for="[name, option] of Object.entries(itemVM.optionsFiltered)"
:key="name"
:name="name"
:display-name="option.displayName"
:option="option"
:component="componentData"
/>
</div>
</RadioGroupItem>
</CollapsibleContainer>
</OptionWidgetLayout>
</template>
<script lang="ts">
import { defineComponent, PropType, ref } from 'vue'
import { OptionMetadata } from '@/components/types'
import ComponentOption from './ComponentOption.vue'
import RadioGroupItem from './RadioGroupItem.vue'
import CollapsibleContainer from './CollapsibleContainer.vue'
import { getComponentSettings } from '@/core/settings'
import { componentsMap } from '@/components/component'
import OptionWidgetLayout from './OptionWidgetLayout.vue'
export type RadioItem = {
/**
* 选项名称
*
* isOption为false时直接显示文本
* isOption为true时显示对应选项的displayName
*/
name: string
/** 是否为脚本设置选项 */
isOption?: boolean
/** 下拉面板中包含的选项名称,可以是名称数组或正则表达式 */
optionsIncluded?: string[] | RegExp
/**
* 是否选中
*
* isOption为true时将忽略设置自动绑定为选项状态
*/
checked?: boolean
/** 选项变化时的回调 */
onChange?: (checked: boolean) => void
}
type RadioItemVM = RadioItem & {
/** 最终显示名称 */
displayName?: string
/** 过滤后的选项 */
optionsFiltered?: Record<string, OptionMetadata>
}
export default defineComponent({
components: {
ComponentOption,
RadioGroupItem,
CollapsibleContainer,
OptionWidgetLayout,
},
props: {
/** 选项组标题 */
title: {
type: String,
default: '',
},
/** RadioButton分组名称全局唯一 */
groupName: {
type: String,
required: true,
},
/** 选项组定义 */
items: {
type: Object as PropType<Record<string, RadioItem>>,
required: true,
},
/** 组件名称 */
componentName: {
type: String,
required: true,
},
/** 是否弹出显示false直接显示内容true显示一个按钮点击弹出显示内容 */
isPopup: {
type: Boolean,
required: true,
},
/** 按钮Icon */
icon: {
type: String,
default: '',
},
},
setup(props) {
const componentData = componentsMap[props.componentName]
const optionDefs = componentData.options
const optionValues = getComponentSettings(componentData).options
// itemVMs
const vms: Record<string, RadioItemVM> = Object.fromEntries(
Object.entries(props.items).map(([key, item]) => {
const vm: RadioItemVM = {
...item,
}
//
const matchOption = (option: string, def: RadioItem): boolean => {
if (def.optionsIncluded) {
const pattern = def.optionsIncluded
if (pattern instanceof RegExp) {
return pattern.test(option)
}
if (Array.isArray(pattern)) {
return pattern.includes(option)
}
return false
}
//
return true
}
//
if (item.isOption) {
vm.displayName = optionDefs[item.name]?.displayName ?? item.name
vm.checked = !!optionValues[item.name]
} else {
vm.displayName = item.name
}
//
if (item.optionsIncluded) {
// options
vm.optionsFiltered = Object.fromEntries(
Object.entries(optionDefs).filter(([name]) => matchOption(name, item)),
)
}
return [key, vm] as [string, RadioItemVM]
}),
)
const itemVMs = ref(vms)
/** 处理单选按钮变化 */
function onRadioChange(itemVM: RadioItemVM, checked: boolean) {
// checked
itemVM.checked = checked
if (itemVM.isOption) {
//
optionValues[itemVM.name] = checked
}
//
if (itemVM.onChange) {
itemVM.onChange(checked)
}
}
return {
componentData,
itemVMs,
onRadioChange,
}
},
})
</script>

View File

@ -0,0 +1,122 @@
<template>
<CollapsibleContainer
:expanded="computedExpanded"
:default-expanded="defaultExpanded"
:hide-expand-button="hideExpandButton"
:enable-expand-by-header="false"
@expanded="onExpanded"
>
<template #title>
<RadioButton
:checked="computedChecked"
:allow-uncheck="allowUncheck"
:group="group"
:checked-icon="checkedIcon"
:not-checked-icon="notCheckedIcon"
@change="onRadioChanged"
>
{{ title }}
</RadioButton>
</template>
<slot />
</CollapsibleContainer>
</template>
<script lang="ts">
import { computed, defineComponent, shallowRef } from 'vue'
import { RadioButton } from '@/ui'
import CollapsibleContainer from './CollapsibleContainer.vue'
export default defineComponent({
components: {
RadioButton,
CollapsibleContainer,
},
props: {
/** 选项文本 */
title: {
type: String,
default: '',
},
// #region CollapsibleContainer
/** 当前展开状态 */
expanded: {
type: Boolean,
default: undefined,
},
/** 初始展开状态 */
defaultExpanded: {
type: Boolean,
default: false,
},
/** 隐藏展开按钮 */
hideExpandButton: {
type: Boolean,
default: false,
},
// #endregion
// #region RadioButton
checked: {
type: Boolean,
default: undefined,
},
allowUncheck: {
type: Boolean,
default: false,
},
group: {
type: String,
default: '',
},
checkedIcon: {
type: String,
default: 'mdi-radiobox-marked',
},
notCheckedIcon: {
type: String,
default: 'mdi-radiobox-blank',
},
// #endregion
},
emits: ['expanded', 'change'],
setup(props, { emit }) {
// 便
const internalExpanded = shallowRef(props.defaultExpanded)
const computedExpanded = computed(() =>
props.expanded !== undefined ? props.expanded : internalExpanded.value,
)
// /
const onExpanded = (expanded: boolean) => {
if (props.expanded !== undefined) {
emit('expanded', expanded)
} else {
internalExpanded.value = expanded
}
}
// 便
const internalChecked = shallowRef(false)
const computedChecked = computed(() =>
props.checked !== undefined ? props.checked : internalChecked.value,
)
//
const onRadioChanged = (checked: boolean) => {
if (props.checked !== undefined) {
emit('change', checked)
} else {
internalChecked.value = checked
}
}
return {
computedExpanded,
onExpanded,
computedChecked,
onRadioChanged,
}
},
})
</script>