mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
feat: 添加选项组组件
This commit is contained in:
parent
f296bbf483
commit
5e5a2c15a5
@ -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>
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user