feat(plugin): support responsive federated config controls (#521)

This commit is contained in:
InfinityPacer
2026-07-11 18:16:31 +08:00
committed by GitHub
parent 5c4511f935
commit 7861afb2d7
3 changed files with 52 additions and 3 deletions

View File

@@ -46,6 +46,14 @@ const isRefreshed = ref(false)
// 渲染模式: 'vuetify' 或 'vue'
const renderMode = ref('vuetify')
// 插件未声明布局偏好时沿用标准配置弹窗宽度。
const dialogMaxWidth = ref('60rem')
interface PluginConfigLayout {
/** 插件配置界面期望的最大宽度,使用合法 CSS 尺寸。 */
maxWidth?: string
}
// Vue 模式:动态加载的组件
const dynamicComponent = defineAsyncComponent({
// 工厂函数
@@ -89,6 +97,7 @@ async function loadPluginUIData() {
pluginFormItems = []
pluginConfigForm.value = {}
renderMode.value = 'vuetify'
dialogMaxWidth.value = '60rem'
try {
// 获取UI定义
@@ -123,6 +132,12 @@ function handleVueComponentSave(newConfig: Record<string, any>) {
savePluginConf()
}
// 联邦配置组件可按自身布局密度覆盖宿主弹窗宽度。
function handleVueComponentLayout(layout?: PluginConfigLayout | null) {
const maxWidth = typeof layout?.maxWidth === 'string' ? layout.maxWidth.trim() : ''
dialogMaxWidth.value = maxWidth || '60rem'
}
// 调用API保存配置数据
async function savePluginConf() {
// 显示等待提示框
@@ -148,7 +163,7 @@ onBeforeMount(async () => {
})
</script>
<template>
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
<VDialog scrollable :max-width="dialogMaxWidth" :fullscreen="!display.mdAndUp.value">
<!-- Vuetify 渲染模式 -->
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`">
<VDialogCloseBtn @click="emit('close')" />
@@ -192,6 +207,7 @@ onBeforeMount(async () => {
:initial-config="pluginConfigForm"
:api="api"
@save="handleVueComponentSave"
@layout="handleVueComponentLayout"
@switch="emit('switch')"
@close="emit('close')"
/>

View File

@@ -8,6 +8,11 @@ const props = defineProps({
type: String,
default: '* * * * *',
},
/** 是否允许直接清空 CRON。 */
clearable: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['update:modelValue'])
@@ -39,7 +44,6 @@ function updateModelValue(value: string) {
:modelValue="innerValue"
@update:modelValue="updateModelValue"
v-bind="{ ...menuprops, ...propsWithoutModelValue }"
clearable
/>
</template>
</CronInput>

View File

@@ -7,6 +7,7 @@ const props = defineProps({
})
const emit = defineEmits(['update:modelValue'])
const { locale } = useI18n()
const menu = ref(false)
const currentCron = ref(props.modelValue)
@@ -14,6 +15,27 @@ const menuRoot = ref<HTMLElement>()
const instance = getCurrentInstance()
const menuContentClass = `cron-input-menu-${instance?.uid ?? 'default'}`
const menuContentSelector = `.${menuContentClass}`
const normalizedLocale = computed(() => locale.value.toLowerCase().replace(/_/g, '-'))
const cronLocale = computed(() => normalizedLocale.value.startsWith('zh') ? 'zh-cn' : 'en')
// vue-js-cron 没有内置繁体中文,只需覆盖简体词典中会显示的 Cron 专有字词。
const cronCustomLocale = computed(() => normalizedLocale.value === 'zh-tw' ? {
'*': {
day: {
value: { text: '{{value.alt}}號' },
range: { text: '{{start.alt}}號-{{end.alt}}號' },
},
dayOfWeek: { empty: { text: '一週的每一天' } },
hour: { empty: { text: '每小時' } },
minute: { empty: { text: '每分鐘' } },
},
hour: {
text: '小時',
minute: { '*': { suffix: '分鐘' } },
},
week: { text: '週' },
'q-minute': { text: '分鐘' },
'q-hour': { text: '小時' },
} : undefined)
function isCronMenuTarget(target: EventTarget | null) {
if (!(target instanceof Element)) return false
@@ -72,7 +94,14 @@ watch(
</template>
<VList>
<VListItem>
<VCronVuetify v-model="currentCron" locale="zh-CN" :chip-props="{ color: 'success' }" class="mt-1" />
<VCronVuetify
:key="locale"
v-model="currentCron"
:chip-props="{ color: 'success' }"
class="mt-1"
:custom-locale="cronCustomLocale"
:locale="cronLocale"
/>
</VListItem>
</VList>
</VMenu>