mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-09 08:11:10 +08:00
【调整】监控页面整体优化
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
/* 通知提供商多选组件样式 */
|
||||
|
||||
/* 主容器样式 */
|
||||
.notifyProviderMultiSelect {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 选择器容器样式 - 确保有足够空间显示多选标签 */
|
||||
.selectContainer {
|
||||
width: 100%;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
/* 修复 NSelect 输入框区域的垂直对齐问题 */
|
||||
.selectContainer :global(.n-base-selection) {
|
||||
align-items: center !important;
|
||||
min-height: 2.5rem !important;
|
||||
}
|
||||
|
||||
/* 修复选择器输入框内容区域对齐 */
|
||||
.selectContainer :global(.n-base-selection-input) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
min-height: 2.5rem !important;
|
||||
}
|
||||
|
||||
/* 修复标签容器的垂直对齐 */
|
||||
.selectContainer :global(.n-base-selection-tags) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
min-height: 2.5rem;
|
||||
padding: 0.25rem;
|
||||
max-height: none !important; /* 移除高度限制,允许换行 */
|
||||
align-items: center !important;
|
||||
align-content: center !important;
|
||||
}
|
||||
|
||||
/* 修复下拉箭头的垂直对齐 */
|
||||
.selectContainer :global(.n-base-suffix) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 修复清除按钮的垂直对齐 */
|
||||
.selectContainer :global(.n-base-clear) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 修复输入框整体的垂直对齐 */
|
||||
.selectContainer :global(.n-base-selection-input-tag) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
min-height: 2.5rem !important;
|
||||
}
|
||||
|
||||
/* 确保占位符文本垂直居中 */
|
||||
.selectContainer :global(.n-base-selection-placeholder) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
line-height: 2.5rem !important;
|
||||
}
|
||||
|
||||
/* 修复多选模式下的输入框对齐 */
|
||||
.selectContainer :global(.n-base-selection-input-tag input) {
|
||||
line-height: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
/* 标签样式优化 */
|
||||
.selectContainer :global(.n-tag) {
|
||||
max-width: 100%;
|
||||
flex-shrink: 0;
|
||||
margin: 2px 4px 2px 0 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
/* 标签内容样式 */
|
||||
.selectContainer :global(.n-tag__content) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.25rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 响应式布局 - 保持按钮原始样式 */
|
||||
|
||||
/* 确保选择器在不同屏幕尺寸下的响应式表现 */
|
||||
@media (max-width: 640px) {
|
||||
.selectContainer {
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.selectContainer :global(.n-base-selection-tags) {
|
||||
min-height: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 优化标签在小屏幕上的显示 */
|
||||
@media (max-width: 480px) {
|
||||
.selectContainer :global(.n-tag) {
|
||||
font-size: 0.75rem;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.selectContainer :global(.n-tag__content) {
|
||||
gap: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保图标不会被压缩 */
|
||||
.selectContainer :global(.svg-icon) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 优化空状态显示 */
|
||||
.emptyState {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
/* 保持按钮原始样式,不做额外修改 */
|
||||
@@ -0,0 +1,229 @@
|
||||
// External Libraries
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { NButton, NDivider, NFlex, NFormItemGi, NGi, NGrid, NSelect, NText, NCheckboxGroup, NCheckbox } from 'naive-ui'
|
||||
|
||||
// Type Imports
|
||||
import type { VNode, PropType } from 'vue'
|
||||
import type { SelectOption } from 'naive-ui'
|
||||
import type { NotifyProviderOption, NotifyProviderMultiSelectProps } from './types'
|
||||
|
||||
// Absolute Internal Imports - Components
|
||||
import SvgIcon from '@components/SvgIcon'
|
||||
// Absolute Internal Imports - Utilities / Others
|
||||
import { $t } from '@locales/index'
|
||||
|
||||
// Relative Internal Imports - Controller
|
||||
import { useNotifyProviderMultiSelectController } from './useController'
|
||||
// Relative Internal Imports - Styles
|
||||
import styles from './index.module.css'
|
||||
|
||||
/**
|
||||
* @description 通知提供商多选组件。允许用户从列表中选择多个通知渠道。
|
||||
* @example
|
||||
* <NotifyProviderMultiSelect
|
||||
* path="form.channels"
|
||||
* v-model:value="selectedChannelValues"
|
||||
* valueType="type"
|
||||
* :isAddMode="true"
|
||||
* @update:value="(options) => handleChannelsChange(options)"
|
||||
* />
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'NotifyProviderMultiSelect',
|
||||
props: {
|
||||
/**
|
||||
* 表单项的路径,用于表单校验或上下文。
|
||||
* @default ''
|
||||
*/
|
||||
path: {
|
||||
type: String as PropType<NotifyProviderMultiSelectProps['path']>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* 当前选中的值数组 (对应 NotifyProviderOption['value'][] 数组)。
|
||||
* @default []
|
||||
*/
|
||||
value: {
|
||||
type: Array as PropType<NotifyProviderMultiSelectProps['value']>,
|
||||
default: () => [],
|
||||
},
|
||||
/**
|
||||
* 决定 `props.value` 和 `NotifyProviderOption.value` 字段是基于原始提供商的 `value` 还是 `type`。
|
||||
* - 'value': 使用原始提供商的 `value` 字段作为 `NotifyProviderOption.value`。
|
||||
* - 'type': 使用原始提供商的 `type` 字段作为 `NotifyProviderOption.value`。
|
||||
* @default 'value'
|
||||
*/
|
||||
valueType: {
|
||||
type: String as PropType<NotifyProviderMultiSelectProps['valueType']>,
|
||||
default: 'value',
|
||||
validator: (val: string) => ['value', 'type'].includes(val),
|
||||
},
|
||||
/**
|
||||
* 是否为添加模式,显示额外的"新增渠道"和"刷新"按钮。
|
||||
* @default false
|
||||
*/
|
||||
isAddMode: {
|
||||
type: Boolean as PropType<NotifyProviderMultiSelectProps['isAddMode']>,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 最大选择数量限制。
|
||||
* @default undefined
|
||||
*/
|
||||
maxCount: {
|
||||
type: Number as PropType<NotifyProviderMultiSelectProps['maxCount']>,
|
||||
default: undefined,
|
||||
},
|
||||
/**
|
||||
* 是否禁用。
|
||||
* @default false
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean as PropType<NotifyProviderMultiSelectProps['disabled']>,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @event update:value - 当选中的通知提供商更新时触发。
|
||||
* @param {NotifyProviderOption[]} options - 选中的通知提供商的完整对象数组。
|
||||
*/
|
||||
emits: {
|
||||
'update:value': (payload: NotifyProviderOption[]) => {
|
||||
return (
|
||||
Array.isArray(payload) &&
|
||||
payload.every(
|
||||
(item) => typeof item === 'object' && item !== null && 'label' in item && 'value' in item && 'type' in item,
|
||||
)
|
||||
)
|
||||
},
|
||||
},
|
||||
setup(props: NotifyProviderMultiSelectProps, { emit }) {
|
||||
const { selectOptions, goToAddNotifyProvider, handleMultiSelectUpdate, fetchNotifyProviderData } =
|
||||
useNotifyProviderMultiSelectController(props, emit)
|
||||
|
||||
/**
|
||||
* @description 渲染多选标签 (Tag)。
|
||||
* @param {object} params - Naive UI 传递的选项包装对象。
|
||||
* @param {SelectOption} params.option - 当前选项的数据。
|
||||
* @returns {VNode} 渲染后的 VNode。
|
||||
*/
|
||||
const renderMultiSelectTag = ({ option }: { option: SelectOption }): VNode => {
|
||||
// 将 SelectOption 转换为 NotifyProviderOption
|
||||
const notifyOption = option as NotifyProviderOption & SelectOption
|
||||
|
||||
// 处理长标签文本的截断
|
||||
const truncateText = (text: string, maxLength: number = 20): string => {
|
||||
if (text.length <= maxLength) return text
|
||||
return text.slice(0, maxLength) + '...'
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex items-center max-w-full h-full">
|
||||
{notifyOption.label ? (
|
||||
<NFlex align="center" justify="center" size="small" class="min-w-0 flex-1 h-full">
|
||||
<SvgIcon icon={`notify-${notifyOption.type || ''}`} size="1.4rem" class="flex-shrink-0" />
|
||||
<span
|
||||
class="text-[12px] truncate min-w-0 block"
|
||||
title={notifyOption.label}
|
||||
style={{
|
||||
maxWidth: '120px',
|
||||
lineHeight: '1.5',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{truncateText(notifyOption.label)}
|
||||
</span>
|
||||
</NFlex>
|
||||
) : (
|
||||
<NText depth="3" class="text-[12px] flex items-center h-full">
|
||||
{$t('t_0_1745887835267')}
|
||||
</NText>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 渲染下拉列表中的选项标签。
|
||||
* @param {SelectOption} option - 当前选项的数据。
|
||||
* @returns {VNode} 渲染后的 VNode。
|
||||
*/
|
||||
const renderLabel = (option: SelectOption): VNode => {
|
||||
// 将 SelectOption 转换为 NotifyProviderOption
|
||||
const notifyOption = option as NotifyProviderOption & SelectOption
|
||||
return (
|
||||
<NFlex align="center" size="small">
|
||||
<SvgIcon icon={`notify-${notifyOption.type || ''}`} size="1.6rem" />
|
||||
<NText>{notifyOption.label}</NText>
|
||||
</NFlex>
|
||||
)
|
||||
}
|
||||
|
||||
// 转换选项格式以兼容 NSelect
|
||||
const naiveSelectOptions = computed(() => {
|
||||
return selectOptions.value.map((option): SelectOption & NotifyProviderOption => ({
|
||||
...option,
|
||||
// 确保兼容 NSelect 的 SelectOption 接口
|
||||
}))
|
||||
})
|
||||
|
||||
return () => (
|
||||
<div class={styles.notifyProviderMultiSelect}>
|
||||
<NGrid cols={24}>
|
||||
<NFormItemGi
|
||||
span={props.isAddMode ? 13 : 24}
|
||||
label={$t('t_1_1745887832941') /* 通知渠道 */}
|
||||
path={props.path}
|
||||
>
|
||||
<div class={styles.selectContainer}>
|
||||
<NSelect
|
||||
class="w-full"
|
||||
style={{
|
||||
'--n-tag-text-color': 'var(--n-text-color)',
|
||||
'--n-tag-border-radius': '6px',
|
||||
'min-height': '2.5rem',
|
||||
'--n-height': '2.5rem',
|
||||
'--n-height-medium': '2.5rem',
|
||||
}}
|
||||
options={naiveSelectOptions.value}
|
||||
renderLabel={renderLabel}
|
||||
renderTag={renderMultiSelectTag}
|
||||
filterable
|
||||
clearable
|
||||
multiple
|
||||
maxTagCount="responsive"
|
||||
placeholder={$t('t_0_1745887835267')}
|
||||
value={props.value} // 直接使用 props.value 数组
|
||||
onUpdateValue={handleMultiSelectUpdate}
|
||||
disabled={props.disabled}
|
||||
v-slots={{
|
||||
empty: () => (
|
||||
<div class={styles.emptyState}>
|
||||
<NText depth="3" class="text-[1.4rem]">
|
||||
{selectOptions.value.length === 0 ? $t('t_0_1745887835267') : '暂无匹配的通知渠道'}
|
||||
</NText>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</NFormItemGi>
|
||||
{props.isAddMode && (
|
||||
<NGi span={11}>
|
||||
<div class="flex items-center h-full">
|
||||
<NDivider vertical />
|
||||
<NButton class="mx-[8px]" onClick={goToAddNotifyProvider} ghost>
|
||||
{$t('t_2_1745887834248')}
|
||||
</NButton>
|
||||
<NButton onClick={fetchNotifyProviderData} ghost>
|
||||
{$t('t_0_1746497662220')}
|
||||
</NButton>
|
||||
</div>
|
||||
</NGi>
|
||||
)}
|
||||
</NGrid>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
111
frontend/apps/allin-ssl/src/components/notifyProviderMultiSelect/types.d.ts
vendored
Normal file
111
frontend/apps/allin-ssl/src/components/notifyProviderMultiSelect/types.d.ts
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Type Imports
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
/**
|
||||
* @description 通知提供商选项的类型定义
|
||||
*/
|
||||
export interface NotifyProviderOption {
|
||||
/**
|
||||
* 选项的显示标签
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* 选项的实际值 (用于多选的 v-model)
|
||||
*/
|
||||
value: string
|
||||
/**
|
||||
* 选项的原始类型 (例如 'email', 'sms'), 用于图标显示等业务逻辑
|
||||
*/
|
||||
type: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从 Store 获取的原始通知提供商项目类型
|
||||
*/
|
||||
export interface RawProviderItem {
|
||||
/**
|
||||
* 显示标签
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* 实际值
|
||||
*/
|
||||
value: string
|
||||
/**
|
||||
* 原始类型
|
||||
*/
|
||||
type: string
|
||||
/**
|
||||
* 允许其他可能的属性
|
||||
*/
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* @description NotifyProviderMultiSelect 组件的 Props 定义
|
||||
*/
|
||||
export interface NotifyProviderMultiSelectProps {
|
||||
/**
|
||||
* 表单项的路径,用于表单校验或上下文
|
||||
* @default ''
|
||||
*/
|
||||
path: string
|
||||
/**
|
||||
* 当前选中的值数组 (对应 NotifyProviderOption['value'][])
|
||||
* @default []
|
||||
*/
|
||||
value: string[]
|
||||
/**
|
||||
* 决定 `props.value` 和 `NotifyProviderOption.value` 字段是基于原始提供商的 `value` 还是 `type`
|
||||
* - 'value': 使用原始提供商的 `value` 字段作为 `NotifyProviderOption.value`
|
||||
* - 'type': 使用原始提供商的 `type` 字段作为 `NotifyProviderOption.value`
|
||||
* @default 'value'
|
||||
*/
|
||||
valueType: 'value' | 'type'
|
||||
/**
|
||||
* 是否为添加模式,显示额外的按钮
|
||||
* @default false
|
||||
*/
|
||||
isAddMode: boolean
|
||||
/**
|
||||
* 最大选择数量限制
|
||||
* @default undefined
|
||||
*/
|
||||
maxCount?: number
|
||||
/**
|
||||
* 是否禁用
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @description NotifyProviderMultiSelect 组件的 Emits 定义
|
||||
*/
|
||||
export interface NotifyProviderMultiSelectEmits {
|
||||
(e: 'update:value', payload: NotifyProviderOption[]): void
|
||||
}
|
||||
|
||||
// Controller暴露给View的类型
|
||||
export interface NotifyProviderMultiSelectControllerExposes {
|
||||
/**
|
||||
* 内部选中的完整通知提供商对象数组
|
||||
*/
|
||||
selectedOptionsFull: Ref<NotifyProviderOption[]>
|
||||
/**
|
||||
* 格式化后用于多选组件的选项列表
|
||||
*/
|
||||
selectOptions: Ref<NotifyProviderOption[]>
|
||||
/**
|
||||
* 打开通知渠道配置页面的方法
|
||||
*/
|
||||
goToAddNotifyProvider: () => void
|
||||
/**
|
||||
* 处理多选值更新的方法
|
||||
*/
|
||||
handleMultiSelectUpdate: (values: string[]) => void
|
||||
/**
|
||||
* 手动刷新通知提供商列表的方法
|
||||
*/
|
||||
fetchNotifyProviderData: () => void
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// External Libraries
|
||||
import { ref, watch, computed } from 'vue'
|
||||
|
||||
// Type Imports
|
||||
import type {
|
||||
NotifyProviderOption,
|
||||
NotifyProviderMultiSelectControllerExposes,
|
||||
NotifyProviderMultiSelectProps,
|
||||
RawProviderItem,
|
||||
} from './types'
|
||||
|
||||
// Absolute Internal Imports - Store
|
||||
import { useStore } from '@layout/useStore'
|
||||
// Absolute Internal Imports - Config
|
||||
import { MessagePushConfig } from '@config/data'
|
||||
|
||||
/**
|
||||
* @description NotifyProviderMultiSelect 组件的控制器逻辑
|
||||
* @param props - 组件的 props
|
||||
* @param emit - 组件的 emit 函数
|
||||
* @returns {NotifyProviderMultiSelectControllerExposes} 暴露给视图的响应式数据和方法
|
||||
*/
|
||||
export function useNotifyProviderMultiSelectController(
|
||||
props: NotifyProviderMultiSelectProps,
|
||||
emit: (event: 'update:value', payload: NotifyProviderOption[]) => void,
|
||||
): NotifyProviderMultiSelectControllerExposes {
|
||||
const { fetchNotifyProvider, notifyProvider } = useStore()
|
||||
|
||||
// 内部存储当前选中的完整 NotifyProviderOption 对象数组
|
||||
const selectedOptionsFull = ref<NotifyProviderOption[]>([])
|
||||
// 存储多选组件使用的选项列表
|
||||
const selectOptions = ref<NotifyProviderOption[]>([])
|
||||
|
||||
/**
|
||||
* @description 从 MessagePushConfig 生成备用选项列表
|
||||
*/
|
||||
const fallbackOptions = computed<NotifyProviderOption[]>(() => {
|
||||
return Object.entries(MessagePushConfig).map(([key, config]) => ({
|
||||
label: config.name,
|
||||
value: props.valueType === 'value' ? key : config.type,
|
||||
type: config.type,
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 根据当前选中的值数组更新内部完整选中项数组
|
||||
* @param currentSelectedValues - 当前选中的值数组 (字符串数组)
|
||||
*/
|
||||
const updateInternalSelectedOptions = (currentSelectedValues: string[]): void => {
|
||||
if (!currentSelectedValues || currentSelectedValues.length === 0) {
|
||||
selectedOptionsFull.value = []
|
||||
return
|
||||
}
|
||||
|
||||
const newSelectedOptions: NotifyProviderOption[] = []
|
||||
|
||||
currentSelectedValues.forEach((value) => {
|
||||
// 首先尝试从当前选项列表中查找
|
||||
let foundOption = selectOptions.value.find((option) => option.value === value)
|
||||
|
||||
if (!foundOption) {
|
||||
// 如果在当前选项列表中找不到,尝试从备用选项中查找
|
||||
foundOption = fallbackOptions.value.find((option) => option.value === value)
|
||||
}
|
||||
|
||||
if (foundOption) {
|
||||
newSelectedOptions.push({ ...foundOption })
|
||||
} else {
|
||||
// 如果都找不到,创建一个临时选项
|
||||
newSelectedOptions.push({
|
||||
label: value,
|
||||
value: value,
|
||||
type: '',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
selectedOptionsFull.value = newSelectedOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 打开通知渠道配置页面
|
||||
*/
|
||||
const goToAddNotifyProvider = (): void => {
|
||||
window.open('/settings?tab=notification', '_blank')
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 处理多选组件的值更新事件
|
||||
* @param newSelectedValues - 多选组件更新的 modelValue (字符串数组)
|
||||
*/
|
||||
const handleMultiSelectUpdate = (newSelectedValues: string[]): void => {
|
||||
updateInternalSelectedOptions(newSelectedValues)
|
||||
emit('update:value', [...selectedOptionsFull.value]) // Emit a copy of the array
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 外部调用以刷新通知提供商列表
|
||||
*/
|
||||
const fetchNotifyProviderData = (): void => {
|
||||
fetchNotifyProvider()
|
||||
}
|
||||
|
||||
// 监听父组件传入的 props.value (可能是初始值或外部更改)
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
// 确保提供商列表已加载或正在加载,然后再尝试更新选中项细节
|
||||
if (selectOptions.value.length === 0 && newVal && newVal.length > 0) {
|
||||
fetchNotifyProviderData() // 如果列表为空且有 props.value,触发加载
|
||||
}
|
||||
updateInternalSelectedOptions(newVal)
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
// 监听从 Store 获取的原始通知提供商列表,并进行转换
|
||||
watch(
|
||||
() => notifyProvider.value,
|
||||
(rawProviders) => {
|
||||
if (rawProviders && rawProviders.length > 0) {
|
||||
// 如果 Store 中有数据,使用 Store 数据
|
||||
selectOptions.value = rawProviders.map((item: RawProviderItem) => ({
|
||||
label: item.label,
|
||||
// `value` 字段给多选组件使用,根据 props.valueType 决定其来源
|
||||
value: props.valueType === 'value' ? item.value : item.type,
|
||||
// `type` 字段始终为原始提供商的 type,用于 SvgIcon
|
||||
type: item.type,
|
||||
}))
|
||||
} else {
|
||||
// 如果 Store 中没有数据,使用备用数据源
|
||||
selectOptions.value = fallbackOptions.value
|
||||
}
|
||||
|
||||
// Store 数据更新后,基于当前 props.value 重新更新内部完整选中项
|
||||
updateInternalSelectedOptions(props.value)
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
// 初始化时如果 Store 为空,先使用备用数据
|
||||
if (!notifyProvider.value || notifyProvider.value.length === 0) {
|
||||
selectOptions.value = fallbackOptions.value
|
||||
// 尝试获取 Store 数据
|
||||
fetchNotifyProviderData()
|
||||
}
|
||||
|
||||
return {
|
||||
selectedOptionsFull,
|
||||
selectOptions,
|
||||
goToAddNotifyProvider,
|
||||
handleMultiSelectUpdate,
|
||||
fetchNotifyProviderData,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { NTag } from 'naive-ui'
|
||||
import { NTag, NFlex } from 'naive-ui'
|
||||
|
||||
// 类型导入
|
||||
import type { AuthApiTypeIconProps } from './types'
|
||||
@@ -24,11 +24,11 @@ export default defineComponent({
|
||||
name: 'AuthApiTypeIcon',
|
||||
props: {
|
||||
/**
|
||||
* 图标类型键。
|
||||
* 图标类型键。支持单个字符串或字符串数组。
|
||||
* 该键用于从 /lib/data.tsx 配置中查找对应的图标和名称。
|
||||
*/
|
||||
icon: {
|
||||
type: String as PropType<AuthApiTypeIconProps['icon']>,
|
||||
type: [String, Array] as PropType<AuthApiTypeIconProps['icon']>,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
@@ -47,17 +47,41 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props: AuthApiTypeIconProps) {
|
||||
const { iconPath, typeName } = useAuthApiTypeIconController(props)
|
||||
const { iconPath, typeName, iconItems } = useAuthApiTypeIconController(props)
|
||||
|
||||
return () => (
|
||||
<NTag
|
||||
type={props.type}
|
||||
size="small"
|
||||
class="w-auto text-ellipsis overflow-hidden whitespace-normal p-[.6rem] h-auto"
|
||||
>
|
||||
<SvgIcon icon={iconPath.value} size="1.2rem" class="mr-[0.4rem]" />
|
||||
<span>{props.text && <span class="text-[12px]">{typeName.value}</span>}</span>
|
||||
</NTag>
|
||||
)
|
||||
return () => {
|
||||
// 如果是多个图标,显示多个标签
|
||||
if (Array.isArray(props.icon) && props.icon.length > 1) {
|
||||
return (
|
||||
<NFlex size="small" wrap={true} style="gap: 4px; flex-wrap: wrap;">
|
||||
{iconItems.value.map((item, index) => (
|
||||
<NTag
|
||||
key={item.key}
|
||||
type={props.type}
|
||||
size="small"
|
||||
class="w-auto text-ellipsis overflow-hidden whitespace-normal p-[.6rem] h-auto mb-1"
|
||||
style="margin-right: 4px; max-width: 100%;"
|
||||
>
|
||||
<SvgIcon icon={item.iconPath} size="1.2rem" class="mr-[0.4rem] flex-shrink-0" />
|
||||
{props.text && <span class="text-[12px] truncate">{item.typeName}</span>}
|
||||
</NTag>
|
||||
))}
|
||||
</NFlex>
|
||||
)
|
||||
}
|
||||
|
||||
// 单个图标的显示(包括数组只有一个元素的情况)
|
||||
return (
|
||||
<NTag
|
||||
type={props.type}
|
||||
size="small"
|
||||
class="w-auto text-ellipsis overflow-hidden whitespace-normal p-[.6rem] h-auto"
|
||||
style="max-width: 100%;"
|
||||
>
|
||||
<SvgIcon icon={iconPath.value} size="1.2rem" class="mr-[0.4rem] flex-shrink-0" />
|
||||
{props.text && <span class="text-[12px] truncate">{typeName.value}</span>}
|
||||
</NTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
export interface AuthApiTypeIconProps {
|
||||
/**
|
||||
* 图标类型键。
|
||||
* 图标类型键。支持单个字符串或字符串数组。
|
||||
* 该键用于从 /lib/data.tsx 配置中查找对应的图标和名称。
|
||||
* 如果未在配置中找到,将尝试使用 'default' 图标,并直接显示该键作为文本。
|
||||
* 当传入数组时,会显示多个图标标签。
|
||||
*/
|
||||
icon: string
|
||||
icon: string | string[]
|
||||
/**
|
||||
* NTag 的类型。
|
||||
* @default 'default'
|
||||
@@ -20,6 +21,15 @@ export interface AuthApiTypeIconProps {
|
||||
text?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 图标项的类型定义
|
||||
*/
|
||||
export interface IconItem {
|
||||
iconPath: string
|
||||
typeName: string
|
||||
key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* useAuthApiTypeIconController Composable 函数暴露的接口。
|
||||
*/
|
||||
@@ -28,6 +38,8 @@ export interface AuthApiTypeIconControllerExposes {
|
||||
iconPath: globalThis.ComputedRef<string>
|
||||
/** 计算得到的类型名称,用于显示 */
|
||||
typeName: globalThis.ComputedRef<string>
|
||||
/** 计算得到的所有图标项,用于多图标显示 */
|
||||
iconItems: globalThis.ComputedRef<IconItem[]>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,34 +53,65 @@ if (ApiProjectConfig.btwaf) {
|
||||
|
||||
/**
|
||||
* @function useAuthApiTypeIconController
|
||||
* @description AuthApiTypeIcon 组件的控制器逻辑。
|
||||
* @description AuthApiTypeIcon 组件的控制器逻辑,支持单个或多个图标。
|
||||
* @param props - 组件的 props。
|
||||
* @returns {AuthApiTypeIconControllerExposes} 控制器暴露给视图的数据和方法。
|
||||
*/
|
||||
export function useAuthApiTypeIconController(props: AuthApiTypeIconProps): AuthApiTypeIconControllerExposes {
|
||||
/**
|
||||
* @computed iconPath
|
||||
* @description 根据 props.icon 计算 SvgIcon 所需的图标名称。
|
||||
* @description 根据 props.icon 计算 SvgIcon 所需的图标名称。支持单个或多个图标。
|
||||
*/
|
||||
const iconPath = computed<string>(() => {
|
||||
const isNotify = notifyKeys.has(props.icon)
|
||||
// 如果是数组,取第一个作为主要图标
|
||||
const iconKey = Array.isArray(props.icon) ? props.icon[0] : props.icon
|
||||
if (!iconKey) return 'resources-default'
|
||||
|
||||
const isNotify = notifyKeys.has(iconKey)
|
||||
const RESOURCE_PREFIX = isNotify ? 'notify-' : 'resources-'
|
||||
// 从映射表中获取图标文件名,如果找不到则使用 'default'
|
||||
const iconStem = iconFileMap[props.icon] || 'default'
|
||||
const iconStem = iconFileMap[iconKey] || 'default'
|
||||
return RESOURCE_PREFIX + iconStem
|
||||
})
|
||||
|
||||
/**
|
||||
* @computed typeName
|
||||
* @description 根据 props.icon 获取对应的显示名称。
|
||||
* @description 根据 props.icon 获取对应的显示名称。支持单个或多个名称。
|
||||
*/
|
||||
const typeName = computed<string>(() => {
|
||||
// 从映射表中获取显示名称,如果找不到则直接使用 props.icon
|
||||
return typeNamesMap[props.icon] || props.icon
|
||||
if (Array.isArray(props.icon)) {
|
||||
// 如果是数组,组合多个名称
|
||||
return props.icon
|
||||
.filter(Boolean)
|
||||
.map((iconKey) => typeNamesMap[iconKey] || iconKey)
|
||||
.join(', ')
|
||||
} else {
|
||||
// 单个图标的处理
|
||||
return typeNamesMap[props.icon] || props.icon
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @computed iconItems
|
||||
* @description 计算所有图标项,用于多图标显示
|
||||
*/
|
||||
const iconItems = computed(() => {
|
||||
const icons = Array.isArray(props.icon) ? props.icon : [props.icon]
|
||||
return icons.filter(Boolean).map((iconKey) => {
|
||||
const isNotify = notifyKeys.has(iconKey)
|
||||
const RESOURCE_PREFIX = isNotify ? 'notify-' : 'resources-'
|
||||
const iconStem = iconFileMap[iconKey] || 'default'
|
||||
return {
|
||||
iconPath: RESOURCE_PREFIX + iconStem,
|
||||
typeName: typeNamesMap[iconKey] || iconKey,
|
||||
key: iconKey,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
iconPath,
|
||||
typeName,
|
||||
iconItems,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user