mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge pull request #3154 from timongh/component-option-types
Change buildin components to define api
This commit is contained in:
commit
d98dd98f1c
@ -1,11 +1,16 @@
|
||||
import { ComponentMetadata, componentsTags } from '@/components/types'
|
||||
import { ComponentEntry, componentsTags } from '@/components/types'
|
||||
import { meta } from '@/core/meta'
|
||||
import {
|
||||
getGeneralSettings,
|
||||
getComponentSettings,
|
||||
getGeneralSettings,
|
||||
isUserComponent,
|
||||
} from '@/core/settings'
|
||||
import { Version } from '@/core/version'
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '@/components/define'
|
||||
import { LaunchBarActionProvider } from '../launch-bar/launch-bar-action'
|
||||
import { ComponentAction } from '../settings-panel/component-actions/component-actions'
|
||||
import { isLocalItem, name, UpdateCheckItem } from './utils'
|
||||
@ -19,68 +24,78 @@ const {
|
||||
silentCheckUpdate,
|
||||
} = checkerMethods
|
||||
|
||||
export const component: ComponentMetadata = {
|
||||
const optionsMetadata = defineOptionsMetadata({
|
||||
lastUpdateCheck: {
|
||||
displayName: '最后检查更新日期',
|
||||
defaultValue: 0,
|
||||
hidden: true,
|
||||
},
|
||||
lastInstalledVersion: {
|
||||
displayName: '最后安装版本',
|
||||
defaultValue: '2.0.0',
|
||||
hidden: true,
|
||||
},
|
||||
localPortOverride: {
|
||||
displayName: '本地组件链接端口',
|
||||
defaultValue: '',
|
||||
hidden: true,
|
||||
},
|
||||
minimumDuration: {
|
||||
displayName: '更新间隔 (ms)',
|
||||
defaultValue: 1000 * 3600 * 24,
|
||||
},
|
||||
urls: {
|
||||
displayName: '更新链接',
|
||||
defaultValue: {
|
||||
components: {} as Record<string, UpdateCheckItem>,
|
||||
plugins: {} as Record<string, UpdateCheckItem>,
|
||||
styles: {} as Record<string, UpdateCheckItem>,
|
||||
},
|
||||
hidden: true,
|
||||
},
|
||||
maxUpdateCount: {
|
||||
displayName: '单次最大更新量 (个)',
|
||||
defaultValue: 4,
|
||||
// hidden: true,
|
||||
},
|
||||
})
|
||||
|
||||
const entry: ComponentEntry<typeof optionsMetadata> = async ({
|
||||
settings: { options: opt },
|
||||
}) => {
|
||||
const now = Number(new Date())
|
||||
const duration = now - opt.lastUpdateCheck
|
||||
|
||||
const isDurationExceeded = duration >= opt.minimumDuration
|
||||
const isVersionOutdated = new Version(meta.version).greaterThan(
|
||||
new Version(opt.lastInstalledVersion),
|
||||
)
|
||||
if (isDurationExceeded) {
|
||||
coreApis.lifeCycle.fullyLoaded(() => silentCheckUpdate())
|
||||
} else if (isVersionOutdated) {
|
||||
coreApis.lifeCycle.fullyLoaded(() => forceCheckUpdate())
|
||||
}
|
||||
|
||||
return checkerMethods
|
||||
}
|
||||
|
||||
type Options = OptionsOfMetadata<typeof optionsMetadata>
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name,
|
||||
displayName: '自动更新器',
|
||||
description: {
|
||||
'zh-CN': '自动检查组件, 插件和样式的更新. (仅限从设置面板中安装的)',
|
||||
},
|
||||
tags: [componentsTags.utils],
|
||||
options: {
|
||||
lastUpdateCheck: {
|
||||
displayName: '最后检查更新日期',
|
||||
defaultValue: 0,
|
||||
hidden: true,
|
||||
},
|
||||
lastInstalledVersion: {
|
||||
displayName: '最后安装版本',
|
||||
defaultValue: '2.0.0',
|
||||
hidden: true,
|
||||
},
|
||||
localPortOverride: {
|
||||
displayName: '本地组件链接端口',
|
||||
defaultValue: '',
|
||||
hidden: true,
|
||||
},
|
||||
minimumDuration: {
|
||||
displayName: '更新间隔 (ms)',
|
||||
defaultValue: 1000 * 3600 * 24,
|
||||
},
|
||||
urls: {
|
||||
displayName: '更新链接',
|
||||
defaultValue: {
|
||||
components: {},
|
||||
plugins: {},
|
||||
styles: {},
|
||||
},
|
||||
hidden: true,
|
||||
},
|
||||
maxUpdateCount: {
|
||||
displayName: '单次最大更新量 (个)',
|
||||
defaultValue: 4,
|
||||
// hidden: true,
|
||||
},
|
||||
},
|
||||
options: optionsMetadata,
|
||||
extraOptions: () => import('./ExtraOptions.vue').then(m => m.default),
|
||||
entry: async ({ settings: { options } }) => {
|
||||
const now = Number(new Date())
|
||||
const duration = now - options.lastUpdateCheck
|
||||
|
||||
const isDurationExceeded = duration >= options.minimumDuration
|
||||
const isVersionOutdated = new Version(meta.version)
|
||||
.greaterThan(new Version(options.lastInstalledVersion))
|
||||
if (isDurationExceeded) {
|
||||
coreApis.lifeCycle.fullyLoaded(() => silentCheckUpdate())
|
||||
} else if (isVersionOutdated) {
|
||||
coreApis.lifeCycle.fullyLoaded(() => forceCheckUpdate())
|
||||
}
|
||||
|
||||
return checkerMethods
|
||||
},
|
||||
entry,
|
||||
plugin: {
|
||||
displayName: '自动更新器 - 功能扩展',
|
||||
description: {
|
||||
'zh-CN': '记录在设置面板中的功能安装/卸载数据供自动更新使用; 并在组件详情中支持手动检查该组件的更新.',
|
||||
'zh-CN':
|
||||
'记录在设置面板中的功能安装/卸载数据供自动更新使用; 并在组件详情中支持手动检查该组件的更新.',
|
||||
},
|
||||
// 非设置面板的组件如果也对能功能进行修改, 建议调用此处的 Hooks 同步状态.
|
||||
setup: ({ addData, addHook }) => {
|
||||
@ -90,7 +105,9 @@ export const component: ComponentMetadata = {
|
||||
after: (_, url: string, metadata: { name: string }) => {
|
||||
// console.log('hook', `user${lodash.startCase(type)}.add`, metadata.name, url)
|
||||
const { options } = getComponentSettings('autoUpdate')
|
||||
const existingItem = options.urls[type][metadata.name] as UpdateCheckItem
|
||||
const existingItem = options.urls[type][
|
||||
metadata.name
|
||||
] as UpdateCheckItem
|
||||
if (!existingItem) {
|
||||
options.urls[type][metadata.name] = {
|
||||
url,
|
||||
@ -116,32 +133,36 @@ export const component: ComponentMetadata = {
|
||||
},
|
||||
})
|
||||
})
|
||||
addData('settingsPanel.componentActions', (actions: ComponentAction[]) => {
|
||||
const { options } = getComponentSettings('autoUpdate')
|
||||
actions.push(metadata => {
|
||||
const item = options.urls.components[metadata.name] as UpdateCheckItem
|
||||
if (!item) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
name: 'checkUpdate',
|
||||
displayName: '检查更新',
|
||||
icon: isLocalItem(item.url) ? 'mdi-file-download-outline' : 'mdi-cloud-download-outline',
|
||||
condition: () => isUserComponent(metadata),
|
||||
title: item.url,
|
||||
action: async () => {
|
||||
const { Toast } = await import('@/core/toast')
|
||||
const toast = Toast.info('检查更新中...', '检查更新')
|
||||
const result = await checkComponentsUpdate({
|
||||
filterNames: [metadata.name],
|
||||
force: true,
|
||||
})
|
||||
toast.message = result
|
||||
toast.duration = 3000
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
addData(
|
||||
'settingsPanel.componentActions',
|
||||
(actions: ComponentAction[]) => {
|
||||
const { options } = getComponentSettings<Options>('autoUpdate')
|
||||
actions.push(metadata => {
|
||||
const item = options.urls.components[metadata.name]
|
||||
if (!item) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
name: 'checkUpdate',
|
||||
displayName: '检查更新',
|
||||
icon: isLocalItem(item.url)
|
||||
? 'mdi-file-download-outline'
|
||||
: 'mdi-cloud-download-outline',
|
||||
condition: () => isUserComponent(metadata),
|
||||
title: item.url,
|
||||
action: async () => {
|
||||
const { Toast } = await import('@/core/toast')
|
||||
const toast = Toast.info('检查更新中...', '检查更新')
|
||||
toast.message = await checkComponentsUpdate({
|
||||
filterNames: [metadata.name],
|
||||
force: true,
|
||||
})
|
||||
toast.duration = 3000
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const icon = 'mdi-cloud-sync-outline'
|
||||
addData('launchBar.actions', (actions: LaunchBarActionProvider[]) => {
|
||||
@ -172,7 +193,10 @@ export const component: ComponentMetadata = {
|
||||
description: 'Check Last Update',
|
||||
action: async () => {
|
||||
const { Toast } = await import('@/core/toast')
|
||||
const toast = Toast.info('正在检查更新...', '检查最近更新的功能')
|
||||
const toast = Toast.info(
|
||||
'正在检查更新...',
|
||||
'检查最近更新的功能',
|
||||
)
|
||||
await checkLastFeature()
|
||||
toast.dismiss()
|
||||
},
|
||||
@ -184,4 +208,4 @@ export const component: ComponentMetadata = {
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { defaultLanguageCode, languageCodeToName } from '@/core/utils/i18n'
|
||||
import { ComponentMetadata, componentsTags } from '../types'
|
||||
import { translateProviderNames, translateProviders } from './machine-translator/translators'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { componentsTags } from '../types'
|
||||
import {
|
||||
translateProviderNames,
|
||||
translateProviders,
|
||||
} from './machine-translator/translators'
|
||||
import { startTranslate } from './dom-translator'
|
||||
|
||||
export const component: ComponentMetadata = {
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'i18n',
|
||||
displayName: '多语言',
|
||||
configurable: false,
|
||||
@ -14,7 +18,8 @@ export const component: ComponentMetadata = {
|
||||
componentsTags.general,
|
||||
],
|
||||
description: {
|
||||
'zh-CN': '安装其他语言包可以更换界面语言, 机器翻译选择可以设定其他一些功能如`动态翻译`, `评论翻译`使用的翻译器. 机器翻译的选择不影响界面语言.',
|
||||
'zh-CN':
|
||||
'安装其他语言包可以更换界面语言, 机器翻译选择可以设定其他一些功能如`动态翻译`, `评论翻译`使用的翻译器. 机器翻译的选择不影响界面语言.',
|
||||
},
|
||||
options: {
|
||||
language: {
|
||||
@ -30,4 +35,4 @@ export const component: ComponentMetadata = {
|
||||
},
|
||||
// 相关功能好像全挂了, 先隐藏了
|
||||
hidden: true,
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { none } from '@/core/utils'
|
||||
import { ComponentMetadata, componentsTags } from '../types'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { componentsTags } from '../types'
|
||||
import { plugin } from './plugin'
|
||||
|
||||
export const component: ComponentMetadata = {
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'launchBar',
|
||||
displayName: '搜索栏',
|
||||
configurable: false,
|
||||
entry: none,
|
||||
plugin,
|
||||
hidden: true,
|
||||
tags: [
|
||||
componentsTags.general,
|
||||
componentsTags.utils,
|
||||
],
|
||||
}
|
||||
tags: [componentsTags.general, componentsTags.utils],
|
||||
})
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { cdnRoots } from '@/core/cdn-types'
|
||||
import { ComponentMetadata, componentsTags } from '../types'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { componentsTags } from '../types'
|
||||
|
||||
export const component: ComponentMetadata = {
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'notifyNewVersion',
|
||||
displayName: '新版本提示',
|
||||
description: '定期检查脚本本体的更新, 并弹出提示.',
|
||||
tags: [
|
||||
componentsTags.utils,
|
||||
],
|
||||
tags: [componentsTags.utils],
|
||||
options: {
|
||||
lastUpdateCheck: {
|
||||
displayName: '最后检查更新日期',
|
||||
@ -28,19 +27,28 @@ export const component: ComponentMetadata = {
|
||||
const { getGeneralSettings } = await import('@/core/settings')
|
||||
const now = Number(new Date())
|
||||
const duration = now - options.lastUpdateCheck
|
||||
if (duration < options.minimumDuration) { // 未到间隔期
|
||||
if (duration < options.minimumDuration) {
|
||||
// 未到间隔期
|
||||
return
|
||||
}
|
||||
// 本地调试版不检查
|
||||
if (!GM_info.scriptUpdateURL) {
|
||||
return
|
||||
}
|
||||
const updateUrl = `${cdnRoots[getGeneralSettings().cdnRoot](meta.compilationInfo.branch)}dist/${meta.originalFilename}`
|
||||
const scriptText: string = await monkey({ url: updateUrl, responseType: 'text' })
|
||||
const updateUrl = `${cdnRoots[getGeneralSettings().cdnRoot](
|
||||
meta.compilationInfo.branch,
|
||||
)}dist/${meta.originalFilename}`
|
||||
const scriptText: string = await monkey({
|
||||
url: updateUrl,
|
||||
responseType: 'text',
|
||||
})
|
||||
options.lastUpdateCheck = Number(new Date())
|
||||
const versionMatch = scriptText.match(/^\/\/ @version\s*([\d\.]+)$/m)
|
||||
const versionMatch = scriptText.match(/^\/\/ @version\s*([\d.]+)$/m)
|
||||
if (!versionMatch?.[1]) {
|
||||
console.warn('[新版本提示] 未能检测出脚本版本, scriptText.length =', scriptText.length)
|
||||
console.warn(
|
||||
'[新版本提示] 未能检测出脚本版本, scriptText.length =',
|
||||
scriptText.length,
|
||||
)
|
||||
return
|
||||
}
|
||||
const latestVersion = new Version(versionMatch[1])
|
||||
@ -48,9 +56,12 @@ export const component: ComponentMetadata = {
|
||||
if (!latestVersion.greaterThan(currentVersion)) {
|
||||
return
|
||||
}
|
||||
Toast.info(/* html */`新版本 <span>${latestVersion.versionString}</span> 已发布. <a href="https://github.com/the1812/Bilibili-Evolved/releases" target="_blank" class="link">更新说明</a><a href="${updateUrl}" target="_blank" class="link">安装</a>`, '检查更新')
|
||||
Toast.info(
|
||||
/* html */ `新版本 <span>${latestVersion.versionString}</span> 已发布. <a href='https://github.com/the1812/Bilibili-Evolved/releases' target='_blank' class='link'>更新说明</a><a href='${updateUrl}' target='_blank' class='link'>安装</a>`,
|
||||
'检查更新',
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn('[新版本提示] 检查更新时发生错误: ', error)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -4,85 +4,103 @@ import { TextColor } from '@/core/text-color'
|
||||
import { CdnTypes } from '@/core/cdn-types'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { DownloadPackageEmitMode } from '@/core/download-mode'
|
||||
import { ComponentEntry, ComponentMetadata, componentsTags } from '../types'
|
||||
import { ComponentEntry, componentsTags } from '../types'
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '../define'
|
||||
import { provideActions } from './external-actions'
|
||||
import description from './desc.md'
|
||||
|
||||
export const WidgetsPlugin = 'widgets'
|
||||
|
||||
export enum SettingsPanelDockSide {
|
||||
Left = '左侧',
|
||||
Right = '右侧',
|
||||
}
|
||||
|
||||
const entry: ComponentEntry = async ({ metadata }) => {
|
||||
const options = defineOptionsMetadata({
|
||||
themeColor: {
|
||||
defaultValue: '#00A0D8',
|
||||
displayName: '主题颜色',
|
||||
color: true,
|
||||
},
|
||||
scriptLoadingMode: {
|
||||
defaultValue: LoadingMode.Delay,
|
||||
displayName: '功能加载模式',
|
||||
dropdownEnum: LoadingMode,
|
||||
},
|
||||
styleLoadingMode: {
|
||||
defaultValue: LoadingMode.Race,
|
||||
displayName: '样式加载模式',
|
||||
dropdownEnum: LoadingMode,
|
||||
},
|
||||
textColor: {
|
||||
defaultValue: TextColor.Auto,
|
||||
displayName: '文本颜色',
|
||||
dropdownEnum: TextColor,
|
||||
},
|
||||
cdnRoot: {
|
||||
defaultValue: CdnTypes.jsDelivr,
|
||||
displayName: '更新源',
|
||||
dropdownEnum: CdnTypes,
|
||||
},
|
||||
dockSide: {
|
||||
defaultValue: SettingsPanelDockSide.Left,
|
||||
displayName: '设置面板停靠',
|
||||
dropdownEnum: SettingsPanelDockSide,
|
||||
},
|
||||
filenameFormat: {
|
||||
defaultValue: '[title][ - ep]',
|
||||
displayName: '文件命名格式',
|
||||
},
|
||||
batchFilenameFormat: {
|
||||
defaultValue: '[n - ][ep]',
|
||||
displayName: '批量命名格式',
|
||||
},
|
||||
downloadPackageEmitMode: {
|
||||
defaultValue: DownloadPackageEmitMode.Packed,
|
||||
displayName: '文件下载模式',
|
||||
dropdownEnum: DownloadPackageEmitMode,
|
||||
},
|
||||
devMode: {
|
||||
defaultValue: false,
|
||||
displayName: '开发者模式',
|
||||
},
|
||||
})
|
||||
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
|
||||
const entry: ComponentEntry<typeof options> = async ({ metadata }) => {
|
||||
const { isIframe } = await import('@/core/utils')
|
||||
if (isIframe()) {
|
||||
return
|
||||
}
|
||||
addComponentListener(`${metadata.name}.dockSide`, (value: SettingsPanelDockSide) => {
|
||||
document.body.classList.toggle('settings-panel-dock-right', value === SettingsPanelDockSide.Right)
|
||||
}, true)
|
||||
addComponentListener(
|
||||
`${metadata.name}.dockSide`,
|
||||
(value: SettingsPanelDockSide) => {
|
||||
document.body.classList.toggle(
|
||||
'settings-panel-dock-right',
|
||||
value === SettingsPanelDockSide.Right,
|
||||
)
|
||||
},
|
||||
true,
|
||||
)
|
||||
requestIdleCallback(async () => {
|
||||
const Container = await import('./SettingsContainer.vue')
|
||||
const instance = mountVueComponent(Container)
|
||||
document.body.insertAdjacentElement('beforeend', instance.$el)
|
||||
})
|
||||
}
|
||||
export const component: ComponentMetadata = {
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'settingsPanel',
|
||||
displayName: '通用设置',
|
||||
configurable: false,
|
||||
description,
|
||||
entry,
|
||||
options: {
|
||||
themeColor: {
|
||||
defaultValue: '#00A0D8',
|
||||
displayName: '主题颜色',
|
||||
color: true,
|
||||
},
|
||||
scriptLoadingMode: {
|
||||
defaultValue: LoadingMode.Delay,
|
||||
displayName: '功能加载模式',
|
||||
dropdownEnum: LoadingMode,
|
||||
},
|
||||
styleLoadingMode: {
|
||||
defaultValue: LoadingMode.Race,
|
||||
displayName: '样式加载模式',
|
||||
dropdownEnum: LoadingMode,
|
||||
},
|
||||
textColor: {
|
||||
defaultValue: TextColor.Auto,
|
||||
displayName: '文本颜色',
|
||||
dropdownEnum: TextColor,
|
||||
},
|
||||
cdnRoot: {
|
||||
defaultValue: CdnTypes.jsDelivr,
|
||||
displayName: '更新源',
|
||||
dropdownEnum: CdnTypes,
|
||||
},
|
||||
dockSide: {
|
||||
defaultValue: SettingsPanelDockSide.Left,
|
||||
displayName: '设置面板停靠',
|
||||
dropdownEnum: SettingsPanelDockSide,
|
||||
},
|
||||
filenameFormat: {
|
||||
defaultValue: '[title][ - ep]',
|
||||
displayName: '文件命名格式',
|
||||
},
|
||||
batchFilenameFormat: {
|
||||
defaultValue: '[n - ][ep]',
|
||||
displayName: '批量命名格式',
|
||||
},
|
||||
downloadPackageEmitMode: {
|
||||
defaultValue: DownloadPackageEmitMode.Packed,
|
||||
displayName: '文件下载模式',
|
||||
dropdownEnum: DownloadPackageEmitMode,
|
||||
},
|
||||
devMode: {
|
||||
defaultValue: false,
|
||||
displayName: '开发者模式',
|
||||
},
|
||||
},
|
||||
options,
|
||||
tags: [componentsTags.general],
|
||||
i18n: {
|
||||
'en-US': {
|
||||
@ -103,4 +121,4 @@ export const component: ComponentMetadata = {
|
||||
provideActions()
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -8,7 +8,8 @@ import {
|
||||
} from '@/components/component'
|
||||
import { PluginMetadata } from '@/plugins/plugin'
|
||||
// import serialize from 'serialize-javascript'
|
||||
import { settings, ComponentSettings } from '../settings'
|
||||
import type { Options as SettingsPanelOptions } from '@/components/settings-panel'
|
||||
import { ComponentSettings, settings } from '../settings'
|
||||
import { matchUrlPattern } from '../utils'
|
||||
|
||||
/**
|
||||
@ -23,11 +24,9 @@ export const metadataToOptions = <O extends UnknownOptions>(
|
||||
* 生成组件设置
|
||||
* @param component 组件定义
|
||||
*/
|
||||
export const componentToSettings = <
|
||||
Om extends OptionalOptionsMetadata
|
||||
>(component: ComponentMetadata<Om>): (
|
||||
ComponentSettings<OptionsFromOptionalMetadata<Om>>
|
||||
) => {
|
||||
export const componentToSettings = <Om extends OptionalOptionsMetadata>(
|
||||
component: ComponentMetadata<Om>,
|
||||
): ComponentSettings<OptionsFromOptionalMetadata<Om>> => {
|
||||
const { options: meta } = component
|
||||
return {
|
||||
enabled: component.enabledByDefault ?? true,
|
||||
@ -99,7 +98,7 @@ export const getComponentSettings = <R extends UnknownOptions = UnknownOptions>(
|
||||
/**
|
||||
* 获取通用设置 (`settingsPanel`组件的`options`)
|
||||
*/
|
||||
export const getGeneralSettings = () => getComponentSettings('settingsPanel').options
|
||||
export const getGeneralSettings = () => getComponentSettings<SettingsPanelOptions>('settingsPanel').options
|
||||
/**
|
||||
* 判断此组件是否启用, 启用的条件为:
|
||||
* - 若定义了排除列表, 当前URL必须不匹配其排除列表中任意一项(`Component.urlExclude`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user