|
@@ -0,0 +1,948 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import {
|
|
|
|
|
+ User_Sys_List,
|
|
|
|
|
+ Menu_List,
|
|
|
|
|
+ Menu_Get,
|
|
|
|
|
+ Menu_Add,
|
|
|
|
|
+ Menu_Edit,
|
|
|
|
|
+ Menu_Del
|
|
|
|
|
+} from '@/api/menu/index'
|
|
|
|
|
+import { GlobalStore } from '@/stores/index'
|
|
|
|
|
+import { ref, reactive, nextTick, onMounted, computed } from 'vue'
|
|
|
|
|
+import Drawer from '@/components/Drawer/index.vue'
|
|
|
|
|
+import Dialog from '@/components/dialog/Dialog.vue'
|
|
|
|
|
+import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
|
+import { Edit, Delete, Plus, Search, ArrowUp, ArrowDown } from '@element-plus/icons-vue'
|
|
|
|
|
+import { ElNotification, ElMessageBox, ElMessage, TabsPaneContext } from 'element-plus'
|
|
|
|
|
+import { icons } from '@/utils/common'
|
|
|
|
|
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
|
|
|
+
|
|
|
|
|
+interface InSys {
|
|
|
|
|
+ T_sys: string
|
|
|
|
|
+ T_name: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Menu {
|
|
|
|
|
+ Id: number
|
|
|
|
|
+ T_mid: number
|
|
|
|
|
+ T_name: string
|
|
|
|
|
+ T_permission: string
|
|
|
|
|
+ T_icon: string
|
|
|
|
|
+ T_uri: string
|
|
|
|
|
+ T_type: 'M' | 'B'
|
|
|
|
|
+ T_sort: number
|
|
|
|
|
+ T_State: number
|
|
|
|
|
+ Children?: Menu[]
|
|
|
|
|
+ apis?: API[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface API {
|
|
|
|
|
+ Id: number
|
|
|
|
|
+ T_Menu_Id: number
|
|
|
|
|
+ T_name: string
|
|
|
|
|
+ T_uri: string
|
|
|
|
|
+ T_method: string
|
|
|
|
|
+ T_enable: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface MenuWithAPIs extends Menu {
|
|
|
|
|
+ apis?: API[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const globalStore = GlobalStore()
|
|
|
|
|
+const User_tokey = globalStore.GET_User_tokey
|
|
|
|
|
+const formLabelWidth = ref('120px')
|
|
|
|
|
+const ruleFormRef = ref<FormInstance>()
|
|
|
|
|
+const apiFormRef = ref<FormInstance>()
|
|
|
|
|
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
|
|
|
|
|
+const dialogRef = ref<InstanceType<typeof Dialog> | null>(null)
|
|
|
|
|
+const tableRef = ref()
|
|
|
|
|
+const iconDialogVisible = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const SysList = ref<InSys[]>([])
|
|
|
|
|
+const tableKey = ref(0) // 用于强制重新渲染表格
|
|
|
|
|
+const menuDir = reactive({
|
|
|
|
|
+ activeName: null as string | null, // 选中的tabs
|
|
|
|
|
+ MenuData: [] as InSys[], // tabs导航栏
|
|
|
|
|
+ menuTree: [] as Menu[], // 菜单树
|
|
|
|
|
+ defaultExpandedKeys: [] as number[], // 默认展开的节点
|
|
|
|
|
+ isExpanded: false, // 是否展开所有节点(默认折叠)
|
|
|
|
|
+ expandedKeys: [] as number[] // 当前展开的节点ID列表
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 图标选择器相关
|
|
|
|
|
+const iconType = ref<'element' | 'custom'>('element')
|
|
|
|
|
+const selectedIcon = ref('')
|
|
|
|
|
+const iconSearchText = ref('')
|
|
|
|
|
+
|
|
|
|
|
+// Element Plus 图标列表
|
|
|
|
|
+const elementIcons = computed(() => {
|
|
|
|
|
+ const iconList = Object.keys(ElementPlusIconsVue)
|
|
|
|
|
+ if (iconSearchText.value) {
|
|
|
|
|
+ return iconList.filter(icon =>
|
|
|
|
|
+ icon.toLowerCase().includes(iconSearchText.value.toLowerCase())
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ return iconList
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 自定义图标列表
|
|
|
|
|
+const customIcons = computed(() => {
|
|
|
|
|
+ const iconList = Object.keys(icons)
|
|
|
|
|
+ if (iconSearchText.value) {
|
|
|
|
|
+ return iconList.filter(icon =>
|
|
|
|
|
+ icon.toLowerCase().includes(iconSearchText.value.toLowerCase())
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ return iconList
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const isNew = ref(true)
|
|
|
|
|
+const currentMenu = ref<Menu | null>(null)
|
|
|
|
|
+const currentParentId = ref<number>(0)
|
|
|
|
|
+const originalApis = ref<Array<{ T_name: string; T_uri: string; T_method: string }>>([])
|
|
|
|
|
+
|
|
|
|
|
+// 菜单表单
|
|
|
|
|
+const menuForm = reactive({
|
|
|
|
|
+ T_name: '',
|
|
|
|
|
+ T_mid: 0,
|
|
|
|
|
+ T_permission: '',
|
|
|
|
|
+ T_icon: '',
|
|
|
|
|
+ T_uri: '',
|
|
|
|
|
+ T_type: 'M' as 'M' | 'B',
|
|
|
|
|
+ T_sort: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// API表单
|
|
|
|
|
+const apiForm = reactive({
|
|
|
|
|
+ apis: [] as Array<{ T_name: string; T_uri: string; T_method: string }>
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const menuRules = reactive<FormRules>({
|
|
|
|
|
+ T_name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
|
|
|
|
|
+ T_code: [{ required: true, message: '请选择系统', trigger: 'change' }]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 获取子系统列表
|
|
|
|
|
+const getSysList = async () => {
|
|
|
|
|
+ const { Data } = await User_Sys_List({ User_tokey })
|
|
|
|
|
+ menuDir.MenuData = (Data as InSys[]) || []
|
|
|
|
|
+ if (menuDir.MenuData && menuDir.MenuData.length > 0) {
|
|
|
|
|
+ menuDir.activeName = menuDir.MenuData[0].T_sys
|
|
|
|
|
+ await getMenuList(menuDir.activeName)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 提取所有菜单ID
|
|
|
|
|
+const extractAllIds = (menus: Menu[]): number[] => {
|
|
|
|
|
+ let ids: number[] = []
|
|
|
|
|
+ menus.forEach(menu => {
|
|
|
|
|
+ ids.push(menu.Id)
|
|
|
|
|
+ if (menu.Children && menu.Children.length > 0) {
|
|
|
|
|
+ ids = ids.concat(extractAllIds(menu.Children))
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ return ids
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 提取所有菜单行(扁平化)
|
|
|
|
|
+const extractAllRows = (menus: Menu[]): Menu[] => {
|
|
|
|
|
+ let rows: Menu[] = []
|
|
|
|
|
+ menus.forEach(menu => {
|
|
|
|
|
+ rows.push(menu)
|
|
|
|
|
+ if (menu.Children && menu.Children.length > 0) {
|
|
|
|
|
+ rows = rows.concat(extractAllRows(menu.Children))
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ return rows
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 一键折叠/展开
|
|
|
|
|
+const toggleExpandAll = () => {
|
|
|
|
|
+ // 切换展开状态
|
|
|
|
|
+ menuDir.isExpanded = !menuDir.isExpanded
|
|
|
|
|
+
|
|
|
|
|
+ // 更新 tableKey 强制重新渲染表格,这样 default-expand-all 会重新生效
|
|
|
|
|
+ tableKey.value++
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取菜单列表
|
|
|
|
|
+const getMenuList = async (T_code: string) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { Data } = await Menu_List({ T_code })
|
|
|
|
|
+ if (Data && (Data as any).Data) {
|
|
|
|
|
+ menuDir.menuTree = (Data as any).Data as Menu[]
|
|
|
|
|
+ // 默认折叠所有节点
|
|
|
|
|
+ menuDir.defaultExpandedKeys = []
|
|
|
|
|
+ menuDir.expandedKeys = []
|
|
|
|
|
+ // 重置展开状态为折叠
|
|
|
|
|
+ menuDir.isExpanded = false
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取菜单列表失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击切换tabs
|
|
|
|
|
+const handleClick = async (tab: TabsPaneContext) => {
|
|
|
|
|
+ menuDir.activeName = tab.props.name as string
|
|
|
|
|
+ await getMenuList(menuDir.activeName)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 打开添加菜单抽屉
|
|
|
|
|
+const openAddDrawer = (parentMenu?: Menu) => {
|
|
|
|
|
+ isNew.value = true
|
|
|
|
|
+ currentMenu.value = null
|
|
|
|
|
+ currentParentId.value = parentMenu ? parentMenu.Id : 0
|
|
|
|
|
+
|
|
|
|
|
+ // 重置表单
|
|
|
|
|
+ Object.assign(menuForm, {
|
|
|
|
|
+ T_name: '',
|
|
|
|
|
+ T_mid: currentParentId.value,
|
|
|
|
|
+ T_permission: '',
|
|
|
|
|
+ T_icon: '',
|
|
|
|
|
+ T_uri: '',
|
|
|
|
|
+ T_type: 'M',
|
|
|
|
|
+ T_sort: 0
|
|
|
|
|
+ })
|
|
|
|
|
+ apiForm.apis = []
|
|
|
|
|
+ originalApis.value = []
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ drawerRef.value?.openDrawer()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 打开编辑菜单抽屉
|
|
|
|
|
+const openEditDrawer = async (menu: Menu) => {
|
|
|
|
|
+ isNew.value = false
|
|
|
|
|
+ currentMenu.value = menu
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取菜单详情(包含API)
|
|
|
|
|
+ const { Data } = await Menu_Get({
|
|
|
|
|
+ id: menu.Id,
|
|
|
|
|
+ T_code: menuDir.activeName || ''
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (Data && (Data as any).Data) {
|
|
|
|
|
+ const menuDetail = (Data as any).Data as MenuWithAPIs
|
|
|
|
|
+ // 处理图标:如果后端返回的图标包含 \,需要保留用于提交,但展示时去掉
|
|
|
|
|
+ const iconValue = menuDetail.T_icon || ''
|
|
|
|
|
+ Object.assign(menuForm, {
|
|
|
|
|
+ T_name: menuDetail.T_name,
|
|
|
|
|
+ T_mid: menuDetail.T_mid,
|
|
|
|
|
+ T_permission: menuDetail.T_permission || '',
|
|
|
|
|
+ T_icon: iconValue, // 保留原始值(可能包含 \)
|
|
|
|
|
+ T_uri: menuDetail.T_uri || '',
|
|
|
|
|
+ T_type: menuDetail.T_type,
|
|
|
|
|
+ T_sort: menuDetail.T_sort
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 设置API列表
|
|
|
|
|
+ if (menuDetail.apis && menuDetail.apis.length > 0) {
|
|
|
|
|
+ apiForm.apis = menuDetail.apis.map((api: API) => ({
|
|
|
|
|
+ T_name: api.T_name,
|
|
|
|
|
+ T_uri: api.T_uri,
|
|
|
|
|
+ T_method: api.T_method
|
|
|
|
|
+ }))
|
|
|
|
|
+ // 保存原始API列表,用于判断是否修改
|
|
|
|
|
+ originalApis.value = JSON.parse(JSON.stringify(apiForm.apis))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ apiForm.apis = []
|
|
|
|
|
+ originalApis.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取菜单详情失败:', error)
|
|
|
|
|
+ ElMessage.error('获取菜单详情失败')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ drawerRef.value?.openDrawer()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加API行
|
|
|
|
|
+const addApiRow = () => {
|
|
|
|
|
+ apiForm.apis.push({
|
|
|
|
|
+ T_name: '',
|
|
|
|
|
+ T_uri: '',
|
|
|
|
|
+ T_method: 'POST'
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除API行
|
|
|
|
|
+const removeApiRow = (index: number) => {
|
|
|
|
|
+ apiForm.apis.splice(index, 1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 提交菜单(添加/编辑)
|
|
|
|
|
+const submitMenu = async (formEl: FormInstance | undefined) => {
|
|
|
|
|
+ if (!formEl) return
|
|
|
|
|
+
|
|
|
|
|
+ formEl.validate(async valid => {
|
|
|
|
|
+ if (valid) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ ...menuForm,
|
|
|
|
|
+ T_code: menuDir.activeName
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let res: any
|
|
|
|
|
+ if (isNew.value) {
|
|
|
|
|
+ // 添加菜单时,如果有API,转换为JSON字符串
|
|
|
|
|
+ if (apiForm.apis && apiForm.apis.length > 0) {
|
|
|
|
|
+ const validApis = apiForm.apis.filter(
|
|
|
|
|
+ api => api.T_name && api.T_uri
|
|
|
|
|
+ )
|
|
|
|
|
+ if (validApis.length > 0) {
|
|
|
|
|
+ params.T_apis = JSON.stringify(validApis)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 添加菜单时,必须传递 T_mid
|
|
|
|
|
+ res = await Menu_Add(params)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 编辑菜单时,不传递 T_mid(除非需要修改父级)
|
|
|
|
|
+ params.id = currentMenu.value?.Id
|
|
|
|
|
+ // 如果父级ID没有变化,可以不传递 T_mid
|
|
|
|
|
+ if (params.T_mid === currentMenu.value?.T_mid) {
|
|
|
|
|
+ delete params.T_mid
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 编辑菜单时,只有API发生变化时才传递 T_apis
|
|
|
|
|
+ if (apiForm.apis && apiForm.apis.length > 0) {
|
|
|
|
|
+ const validApis = apiForm.apis.filter(
|
|
|
|
|
+ api => api.T_name && api.T_uri
|
|
|
|
|
+ )
|
|
|
|
|
+ // 检查API是否发生变化
|
|
|
|
|
+ const apisChanged = JSON.stringify(validApis.sort()) !== JSON.stringify(originalApis.value.sort())
|
|
|
|
|
+ if (apisChanged && validApis.length > 0) {
|
|
|
|
|
+ params.T_apis = JSON.stringify(validApis)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (originalApis.value.length > 0) {
|
|
|
|
|
+ // 如果原来有API,现在清空了,需要传递空数组来删除
|
|
|
|
|
+ params.T_apis = JSON.stringify([])
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res = await Menu_Edit(params)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (res.Code === 200) {
|
|
|
|
|
+ ElNotification.success({
|
|
|
|
|
+ title: isNew.value ? '添加菜单' : '编辑菜单',
|
|
|
|
|
+ message: '操作成功!',
|
|
|
|
|
+ position: 'bottom-right'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 检查用户权限,如果 T_menu === "*" 则刷新页面
|
|
|
|
|
+ const userInfo = globalStore.GET_User_Info
|
|
|
|
|
+ if (userInfo?.Power?.T_menu === '*') {
|
|
|
|
|
+ // 延迟刷新,确保通知显示
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ window.location.reload()
|
|
|
|
|
+ }, 500)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ drawerRef.value?.closeDrawer()
|
|
|
|
|
+ getMenuList(menuDir.activeName || '')
|
|
|
|
|
+ resetForm(ruleFormRef.value)
|
|
|
|
|
+ isNew.value = true
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('操作失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除菜单
|
|
|
|
|
+const deleteMenu = (menu: Menu) => {
|
|
|
|
|
+ ElMessageBox.confirm('您确定要删除该菜单吗?', '警告', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ })
|
|
|
|
|
+ .then(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res: any = await Menu_Del({
|
|
|
|
|
+ id: menu.Id,
|
|
|
|
|
+ T_code: menuDir.activeName
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res.Code === 200) {
|
|
|
|
|
+ ElMessage.success('删除成功!')
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ getMenuList(menuDir.activeName || '')
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('删除失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ ElMessage.info('已取消删除')
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置表单
|
|
|
|
|
+const resetForm = (formEl: FormInstance | undefined) => {
|
|
|
|
|
+ if (!formEl) return
|
|
|
|
|
+ formEl.resetFields()
|
|
|
|
|
+ Object.assign(menuForm, {
|
|
|
|
|
+ T_name: '',
|
|
|
|
|
+ T_mid: 0,
|
|
|
|
|
+ T_permission: '',
|
|
|
|
|
+ T_icon: '',
|
|
|
|
|
+ T_uri: '',
|
|
|
|
|
+ T_type: 'M',
|
|
|
|
|
+ T_sort: 0
|
|
|
|
|
+ })
|
|
|
|
|
+ apiForm.apis = []
|
|
|
|
|
+ originalApis.value = []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 抽屉关闭回调
|
|
|
|
|
+const callbackDrawer = (done: () => void) => {
|
|
|
|
|
+ resetForm(ruleFormRef.value)
|
|
|
|
|
+ done()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断是否为 Element Plus 图标
|
|
|
|
|
+const isElementIcon = (icon: string) => {
|
|
|
|
|
+ if (!icon) return false
|
|
|
|
|
+ return !/ue/g.test(icon)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 打开图标选择器
|
|
|
|
|
+const openIconPicker = () => {
|
|
|
|
|
+ // 展示时去掉 \ 前缀
|
|
|
|
|
+ selectedIcon.value = processIconForDisplay(menuForm.T_icon)
|
|
|
|
|
+ iconDialogVisible.value = true
|
|
|
|
|
+ iconSearchText.value = ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 选择图标
|
|
|
|
|
+const selectIcon = (icon: string) => {
|
|
|
|
|
+ selectedIcon.value = icon
|
|
|
|
|
+ // 如果是自定义图标(不是 Element Plus 图标,也不是 icon- 开头的),需要加上 \ 传递给后端
|
|
|
|
|
+ if (!isElementIcon(icon) && !icon.startsWith('icon-')) {
|
|
|
|
|
+ // 如果图标在 icons 对象中,需要加上 \ 前缀
|
|
|
|
|
+ if (icons[icon]) {
|
|
|
|
|
+ menuForm.T_icon = '\\' + icon
|
|
|
|
|
+ } else {
|
|
|
|
|
+ menuForm.T_icon = icon
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ menuForm.T_icon = icon
|
|
|
|
|
+ }
|
|
|
|
|
+ iconDialogVisible.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 处理图标值(去掉 \ 前缀用于展示)
|
|
|
|
|
+const processIconForDisplay = (icon: string): string => {
|
|
|
|
|
+ if (!icon) return ''
|
|
|
|
|
+ // 如果图标值以 \ 开头,去掉 \ 用于展示
|
|
|
|
|
+ if (icon.startsWith('\\') && icon.length > 1) {
|
|
|
|
|
+ return icon.substring(1)
|
|
|
|
|
+ }
|
|
|
|
|
+ return icon
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 渲染图标
|
|
|
|
|
+const renderIcon = (icon: string) => {
|
|
|
|
|
+ if (!icon) return ''
|
|
|
|
|
+ const displayIcon = processIconForDisplay(icon)
|
|
|
|
|
+
|
|
|
|
|
+ if (isElementIcon(displayIcon)) {
|
|
|
|
|
+ const IconComponent = (ElementPlusIconsVue as any)[displayIcon]
|
|
|
|
|
+ return IconComponent ? IconComponent : null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果是 \ 开头的,去掉 \ 后查找
|
|
|
|
|
+ return icons[displayIcon] || ''
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 初始化
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getSysList()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="menu">
|
|
|
|
|
+ <el-card>
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <span>菜单管理</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tabs
|
|
|
|
|
+ v-model="menuDir.activeName"
|
|
|
|
|
+ class="demo-tabs"
|
|
|
|
|
+ tab-position="left"
|
|
|
|
|
+ @tab-click="handleClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-tab-pane
|
|
|
|
|
+ v-for="item in menuDir.MenuData"
|
|
|
|
|
+ :key="item.T_sys"
|
|
|
|
|
+ :label="item.T_name"
|
|
|
|
|
+ :name="item.T_sys"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="menu-table-container">
|
|
|
|
|
+ <div class="table-toolbar">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :icon="Plus"
|
|
|
|
|
+ @click="openAddDrawer()"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加菜单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :icon="menuDir.isExpanded ? ArrowUp : ArrowDown"
|
|
|
|
|
+ @click="toggleExpandAll"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ menuDir.isExpanded ? '一键折叠' : '一键展开' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ ref="tableRef"
|
|
|
|
|
+ :key="`table-${tableKey}`"
|
|
|
|
|
+ :data="menuDir.menuTree"
|
|
|
|
|
+ row-key="Id"
|
|
|
|
|
+ :tree-props="{ children: 'Children' }"
|
|
|
|
|
+ :default-expand-all="menuDir.isExpanded"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column prop="T_name" label="菜单名称" width="200" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="menu-name-cell">
|
|
|
|
|
+ <span class="menu-icon">
|
|
|
|
|
+ <el-icon v-if="row.T_icon && isElementIcon(processIconForDisplay(row.T_icon))" style="font-size: 16px; margin-right: 8px;">
|
|
|
|
|
+ <component :is="renderIcon(row.T_icon)" />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <i
|
|
|
|
|
+ v-else-if="row.T_icon && processIconForDisplay(row.T_icon).startsWith('icon-')"
|
|
|
|
|
+ :class="['iconfont', processIconForDisplay(row.T_icon)]"
|
|
|
|
|
+ style="font-size: 16px; margin-right: 8px;"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ v-else-if="row.T_icon"
|
|
|
|
|
+ class="iconfont"
|
|
|
|
|
+ style="font-size: 16px; margin-right: 8px;"
|
|
|
|
|
+ >{{ renderIcon(row.T_icon) }}</i>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span>{{ row.T_name }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="T_permission" label="权限" width="200" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span>{{ row.T_permission || '-' }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="T_type" label="类型" width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag v-if="row.T_type === 'M'" type="primary" size="small">菜单</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="row.T_type === 'B'" type="warning" size="small">按钮</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="T_sort" label="排序" width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span>{{ row.T_sort }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="T_uri" label="路由路径" min-width="200" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span>{{ row.T_uri || '-' }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="操作" width="250" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :icon="Plus"
|
|
|
|
|
+ @click="openAddDrawer(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加子菜单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :icon="Edit"
|
|
|
|
|
+ @click="openEditDrawer(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :icon="Delete"
|
|
|
|
|
+ @click="deleteMenu(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 删除
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ </el-tabs>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 添加/编辑菜单抽屉 -->
|
|
|
|
|
+ <Drawer ref="drawerRef" :handleClose="callbackDrawer" size="50%">
|
|
|
|
|
+ <template #header="{ params }">
|
|
|
|
|
+ <h4 :id="params.titleId" :class="params.titleClass">
|
|
|
|
|
+ {{ isNew ? '添加' : '编辑' }} - 菜单
|
|
|
|
|
+ </h4>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="ruleFormRef"
|
|
|
|
|
+ :model="menuForm"
|
|
|
|
|
+ :rules="menuRules"
|
|
|
|
|
+ label-position="right"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item label="菜单名称:" :label-width="formLabelWidth" prop="T_name">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="menuForm.T_name"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ placeholder="请输入菜单名称"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="父级菜单:" :label-width="formLabelWidth">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ :value="currentParentId === 0 ? '顶级菜单' : `父级ID: ${currentParentId}`"
|
|
|
|
|
+ disabled
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="权限标识:" :label-width="formLabelWidth">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="menuForm.T_permission"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ placeholder="请输入权限标识"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="图标:" :label-width="formLabelWidth">
|
|
|
|
|
+ <div class="icon-selector">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="menuForm.T_icon"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ placeholder="请选择图标"
|
|
|
|
|
+ readonly
|
|
|
|
|
+ style="width: 200px; margin-right: 10px;"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <el-icon v-if="menuForm.T_icon && isElementIcon(processIconForDisplay(menuForm.T_icon))" style="font-size: 16px;">
|
|
|
|
|
+ <component :is="renderIcon(menuForm.T_icon)" />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <i
|
|
|
|
|
+ v-else-if="menuForm.T_icon && processIconForDisplay(menuForm.T_icon).startsWith('icon-')"
|
|
|
|
|
+ :class="['iconfont', processIconForDisplay(menuForm.T_icon)]"
|
|
|
|
|
+ style="font-size: 16px;"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ v-else-if="menuForm.T_icon"
|
|
|
|
|
+ class="iconfont"
|
|
|
|
|
+ style="font-size: 16px;"
|
|
|
|
|
+ >{{ renderIcon(menuForm.T_icon) }}</i>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ <el-button type="primary" @click="openIconPicker">选择图标</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="路由路径:" :label-width="formLabelWidth">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="menuForm.T_uri"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ placeholder="请输入路由路径"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="菜单类型:" :label-width="formLabelWidth">
|
|
|
|
|
+ <el-radio-group v-model="menuForm.T_type">
|
|
|
|
|
+ <el-radio label="M">菜单</el-radio>
|
|
|
|
|
+ <el-radio label="B">按钮</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="排序值:" :label-width="formLabelWidth">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="menuForm.T_sort"
|
|
|
|
|
+ :min="-999"
|
|
|
|
|
+ :max="999"
|
|
|
|
|
+ placeholder="排序值,越小越靠前"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="关联API:" :label-width="formLabelWidth">
|
|
|
|
|
+ <div class="api-list">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(api, index) in apiForm.apis"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="api-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="api.T_name"
|
|
|
|
|
+ placeholder="API名称"
|
|
|
|
|
+ style="width: 30%; margin-right: 10px"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="api.T_uri"
|
|
|
|
|
+ placeholder="API路径"
|
|
|
|
|
+ style="width: 40%; margin-right: 10px"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="api.T_method"
|
|
|
|
|
+ placeholder="请求方式"
|
|
|
|
|
+ style="width: 20%; margin-right: 10px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option label="GET" value="GET" />
|
|
|
|
|
+ <el-option label="POST" value="POST" />
|
|
|
|
|
+ <el-option label="PUT" value="PUT" />
|
|
|
|
|
+ <el-option label="DELETE" value="DELETE" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ :icon="Delete"
|
|
|
|
|
+ circle
|
|
|
|
|
+ @click="removeApiRow(index)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :icon="Plus"
|
|
|
|
|
+ @click="addApiRow"
|
|
|
|
|
+ style="margin-top: 10px"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加API
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item :label-width="formLabelWidth">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="isNew"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="submitMenu(ruleFormRef)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-else
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="submitMenu(ruleFormRef)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 修改
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </Drawer>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图标选择器对话框 -->
|
|
|
|
|
+ <el-dialog v-model="iconDialogVisible" title="选择图标" width="70%">
|
|
|
|
|
+ <div class="icon-picker">
|
|
|
|
|
+ <div class="icon-picker-header">
|
|
|
|
|
+ <el-radio-group v-model="iconType">
|
|
|
|
|
+ <el-radio label="element">Element Plus 图标</el-radio>
|
|
|
|
|
+ <el-radio label="custom">自定义图标</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="iconSearchText"
|
|
|
|
|
+ placeholder="搜索图标"
|
|
|
|
|
+ style="width: 200px; margin-left: 20px;"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="icon-list">
|
|
|
|
|
+ <!-- Element Plus 图标 -->
|
|
|
|
|
+ <template v-if="iconType === 'element'">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="icon in elementIcons"
|
|
|
|
|
+ :key="icon"
|
|
|
|
|
+ class="icon-item"
|
|
|
|
|
+ :class="{ active: selectedIcon === icon }"
|
|
|
|
|
+ @click="selectIcon(icon)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon style="font-size: 24px;">
|
|
|
|
|
+ <component :is="(ElementPlusIconsVue as any)[icon]" />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <div class="icon-name">{{ icon }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自定义图标 -->
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="icon in customIcons"
|
|
|
|
|
+ :key="icon"
|
|
|
|
|
+ class="icon-item"
|
|
|
|
|
+ :class="{ active: selectedIcon === icon }"
|
|
|
|
|
+ @click="selectIcon(icon)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="iconfont" style="font-size: 24px;">{{ icons[icon] }}</i>
|
|
|
|
|
+ <div class="icon-name">{{ icon }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+@import './index.scss';
|
|
|
|
|
+@import '@/styles/var.scss';
|
|
|
|
|
+
|
|
|
|
|
+.menu {
|
|
|
|
|
+ @include f-direction;
|
|
|
|
|
+
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .menu-table-container {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ min-height: 400px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .table-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table__body-wrapper) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table__header-wrapper) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保树形表格的展开/折叠按钮在左边
|
|
|
|
|
+ :deep(.el-table__expand-icon) {
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保菜单名称单元格内容正确对齐
|
|
|
|
|
+ :deep(.el-table__cell) {
|
|
|
|
|
+ .cell {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .menu-name-cell {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+
|
|
|
|
|
+ .menu-icon {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .icon-selector {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .icon-picker {
|
|
|
|
|
+ .icon-picker-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .icon-list {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ max-height: 500px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+
|
|
|
|
|
+ .icon-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .icon-name {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .api-list {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ .api-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|