更新
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,3 +37,4 @@ __screenshots__/
|
|||||||
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.clinerules
|
||||||
508
src/components/scIconPicker/index.vue
Normal file
508
src/components/scIconPicker/index.vue
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sc-icon-picker">
|
||||||
|
<a-input :value="selectedIcon ? '' : ''" :placeholder="placeholder" readonly @click="handleOpenPicker">
|
||||||
|
<template #prefix v-if="selectedIcon">
|
||||||
|
<component :is="selectedIcon" />
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<SearchOutlined v-if="!selectedIcon" />
|
||||||
|
<CloseCircleFilled v-else @click.stop="handleClear" />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
|
||||||
|
<a-modal v-model:open="visible" title="选择图标" :width="800" :footer="null" @cancel="handleCancel">
|
||||||
|
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||||
|
<a-tab-pane key="antd" tab="Ant Design">
|
||||||
|
<div class="icon-search">
|
||||||
|
<a-input v-model:value="searchAntdValue" placeholder="搜索图标..." allow-clear>
|
||||||
|
<template #prefix>
|
||||||
|
<SearchOutlined />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
<div class="icon-list">
|
||||||
|
<div
|
||||||
|
v-for="icon in filteredAntdIcons"
|
||||||
|
:key="icon"
|
||||||
|
:class="['icon-item', { active: tempIcon === icon }]"
|
||||||
|
@click="handleSelectIcon(icon)"
|
||||||
|
>
|
||||||
|
<component :is="icon" />
|
||||||
|
<div class="icon-name">{{ icon }}</div>
|
||||||
|
</div>
|
||||||
|
<a-empty v-if="filteredAntdIcons.length === 0" description="暂无图标" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="element" tab="Element Plus">
|
||||||
|
<div class="icon-search">
|
||||||
|
<a-input v-model:value="searchElementValue" placeholder="搜索图标..." allow-clear>
|
||||||
|
<template #prefix>
|
||||||
|
<SearchOutlined />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
<div class="icon-list">
|
||||||
|
<div
|
||||||
|
v-for="icon in filteredElementIcons"
|
||||||
|
:key="icon"
|
||||||
|
:class="['icon-item', { active: tempIcon === icon }]"
|
||||||
|
@click="handleSelectIcon(icon)"
|
||||||
|
>
|
||||||
|
<component :is="icon" />
|
||||||
|
<div class="icon-name">{{ icon.replace('El', '') }}</div>
|
||||||
|
</div>
|
||||||
|
<a-empty v-if="filteredElementIcons.length === 0" description="暂无图标" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* @component scIconPicker
|
||||||
|
*/
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { Empty } from 'ant-design-vue'
|
||||||
|
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择图标',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const activeTab = ref('antd')
|
||||||
|
const searchAntdValue = ref('')
|
||||||
|
const searchElementValue = ref('')
|
||||||
|
const tempIcon = ref('')
|
||||||
|
|
||||||
|
// Ant Design 图标列表(常用图标)
|
||||||
|
const antdIcons = [
|
||||||
|
'HomeOutlined',
|
||||||
|
'UserOutlined',
|
||||||
|
'SettingOutlined',
|
||||||
|
'EditOutlined',
|
||||||
|
'DeleteOutlined',
|
||||||
|
'PlusOutlined',
|
||||||
|
'MinusOutlined',
|
||||||
|
'CheckOutlined',
|
||||||
|
'CloseOutlined',
|
||||||
|
'SearchOutlined',
|
||||||
|
'FilterOutlined',
|
||||||
|
'ReloadOutlined',
|
||||||
|
'DownloadOutlined',
|
||||||
|
'UploadOutlined',
|
||||||
|
'FileTextOutlined',
|
||||||
|
'FolderOutlined',
|
||||||
|
'PictureOutlined',
|
||||||
|
'VideoCameraOutlined',
|
||||||
|
'AudioOutlined',
|
||||||
|
'FileOutlined',
|
||||||
|
'CalendarOutlined',
|
||||||
|
'ClockCircleOutlined',
|
||||||
|
'HeartOutlined',
|
||||||
|
'StarOutlined',
|
||||||
|
'ThumbUpOutlined',
|
||||||
|
'MessageOutlined',
|
||||||
|
'PhoneOutlined',
|
||||||
|
'MailOutlined',
|
||||||
|
'EnvironmentOutlined',
|
||||||
|
'GlobalOutlined',
|
||||||
|
'LinkOutlined',
|
||||||
|
'LockOutlined',
|
||||||
|
'UnlockOutlined',
|
||||||
|
'EyeOutlined',
|
||||||
|
'EyeInvisibleOutlined',
|
||||||
|
'ArrowLeftOutlined',
|
||||||
|
'ArrowRightOutlined',
|
||||||
|
'ArrowUpOutlined',
|
||||||
|
'ArrowDownOutlined',
|
||||||
|
'CaretLeftOutlined',
|
||||||
|
'CaretRightOutlined',
|
||||||
|
'CaretUpOutlined',
|
||||||
|
'CaretDownOutlined',
|
||||||
|
'LeftOutlined',
|
||||||
|
'RightOutlined',
|
||||||
|
'UpOutlined',
|
||||||
|
'DownOutlined',
|
||||||
|
'MenuFoldOutlined',
|
||||||
|
'MenuUnfoldOutlined',
|
||||||
|
'BarsOutlined',
|
||||||
|
'MoreOutlined',
|
||||||
|
'EllipsisOutlined',
|
||||||
|
'DashboardOutlined',
|
||||||
|
'AppstoreOutlined',
|
||||||
|
'LaptopOutlined',
|
||||||
|
'DesktopOutlined',
|
||||||
|
'TabletOutlined',
|
||||||
|
'MobileOutlined',
|
||||||
|
'WifiOutlined',
|
||||||
|
'BluetoothOutlined',
|
||||||
|
'ThunderboltOutlined',
|
||||||
|
'BulbOutlined',
|
||||||
|
'SoundOutlined',
|
||||||
|
'NotificationOutlined',
|
||||||
|
'BellOutlined',
|
||||||
|
'AlertOutlined',
|
||||||
|
'WarningOutlined',
|
||||||
|
'InfoCircleOutlined',
|
||||||
|
'QuestionCircleOutlined',
|
||||||
|
'CheckCircleOutlined',
|
||||||
|
'CloseCircleOutlined',
|
||||||
|
'StopOutlined',
|
||||||
|
'ExclamationCircleOutlined',
|
||||||
|
'SafetyOutlined',
|
||||||
|
'ShieldCheckOutlined',
|
||||||
|
'SecurityScanOutlined',
|
||||||
|
'KeyOutlined',
|
||||||
|
'IdcardOutlined',
|
||||||
|
'ProfileOutlined',
|
||||||
|
'SolutionOutlined',
|
||||||
|
'ContactsOutlined',
|
||||||
|
'TeamOutlined',
|
||||||
|
'UsergroupAddOutlined',
|
||||||
|
'UsergroupDeleteOutlined',
|
||||||
|
'CrownOutlined',
|
||||||
|
'GoldOutlined',
|
||||||
|
'MoneyCollectOutlined',
|
||||||
|
'BankOutlined',
|
||||||
|
'PayCircleOutlined',
|
||||||
|
'CreditCardOutlined',
|
||||||
|
'WalletOutlined',
|
||||||
|
'ShoppingCartOutlined',
|
||||||
|
'ShoppingOutlined',
|
||||||
|
'GiftOutlined',
|
||||||
|
'HddOutlined',
|
||||||
|
'DatabaseOutlined',
|
||||||
|
'CloudOutlined',
|
||||||
|
'CloudUploadOutlined',
|
||||||
|
'CloudDownloadOutlined',
|
||||||
|
'ServerOutlined',
|
||||||
|
'AuditOutlined',
|
||||||
|
'NodeIndexOutlined',
|
||||||
|
'ReconciliationOutlined',
|
||||||
|
'PartitionOutlined',
|
||||||
|
'AccountBookOutlined',
|
||||||
|
'ProjectOutlined',
|
||||||
|
'ControlOutlined',
|
||||||
|
'MonitorOutlined',
|
||||||
|
'TagsOutlined',
|
||||||
|
'TagOutlined',
|
||||||
|
'BookOutlined',
|
||||||
|
'ReadOutlined',
|
||||||
|
'ExperimentOutlined',
|
||||||
|
'FireOutlined',
|
||||||
|
'RocketOutlined',
|
||||||
|
'TrophyOutlined',
|
||||||
|
'MedalOutlined',
|
||||||
|
'DiamondOutlined',
|
||||||
|
'ThunderboltTwoTone',
|
||||||
|
]
|
||||||
|
|
||||||
|
// Element Plus 图标列表(常用图标)
|
||||||
|
const elementIcons = [
|
||||||
|
'ElIconEdit',
|
||||||
|
'ElIconDelete',
|
||||||
|
'ElIconSearch',
|
||||||
|
'ElIconClose',
|
||||||
|
'ElIconCheck',
|
||||||
|
'ElIconPlus',
|
||||||
|
'ElIconMinus',
|
||||||
|
'ElIconUpload',
|
||||||
|
'ElIconDownload',
|
||||||
|
'ElIconSetting',
|
||||||
|
'ElIconRefresh',
|
||||||
|
'ElIconRefreshLeft',
|
||||||
|
'ElIconRefreshRight',
|
||||||
|
'ElIconMenu',
|
||||||
|
'ElIconMore',
|
||||||
|
'ElIconMoreFilled',
|
||||||
|
'ElIconStar',
|
||||||
|
'ElIconStarFilled',
|
||||||
|
'ElIconSunny',
|
||||||
|
'ElIconMoon',
|
||||||
|
'ElIconBell',
|
||||||
|
'ElIconBellFilled',
|
||||||
|
'ElIconMessage',
|
||||||
|
'ElIconMessageFilled',
|
||||||
|
'ElIconChatDotRound',
|
||||||
|
'ElIconChatLineSquare',
|
||||||
|
'ElIconChatDotSquare',
|
||||||
|
'ElIconPhone',
|
||||||
|
'ElIconPhoneFilled',
|
||||||
|
'ElIconLocation',
|
||||||
|
'ElIconLocationFilled',
|
||||||
|
'ElIconLocationInformation',
|
||||||
|
'ElIconView',
|
||||||
|
'ElIconHide',
|
||||||
|
'ElIconLock',
|
||||||
|
'ElIconUnlock',
|
||||||
|
'ElIconKey',
|
||||||
|
'ElIconTickets',
|
||||||
|
'ElIconDocument',
|
||||||
|
'ElIconDocumentAdd',
|
||||||
|
'ElIconDocumentDelete',
|
||||||
|
'ElIconDocumentCopy',
|
||||||
|
'ElIconDocumentChecked',
|
||||||
|
'ElIconDocumentRemove',
|
||||||
|
'ElIconFolder',
|
||||||
|
'ElIconFolderOpened',
|
||||||
|
'ElIconFolderAdd',
|
||||||
|
'ElIconFolderDelete',
|
||||||
|
'ElIconFolderChecked',
|
||||||
|
'ElIconFiles',
|
||||||
|
'ElIconPicture',
|
||||||
|
'ElIconPictureRounded',
|
||||||
|
'ElIconPictureFilled',
|
||||||
|
'ElIconVideoCamera',
|
||||||
|
'ElIconVideoCameraFilled',
|
||||||
|
'ElIconMicrophone',
|
||||||
|
'ElIconMicrophoneFilled',
|
||||||
|
'ElIconHeadset',
|
||||||
|
'ElIconHeadsetFilled',
|
||||||
|
'ElIconMuteNotification',
|
||||||
|
'ElIconNotification',
|
||||||
|
'ElIconWarning',
|
||||||
|
'ElIconWarningFilled',
|
||||||
|
'ElIconInfoFilled',
|
||||||
|
'ElIconSuccessFilled',
|
||||||
|
'ElIconCircleCheck',
|
||||||
|
'ElIconCircleCheckFilled',
|
||||||
|
'ElIconCircleClose',
|
||||||
|
'ElIconCircleCloseFilled',
|
||||||
|
'ElIconCirclePlus',
|
||||||
|
'ElIconCirclePlusFilled',
|
||||||
|
'ElIconCircleMinus',
|
||||||
|
'ElIconCircleMinusFilled',
|
||||||
|
'ElIconAim',
|
||||||
|
'ElIconPosition',
|
||||||
|
'ElIconCompass',
|
||||||
|
'ElIconMapLocation',
|
||||||
|
'ElIconPromotion',
|
||||||
|
'ElIconDownload',
|
||||||
|
'ElIconUploadFilled',
|
||||||
|
'ElIconShare',
|
||||||
|
'ElIconConnection',
|
||||||
|
'ElIconLink',
|
||||||
|
'ElIconUnlink',
|
||||||
|
'ElIconOperation',
|
||||||
|
'ElIconDataAnalysis',
|
||||||
|
'ElIconDataLine',
|
||||||
|
'ElIconDataBoard',
|
||||||
|
'ElIconHistogram',
|
||||||
|
'ElIconTrendCharts',
|
||||||
|
'ElIconPieChart',
|
||||||
|
'ElIconOdometer',
|
||||||
|
'ElIconMonitor',
|
||||||
|
'ElIconTimer',
|
||||||
|
'ElIconClock',
|
||||||
|
'ElIconAlarmClock',
|
||||||
|
'ElIconCalendar',
|
||||||
|
'ElIconDate',
|
||||||
|
'ElIconSwitch',
|
||||||
|
'ElIconSwitchButton',
|
||||||
|
'ElIconTools',
|
||||||
|
'ElIconScrewdriver',
|
||||||
|
'ElIconHammer',
|
||||||
|
'ElIconBrush',
|
||||||
|
'ElIconEditPen',
|
||||||
|
'ElIconBriefcase',
|
||||||
|
'ElIconWallet',
|
||||||
|
'ElIconGoods',
|
||||||
|
'ElIconShoppingCart',
|
||||||
|
'ElIconShoppingCartFull',
|
||||||
|
'ElIconShoppingBag',
|
||||||
|
'ElIconPresent',
|
||||||
|
'ElIconSoldOut',
|
||||||
|
'ElIconSell',
|
||||||
|
'ElIconDiscount',
|
||||||
|
'ElIconTicket',
|
||||||
|
'ElIconCoin',
|
||||||
|
'ElIconMoney',
|
||||||
|
'ElIconWalletFilled',
|
||||||
|
'ElIconCreditCard',
|
||||||
|
'ElIconUser',
|
||||||
|
'ElIconUserFilled',
|
||||||
|
'ElIconAvatar',
|
||||||
|
'ElIconSuitcase',
|
||||||
|
'ElIconGrid',
|
||||||
|
'ElIconMenuFilled',
|
||||||
|
'ElIconHomeFilled',
|
||||||
|
'ElIconHouse',
|
||||||
|
'ElIconOfficeBuilding',
|
||||||
|
'ElIconSchool',
|
||||||
|
'ElIconReading',
|
||||||
|
'ElIconReadingLamp',
|
||||||
|
'ElIconNotebook',
|
||||||
|
'ElIconNotebookFilled',
|
||||||
|
'ElIconFinished',
|
||||||
|
'ElIconCollection',
|
||||||
|
'ElIconCollectionTag',
|
||||||
|
'ElIconFiles',
|
||||||
|
'ElIconPostcard',
|
||||||
|
'ElIconMemo',
|
||||||
|
'ElIconStamp',
|
||||||
|
'ElIconPriceTag',
|
||||||
|
'ElIconMedal',
|
||||||
|
'ElIconTrophy',
|
||||||
|
'ElIconTrophyBase',
|
||||||
|
'ElIconFirstAidKit',
|
||||||
|
'ElIconToiletPaper',
|
||||||
|
'ElIconAim',
|
||||||
|
'ElIconSFlag',
|
||||||
|
'ElIconSOpportunity',
|
||||||
|
'ElIconMagicStick',
|
||||||
|
'ElIconHelp',
|
||||||
|
'ElIconQuestionFilled',
|
||||||
|
'ElIconWarning',
|
||||||
|
'ElIconWarningFilled',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 当前选中的图标
|
||||||
|
const selectedIcon = ref(props.modelValue)
|
||||||
|
|
||||||
|
// 过滤后的 Ant Design 图标
|
||||||
|
const filteredAntdIcons = computed(() => {
|
||||||
|
if (!searchAntdValue.value) {
|
||||||
|
return antdIcons
|
||||||
|
}
|
||||||
|
return antdIcons.filter((icon) =>
|
||||||
|
icon.toLowerCase().includes(searchAntdValue.value.toLowerCase()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 过滤后的 Element 图标
|
||||||
|
const filteredElementIcons = computed(() => {
|
||||||
|
if (!searchElementValue.value) {
|
||||||
|
return elementIcons
|
||||||
|
}
|
||||||
|
return elementIcons.filter((icon) =>
|
||||||
|
icon.toLowerCase().includes(searchElementValue.value.toLowerCase()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 打开选择器
|
||||||
|
const handleOpenPicker = () => {
|
||||||
|
tempIcon.value = props.modelValue
|
||||||
|
// 根据当前图标设置默认标签页
|
||||||
|
if (props.modelValue) {
|
||||||
|
activeTab.value = props.modelValue.startsWith('El') ? 'element' : 'antd'
|
||||||
|
}
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除选择
|
||||||
|
const handleClear = () => {
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
emit('change', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换标签页
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
activeTab.value = key
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择图标(直接确认并关闭)
|
||||||
|
const handleSelectIcon = (icon) => {
|
||||||
|
emit('update:modelValue', icon)
|
||||||
|
emit('change', icon)
|
||||||
|
selectedIcon.value = icon
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消选择
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听props变化,更新本地状态
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newVal) => {
|
||||||
|
selectedIcon.value = newVal
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.sc-icon-picker {
|
||||||
|
:deep(.ant-input) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #d9d9d9;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #bfbfbf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<template v-for="item in menuItems" :key="item.path || item.name">
|
<template v-for="item in menuItems" :key="item.path || item.name">
|
||||||
<!-- 有子菜单 - 使用递归 -->
|
<!-- 有子菜单 - 使用递归 -->
|
||||||
<a-sub-menu v-if="item.children && item.children.length > 0" :key="`submenu-${item.path}`">
|
<a-sub-menu v-if="item.children && item.children.length > 0" :key="`${item.path}`">
|
||||||
<template #icon v-if="item.meta?.icon">
|
<template #icon v-if="item.meta?.icon">
|
||||||
<component :is="getIconComponent(item.meta.icon)" />
|
<component :is="getIconComponent(item.meta.icon)" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,21 +20,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tags-actions">
|
<div class="tags-actions">
|
||||||
<a-tooltip title="刷新当前页">
|
<a-dropdown v-model:open="actionMenuVisible" trigger="click" placement="bottomRight">
|
||||||
<a-button size="small" type="text" @click="refreshSelectedTag">
|
<a-button size="small" type="text">
|
||||||
|
<MoreOutlined />
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="handleActionMenuClick">
|
||||||
|
<a-menu-item key="refresh">
|
||||||
<ReloadOutlined />
|
<ReloadOutlined />
|
||||||
</a-button>
|
<span>刷新当前页</span>
|
||||||
</a-tooltip>
|
</a-menu-item>
|
||||||
<a-tooltip title="关闭其他">
|
<a-menu-item key="closeOthers">
|
||||||
<a-button size="small" type="text" @click="closeOthersTags">
|
|
||||||
<ColumnWidthOutlined />
|
<ColumnWidthOutlined />
|
||||||
</a-button>
|
<span>关闭其他</span>
|
||||||
</a-tooltip>
|
</a-menu-item>
|
||||||
<a-tooltip title="关闭所有">
|
<a-menu-item key="closeLeft">
|
||||||
<a-button size="small" type="text" @click="closeAllTags">
|
<LeftOutlined />
|
||||||
|
<span>关闭左侧</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="closeRight">
|
||||||
|
<RightOutlined />
|
||||||
|
<span>关闭右侧</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="closeAll">
|
||||||
<CloseCircleOutlined />
|
<CloseCircleOutlined />
|
||||||
</a-button>
|
<span>关闭所有</span>
|
||||||
</a-tooltip>
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
@@ -76,13 +90,6 @@
|
|||||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useLayoutStore } from '@/stores/modules/layout'
|
import { useLayoutStore } from '@/stores/modules/layout'
|
||||||
import {
|
|
||||||
ReloadOutlined,
|
|
||||||
CloseOutlined,
|
|
||||||
ColumnWidthOutlined,
|
|
||||||
CloseCircleOutlined,
|
|
||||||
PushpinFilled
|
|
||||||
} from '@ant-design/icons-vue'
|
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -102,6 +109,8 @@ const contextMenu = ref({
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
})
|
})
|
||||||
|
// 顶部操作菜单状态
|
||||||
|
const actionMenuVisible = ref(false)
|
||||||
|
|
||||||
// 判断是否是当前激活的标签
|
// 判断是否是当前激活的标签
|
||||||
const isActive = (tag) => {
|
const isActive = (tag) => {
|
||||||
@@ -179,6 +188,38 @@ const closeAllTags = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭左侧标签
|
||||||
|
const closeLeftTags = () => {
|
||||||
|
const currentTag = selectedTag.value || visitedViews.value.find((tag) => isActive(tag))
|
||||||
|
if (!currentTag) return
|
||||||
|
|
||||||
|
const currentIndex = visitedViews.value.findIndex((tag) => tag.fullPath === currentTag.fullPath)
|
||||||
|
if (currentIndex === -1) return
|
||||||
|
|
||||||
|
// 保留当前标签及其右侧的标签,以及所有固定标签
|
||||||
|
const tagsToKeep = visitedViews.value.filter((tag, index) => {
|
||||||
|
return tag.meta?.affix || index >= currentIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
layoutStore.viewTags = tagsToKeep
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭右侧标签
|
||||||
|
const closeRightTags = () => {
|
||||||
|
const currentTag = selectedTag.value || visitedViews.value.find((tag) => isActive(tag))
|
||||||
|
if (!currentTag) return
|
||||||
|
|
||||||
|
const currentIndex = visitedViews.value.findIndex((tag) => tag.fullPath === currentTag.fullPath)
|
||||||
|
if (currentIndex === -1) return
|
||||||
|
|
||||||
|
// 保留当前标签及其左侧的标签,以及所有固定标签
|
||||||
|
const tagsToKeep = visitedViews.value.filter((tag, index) => {
|
||||||
|
return tag.meta?.affix || index <= currentIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
layoutStore.viewTags = tagsToKeep
|
||||||
|
}
|
||||||
|
|
||||||
// 点击标签
|
// 点击标签
|
||||||
const clickTag = (tag) => {
|
const clickTag = (tag) => {
|
||||||
if (!isActive(tag)) {
|
if (!isActive(tag)) {
|
||||||
@@ -248,6 +289,28 @@ const handleMenuClick = ({ key }) => {
|
|||||||
closeContextMenu()
|
closeContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 顶部操作菜单点击处理
|
||||||
|
const handleActionMenuClick = ({ key }) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'refresh':
|
||||||
|
refreshSelectedTag()
|
||||||
|
break
|
||||||
|
case 'closeOthers':
|
||||||
|
closeOthersTags()
|
||||||
|
break
|
||||||
|
case 'closeLeft':
|
||||||
|
closeLeftTags()
|
||||||
|
break
|
||||||
|
case 'closeRight':
|
||||||
|
closeRightTags()
|
||||||
|
break
|
||||||
|
case 'closeAll':
|
||||||
|
closeAllTags()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
actionMenuVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// 点击其他地方关闭右键菜单
|
// 点击其他地方关闭右键菜单
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (contextMenu.value.visible) {
|
if (contextMenu.value.visible) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="菜单图标" name="icon">
|
<a-form-item label="菜单图标" name="icon">
|
||||||
<a-input v-model:value="form.icon" placeholder="请输入图标类名" allow-clear />
|
<sc-icon-picker v-model="form.icon" placeholder="请选择图标" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -169,6 +169,7 @@ import { ref, reactive, watch, onMounted } from 'vue'
|
|||||||
import { message, Empty } from 'ant-design-vue'
|
import { message, Empty } from 'ant-design-vue'
|
||||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||||
import authApi from '@/api/auth'
|
import authApi from '@/api/auth'
|
||||||
|
import ScIconPicker from '@/components/scIconPicker/index.vue'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'PermissionSave'
|
name: 'PermissionSave'
|
||||||
|
|||||||
50
src/pages/home/iconPickerDemo.vue
Normal file
50
src/pages/home/iconPickerDemo.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="icon-picker-demo">
|
||||||
|
<a-card title="图标选择器演示" style="max-width: 600px; margin: 20px auto;">
|
||||||
|
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||||
|
<a-form-item label="选择图标">
|
||||||
|
<sc-icon-picker v-model="selectedIcon" @change="handleIconChange" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="选中值">
|
||||||
|
<a-input v-model:value="selectedIcon" readonly />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="图标预览">
|
||||||
|
<div v-if="selectedIcon" class="icon-preview">
|
||||||
|
<component :is="selectedIcon" style="font-size: 48px;" />
|
||||||
|
</div>
|
||||||
|
<a-empty v-else description="未选择图标" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Empty } from 'ant-design-vue'
|
||||||
|
import ScIconPicker from '@/components/scIconPicker/index.vue'
|
||||||
|
|
||||||
|
const selectedIcon = ref('')
|
||||||
|
|
||||||
|
const handleIconChange = (value) => {
|
||||||
|
console.log('图标已选择:', value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.icon-picker-demo {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.icon-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 88px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user