jgy-public-component 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.cjs","sources":["../src/components/JButton/JButton.vue","../src/components/JUploadImg/JUploadImg.vue","../src/components/JIconRender/JIconRender.vue","../src/components/JSearchHeader/JSearchHeader.vue","../src/components/JImportExcel/JImportExcel.vue","../src/components/JSidebar/JSideMenuItem.vue","../src/components/JSidebar/JSidebar.vue","../src/components/JAdminLayout/JAdminLayout.vue","../src/utils/transform.ts","../src/router/helpers.ts","../src/router/guards.ts","../src/index.ts"],"sourcesContent":["<template>\r\n <button\r\n class=\"j-button\"\r\n :class=\"[\r\n `j-button--${type}`,\r\n `j-button--${size}`,\r\n {\r\n 'j-button--plain': plain,\r\n 'j-button--round': round,\r\n 'j-button--disabled': disabled,\r\n 'j-button--loading': loading,\r\n },\r\n ]\"\r\n :disabled=\"disabled || loading\"\r\n @click=\"handleClick\"\r\n >\r\n <span v-if=\"loading\" class=\"j-button__loading-icon\" />\r\n <span v-if=\"$slots.default\" class=\"j-button__text\">\r\n <slot />\r\n </span>\r\n </button>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { ButtonType, ButtonSize } from './types'\r\n\r\ninterface Props {\r\n type?: ButtonType\r\n size?: ButtonSize\r\n plain?: boolean\r\n round?: boolean\r\n disabled?: boolean\r\n loading?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n type: 'default',\r\n size: 'medium',\r\n plain: false,\r\n round: false,\r\n disabled: false,\r\n loading: false,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n click: [event: MouseEvent]\r\n}>()\r\n\r\nfunction handleClick(event: MouseEvent) {\r\n if (!props.disabled && !props.loading) {\r\n emit('click', event)\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.j-button {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: $spacing-xs;\r\n padding: 8px 16px;\r\n font-size: $font-size-base;\r\n font-weight: 400;\r\n line-height: 1;\r\n white-space: nowrap;\r\n cursor: pointer;\r\n border: 1px solid $color-border;\r\n border-radius: $border-radius-base;\r\n background-color: $color-white;\r\n color: $color-text-regular;\r\n transition: $transition-fast;\r\n outline: none;\r\n user-select: none;\r\n\r\n &:hover {\r\n color: $color-primary;\r\n border-color: $color-primary;\r\n background-color: color.adjust($color-primary, $lightness: 40%);\r\n }\r\n\r\n // 尺寸\r\n &--small {\r\n padding: 5px 12px;\r\n font-size: $font-size-xs;\r\n }\r\n\r\n &--medium {\r\n padding: 8px 16px;\r\n font-size: $font-size-base;\r\n }\r\n\r\n &--large {\r\n padding: 12px 20px;\r\n font-size: $font-size-md;\r\n }\r\n\r\n // 类型\r\n &--primary {\r\n background-color: $color-primary;\r\n border-color: $color-primary;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-primary, $lightness: 10%);\r\n border-color: color.adjust($color-primary, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--success {\r\n background-color: $color-success;\r\n border-color: $color-success;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-success, $lightness: 10%);\r\n border-color: color.adjust($color-success, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--danger {\r\n background-color: $color-danger;\r\n border-color: $color-danger;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-danger, $lightness: 10%);\r\n border-color: color.adjust($color-danger, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--warning {\r\n background-color: $color-warning;\r\n border-color: $color-warning;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-warning, $lightness: 10%);\r\n border-color: color.adjust($color-warning, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n // 朴素按钮\r\n &--plain {\r\n &.j-button--primary {\r\n background-color: color.adjust($color-primary, $lightness: 40%);\r\n color: $color-primary;\r\n &:hover {\r\n background-color: $color-primary;\r\n color: $color-white;\r\n }\r\n }\r\n }\r\n\r\n // 圆角\r\n &--round {\r\n border-radius: $border-radius-round;\r\n }\r\n\r\n // 禁用\r\n &--disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n pointer-events: none;\r\n }\r\n\r\n // 加载\r\n &--loading {\r\n opacity: 0.7;\r\n cursor: not-allowed;\r\n pointer-events: none;\r\n }\r\n\r\n &__loading-icon {\r\n display: inline-block;\r\n width: 14px;\r\n height: 14px;\r\n border: 2px solid currentColor;\r\n border-top-color: transparent;\r\n border-radius: 50%;\r\n animation: j-spin 0.6s linear infinite;\r\n }\r\n\r\n @keyframes j-spin {\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"j-upload-img-container\">\r\n <!-- 多图模式 -->\r\n <template v-if=\"multiple\">\r\n <div class=\"images-wrapper\">\r\n <draggable\r\n v-model=\"imageList\"\r\n class=\"images-list\"\r\n item-key=\"uid\"\r\n :animation=\"200\"\r\n :disabled=\"disabled\"\r\n @end=\"handleDragEnd\"\r\n >\r\n <template #item=\"{ element, index }\">\r\n <div class=\"image-item\">\r\n <el-image\r\n :src=\"element.url\"\r\n :preview-src-list=\"imageList.map(img => img.url)\"\r\n :initial-index=\"index\"\r\n fit=\"cover\"\r\n class=\"image-preview\"\r\n :preview-teleported=\"true\"\r\n :hide-on-click-modal=\"true\"\r\n />\r\n <div v-if=\"!disabled\" class=\"delete-btn\" @click.stop=\"handleRemove(index)\">\r\n <el-icon class=\"icon-delete\">\r\n <Delete />\r\n </el-icon>\r\n </div>\r\n </div>\r\n </template>\r\n </draggable>\r\n\r\n <!-- 多图上传按钮 -->\r\n <el-upload\r\n v-if=\"!disabled && (!limit || imageList.length < limit)\"\r\n class=\"image-uploader\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n :disabled=\"uploading\"\r\n list-type=\"picture-card\"\r\n :accept=\"accept\"\r\n >\r\n <el-icon v-if=\"!uploading\" class=\"uploader-icon\">\r\n <Plus />\r\n </el-icon>\r\n <el-icon v-else class=\"uploader-icon is-loading\">\r\n <Loading />\r\n </el-icon>\r\n </el-upload>\r\n </div>\r\n </template>\r\n\r\n <!-- 单图模式 -->\r\n <template v-else>\r\n <!-- 已有图片:独立展示,hover 显示操作遮罩 -->\r\n <div v-if=\"imageUrl\" class=\"image-item single-image-item\">\r\n <el-image\r\n :src=\"imageUrl\"\r\n fit=\"cover\"\r\n class=\"image-preview\"\r\n />\r\n <div class=\"image-actions\">\r\n <el-icon class=\"action-icon\" @click.stop=\"handlePreviewSingle\">\r\n <ZoomIn />\r\n </el-icon>\r\n <el-icon v-if=\"!disabled\" class=\"action-icon\" @click.stop=\"handleRemoveSingle\">\r\n <Delete />\r\n </el-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- 无图片:显示上传按钮 -->\r\n <el-upload\r\n v-else\r\n class=\"image-uploader\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n :disabled=\"disabled || uploading\"\r\n :accept=\"accept\"\r\n list-type=\"picture-card\"\r\n >\r\n <el-icon class=\"uploader-icon\">\r\n <Plus v-if=\"!uploading\" />\r\n <Loading v-else class=\"is-loading\" />\r\n </el-icon>\r\n </el-upload>\r\n\r\n <!-- 大图预览 -->\r\n <el-image-viewer\r\n v-if=\"showPreview\"\r\n :url-list=\"[imageUrl]\"\r\n :teleported=\"true\"\r\n @close=\"showPreview = false\"\r\n />\r\n </template>\r\n\r\n <div v-if=\"showTipText\" class=\"upload-tip\">\r\n <span>{{ tip }}</span>\r\n <span v-if=\"multiple && limit\"> (最多 {{ limit }} 张)</span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch } from 'vue'\r\nimport { ElMessage } from 'element-plus'\r\nimport type { UploadFile } from 'element-plus'\r\nimport { Plus, Loading, Delete, ZoomIn } from '@element-plus/icons-vue'\r\nimport draggable from 'vuedraggable'\r\nimport type { ImageItem } from './types'\r\n\r\ndefineOptions({\r\n name: 'JUploadImg'\r\n})\r\n\r\nconst props = withDefaults(defineProps<{\r\n modelValue?: string | string[]\r\n disabled?: boolean\r\n multiple?: boolean\r\n limit?: number\r\n maxSize?: number\r\n accept?: string\r\n tip?: string\r\n showTip?: boolean\r\n /** 自定义上传函数,接收 File 返回图片 URL */\r\n uploadFn?: (file: File) => Promise<string>\r\n}>(), {\r\n modelValue: '',\r\n disabled: false,\r\n multiple: false,\r\n limit: 9,\r\n maxSize: 2,\r\n accept: 'image/jpeg,image/png,image/jpg,image/gif',\r\n tip: '只能上传jpg/png/gif格式的图片,且不超过2MB',\r\n showTip: true,\r\n uploadFn: undefined\r\n})\r\n\r\nconst emit = defineEmits<{\r\n (e: 'update:modelValue', value: string | string[]): void\r\n (e: 'success', url: string): void\r\n (e: 'error', error: any): void\r\n}>()\r\n\r\n// 单图模式\r\nconst imageUrl = ref<string>('')\r\nconst showPreview = ref(false)\r\nconst showTipText = props.showTip\r\n\r\nconst handlePreviewSingle = () => {\r\n showPreview.value = true\r\n}\r\n\r\nconst handleRemoveSingle = () => {\r\n imageUrl.value = ''\r\n emit('update:modelValue', '')\r\n ElMessage.success('删除成功')\r\n}\r\n\r\n// 多图模式\r\nconst imageList = ref<ImageItem[]>([])\r\nconst uploading = ref(false)\r\n\r\n// 初始化图片列表(多图模式)\r\nconst initImageList = (urls: string[]) => {\r\n imageList.value = urls.map((url, index) => ({\r\n uid: Date.now() + index,\r\n url\r\n }))\r\n}\r\n\r\n// 监听外部值变化\r\nwatch(() => props.modelValue, (val) => {\r\n if (props.multiple) {\r\n if (Array.isArray(val) && val.length > 0) {\r\n initImageList(val)\r\n } else {\r\n imageList.value = []\r\n }\r\n } else {\r\n if (typeof val === 'string') {\r\n imageUrl.value = val\r\n } else {\r\n imageUrl.value = ''\r\n }\r\n }\r\n}, { immediate: true })\r\n\r\n// 拖拽结束(多图模式)\r\nconst handleDragEnd = () => {\r\n emitMultipleUpdate()\r\n}\r\n\r\n// 删除图片(多图模式)\r\nconst handleRemove = (index: number) => {\r\n imageList.value.splice(index, 1)\r\n emitMultipleUpdate()\r\n ElMessage.success('删除成功')\r\n}\r\n\r\n// 文件改变时触发\r\nconst handleFileChange = async (uploadFile: UploadFile) => {\r\n const file = uploadFile.raw as File\r\n\r\n if (!file) {\r\n ElMessage.error('文件不存在,请重试')\r\n return\r\n }\r\n\r\n if (file.size > props.maxSize * 1024 * 1024) {\r\n ElMessage.error(`图片大小不能超过 ${props.maxSize}MB`)\r\n return\r\n }\r\n\r\n if (!props.uploadFn) {\r\n ElMessage.error('未配置上传函数 (uploadFn)')\r\n return\r\n }\r\n\r\n try {\r\n uploading.value = true\r\n const url = await props.uploadFn(file)\r\n\r\n if (props.multiple) {\r\n imageList.value.push({ uid: Date.now(), url })\r\n emitMultipleUpdate()\r\n } else {\r\n imageUrl.value = url\r\n emit('update:modelValue', url)\r\n }\r\n\r\n emit('success', url)\r\n ElMessage.success('上传成功')\r\n } catch (error) {\r\n console.error('上传出错:', error)\r\n ElMessage.error('上传失败,请重试')\r\n emit('error', error)\r\n } finally {\r\n uploading.value = false\r\n }\r\n}\r\n\r\n// 触发多图更新\r\nconst emitMultipleUpdate = () => {\r\n const urls = imageList.value.map(item => item.url)\r\n emit('update:modelValue', urls)\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-upload-img-container {\r\n display: inline-block;\r\n}\r\n\r\n/* 多图模式样式 */\r\n.images-wrapper {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n align-items: flex-start;\r\n}\r\n\r\n.images-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n}\r\n\r\n.image-item {\r\n position: relative;\r\n width: 74px;\r\n height: 74px;\r\n border: 1px solid var(--el-border-color);\r\n border-radius: 6px;\r\n overflow: hidden;\r\n cursor: move;\r\n}\r\n\r\n.image-preview {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: 2px;\r\n right: 2px;\r\n width: 20px;\r\n height: 20px;\r\n background-color: rgba(0, 0, 0, 0.6);\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n opacity: 0;\r\n transition: opacity 0.3s;\r\n cursor: pointer;\r\n z-index: 1;\r\n}\r\n\r\n.image-item:hover .delete-btn {\r\n opacity: 1;\r\n}\r\n\r\n.delete-btn:hover {\r\n background-color: rgba(0, 0, 0, 0.8);\r\n}\r\n\r\n.icon-delete {\r\n font-size: 12px;\r\n color: #fff;\r\n}\r\n\r\n/* 单图和多图共用样式 */\r\n.image-uploader {\r\n display: inline-block;\r\n vertical-align: top;\r\n}\r\n\r\n.image-uploader :deep(.el-upload) {\r\n border: 1px dashed var(--el-border-color);\r\n border-radius: 6px;\r\n cursor: pointer;\r\n position: relative;\r\n overflow: hidden;\r\n transition: var(--el-transition-duration-fast);\r\n width: 74px !important;\r\n height: 74px !important;\r\n}\r\n\r\n.image-uploader :deep(.el-upload:hover) {\r\n border-color: var(--el-color-primary);\r\n}\r\n\r\n.uploader-icon {\r\n font-size: 20px;\r\n color: #8c939d;\r\n width: 74px;\r\n height: 74px;\r\n text-align: center;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.single-image-item {\r\n cursor: default;\r\n}\r\n\r\n.image-actions {\r\n position: absolute;\r\n inset: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n opacity: 0;\r\n transition: opacity 0.3s;\r\n}\r\n\r\n.single-image-item:hover .image-actions,\r\n.image-item:hover .image-actions {\r\n opacity: 1;\r\n}\r\n\r\n.action-icon {\r\n font-size: 18px;\r\n color: #fff;\r\n cursor: pointer;\r\n transition: transform 0.2s;\r\n}\r\n\r\n.action-icon:hover {\r\n transform: scale(1.2);\r\n}\r\n\r\n.upload-tip {\r\n margin-top: 8px;\r\n font-size: 12px;\r\n color: #909399;\r\n line-height: 1.5;\r\n}\r\n</style>\r\n","<template>\r\n <Icon v-if=\"icon\" :icon=\"icon\" :width=\"size\" :height=\"size\" v-bind=\"$attrs\" />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { Icon } from '@iconify/vue'\r\n\r\ndefineOptions({\r\n name: 'JIconRender'\r\n})\r\n\r\nwithDefaults(defineProps<{\r\n icon: string\r\n size?: string | number\r\n}>(), {\r\n size: 24,\r\n})\r\n</script>\r\n","<template>\r\n <el-card shadow=\"never\" class=\"j-search-header-card\">\r\n <el-form inline class=\"search-form\">\r\n <div\r\n ref=\"formItemsRef\"\r\n class=\"form-items-wrapper\"\r\n :class=\"{ 'is-animated': isAnimated }\"\r\n >\r\n <slot />\r\n </div>\r\n </el-form>\r\n\r\n <div class=\"action-bar\" :style=\"{ marginTop: expanded ? '0px' : '10px' }\">\r\n <div class=\"action-left\">\r\n <el-button v-if=\"showAdd\" type=\"primary\" :icon=\"Plus\" @click=\"emit('add')\">\r\n {{ addText }}\r\n </el-button>\r\n <el-button\r\n v-if=\"showBatchDelete\"\r\n type=\"danger\"\r\n :icon=\"Delete\"\r\n :disabled=\"selectedCount === 0\"\r\n @click=\"emit('batchDelete')\"\r\n >\r\n 批量删除{{ selectedCount ? `(${selectedCount})` : '' }}\r\n </el-button>\r\n <el-button v-if=\"showSearch\" type=\"primary\" :icon=\"Search\" @click=\"emit('search')\">搜索</el-button>\r\n <el-button v-if=\"showReset\" :icon=\"Refresh\" @click=\"emit('reset')\">重置</el-button>\r\n <slot name=\"actions\" />\r\n </div>\r\n <el-button\r\n v-if=\"shouldCollapse\"\r\n link\r\n type=\"primary\"\r\n @click=\"toggleExpand\"\r\n >\r\n {{ expanded ? '收起' : '展开' }}\r\n <el-icon class=\"toggle-icon\" :class=\"{ 'is-expanded': expanded }\">\r\n <ArrowDown />\r\n </el-icon>\r\n </el-button>\r\n </div>\r\n </el-card>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from 'vue'\r\nimport { Search, Refresh, Plus, Delete, ArrowDown } from '@element-plus/icons-vue'\r\n\r\ndefineOptions({\r\n name: 'JSearchHeader'\r\n})\r\n\r\nconst props = withDefaults(defineProps<{\r\n selectedCount?: number\r\n addText?: string\r\n defaultVisible?: number\r\n showAdd?: boolean\r\n showBatchDelete?: boolean\r\n showSearch?: boolean\r\n showReset?: boolean\r\n}>(), {\r\n selectedCount: 0,\r\n addText: '新增',\r\n defaultVisible: 5,\r\n showAdd: true,\r\n showBatchDelete: true,\r\n showSearch: true,\r\n showReset: true\r\n})\r\n\r\nconst emit = defineEmits<{\r\n search: []\r\n reset: []\r\n add: []\r\n batchDelete: []\r\n}>()\r\n\r\nconst formItemsRef = ref<HTMLElement>()\r\nconst expanded = ref(false)\r\nconst isAnimated = ref(false)\r\nconst itemCount = ref(0)\r\nconst collapsedHeight = ref(0)\r\n\r\nconst shouldCollapse = computed(() => itemCount.value > props.defaultVisible)\r\n\r\nconst measureItems = () => {\r\n if (!formItemsRef.value) return\r\n const items = Array.from(formItemsRef.value.children) as HTMLElement[]\r\n itemCount.value = items.length\r\n\r\n const lastVisibleItem = items[props.defaultVisible - 1]\r\n if (lastVisibleItem && formItemsRef.value) {\r\n const wrapperRect = formItemsRef.value.getBoundingClientRect()\r\n const lastVisibleRect = lastVisibleItem.getBoundingClientRect()\r\n collapsedHeight.value = lastVisibleRect.bottom - wrapperRect.top\r\n }\r\n}\r\n\r\nconst toggleExpand = () => {\r\n const el = formItemsRef.value\r\n if (!el) return\r\n\r\n if (!expanded.value) {\r\n expanded.value = true\r\n el.style.maxHeight = el.scrollHeight + 'px'\r\n const onEnd = () => {\r\n el.style.maxHeight = 'none'\r\n el.removeEventListener('transitionend', onEnd)\r\n }\r\n el.addEventListener('transitionend', onEnd)\r\n } else {\r\n measureItems()\r\n expanded.value = false\r\n el.style.maxHeight = el.scrollHeight + 'px'\r\n el.offsetHeight\r\n el.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n}\r\n\r\nlet observer: MutationObserver | null = null\r\n\r\nonMounted(() => {\r\n measureItems()\r\n if (shouldCollapse.value && formItemsRef.value) {\r\n formItemsRef.value.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n isAnimated.value = true\r\n })\r\n })\r\n if (formItemsRef.value) {\r\n observer = new MutationObserver(() => {\r\n measureItems()\r\n if (shouldCollapse.value && !expanded.value && formItemsRef.value) {\r\n formItemsRef.value.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n })\r\n observer.observe(formItemsRef.value, { childList: true })\r\n }\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n observer?.disconnect()\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.j-search-header-card :deep(.el-card__body) {\r\n padding: 16px 20px 12px;\r\n}\r\n\r\n.search-form {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: 0;\r\n}\r\n\r\n.form-items-wrapper {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n overflow: hidden;\r\n}\r\n\r\n.form-items-wrapper.is-animated {\r\n transition: max-height 0.3s ease-in-out;\r\n}\r\n\r\n.form-items-wrapper :deep(.el-form-item) {\r\n margin-bottom: 10px;\r\n margin-right: 16px;\r\n}\r\n\r\n.action-bar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding-top: 10px;\r\n border-top: 1px solid #ebeef5;\r\n}\r\n\r\n.action-left {\r\n display: flex;\r\n gap: 8px;\r\n}\r\n\r\n.toggle-icon {\r\n margin-left: 2px;\r\n transition: transform 0.25s;\r\n}\r\n\r\n.toggle-icon.is-expanded {\r\n transform: rotate(180deg);\r\n}\r\n</style>\r\n","<template>\r\n <el-dialog\r\n v-model=\"visible\"\r\n :title=\"props.title\"\r\n width=\"900px\"\r\n :close-on-click-modal=\"false\"\r\n @open=\"handleOpen\"\r\n @close=\"handleClose\"\r\n >\r\n <!-- 上传区域 -->\r\n <el-card shadow=\"never\" class=\"j-import-upload-card\">\r\n <div class=\"j-import-upload-card-title\">导入 EXCEL</div>\r\n <el-divider style=\"margin: 10px 0\" />\r\n <div class=\"j-import-upload-row\">\r\n <el-upload\r\n accept=\".xlsx,.xls\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n >\r\n <el-button size=\"small\">点击上传</el-button>\r\n </el-upload>\r\n <el-link\r\n v-if=\"dataColumns.length\"\r\n type=\"primary\"\r\n class=\"j-import-no-wrap\"\r\n @click=\"handleDownloadTemplate\"\r\n >\r\n 下载导入模板.xlsx\r\n </el-link>\r\n </div>\r\n </el-card>\r\n\r\n <!-- 统计信息 + 确认导入 -->\r\n <el-row class=\"j-import-stats-row\" :gutter=\"20\" align=\"middle\">\r\n <el-col :span=\"18\">\r\n <el-row class=\"j-import-stats-cols\">\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.total }}</div>\r\n <div class=\"j-import-stat-label\">导入数量</div>\r\n </el-col>\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.fail }}</div>\r\n <div class=\"j-import-stat-label\">失败数量</div>\r\n </el-col>\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.success }}</div>\r\n <div class=\"j-import-stat-label\">成功数量</div>\r\n </el-col>\r\n </el-row>\r\n </el-col>\r\n <el-col :span=\"6\" class=\"j-import-confirm-col\">\r\n <el-button\r\n type=\"primary\"\r\n size=\"small\"\r\n :loading=\"submitting\"\r\n :disabled=\"!renderData.length\"\r\n @click=\"handleConfirm\"\r\n >\r\n 确认导入\r\n </el-button>\r\n </el-col>\r\n </el-row>\r\n\r\n <!-- 数据表格 -->\r\n <el-table :data=\"renderData\" border size=\"small\" max-height=\"360\" class=\"j-import-data-table\">\r\n <el-table-column\r\n v-for=\"col in dataColumns\"\r\n :key=\"col.prop\"\r\n :prop=\"col.prop\"\r\n :label=\"col.label\"\r\n :min-width=\"120\"\r\n show-overflow-tooltip\r\n />\r\n <el-table-column label=\"状态\" width=\"110\" align=\"center\" fixed=\"right\">\r\n <template #default=\"{ row }\">\r\n <span v-if=\"row._status === -1\" class=\"j-import-status-fail\">\r\n {{ row._error || '创建失败' }}\r\n </span>\r\n <span v-else>{{ statusMap[row._status] }}</span>\r\n </template>\r\n </el-table-column>\r\n <el-table-column label=\"操作\" width=\"80\" align=\"center\" fixed=\"right\">\r\n <template #default=\"{ $index, row }\">\r\n <el-button\r\n v-if=\"row._status === 0\"\r\n size=\"small\"\r\n @click=\"handleDel($index)\"\r\n >\r\n 删除\r\n </el-button>\r\n </template>\r\n </el-table-column>\r\n </el-table>\r\n\r\n <template #footer>\r\n <el-button @click=\"handleClose\">关闭</el-button>\r\n </template>\r\n </el-dialog>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from 'vue'\r\nimport { ElMessage } from 'element-plus'\r\nimport type { UploadFile } from 'element-plus'\r\nimport * as XLSX from 'xlsx'\r\nimport type { JImportExcelProps } from './types'\r\n\r\ndefineOptions({ name: 'JImportExcel' })\r\n\r\nconst props = withDefaults(defineProps<JImportExcelProps>(), {\r\n excludeFields: () => ['id', 'ctime', 'mtime'],\r\n title: 'Excel 批量导入',\r\n})\r\n\r\nconst emit = defineEmits<{\r\n success: []\r\n}>()\r\n\r\nconst visible = defineModel<boolean>({ default: false })\r\nconst submitting = ref(false)\r\nconst fieldMap = ref<Record<string, string>>({})\r\nconst renderData = ref<Record<string, any>[]>([])\r\nconst stats = ref({ total: 0, success: 0, fail: 0 })\r\n\r\nconst statusMap: Record<number, string> = {\r\n 0: '待创建',\r\n 1: '创建成功',\r\n 2: '创建中',\r\n [-1]: '创建失败',\r\n}\r\n\r\n/** 过滤后的表头列 */\r\nconst dataColumns = computed(() => {\r\n return Object.entries(fieldMap.value)\r\n .filter(([key]) => !props.excludeFields.includes(key))\r\n .map(([key, label]) => ({ id: key, label, prop: key }))\r\n})\r\n\r\n/** 弹窗打开时获取字段列表 */\r\nconst handleOpen = async () => {\r\n try {\r\n const res = await props.getFieldListApi()\r\n fieldMap.value = res.data.tableFieldsInfo ?? {}\r\n } catch {\r\n ElMessage.error('获取字段列表失败')\r\n }\r\n}\r\n\r\n/** 文件上传解析 */\r\nconst handleFileChange = (uploadFile: UploadFile) => {\r\n const raw = uploadFile.raw\r\n if (!raw) return\r\n\r\n const validTypes = [\r\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\r\n 'application/vnd.ms-excel',\r\n ]\r\n if (!validTypes.includes(raw.type) && !raw.name.match(/\\.xlsx?$/i)) {\r\n ElMessage.warning('请上传 .xlsx 或 .xls 格式文件')\r\n return\r\n }\r\n\r\n const reader = new FileReader()\r\n reader.onload = (e) => {\r\n try {\r\n const workbook = XLSX.read(e.target!.result, { type: 'binary' })\r\n const wsName = workbook.SheetNames[0] as string\r\n const ws = workbook.Sheets[wsName]!\r\n const rows = XLSX.utils.sheet_to_json(ws, {\r\n header: 1,\r\n defval: '',\r\n }) as any[][]\r\n\r\n const dataRows = rows.slice(1).filter((row) => row.some((cell) => cell !== ''))\r\n\r\n if (!dataRows.length) {\r\n ElMessage.warning('文件内容为空')\r\n return\r\n }\r\n\r\n // 按后端字段顺序一一映射,空单元格由 defval 补 '',避免后续列顶上来\r\n const list: Record<string, any>[] = dataRows.map((row) => {\r\n const item: Record<string, any> = { _status: 0, _error: '' }\r\n dataColumns.value.forEach((col, index) => {\r\n item[col.prop] = row[index] !== undefined ? row[index] : ''\r\n })\r\n return item\r\n })\r\n\r\n renderData.value = list\r\n stats.value = { total: list.length, success: 0, fail: 0 }\r\n } catch {\r\n ElMessage.error('解析文件失败,请检查文件格式')\r\n }\r\n }\r\n reader.readAsBinaryString(raw)\r\n}\r\n\r\n/** 删除行 */\r\nconst handleDel = (idx: number) => {\r\n renderData.value.splice(idx, 1)\r\n stats.value.total = renderData.value.length\r\n}\r\n\r\n/** 确认导入 */\r\nconst handleConfirm = async () => {\r\n const data = renderData.value\r\n .filter((row) => row._status === 0)\r\n .map(({ _status, _error, ...rest }) => rest)\r\n\r\n if (!data.length) {\r\n ElMessage.warning('没有可导入的数据')\r\n return\r\n }\r\n\r\n submitting.value = true\r\n try {\r\n const res = await props.importApi(data)\r\n if (res.code === 1) {\r\n // 全部成功\r\n renderData.value.forEach((row) => {\r\n if (row._status === 0) row._status = 1\r\n })\r\n stats.value.success = renderData.value.filter((r) => r._status === 1).length\r\n ElMessage.success('导入成功')\r\n emit('success')\r\n } else {\r\n ElMessage.error(res.msg || '导入失败')\r\n }\r\n } catch {\r\n ElMessage.error('导入失败,请重试')\r\n } finally {\r\n submitting.value = false\r\n }\r\n}\r\n\r\n/** 下载导入模板 */\r\nconst handleDownloadTemplate = () => {\r\n try {\r\n const wb = XLSX.utils.book_new()\r\n const headers = dataColumns.value.map((col) => col.label)\r\n const tr: Record<string, string> = {}\r\n headers.forEach((h) => (tr[h] = ''))\r\n const ws = XLSX.utils.json_to_sheet([tr])\r\n XLSX.utils.book_append_sheet(wb, ws, '模板')\r\n XLSX.writeFile(wb, '导入模板.xlsx')\r\n } catch {\r\n ElMessage.error('模板下载失败')\r\n }\r\n}\r\n\r\nconst handleClose = () => {\r\n if (!submitting.value) {\r\n visible.value = false\r\n renderData.value = []\r\n stats.value = { total: 0, success: 0, fail: 0 }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-import-upload-card {\r\n margin-bottom: 16px;\r\n}\r\n.j-import-upload-card-title {\r\n font-size: 14px;\r\n color: #606266;\r\n}\r\n.j-import-upload-row {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n.j-import-no-wrap {\r\n white-space: nowrap;\r\n}\r\n.j-import-stats-row {\r\n margin: 12px 0;\r\n}\r\n.j-import-stats-cols {\r\n text-align: center;\r\n line-height: 1.5;\r\n}\r\n.j-import-stat-item {\r\n padding: 0 8px;\r\n}\r\n.j-import-stat-num {\r\n font-size: 20px;\r\n}\r\n.j-import-stat-label {\r\n font-size: 12px;\r\n color: #909399;\r\n}\r\n.j-import-confirm-col {\r\n text-align: right;\r\n padding-top: 10px;\r\n}\r\n.j-import-data-table {\r\n margin-top: 10px;\r\n}\r\n.j-import-status-fail {\r\n color: #f56c6c;\r\n font-size: 12px;\r\n}\r\n</style>\r\n","<template>\r\n <!-- 有子菜单:渲染 el-sub-menu -->\r\n <el-sub-menu\r\n v-if=\"menuItem.children && menuItem.children.length > 0\"\r\n :index=\"menuItem.path\"\r\n :class=\"[`j-sidebar__depth-${depth}`]\"\r\n >\r\n <template #title>\r\n <JIconRender v-if=\"menuItem.icon && depth === 0\" :icon=\"menuItem.icon\" />\r\n <span class=\"j-sidebar__menu-item-title\">{{ menuItem.title || menuItem.name }}</span>\r\n </template>\r\n <!-- 递归渲染子菜单 -->\r\n <JSideMenuItem\r\n v-for=\"child in menuItem.children\"\r\n :key=\"child.path\"\r\n :menu-item=\"child\"\r\n :is-collapse=\"isCollapse\"\r\n :depth=\"depth + 1\"\r\n @menu-click=\"(item) => emit('menu-click', item)\"\r\n />\r\n </el-sub-menu>\r\n\r\n <!-- 无子菜单:渲染 el-menu-item -->\r\n <el-menu-item\r\n v-else\r\n :index=\"menuItem.path\"\r\n :class=\"[`j-sidebar__depth-${depth}`]\"\r\n @click=\"handleClick\"\r\n >\r\n <JIconRender v-if=\"menuItem.icon && depth === 0\" :icon=\"menuItem.icon\" />\r\n <template #title>\r\n <span>{{ menuItem.title || menuItem.name }}</span>\r\n </template>\r\n </el-menu-item>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { SidebarMenuItem } from './types'\r\nimport { JIconRender } from '../JIconRender'\r\n\r\ndefineOptions({\r\n name: 'JSideMenuItem'\r\n})\r\n\r\ninterface Props {\r\n menuItem: SidebarMenuItem\r\n isCollapse?: boolean\r\n depth?: number\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), { depth: 0 })\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem]\r\n}>()\r\n\r\nconst handleClick = () => {\r\n emit('menu-click', props.menuItem)\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-sidebar__menu-item-title {\r\n padding: 0 10px;\r\n}\r\n\r\n/* 二级菜单:灰色标题,作为分类标签 */\r\n.j-sidebar__depth-1 > :deep(.el-sub-menu__title) {\r\n color: #909399;\r\n font-size: 13px;\r\n}\r\n\r\n/* 二级叶子菜单(无子项)也用灰色 */\r\n:deep(.j-sidebar__depth-1.el-menu-item) {\r\n color: #909399;\r\n font-size: 13px;\r\n}\r\n\r\n/* 三级菜单:文字稍大 */\r\n:deep(.j-sidebar__depth-2.el-menu-item) {\r\n font-size: 15px;\r\n}\r\n</style>\r\n","<template>\r\n <div\r\n :class=\"['j-sidebar', innerCollapsed ? 'j-sidebar--collapsed' : 'j-sidebar--expanded']\"\r\n :style=\"{ width: innerCollapsed ? `${collapsedWidth}px` : `${expandedWidth}px` }\"\r\n >\r\n <!-- Logo 区域 -->\r\n <div class=\"j-sidebar__logo\">\r\n <slot name=\"logo\" :collapsed=\"innerCollapsed\">\r\n <h2 class=\"j-sidebar__title\">\r\n {{ innerCollapsed ? (collapsedTitle || title?.charAt(0) || '') : title }}\r\n </h2>\r\n </slot>\r\n </div>\r\n\r\n <!-- 菜单区域 -->\r\n <div class=\"j-sidebar__menu-wrapper\">\r\n <el-menu\r\n :default-active=\"activeMenu\"\r\n class=\"j-sidebar__menu\"\r\n :router=\"router\"\r\n :collapse=\"innerCollapsed\"\r\n :collapse-transition=\"false\"\r\n >\r\n <JSideMenuItem\r\n v-for=\"route in menus\"\r\n :key=\"route.path\"\r\n :menu-item=\"route\"\r\n :is-collapse=\"innerCollapsed\"\r\n @menu-click=\"(item) => emit('menu-click', item)\"\r\n />\r\n </el-menu>\r\n </div>\r\n\r\n <!-- 底部折叠按钮 -->\r\n <div class=\"j-sidebar__footer\">\r\n <slot name=\"footer\" :collapsed=\"innerCollapsed\" :toggle=\"toggleCollapse\">\r\n <button\r\n class=\"j-sidebar__collapse-btn\"\r\n @click=\"toggleCollapse\"\r\n >\r\n <JIconRender :icon=\"innerCollapsed ? 'mdi:chevron-right' : 'mdi:chevron-left'\" />\r\n </button>\r\n </slot>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch } from 'vue'\r\nimport type { SidebarMenuItem } from './types'\r\nimport JSideMenuItem from './JSideMenuItem.vue'\r\nimport { JIconRender } from '../JIconRender'\r\n\r\ndefineOptions({\r\n name: 'JSidebar'\r\n})\r\n\r\ninterface Props {\r\n menus: SidebarMenuItem[]\r\n activeMenu?: string\r\n title?: string\r\n collapsedTitle?: string\r\n collapsed?: boolean\r\n collapsedWidth?: number\r\n expandedWidth?: number\r\n router?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n activeMenu: '',\r\n title: '',\r\n collapsedTitle: '',\r\n collapsed: false,\r\n collapsedWidth: 80,\r\n expandedWidth: 200,\r\n router: true,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'menu-click': [item: SidebarMenuItem]\r\n}>()\r\n\r\nconst innerCollapsed = ref(props.collapsed)\r\n\r\nwatch(() => props.collapsed, (val) => {\r\n innerCollapsed.value = val\r\n})\r\n\r\nconst toggleCollapse = () => {\r\n innerCollapsed.value = !innerCollapsed.value\r\n emit('update:collapsed', innerCollapsed.value)\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.j-sidebar {\r\n position: relative;\r\n background-color: $color-white;\r\n box-shadow: 2px 0 6px rgba(0, 21, 41, 0.08);\r\n overflow-x: hidden;\r\n transition: width 0.3s ease-in-out;\r\n flex-shrink: 0;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n}\r\n\r\n.j-sidebar__logo {\r\n padding: 0 $spacing-md;\r\n display: flex;\r\n align-items: center;\r\n height: 50px;\r\n flex-shrink: 0;\r\n}\r\n\r\n\r\n.j-sidebar__title {\r\n margin: 0;\r\n font-size: $font-size-lg;\r\n font-weight: bold;\r\n color: $color-primary;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n}\r\n\r\n.j-sidebar__menu-wrapper {\r\n flex: 1;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n}\r\n\r\n.j-sidebar__menu {\r\n border: none;\r\n width: 100%;\r\n}\r\n\r\n.j-sidebar__footer {\r\n flex-shrink: 0;\r\n padding: $spacing-sm;\r\n display: flex;\r\n}\r\n\r\n// 折叠状态:覆盖 el-menu 样式,图标+文字上下排列\r\n.j-sidebar--collapsed {\r\n :deep(.el-menu--collapse) {\r\n width: 100%;\r\n\r\n .el-menu-item,\r\n .el-sub-menu__title {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n height: auto;\r\n line-height: normal;\r\n padding: $spacing-sm 0 !important;\r\n\r\n .el-icon,\r\n .iconify {\r\n margin: 0;\r\n }\r\n\r\n span {\r\n display: block !important;\r\n visibility: visible !important;\r\n overflow: visible !important;\r\n width: auto !important;\r\n height: auto !important;\r\n font-size: $font-size-xs;\r\n margin-top: $spacing-xs;\r\n }\r\n }\r\n\r\n // 隐藏子菜单展开箭头\r\n .el-sub-menu__icon-arrow {\r\n display: none;\r\n }\r\n }\r\n}\r\n\r\n.j-sidebar__collapse-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: $spacing-sm;\r\n border: none;\r\n border-radius: $border-radius-base;\r\n background-color: transparent;\r\n color: $color-text-secondary;\r\n cursor: pointer;\r\n transition: $transition-fast;\r\n\r\n &:hover {\r\n background-color: $color-bg-base;\r\n color: $color-primary;\r\n }\r\n\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"j-admin-layout\" :style=\"layoutStyle\">\r\n <JSidebar\r\n v-if=\"sideMenus.length > 0\"\r\n v-model:collapsed=\"innerCollapsed\"\r\n :menus=\"sideMenus\"\r\n :active-menu=\"activeMenu\"\r\n :title=\"title\"\r\n :collapsed-title=\"collapsedTitle\"\r\n :collapsed-width=\"sidebarCollapsedWidth\"\r\n :expanded-width=\"sidebarExpandedWidth\"\r\n @menu-click=\"handleSideMenuClick\"\r\n >\r\n <template v-if=\"$slots.sidebarFooter\" #footer=\"{ collapsed: sidebarCollapsed, toggle }\">\r\n <slot name=\"sidebarFooter\" :collapsed=\"sidebarCollapsed\" :toggle=\"toggle\" />\r\n </template>\r\n </JSidebar>\r\n\r\n <div class=\"j-admin-layout__right-panel\">\r\n <div class=\"j-admin-layout__header\">\r\n <div class=\"j-admin-layout__header-left\">\r\n <div v-if=\"sideMenus.length === 0\" class=\"j-admin-layout__logo\">\r\n <slot name=\"logo\">\r\n <h2>{{ logoTitle || title }}</h2>\r\n </slot>\r\n </div>\r\n\r\n <el-menu\r\n mode=\"horizontal\"\r\n :default-active=\"activeTopMenu\"\r\n class=\"j-admin-layout__top-menu\"\r\n @select=\"handleTopMenuSelect\"\r\n >\r\n <el-menu-item\r\n v-for=\"menu in topMenus\"\r\n :key=\"menu.path\"\r\n :index=\"menu.path\"\r\n >\r\n <JIconRender v-if=\"menu.icon\" :icon=\"menu.icon\" class=\"j-admin-layout__top-menu-icon\" />\r\n <span>{{ menu.title || menu.name }}</span>\r\n </el-menu-item>\r\n </el-menu>\r\n </div>\r\n\r\n <div class=\"j-admin-layout__header-right\">\r\n <slot name=\"headerRight\" :username=\"username\">\r\n <el-dropdown v-if=\"showProfile || showLogout\">\r\n <span class=\"j-admin-layout__user\">\r\n {{ username }}\r\n <el-icon>\r\n <ArrowDown />\r\n </el-icon>\r\n </span>\r\n <template #dropdown>\r\n <el-dropdown-menu>\r\n <el-dropdown-item v-if=\"showProfile\" @click=\"emit('profile')\">\r\n {{ profileText }}\r\n </el-dropdown-item>\r\n <el-dropdown-item v-if=\"showLogout\" @click=\"emit('logout')\">\r\n {{ logoutText }}\r\n </el-dropdown-item>\r\n </el-dropdown-menu>\r\n </template>\r\n </el-dropdown>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div class=\"j-admin-layout__content-container\">\r\n <slot name=\"tabs\" />\r\n <div\r\n :class=\"[\r\n 'j-admin-layout__content',\r\n { 'j-admin-layout__content--scrollbar-hidden': hideContentScrollbar }\r\n ]\"\r\n >\r\n <slot />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ArrowDown } from '@element-plus/icons-vue'\r\nimport { JSidebar, type SidebarMenuItem } from '../JSidebar'\r\nimport { JIconRender } from '../JIconRender'\r\nimport type { AdminLayoutMenuItem } from './types'\r\n\r\ndefineOptions({\r\n name: 'JAdminLayout'\r\n})\r\n\r\ninterface Props {\r\n topMenus: AdminLayoutMenuItem[]\r\n sideMenus: AdminLayoutMenuItem[]\r\n activeTopMenu?: string\r\n activeMenu?: string\r\n title?: string\r\n logoTitle?: string\r\n collapsedTitle?: string\r\n collapsed?: boolean\r\n username?: string\r\n profileText?: string\r\n logoutText?: string\r\n sidebarCollapsedWidth?: number\r\n sidebarExpandedWidth?: number\r\n headerHeight?: string\r\n contentPadding?: string\r\n contentBackground?: string\r\n headerBackground?: string\r\n showProfile?: boolean\r\n showLogout?: boolean\r\n hideContentScrollbar?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n activeTopMenu: '',\r\n activeMenu: '',\r\n title: '',\r\n logoTitle: '',\r\n collapsedTitle: '',\r\n collapsed: false,\r\n username: '',\r\n profileText: '个人资料',\r\n logoutText: '退出登录',\r\n sidebarCollapsedWidth: 80,\r\n sidebarExpandedWidth: 200,\r\n headerHeight: '50px',\r\n contentPadding: '10px',\r\n contentBackground: '#f0f2f5',\r\n headerBackground: '#ffffff',\r\n showProfile: true,\r\n showLogout: true,\r\n hideContentScrollbar: false,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'top-menu-select': [path: string]\r\n 'side-menu-click': [item: SidebarMenuItem]\r\n profile: []\r\n logout: []\r\n}>()\r\n\r\nconst innerCollapsed = computed({\r\n get: () => props.collapsed,\r\n set: value => emit('update:collapsed', value),\r\n})\r\n\r\nconst layoutStyle = computed(() => ({\r\n '--j-admin-layout-header-height': props.headerHeight,\r\n '--j-admin-layout-content-padding': props.contentPadding,\r\n '--j-admin-layout-content-bg': props.contentBackground,\r\n '--j-admin-layout-header-bg': props.headerBackground,\r\n}))\r\n\r\nconst handleTopMenuSelect = (path: string) => {\r\n emit('top-menu-select', path)\r\n}\r\n\r\nconst handleSideMenuClick = (item: SidebarMenuItem) => {\r\n emit('side-menu-click', item)\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.j-admin-layout {\r\n width: 100vw;\r\n height: 100vh;\r\n display: flex;\r\n flex-direction: row;\r\n overflow: hidden;\r\n}\r\n\r\n.j-admin-layout__right-panel {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n background-color: var(--j-admin-layout-content-bg);\r\n}\r\n\r\n.j-admin-layout__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n background-color: var(--j-admin-layout-header-bg);\r\n box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);\r\n height: var(--j-admin-layout-header-height);\r\n flex-shrink: 0;\r\n z-index: 100;\r\n}\r\n\r\n.j-admin-layout__header-left {\r\n display: flex;\r\n align-items: center;\r\n flex: 1;\r\n height: 100%;\r\n min-width: 0;\r\n}\r\n\r\n.j-admin-layout__logo {\r\n padding: 0 $spacing-md;\r\n display: flex;\r\n align-items: center;\r\n height: var(--j-admin-layout-header-height);\r\n flex-shrink: 0;\r\n}\r\n\r\n.j-admin-layout__logo h2 {\r\n margin: 0;\r\n font-size: $font-size-lg;\r\n font-weight: bold;\r\n color: $color-primary;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n}\r\n\r\n.j-admin-layout__top-menu {\r\n flex: 1;\r\n height: var(--j-admin-layout-header-height);\r\n border-bottom: none;\r\n min-width: 0;\r\n}\r\n\r\n.j-admin-layout__top-menu-icon {\r\n margin-right: $spacing-xs;\r\n}\r\n\r\n.j-admin-layout__header-right {\r\n display: flex;\r\n align-items: center;\r\n gap: $spacing-md;\r\n padding-right: $spacing-md;\r\n flex-shrink: 0;\r\n}\r\n\r\n.j-admin-layout__user {\r\n cursor: pointer;\r\n display: flex;\r\n align-items: center;\r\n color: $color-text-regular;\r\n}\r\n\r\n.j-admin-layout__user:hover {\r\n color: $color-primary;\r\n}\r\n\r\n.j-admin-layout__content-container {\r\n flex: 1;\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n background-color: var(--j-admin-layout-content-bg);\r\n}\r\n\r\n.j-admin-layout__content {\r\n padding: var(--j-admin-layout-content-padding);\r\n flex: 1;\r\n overflow: auto;\r\n}\r\n\r\n.j-admin-layout__content--scrollbar-hidden {\r\n scrollbar-width: none;\r\n -ms-overflow-style: none;\r\n\r\n &::-webkit-scrollbar {\r\n display: none;\r\n }\r\n}\r\n</style>\r\n","/**\r\n * 递归转换值(处理嵌套对象和数组)\r\n */\r\nfunction deepTransformValue(value: any, transformer: (obj: Record<string, any>) => Record<string, any>): any {\r\n if (Array.isArray(value)) {\r\n return value.map((item) => deepTransformValue(item, transformer))\r\n }\r\n if (value !== null && typeof value === 'object' && !(value instanceof Date)) {\r\n return transformer(value)\r\n }\r\n return value\r\n}\r\n\r\n/**\r\n * 将对象的 key 从小驼峰转为下划线格式(支持深度嵌套)\r\n * @param obj 需要转换的对象\r\n * @param excludeKeys 不需要转换的 key 数组(可选)\r\n */\r\nexport function toSnakeCase<T extends Record<string, any>>(\r\n obj: T,\r\n excludeKeys: string[] = []\r\n): Record<string, any> {\r\n return Object.fromEntries(\r\n Object.entries(obj).map(([key, value]) => {\r\n const newValue = deepTransformValue(value, (v) => toSnakeCase(v, excludeKeys))\r\n if (excludeKeys.includes(key)) {\r\n return [key, newValue]\r\n }\r\n const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase()\r\n return [snakeKey, newValue]\r\n })\r\n )\r\n}\r\n\r\n/**\r\n * 将对象的 key 从下划线格式转为小驼峰(支持深度嵌套)\r\n * @param obj 需要转换的对象\r\n * @param excludeKeys 不需要转换的 key 数组(可选)\r\n */\r\nexport function toCamelCase<T extends Record<string, any>>(\r\n obj: T,\r\n excludeKeys: string[] = []\r\n): Record<string, any> {\r\n return Object.fromEntries(\r\n Object.entries(obj).map(([key, value]) => {\r\n const newValue = deepTransformValue(value, (v) => toCamelCase(v, excludeKeys))\r\n if (excludeKeys.includes(key)) {\r\n return [key, newValue]\r\n }\r\n const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase())\r\n return [camelKey, newValue]\r\n })\r\n )\r\n}\r\n\r\n/**\r\n * 从对象中提取指定的 key\r\n * @param obj 源对象\r\n * @param keys 要提取的 key 数组\r\n */\r\nexport function pick<T extends Record<string, any>, K extends keyof T>(\r\n obj: T,\r\n keys: K[]\r\n): Pick<T, K> {\r\n const result = {} as Pick<T, K>\r\n for (const key of keys) {\r\n if (key in obj) {\r\n result[key] = obj[key]\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 从对象中排除指定的 key\r\n * @param obj 源对象\r\n * @param keys 要排除的 key 数组\r\n */\r\nexport function omit<T extends Record<string, any>, K extends keyof T>(\r\n obj: T,\r\n keys: K[]\r\n): Omit<T, K> {\r\n const result = { ...obj }\r\n for (const key of keys) {\r\n delete result[key]\r\n }\r\n return result as Omit<T, K>\r\n}\r\n\r\n/**\r\n * 深拷贝对象\r\n * @param obj 需要拷贝的对象\r\n */\r\nexport function deepClone<T>(obj: T): T {\r\n if (obj === null || typeof obj !== 'object') return obj\r\n if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T\r\n if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags) as unknown as T\r\n if (Array.isArray(obj)) return obj.map((item) => deepClone(item)) as unknown as T\r\n const cloned = {} as Record<string, any>\r\n for (const key of Object.keys(obj)) {\r\n cloned[key] = deepClone((obj as Record<string, any>)[key])\r\n }\r\n return cloned as T\r\n}\r\n\r\n/**\r\n * 过滤对象中的空值(null、undefined、空字符串)\r\n * @param obj 需要过滤的对象\r\n * @param deep 是否递归过滤嵌套对象,默认 false\r\n */\r\nexport function filterEmpty<T extends Record<string, any>>(\r\n obj: T,\r\n deep = false\r\n): Partial<T> {\r\n const result = {} as Record<string, any>\r\n for (const [key, value] of Object.entries(obj)) {\r\n if (value === null || value === undefined || value === '') continue\r\n if (deep && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\r\n const filtered = filterEmpty(value, true)\r\n if (Object.keys(filtered).length > 0) {\r\n result[key] = filtered\r\n }\r\n } else {\r\n result[key] = value\r\n }\r\n }\r\n return result as Partial<T>\r\n}\r\n\r\n/**\r\n * 将嵌套对象扁平化为单层对象\r\n * @param obj 需要扁平化的对象\r\n * @param prefix key 前缀,默认为空\r\n * @param separator 分隔符,默认为 '.'\r\n * @example flatten({ a: { b: 1, c: { d: 2 } } }) => { 'a.b': 1, 'a.c.d': 2 }\r\n */\r\nexport function flatten(\r\n obj: Record<string, any>,\r\n prefix = '',\r\n separator = '.'\r\n): Record<string, any> {\r\n const result: Record<string, any> = {}\r\n for (const [key, value] of Object.entries(obj)) {\r\n const newKey = prefix ? `${prefix}${separator}${key}` : key\r\n if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\r\n Object.assign(result, flatten(value, newKey, separator))\r\n } else {\r\n result[newKey] = value\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 将扁平化的对象还原为嵌套对象\r\n * @param obj 扁平化的对象\r\n * @param separator 分隔符,默认为 '.'\r\n * @example unflatten({ 'a.b': 1, 'a.c.d': 2 }) => { a: { b: 1, c: { d: 2 } } }\r\n */\r\nexport function unflatten(\r\n obj: Record<string, any>,\r\n separator = '.'\r\n): Record<string, any> {\r\n const result: Record<string, any> = {}\r\n for (const [key, value] of Object.entries(obj)) {\r\n const parts = key.split(separator)\r\n let current = result\r\n for (let i = 0; i < parts.length - 1; i++) {\r\n const k = parts[i] as string\r\n if (!(k in current)) {\r\n current[k] = {}\r\n }\r\n current = current[k]\r\n }\r\n current[parts[parts.length - 1] as string] = value\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 将 { key: label } 对象转换为 el-table-column 所需的表头数组\r\n * @param fieldMap 字段映射对象\r\n * @example toTableColumns({ id: '主键', name: '名称' }) => [{ id: 'id', label: '主键', prop: 'id' }, ...]\r\n */\r\nexport function toTableColumns(fieldMap: Record<string, string>) {\r\n return Object.entries(fieldMap).map(([key, label]) => ({\r\n id: key,\r\n label,\r\n prop: key,\r\n }))\r\n}\r\n","import type { RouteRecordRaw } from 'vue-router'\r\nimport type { BackendRoute } from './types'\r\n\r\ntype ViewModules = Record<string, () => Promise<unknown>>\r\n\r\ninterface RouteTreeNode extends BackendRoute {\r\n children?: RouteTreeNode[]\r\n}\r\n\r\nexport function getComponentImport(componentPath: string, viewModules: ViewModules): (() => Promise<unknown>) | null {\r\n if (!componentPath) return null\r\n\r\n let normalizedPath = componentPath.replace(/^@\\//, '')\r\n if (!normalizedPath.startsWith('views/')) normalizedPath = `views/${normalizedPath}`\r\n if (!normalizedPath.endsWith('.vue')) normalizedPath = `${normalizedPath}.vue`\r\n\r\n const fullPath = `/src/${normalizedPath}`\r\n\r\n return (viewModules[fullPath] ?? viewModules['/src/views/common/NotFound.vue'] ?? null) as (() => Promise<unknown>) | null\r\n}\r\n\r\nexport function buildRouteTree(routes: BackendRoute[]): RouteTreeNode[] {\r\n const routeMap = new Map<string, RouteTreeNode>()\r\n const rootRoutes: RouteTreeNode[] = []\r\n\r\n routes.forEach(route => routeMap.set(route.name, { ...route, children: [] }))\r\n\r\n routes.forEach(route => {\r\n const currentRoute = routeMap.get(route.name)!\r\n if (!route.parent_name) {\r\n rootRoutes.push(currentRoute)\r\n } else {\r\n const parentRoute = routeMap.get(route.parent_name)\r\n if (parentRoute) {\r\n parentRoute.children ??= []\r\n parentRoute.children.push(currentRoute)\r\n } else {\r\n rootRoutes.push(currentRoute)\r\n }\r\n }\r\n })\r\n return rootRoutes\r\n}\r\n\r\nexport function transformBackendRoutes(\r\n backendRoutes: RouteTreeNode[],\r\n viewModules: ViewModules,\r\n projectName?: string\r\n): RouteRecordRaw[] {\r\n return backendRoutes\r\n .filter(route => !projectName || !route.project || route.project === projectName)\r\n .map(route => {\r\n const routeConfig: Partial<RouteRecordRaw> = {\r\n path: route.path,\r\n name: route.name,\r\n meta: {\r\n requiresAuth: route.requiresAuth === 1,\r\n title: route.title,\r\n permissions: route.permissions\r\n ? route.permissions.split(',').map(p => p.trim()).filter(Boolean)\r\n : [],\r\n params: route.params\r\n ? route.params.split('&').map(p => p.trim()).filter(Boolean)\r\n : [],\r\n },\r\n }\r\n\r\n const component = getComponentImport(route.component, viewModules)\r\n if (component) routeConfig.component = component as RouteRecordRaw['component']\r\n\r\n if (route.redirect) routeConfig.redirect = route.redirect\r\n\r\n if (route.children?.length) {\r\n routeConfig.children = transformBackendRoutes(route.children, viewModules, projectName)\r\n }\r\n\r\n return routeConfig as RouteRecordRaw\r\n })\r\n}\r\n\r\nexport function findFirstAllowedRoute(\r\n backendRoutes: BackendRoute[],\r\n routePermissions: string[],\r\n layoutRouteName: string,\r\n projectName?: string\r\n): BackendRoute | undefined {\r\n const routeMap = new Map<string, BackendRoute>()\r\n backendRoutes.forEach(route => routeMap.set(route.name, route))\r\n\r\n const isDescendantOfLayout = (route: BackendRoute): boolean => {\r\n let parentName = route.parent_name\r\n const visited = new Set<string>()\r\n\r\n while (parentName) {\r\n if (parentName === layoutRouteName) return true\r\n if (visited.has(parentName)) return false\r\n\r\n visited.add(parentName)\r\n const parentRoute = routeMap.get(parentName)\r\n if (!parentRoute) return false\r\n parentName = parentRoute.parent_name\r\n }\r\n\r\n return false\r\n }\r\n\r\n return backendRoutes\r\n .filter(route =>\r\n route.name !== layoutRouteName &&\r\n route.is_show === 1 &&\r\n route.component &&\r\n routePermissions.includes(route.code) &&\r\n isDescendantOfLayout(route) &&\r\n (!projectName || !route.project || route.project === projectName)\r\n )\r\n .sort((a, b) => a.sorts - b.sorts)[0]\r\n}\r\n","import type { NavigationGuardNext, Router } from 'vue-router'\r\nimport NProgress from 'nprogress'\r\nimport 'nprogress/nprogress.css'\r\nimport { ElMessage } from 'element-plus'\r\nimport type { RouterGuardOptions } from './types'\r\nimport { buildRouteTree, transformBackendRoutes } from './helpers'\r\n\r\nNProgress.configure({ showSpinner: false })\r\n\r\nconst isDev = import.meta.env.DEV\r\nconst log = (...args: unknown[]) => { if (isDev) console.log('[Router]', ...args) }\r\nconst warn = (...args: unknown[]) => { if (isDev) console.warn('[Router]', ...args) }\r\nconst logError = (...args: unknown[]) => { console.error('[Router]', ...args) }\r\n\r\nexport function createRouterGuard(router: Router, options: RouterGuardOptions) {\r\n const {\r\n getRouteStore,\r\n getPermissionStore,\r\n getUserStore,\r\n isAuthenticated,\r\n viewModules,\r\n homeComponent,\r\n notFoundComponent,\r\n projectName,\r\n layoutRouteName = 'Layout',\r\n publicPaths = ['/login', '/register'],\r\n loginRouteName = 'login',\r\n forbiddenRouteName = 'Forbidden',\r\n homeRouteName = 'Home',\r\n homeRoutePath = 'home',\r\n homeTitle = '\\u9996\\u9875',\r\n noPermissionMessage = '\\u6682\\u65e0\\u6743\\u9650',\r\n } = options\r\n\r\n let hasAddedRoutes = false\r\n\r\n function addDynamicRoutes(backendRoutes: ReturnType<typeof getRouteStore>['backendRoutes']) {\r\n const treeRoutes = buildRouteTree(backendRoutes)\r\n const layoutRoute = treeRoutes.find(route => route.name === layoutRouteName)\r\n const childRoutes = layoutRoute?.children\r\n ? transformBackendRoutes(layoutRoute.children, viewModules, projectName)\r\n : []\r\n\r\n childRoutes.forEach(route => {\r\n if (route.name && router.hasRoute(route.name)) return\r\n router.addRoute(layoutRouteName, route)\r\n log(`Add route: ${String(route.name)} -> ${route.path}`)\r\n })\r\n\r\n if (!router.hasRoute(homeRouteName)) {\r\n router.addRoute(layoutRouteName, {\r\n path: homeRoutePath,\r\n name: homeRouteName,\r\n component: homeComponent as never,\r\n meta: { title: homeTitle },\r\n })\r\n }\r\n\r\n if (!router.hasRoute('NotFound')) {\r\n router.addRoute({\r\n path: '/:pathMatch(.*)*',\r\n name: 'NotFound',\r\n component: notFoundComponent as never,\r\n })\r\n }\r\n\r\n log(`Dynamic routes added: ${childRoutes.length}`)\r\n }\r\n\r\n function redirectToHomeOrContinue(\r\n toName: ReturnType<Router['resolve']>['name'],\r\n next: NavigationGuardNext\r\n ) {\r\n if (toName === homeRouteName) {\r\n next()\r\n return\r\n }\r\n\r\n if (router.hasRoute(homeRouteName)) {\r\n next({ name: homeRouteName, replace: true })\r\n } else {\r\n next()\r\n }\r\n }\r\n\r\n router.beforeEach(async (to, _from, next) => {\r\n NProgress.start()\r\n\r\n if (isAuthenticated() && !hasAddedRoutes) {\r\n try {\r\n const routeStore = getRouteStore()\r\n const permissionStore = getPermissionStore()\r\n\r\n await routeStore.initRoutes()\r\n await permissionStore.loadUserPermissions()\r\n\r\n addDynamicRoutes(routeStore.backendRoutes)\r\n hasAddedRoutes = true\r\n\r\n if (to.path === '/' || to.path === '') {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n if (to.name === layoutRouteName) {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n const currentRoute = routeStore.backendRoutes.find(route => route.name === to.name)\r\n if (currentRoute && !permissionStore.routePermissions.includes(currentRoute.code)) {\r\n warn('No route permission:', to.path)\r\n next({ name: forbiddenRouteName, replace: true })\r\n return\r\n }\r\n\r\n next({\r\n path: to.path,\r\n query: to.query,\r\n hash: to.hash,\r\n replace: true,\r\n })\r\n return\r\n } catch (err) {\r\n logError('Failed to load route permissions:', err)\r\n hasAddedRoutes = false\r\n await getUserStore().logout()\r\n next({ name: loginRouteName, replace: true })\r\n return\r\n }\r\n }\r\n\r\n if (publicPaths.includes(to.path)) {\r\n if (isAuthenticated()) {\r\n next({ path: '/', replace: true })\r\n } else {\r\n next()\r\n }\r\n return\r\n }\r\n\r\n if (!isAuthenticated()) {\r\n next({ name: loginRouteName, replace: true })\r\n return\r\n }\r\n\r\n const permissionStore = getPermissionStore()\r\n const routeStore = getRouteStore()\r\n\r\n if (to.name === homeRouteName) {\r\n next()\r\n return\r\n }\r\n\r\n if (permissionStore.routePermissions.length === 0) {\r\n if (to.path !== '/') {\r\n ElMessage.warning(noPermissionMessage)\r\n next({ path: '/', replace: true })\r\n } else {\r\n redirectToHomeOrContinue(to.name, next)\r\n }\r\n return\r\n }\r\n\r\n if (to.path === '/') {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n if (to.name === layoutRouteName) {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n const currentRoute = routeStore.backendRoutes.find(route => route.name === to.name)\r\n if (currentRoute && !permissionStore.routePermissions.includes(currentRoute.code)) {\r\n warn('No route permission:', to.path)\r\n next({ name: forbiddenRouteName, replace: true })\r\n return\r\n }\r\n\r\n next()\r\n })\r\n\r\n router.afterEach(() => NProgress.done())\r\n\r\n return {\r\n resetRouterState: () => { hasAddedRoutes = false },\r\n }\r\n}\r\n","import type { App } from 'vue'\r\nimport { JButton } from './components/JButton'\r\nimport { JUploadImg } from './components/JUploadImg'\r\nimport { JIconRender } from './components/JIconRender'\r\nimport { JSearchHeader } from './components/JSearchHeader'\r\nimport { JImportExcel } from './components/JImportExcel'\r\nimport { JSidebar, JSideMenuItem } from './components/JSidebar'\r\nimport { JAdminLayout } from './components/JAdminLayout'\r\n\r\n// 组件列表,新增组件后在此处注册\r\nconst components = [JButton, JUploadImg, JIconRender, JSearchHeader, JImportExcel, JSidebar, JSideMenuItem, JAdminLayout]\r\n\r\n// 全量安装插件(app.use(JgyComponents))\r\nconst install = (app: App) => {\r\n components.forEach((component) => {\r\n app.component(component.name ?? component.__name ?? '', component)\r\n })\r\n}\r\n\r\n// 默认导出插件对象\r\nexport default { install }\r\n\r\n// 按需导出各组件\r\nexport { JButton, JUploadImg, JIconRender, JSearchHeader, JImportExcel, JSidebar, JSideMenuItem, JAdminLayout }\r\n\r\n// 导出工具方法\r\nexport { toSnakeCase, toCamelCase, pick, omit, deepClone, filterEmpty, flatten, unflatten, toTableColumns } from './utils'\r\n\r\n// 导出路由工具\r\nexport { createRouterGuard, buildRouteTree, transformBackendRoutes, findFirstAllowedRoute, getComponentImport } from './router'\r\nexport type { BackendRoute, RouteStoreAdapter, PermissionStoreAdapter, UserStoreAdapter, RouterGuardOptions } from './router'\r\n\r\n// 导出类型\r\nexport type { ButtonType, ButtonSize } from './components/JButton'\r\nexport type { JUploadImgProps, JUploadImgEmits, ImageItem } from './components/JUploadImg'\r\nexport type { JIconRenderProps } from './components/JIconRender'\r\nexport type { JSearchHeaderProps } from './components/JSearchHeader'\r\nexport type { JImportExcelProps, ApiFn } from './components/JImportExcel'\r\nexport type { SidebarMenuItem, JSidebarProps, JSideMenuItemProps } from './components/JSidebar'\r\nexport type { AdminLayoutMenuItem, JAdminLayoutProps } from './components/JAdminLayout'\r\n"],"names":["props","__props","emit","__emit","handleClick","event","_createElementBlock","_openBlock","_hoisted_2","$slots","_hoisted_3","_renderSlot","_ctx","imageUrl","ref","showPreview","showTipText","handlePreviewSingle","handleRemoveSingle","ElMessage","imageList","uploading","initImageList","urls","url","index","watch","val","handleDragEnd","emitMultipleUpdate","handleRemove","handleFileChange","uploadFile","file","error","item","_hoisted_1","_createVNode","_unref","draggable","$event","_withCtx","element","_createElementVNode","_component_el_image","img","_withModifiers","_component_el_icon","Delete","_createBlock","_component_el_upload","Loading","Plus","_Fragment","_hoisted_5","_hoisted_6","ZoomIn","_component_el_image_viewer","_hoisted_7","_hoisted_8","_toDisplayString","_mergeProps","$attrs","formItemsRef","expanded","isAnimated","itemCount","collapsedHeight","shouldCollapse","computed","measureItems","items","lastVisibleItem","wrapperRect","lastVisibleRect","toggleExpand","el","onEnd","observer","onMounted","onBeforeUnmount","_component_el_card","_component_el_form","_normalizeClass","_component_el_button","Search","_cache","Refresh","_createTextVNode","ArrowDown","visible","_useModel","submitting","fieldMap","renderData","stats","statusMap","dataColumns","key","label","handleOpen","res","raw","reader","e","workbook","XLSX","wsName","ws","dataRows","row","cell","list","col","handleDel","idx","handleConfirm","data","_status","_error","rest","r","handleDownloadTemplate","wb","headers","tr","handleClose","_component_el_dialog","_component_el_divider","_component_el_link","_component_el_row","_component_el_col","_hoisted_4","_component_el_table","_renderList","_component_el_table_column","$index","_component_el_sub_menu","JIconRender","child","_component_JSideMenuItem","_component_el_menu_item","innerCollapsed","toggleCollapse","_normalizeStyle","_a","_component_el_menu","route","JSideMenuItem","value","layoutStyle","handleTopMenuSelect","path","handleSideMenuClick","JSidebar","sidebarCollapsed","toggle","menu","_component_el_dropdown","_component_el_dropdown_menu","_component_el_dropdown_item","deepTransformValue","transformer","toSnakeCase","obj","excludeKeys","newValue","v","toCamelCase","_","char","pick","keys","result","omit","deepClone","cloned","filterEmpty","deep","filtered","flatten","prefix","separator","newKey","unflatten","parts","current","i","k","toTableColumns","getComponentImport","componentPath","viewModules","normalizedPath","fullPath","buildRouteTree","routes","routeMap","rootRoutes","currentRoute","parentRoute","transformBackendRoutes","backendRoutes","projectName","routeConfig","p","component","findFirstAllowedRoute","routePermissions","layoutRouteName","isDescendantOfLayout","parentName","visited","a","b","NProgress","log","args","warn","logError","createRouterGuard","router","options","getRouteStore","getPermissionStore","getUserStore","isAuthenticated","homeComponent","notFoundComponent","publicPaths","loginRouteName","forbiddenRouteName","homeRouteName","homeRoutePath","homeTitle","noPermissionMessage","hasAddedRoutes","addDynamicRoutes","layoutRoute","childRoutes","redirectToHomeOrContinue","toName","next","to","_from","routeStore","permissionStore","err","components","JButton","JUploadImg","JSearchHeader","JImportExcel","JAdminLayout","app"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.cjs","sources":["../src/components/JButton/JButton.vue","../src/components/JUploadImg/JUploadImg.vue","../src/components/JIconRender/JIconRender.vue","../src/components/JSearchHeader/JSearchHeader.vue","../src/components/JImportExcel/JImportExcel.vue","../src/components/JSidebar/JSideMenuItem.vue","../src/components/JSidebar/JSidebar.vue","../src/components/JAdminLayout/JAdminLayout.vue","../src/utils/transform.ts","../src/router/helpers.ts","../src/router/guards.ts","../src/index.ts"],"sourcesContent":["<template>\r\n <button\r\n class=\"j-button\"\r\n :class=\"[\r\n `j-button--${type}`,\r\n `j-button--${size}`,\r\n {\r\n 'j-button--plain': plain,\r\n 'j-button--round': round,\r\n 'j-button--disabled': disabled,\r\n 'j-button--loading': loading,\r\n },\r\n ]\"\r\n :disabled=\"disabled || loading\"\r\n @click=\"handleClick\"\r\n >\r\n <span v-if=\"loading\" class=\"j-button__loading-icon\" />\r\n <span v-if=\"$slots.default\" class=\"j-button__text\">\r\n <slot />\r\n </span>\r\n </button>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { ButtonType, ButtonSize } from './types'\r\n\r\ninterface Props {\r\n type?: ButtonType\r\n size?: ButtonSize\r\n plain?: boolean\r\n round?: boolean\r\n disabled?: boolean\r\n loading?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n type: 'default',\r\n size: 'medium',\r\n plain: false,\r\n round: false,\r\n disabled: false,\r\n loading: false,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n click: [event: MouseEvent]\r\n}>()\r\n\r\nfunction handleClick(event: MouseEvent) {\r\n if (!props.disabled && !props.loading) {\r\n emit('click', event)\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.j-button {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: $spacing-xs;\r\n padding: 8px 16px;\r\n font-size: $font-size-base;\r\n font-weight: 400;\r\n line-height: 1;\r\n white-space: nowrap;\r\n cursor: pointer;\r\n border: 1px solid $color-border;\r\n border-radius: $border-radius-base;\r\n background-color: $color-white;\r\n color: $color-text-regular;\r\n transition: $transition-fast;\r\n outline: none;\r\n user-select: none;\r\n\r\n &:hover {\r\n color: $color-primary;\r\n border-color: $color-primary;\r\n background-color: color.adjust($color-primary, $lightness: 40%);\r\n }\r\n\r\n // 尺寸\r\n &--small {\r\n padding: 5px 12px;\r\n font-size: $font-size-xs;\r\n }\r\n\r\n &--medium {\r\n padding: 8px 16px;\r\n font-size: $font-size-base;\r\n }\r\n\r\n &--large {\r\n padding: 12px 20px;\r\n font-size: $font-size-md;\r\n }\r\n\r\n // 类型\r\n &--primary {\r\n background-color: $color-primary;\r\n border-color: $color-primary;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-primary, $lightness: 10%);\r\n border-color: color.adjust($color-primary, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--success {\r\n background-color: $color-success;\r\n border-color: $color-success;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-success, $lightness: 10%);\r\n border-color: color.adjust($color-success, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--danger {\r\n background-color: $color-danger;\r\n border-color: $color-danger;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-danger, $lightness: 10%);\r\n border-color: color.adjust($color-danger, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n &--warning {\r\n background-color: $color-warning;\r\n border-color: $color-warning;\r\n color: $color-white;\r\n &:hover {\r\n background-color: color.adjust($color-warning, $lightness: 10%);\r\n border-color: color.adjust($color-warning, $lightness: 10%);\r\n color: $color-white;\r\n }\r\n }\r\n\r\n // 朴素按钮\r\n &--plain {\r\n &.j-button--primary {\r\n background-color: color.adjust($color-primary, $lightness: 40%);\r\n color: $color-primary;\r\n &:hover {\r\n background-color: $color-primary;\r\n color: $color-white;\r\n }\r\n }\r\n }\r\n\r\n // 圆角\r\n &--round {\r\n border-radius: $border-radius-round;\r\n }\r\n\r\n // 禁用\r\n &--disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n pointer-events: none;\r\n }\r\n\r\n // 加载\r\n &--loading {\r\n opacity: 0.7;\r\n cursor: not-allowed;\r\n pointer-events: none;\r\n }\r\n\r\n &__loading-icon {\r\n display: inline-block;\r\n width: 14px;\r\n height: 14px;\r\n border: 2px solid currentColor;\r\n border-top-color: transparent;\r\n border-radius: 50%;\r\n animation: j-spin 0.6s linear infinite;\r\n }\r\n\r\n @keyframes j-spin {\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"j-upload-img-container\">\r\n <!-- 多图模式 -->\r\n <template v-if=\"multiple\">\r\n <div class=\"images-wrapper\">\r\n <draggable\r\n v-model=\"imageList\"\r\n class=\"images-list\"\r\n item-key=\"uid\"\r\n :animation=\"200\"\r\n :disabled=\"disabled\"\r\n @end=\"handleDragEnd\"\r\n >\r\n <template #item=\"{ element, index }\">\r\n <div class=\"image-item\">\r\n <el-image\r\n :src=\"element.url\"\r\n :preview-src-list=\"imageList.map(img => img.url)\"\r\n :initial-index=\"index\"\r\n fit=\"cover\"\r\n class=\"image-preview\"\r\n :preview-teleported=\"true\"\r\n :hide-on-click-modal=\"true\"\r\n />\r\n <div v-if=\"!disabled\" class=\"delete-btn\" @click.stop=\"handleRemove(index)\">\r\n <el-icon class=\"icon-delete\">\r\n <Delete />\r\n </el-icon>\r\n </div>\r\n </div>\r\n </template>\r\n </draggable>\r\n\r\n <!-- 多图上传按钮 -->\r\n <el-upload\r\n v-if=\"!disabled && (!limit || imageList.length < limit)\"\r\n class=\"image-uploader\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n :disabled=\"uploading\"\r\n list-type=\"picture-card\"\r\n :accept=\"accept\"\r\n >\r\n <el-icon v-if=\"!uploading\" class=\"uploader-icon\">\r\n <Plus />\r\n </el-icon>\r\n <el-icon v-else class=\"uploader-icon is-loading\">\r\n <Loading />\r\n </el-icon>\r\n </el-upload>\r\n </div>\r\n </template>\r\n\r\n <!-- 单图模式 -->\r\n <template v-else>\r\n <!-- 已有图片:独立展示,hover 显示操作遮罩 -->\r\n <div v-if=\"imageUrl\" class=\"image-item single-image-item\">\r\n <el-image\r\n :src=\"imageUrl\"\r\n fit=\"cover\"\r\n class=\"image-preview\"\r\n />\r\n <div class=\"image-actions\">\r\n <el-icon class=\"action-icon\" @click.stop=\"handlePreviewSingle\">\r\n <ZoomIn />\r\n </el-icon>\r\n <el-icon v-if=\"!disabled\" class=\"action-icon\" @click.stop=\"handleRemoveSingle\">\r\n <Delete />\r\n </el-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- 无图片:显示上传按钮 -->\r\n <el-upload\r\n v-else\r\n class=\"image-uploader\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n :disabled=\"disabled || uploading\"\r\n :accept=\"accept\"\r\n list-type=\"picture-card\"\r\n >\r\n <el-icon class=\"uploader-icon\">\r\n <Plus v-if=\"!uploading\" />\r\n <Loading v-else class=\"is-loading\" />\r\n </el-icon>\r\n </el-upload>\r\n\r\n <!-- 大图预览 -->\r\n <el-image-viewer\r\n v-if=\"showPreview\"\r\n :url-list=\"[imageUrl]\"\r\n :teleported=\"true\"\r\n @close=\"showPreview = false\"\r\n />\r\n </template>\r\n\r\n <div v-if=\"showTipText\" class=\"upload-tip\">\r\n <span>{{ tip }}</span>\r\n <span v-if=\"multiple && limit\"> (最多 {{ limit }} 张)</span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch } from 'vue'\r\nimport { ElMessage, ElImage, ElIcon, ElUpload, ElImageViewer } from 'element-plus'\r\nimport type { UploadFile } from 'element-plus'\r\nimport { Plus, Loading, Delete, ZoomIn } from '@element-plus/icons-vue'\r\nimport draggable from 'vuedraggable'\r\nimport type { ImageItem } from './types'\r\n\r\ndefineOptions({\r\n name: 'JUploadImg'\r\n})\r\n\r\nconst props = withDefaults(defineProps<{\r\n modelValue?: string | string[]\r\n disabled?: boolean\r\n multiple?: boolean\r\n limit?: number\r\n maxSize?: number\r\n accept?: string\r\n tip?: string\r\n showTip?: boolean\r\n /** 自定义上传函数,接收 File 返回图片 URL */\r\n uploadFn?: (file: File) => Promise<string>\r\n}>(), {\r\n modelValue: '',\r\n disabled: false,\r\n multiple: false,\r\n limit: 9,\r\n maxSize: 2,\r\n accept: 'image/jpeg,image/png,image/jpg,image/gif',\r\n tip: '只能上传jpg/png/gif格式的图片,且不超过2MB',\r\n showTip: true,\r\n uploadFn: undefined\r\n})\r\n\r\nconst emit = defineEmits<{\r\n (e: 'update:modelValue', value: string | string[]): void\r\n (e: 'success', url: string): void\r\n (e: 'error', error: any): void\r\n}>()\r\n\r\n// 单图模式\r\nconst imageUrl = ref<string>('')\r\nconst showPreview = ref(false)\r\nconst showTipText = props.showTip\r\n\r\nconst handlePreviewSingle = () => {\r\n showPreview.value = true\r\n}\r\n\r\nconst handleRemoveSingle = () => {\r\n imageUrl.value = ''\r\n emit('update:modelValue', '')\r\n ElMessage.success('删除成功')\r\n}\r\n\r\n// 多图模式\r\nconst imageList = ref<ImageItem[]>([])\r\nconst uploading = ref(false)\r\n\r\n// 初始化图片列表(多图模式)\r\nconst initImageList = (urls: string[]) => {\r\n imageList.value = urls.map((url, index) => ({\r\n uid: Date.now() + index,\r\n url\r\n }))\r\n}\r\n\r\n// 监听外部值变化\r\nwatch(() => props.modelValue, (val) => {\r\n if (props.multiple) {\r\n if (Array.isArray(val) && val.length > 0) {\r\n initImageList(val)\r\n } else {\r\n imageList.value = []\r\n }\r\n } else {\r\n if (typeof val === 'string') {\r\n imageUrl.value = val\r\n } else {\r\n imageUrl.value = ''\r\n }\r\n }\r\n}, { immediate: true })\r\n\r\n// 拖拽结束(多图模式)\r\nconst handleDragEnd = () => {\r\n emitMultipleUpdate()\r\n}\r\n\r\n// 删除图片(多图模式)\r\nconst handleRemove = (index: number) => {\r\n imageList.value.splice(index, 1)\r\n emitMultipleUpdate()\r\n ElMessage.success('删除成功')\r\n}\r\n\r\n// 文件改变时触发\r\nconst handleFileChange = async (uploadFile: UploadFile) => {\r\n const file = uploadFile.raw as File\r\n\r\n if (!file) {\r\n ElMessage.error('文件不存在,请重试')\r\n return\r\n }\r\n\r\n if (file.size > props.maxSize * 1024 * 1024) {\r\n ElMessage.error(`图片大小不能超过 ${props.maxSize}MB`)\r\n return\r\n }\r\n\r\n if (!props.uploadFn) {\r\n ElMessage.error('未配置上传函数 (uploadFn)')\r\n return\r\n }\r\n\r\n try {\r\n uploading.value = true\r\n const url = await props.uploadFn(file)\r\n\r\n if (props.multiple) {\r\n imageList.value.push({ uid: Date.now(), url })\r\n emitMultipleUpdate()\r\n } else {\r\n imageUrl.value = url\r\n emit('update:modelValue', url)\r\n }\r\n\r\n emit('success', url)\r\n ElMessage.success('上传成功')\r\n } catch (error) {\r\n console.error('上传出错:', error)\r\n ElMessage.error('上传失败,请重试')\r\n emit('error', error)\r\n } finally {\r\n uploading.value = false\r\n }\r\n}\r\n\r\n// 触发多图更新\r\nconst emitMultipleUpdate = () => {\r\n const urls = imageList.value.map(item => item.url)\r\n emit('update:modelValue', urls)\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-upload-img-container {\r\n display: inline-block;\r\n}\r\n\r\n/* 多图模式样式 */\r\n.images-wrapper {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n align-items: flex-start;\r\n}\r\n\r\n.images-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n}\r\n\r\n.image-item {\r\n position: relative;\r\n width: 74px;\r\n height: 74px;\r\n border: 1px solid var(--el-border-color);\r\n border-radius: 6px;\r\n overflow: hidden;\r\n cursor: move;\r\n}\r\n\r\n.image-preview {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: 2px;\r\n right: 2px;\r\n width: 20px;\r\n height: 20px;\r\n background-color: rgba(0, 0, 0, 0.6);\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n opacity: 0;\r\n transition: opacity 0.3s;\r\n cursor: pointer;\r\n z-index: 1;\r\n}\r\n\r\n.image-item:hover .delete-btn {\r\n opacity: 1;\r\n}\r\n\r\n.delete-btn:hover {\r\n background-color: rgba(0, 0, 0, 0.8);\r\n}\r\n\r\n.icon-delete {\r\n font-size: 12px;\r\n color: #fff;\r\n}\r\n\r\n/* 单图和多图共用样式 */\r\n.image-uploader {\r\n display: inline-block;\r\n vertical-align: top;\r\n}\r\n\r\n.image-uploader :deep(.el-upload) {\r\n border: 1px dashed var(--el-border-color);\r\n border-radius: 6px;\r\n cursor: pointer;\r\n position: relative;\r\n overflow: hidden;\r\n transition: var(--el-transition-duration-fast);\r\n width: 74px !important;\r\n height: 74px !important;\r\n}\r\n\r\n.image-uploader :deep(.el-upload:hover) {\r\n border-color: var(--el-color-primary);\r\n}\r\n\r\n.uploader-icon {\r\n font-size: 20px;\r\n color: #8c939d;\r\n width: 74px;\r\n height: 74px;\r\n text-align: center;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.single-image-item {\r\n cursor: default;\r\n}\r\n\r\n.image-actions {\r\n position: absolute;\r\n inset: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n opacity: 0;\r\n transition: opacity 0.3s;\r\n}\r\n\r\n.single-image-item:hover .image-actions,\r\n.image-item:hover .image-actions {\r\n opacity: 1;\r\n}\r\n\r\n.action-icon {\r\n font-size: 18px;\r\n color: #fff;\r\n cursor: pointer;\r\n transition: transform 0.2s;\r\n}\r\n\r\n.action-icon:hover {\r\n transform: scale(1.2);\r\n}\r\n\r\n.upload-tip {\r\n margin-top: 8px;\r\n font-size: 12px;\r\n color: #909399;\r\n line-height: 1.5;\r\n}\r\n</style>\r\n","<template>\r\n <Icon v-if=\"icon\" :icon=\"icon\" :width=\"size\" :height=\"size\" v-bind=\"$attrs\" />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { Icon } from '@iconify/vue'\r\n\r\ndefineOptions({\r\n name: 'JIconRender'\r\n})\r\n\r\nwithDefaults(defineProps<{\r\n icon: string\r\n size?: string | number\r\n}>(), {\r\n size: 24,\r\n})\r\n</script>\r\n","<template>\r\n <el-card shadow=\"never\" class=\"j-search-header-card\">\r\n <el-form inline class=\"search-form\">\r\n <div\r\n ref=\"formItemsRef\"\r\n class=\"form-items-wrapper\"\r\n :class=\"{ 'is-animated': isAnimated }\"\r\n >\r\n <slot />\r\n </div>\r\n </el-form>\r\n\r\n <div class=\"action-bar\" :style=\"{ marginTop: expanded ? '0px' : '10px' }\">\r\n <div class=\"action-left\">\r\n <el-button v-if=\"showAdd\" type=\"primary\" :icon=\"Plus\" @click=\"emit('add')\">\r\n {{ addText }}\r\n </el-button>\r\n <el-button\r\n v-if=\"showBatchDelete\"\r\n type=\"danger\"\r\n :icon=\"Delete\"\r\n :disabled=\"selectedCount === 0\"\r\n @click=\"emit('batchDelete')\"\r\n >\r\n 批量删除{{ selectedCount ? `(${selectedCount})` : '' }}\r\n </el-button>\r\n <el-button v-if=\"showSearch\" type=\"primary\" :icon=\"Search\" @click=\"emit('search')\">搜索</el-button>\r\n <el-button v-if=\"showReset\" :icon=\"Refresh\" @click=\"emit('reset')\">重置</el-button>\r\n <slot name=\"actions\" />\r\n </div>\r\n <el-button\r\n v-if=\"shouldCollapse\"\r\n link\r\n type=\"primary\"\r\n @click=\"toggleExpand\"\r\n >\r\n {{ expanded ? '收起' : '展开' }}\r\n <el-icon class=\"toggle-icon\" :class=\"{ 'is-expanded': expanded }\">\r\n <ArrowDown />\r\n </el-icon>\r\n </el-button>\r\n </div>\r\n </el-card>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from 'vue'\r\nimport { ElCard, ElForm, ElButton, ElIcon } from 'element-plus'\r\nimport { Search, Refresh, Plus, Delete, ArrowDown } from '@element-plus/icons-vue'\r\n\r\ndefineOptions({\r\n name: 'JSearchHeader'\r\n})\r\n\r\nconst props = withDefaults(defineProps<{\r\n selectedCount?: number\r\n addText?: string\r\n defaultVisible?: number\r\n showAdd?: boolean\r\n showBatchDelete?: boolean\r\n showSearch?: boolean\r\n showReset?: boolean\r\n}>(), {\r\n selectedCount: 0,\r\n addText: '新增',\r\n defaultVisible: 5,\r\n showAdd: true,\r\n showBatchDelete: true,\r\n showSearch: true,\r\n showReset: true\r\n})\r\n\r\nconst emit = defineEmits<{\r\n search: []\r\n reset: []\r\n add: []\r\n batchDelete: []\r\n}>()\r\n\r\nconst formItemsRef = ref<HTMLElement>()\r\nconst expanded = ref(false)\r\nconst isAnimated = ref(false)\r\nconst itemCount = ref(0)\r\nconst collapsedHeight = ref(0)\r\n\r\nconst shouldCollapse = computed(() => itemCount.value > props.defaultVisible)\r\n\r\nconst measureItems = () => {\r\n if (!formItemsRef.value) return\r\n const items = Array.from(formItemsRef.value.children) as HTMLElement[]\r\n itemCount.value = items.length\r\n\r\n const lastVisibleItem = items[props.defaultVisible - 1]\r\n if (lastVisibleItem && formItemsRef.value) {\r\n const wrapperRect = formItemsRef.value.getBoundingClientRect()\r\n const lastVisibleRect = lastVisibleItem.getBoundingClientRect()\r\n collapsedHeight.value = lastVisibleRect.bottom - wrapperRect.top\r\n }\r\n}\r\n\r\nconst toggleExpand = () => {\r\n const el = formItemsRef.value\r\n if (!el) return\r\n\r\n if (!expanded.value) {\r\n expanded.value = true\r\n el.style.maxHeight = el.scrollHeight + 'px'\r\n const onEnd = () => {\r\n el.style.maxHeight = 'none'\r\n el.removeEventListener('transitionend', onEnd)\r\n }\r\n el.addEventListener('transitionend', onEnd)\r\n } else {\r\n measureItems()\r\n expanded.value = false\r\n el.style.maxHeight = el.scrollHeight + 'px'\r\n el.offsetHeight\r\n el.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n}\r\n\r\nlet observer: MutationObserver | null = null\r\n\r\nonMounted(() => {\r\n measureItems()\r\n if (shouldCollapse.value && formItemsRef.value) {\r\n formItemsRef.value.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n isAnimated.value = true\r\n })\r\n })\r\n if (formItemsRef.value) {\r\n observer = new MutationObserver(() => {\r\n measureItems()\r\n if (shouldCollapse.value && !expanded.value && formItemsRef.value) {\r\n formItemsRef.value.style.maxHeight = collapsedHeight.value + 'px'\r\n }\r\n })\r\n observer.observe(formItemsRef.value, { childList: true })\r\n }\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n observer?.disconnect()\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.j-search-header-card :deep(.el-card__body) {\r\n padding: 16px 20px 12px;\r\n}\r\n\r\n.search-form {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: 0;\r\n}\r\n\r\n.form-items-wrapper {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n overflow: hidden;\r\n}\r\n\r\n.form-items-wrapper.is-animated {\r\n transition: max-height 0.3s ease-in-out;\r\n}\r\n\r\n.form-items-wrapper :deep(.el-form-item) {\r\n margin-bottom: 10px;\r\n margin-right: 16px;\r\n}\r\n\r\n.action-bar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding-top: 10px;\r\n border-top: 1px solid #ebeef5;\r\n}\r\n\r\n.action-left {\r\n display: flex;\r\n gap: 8px;\r\n}\r\n\r\n.toggle-icon {\r\n margin-left: 2px;\r\n transition: transform 0.25s;\r\n}\r\n\r\n.toggle-icon.is-expanded {\r\n transform: rotate(180deg);\r\n}\r\n</style>\r\n","<template>\r\n <el-dialog\r\n v-model=\"visible\"\r\n :title=\"props.title\"\r\n width=\"900px\"\r\n :close-on-click-modal=\"false\"\r\n @open=\"handleOpen\"\r\n @close=\"handleClose\"\r\n >\r\n <!-- 上传区域 -->\r\n <el-card shadow=\"never\" class=\"j-import-upload-card\">\r\n <div class=\"j-import-upload-card-title\">导入 EXCEL</div>\r\n <el-divider style=\"margin: 10px 0\" />\r\n <div class=\"j-import-upload-row\">\r\n <el-upload\r\n accept=\".xlsx,.xls\"\r\n :auto-upload=\"false\"\r\n :show-file-list=\"false\"\r\n :on-change=\"handleFileChange\"\r\n >\r\n <el-button size=\"small\">点击上传</el-button>\r\n </el-upload>\r\n <el-link\r\n v-if=\"dataColumns.length\"\r\n type=\"primary\"\r\n class=\"j-import-no-wrap\"\r\n @click=\"handleDownloadTemplate\"\r\n >\r\n 下载导入模板.xlsx\r\n </el-link>\r\n </div>\r\n </el-card>\r\n\r\n <!-- 统计信息 + 确认导入 -->\r\n <el-row class=\"j-import-stats-row\" :gutter=\"20\" align=\"middle\">\r\n <el-col :span=\"18\">\r\n <el-row class=\"j-import-stats-cols\">\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.total }}</div>\r\n <div class=\"j-import-stat-label\">导入数量</div>\r\n </el-col>\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.fail }}</div>\r\n <div class=\"j-import-stat-label\">失败数量</div>\r\n </el-col>\r\n <el-col :span=\"4\" class=\"j-import-stat-item\">\r\n <div class=\"j-import-stat-num\">{{ stats.success }}</div>\r\n <div class=\"j-import-stat-label\">成功数量</div>\r\n </el-col>\r\n </el-row>\r\n </el-col>\r\n <el-col :span=\"6\" class=\"j-import-confirm-col\">\r\n <el-button\r\n type=\"primary\"\r\n size=\"small\"\r\n :loading=\"submitting\"\r\n :disabled=\"!renderData.length\"\r\n @click=\"handleConfirm\"\r\n >\r\n 确认导入\r\n </el-button>\r\n </el-col>\r\n </el-row>\r\n\r\n <!-- 数据表格 -->\r\n <el-table :data=\"renderData\" border size=\"small\" max-height=\"360\" class=\"j-import-data-table\">\r\n <el-table-column\r\n v-for=\"col in dataColumns\"\r\n :key=\"col.prop\"\r\n :prop=\"col.prop\"\r\n :label=\"col.label\"\r\n :min-width=\"120\"\r\n show-overflow-tooltip\r\n />\r\n <el-table-column label=\"状态\" width=\"110\" align=\"center\" fixed=\"right\">\r\n <template #default=\"{ row }\">\r\n <span v-if=\"row._status === -1\" class=\"j-import-status-fail\">\r\n {{ row._error || '创建失败' }}\r\n </span>\r\n <span v-else>{{ statusMap[row._status] }}</span>\r\n </template>\r\n </el-table-column>\r\n <el-table-column label=\"操作\" width=\"80\" align=\"center\" fixed=\"right\">\r\n <template #default=\"{ $index, row }\">\r\n <el-button\r\n v-if=\"row._status === 0\"\r\n size=\"small\"\r\n @click=\"handleDel($index)\"\r\n >\r\n 删除\r\n </el-button>\r\n </template>\r\n </el-table-column>\r\n </el-table>\r\n\r\n <template #footer>\r\n <el-button @click=\"handleClose\">关闭</el-button>\r\n </template>\r\n </el-dialog>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from 'vue'\r\nimport { ElMessage, ElDialog, ElCard, ElDivider, ElUpload, ElButton, ElLink, ElRow, ElCol, ElTable, ElTableColumn } from 'element-plus'\r\nimport type { UploadFile } from 'element-plus'\r\nimport * as XLSX from 'xlsx'\r\nimport type { JImportExcelProps } from './types'\r\n\r\ndefineOptions({ name: 'JImportExcel' })\r\n\r\nconst props = withDefaults(defineProps<JImportExcelProps>(), {\r\n excludeFields: () => ['id', 'ctime', 'mtime'],\r\n title: 'Excel 批量导入',\r\n})\r\n\r\nconst emit = defineEmits<{\r\n success: []\r\n}>()\r\n\r\nconst visible = defineModel<boolean>({ default: false })\r\nconst submitting = ref(false)\r\nconst fieldMap = ref<Record<string, string>>({})\r\nconst renderData = ref<Record<string, any>[]>([])\r\nconst stats = ref({ total: 0, success: 0, fail: 0 })\r\n\r\nconst statusMap: Record<number, string> = {\r\n 0: '待创建',\r\n 1: '创建成功',\r\n 2: '创建中',\r\n [-1]: '创建失败',\r\n}\r\n\r\n/** 过滤后的表头列 */\r\nconst dataColumns = computed(() => {\r\n return Object.entries(fieldMap.value)\r\n .filter(([key]) => !props.excludeFields.includes(key))\r\n .map(([key, label]) => ({ id: key, label, prop: key }))\r\n})\r\n\r\n/** 弹窗打开时获取字段列表 */\r\nconst handleOpen = async () => {\r\n try {\r\n const res = await props.getFieldListApi()\r\n fieldMap.value = res.data.tableFieldsInfo ?? {}\r\n } catch {\r\n ElMessage.error('获取字段列表失败')\r\n }\r\n}\r\n\r\n/** 文件上传解析 */\r\nconst handleFileChange = (uploadFile: UploadFile) => {\r\n const raw = uploadFile.raw\r\n if (!raw) return\r\n\r\n const validTypes = [\r\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\r\n 'application/vnd.ms-excel',\r\n ]\r\n if (!validTypes.includes(raw.type) && !raw.name.match(/\\.xlsx?$/i)) {\r\n ElMessage.warning('请上传 .xlsx 或 .xls 格式文件')\r\n return\r\n }\r\n\r\n const reader = new FileReader()\r\n reader.onload = (e) => {\r\n try {\r\n const workbook = XLSX.read(e.target!.result, { type: 'binary' })\r\n const wsName = workbook.SheetNames[0] as string\r\n const ws = workbook.Sheets[wsName]!\r\n const rows = XLSX.utils.sheet_to_json(ws, {\r\n header: 1,\r\n defval: '',\r\n }) as any[][]\r\n\r\n const dataRows = rows.slice(1).filter((row) => row.some((cell) => cell !== ''))\r\n\r\n if (!dataRows.length) {\r\n ElMessage.warning('文件内容为空')\r\n return\r\n }\r\n\r\n // 按后端字段顺序一一映射,空单元格由 defval 补 '',避免后续列顶上来\r\n const list: Record<string, any>[] = dataRows.map((row) => {\r\n const item: Record<string, any> = { _status: 0, _error: '' }\r\n dataColumns.value.forEach((col, index) => {\r\n item[col.prop] = row[index] !== undefined ? row[index] : ''\r\n })\r\n return item\r\n })\r\n\r\n renderData.value = list\r\n stats.value = { total: list.length, success: 0, fail: 0 }\r\n } catch {\r\n ElMessage.error('解析文件失败,请检查文件格式')\r\n }\r\n }\r\n reader.readAsBinaryString(raw)\r\n}\r\n\r\n/** 删除行 */\r\nconst handleDel = (idx: number) => {\r\n renderData.value.splice(idx, 1)\r\n stats.value.total = renderData.value.length\r\n}\r\n\r\n/** 确认导入 */\r\nconst handleConfirm = async () => {\r\n const data = renderData.value\r\n .filter((row) => row._status === 0)\r\n .map(({ _status, _error, ...rest }) => rest)\r\n\r\n if (!data.length) {\r\n ElMessage.warning('没有可导入的数据')\r\n return\r\n }\r\n\r\n submitting.value = true\r\n try {\r\n const res = await props.importApi(data)\r\n if (res.code === 1) {\r\n // 全部成功\r\n renderData.value.forEach((row) => {\r\n if (row._status === 0) row._status = 1\r\n })\r\n stats.value.success = renderData.value.filter((r) => r._status === 1).length\r\n ElMessage.success('导入成功')\r\n emit('success')\r\n } else {\r\n ElMessage.error(res.msg || '导入失败')\r\n }\r\n } catch {\r\n ElMessage.error('导入失败,请重试')\r\n } finally {\r\n submitting.value = false\r\n }\r\n}\r\n\r\n/** 下载导入模板 */\r\nconst handleDownloadTemplate = () => {\r\n try {\r\n const wb = XLSX.utils.book_new()\r\n const headers = dataColumns.value.map((col) => col.label)\r\n const tr: Record<string, string> = {}\r\n headers.forEach((h) => (tr[h] = ''))\r\n const ws = XLSX.utils.json_to_sheet([tr])\r\n XLSX.utils.book_append_sheet(wb, ws, '模板')\r\n XLSX.writeFile(wb, '导入模板.xlsx')\r\n } catch {\r\n ElMessage.error('模板下载失败')\r\n }\r\n}\r\n\r\nconst handleClose = () => {\r\n if (!submitting.value) {\r\n visible.value = false\r\n renderData.value = []\r\n stats.value = { total: 0, success: 0, fail: 0 }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-import-upload-card {\r\n margin-bottom: 16px;\r\n}\r\n.j-import-upload-card-title {\r\n font-size: 14px;\r\n color: #606266;\r\n}\r\n.j-import-upload-row {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n.j-import-no-wrap {\r\n white-space: nowrap;\r\n}\r\n.j-import-stats-row {\r\n margin: 12px 0;\r\n}\r\n.j-import-stats-cols {\r\n text-align: center;\r\n line-height: 1.5;\r\n}\r\n.j-import-stat-item {\r\n padding: 0 8px;\r\n}\r\n.j-import-stat-num {\r\n font-size: 20px;\r\n}\r\n.j-import-stat-label {\r\n font-size: 12px;\r\n color: #909399;\r\n}\r\n.j-import-confirm-col {\r\n text-align: right;\r\n padding-top: 10px;\r\n}\r\n.j-import-data-table {\r\n margin-top: 10px;\r\n}\r\n.j-import-status-fail {\r\n color: #f56c6c;\r\n font-size: 12px;\r\n}\r\n</style>\r\n","<template>\r\n <!-- 有子菜单:渲染 el-sub-menu -->\r\n <el-sub-menu\r\n v-if=\"menuItem.children && menuItem.children.length > 0\"\r\n :index=\"menuItem.path\"\r\n :class=\"[`j-sidebar__depth-${depth}`]\"\r\n >\r\n <template #title>\r\n <JIconRender v-if=\"menuItem.icon && depth === 0\" :icon=\"menuItem.icon\" />\r\n <span class=\"j-sidebar__menu-item-title\">{{ menuItem.title || menuItem.name }}</span>\r\n </template>\r\n <!-- 递归渲染子菜单 -->\r\n <JSideMenuItem\r\n v-for=\"child in menuItem.children\"\r\n :key=\"child.path\"\r\n :menu-item=\"child\"\r\n :is-collapse=\"isCollapse\"\r\n :depth=\"depth + 1\"\r\n @menu-click=\"(item) => emit('menu-click', item)\"\r\n />\r\n </el-sub-menu>\r\n\r\n <!-- 无子菜单:渲染 el-menu-item -->\r\n <el-menu-item\r\n v-else\r\n :index=\"menuItem.path\"\r\n :class=\"[`j-sidebar__depth-${depth}`]\"\r\n @click=\"handleClick\"\r\n >\r\n <JIconRender v-if=\"menuItem.icon && depth === 0\" :icon=\"menuItem.icon\" />\r\n <template #title>\r\n <span>{{ menuItem.title || menuItem.name }}</span>\r\n </template>\r\n </el-menu-item>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { SidebarMenuItem } from './types'\r\nimport { ElSubMenu, ElMenuItem } from 'element-plus'\r\nimport { JIconRender } from '../JIconRender'\r\n\r\ndefineOptions({\r\n name: 'JSideMenuItem'\r\n})\r\n\r\ninterface Props {\r\n menuItem: SidebarMenuItem\r\n isCollapse?: boolean\r\n depth?: number\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), { depth: 0 })\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem]\r\n}>()\r\n\r\nconst handleClick = () => {\r\n emit('menu-click', props.menuItem)\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.j-sidebar__menu-item-title {\r\n padding: 0 10px;\r\n}\r\n\r\n/* 二级菜单:灰色标题,作为分类标签 */\r\n.j-sidebar__depth-1 > :deep(.el-sub-menu__title) {\r\n color: #909399;\r\n font-size: 13px;\r\n}\r\n\r\n/* 二级叶子菜单(无子项)也用灰色 */\r\n:deep(.j-sidebar__depth-1.el-menu-item) {\r\n color: #909399;\r\n font-size: 13px;\r\n}\r\n\r\n/* 三级菜单:文字稍大 */\r\n:deep(.j-sidebar__depth-2.el-menu-item) {\r\n font-size: 15px;\r\n}\r\n</style>\r\n","<template>\r\n <div\r\n :class=\"['j-sidebar', innerCollapsed ? 'j-sidebar--collapsed' : 'j-sidebar--expanded']\"\r\n :style=\"{ width: innerCollapsed ? `${collapsedWidth}px` : `${expandedWidth}px` }\"\r\n >\r\n <!-- Logo 区域 -->\r\n <div class=\"j-sidebar__logo\">\r\n <slot name=\"logo\" :collapsed=\"innerCollapsed\">\r\n <h2 class=\"j-sidebar__title\">\r\n {{ innerCollapsed ? (collapsedTitle || title?.charAt(0) || '') : title }}\r\n </h2>\r\n </slot>\r\n </div>\r\n\r\n <!-- 菜单区域 -->\r\n <div class=\"j-sidebar__menu-wrapper\">\r\n <el-menu\r\n :default-active=\"activeMenu\"\r\n class=\"j-sidebar__menu\"\r\n :router=\"router\"\r\n :collapse=\"innerCollapsed\"\r\n :collapse-transition=\"false\"\r\n >\r\n <JSideMenuItem\r\n v-for=\"route in menus\"\r\n :key=\"route.path\"\r\n :menu-item=\"route\"\r\n :is-collapse=\"innerCollapsed\"\r\n @menu-click=\"(item) => emit('menu-click', item)\"\r\n />\r\n </el-menu>\r\n </div>\r\n\r\n <!-- 底部折叠按钮 -->\r\n <div class=\"j-sidebar__footer\">\r\n <slot name=\"footer\" :collapsed=\"innerCollapsed\" :toggle=\"toggleCollapse\">\r\n <button\r\n class=\"j-sidebar__collapse-btn\"\r\n @click=\"toggleCollapse\"\r\n >\r\n <JIconRender :icon=\"innerCollapsed ? 'mdi:chevron-right' : 'mdi:chevron-left'\" />\r\n </button>\r\n </slot>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch } from 'vue'\r\nimport { ElMenu } from 'element-plus'\r\nimport type { SidebarMenuItem } from './types'\r\nimport JSideMenuItem from './JSideMenuItem.vue'\r\nimport { JIconRender } from '../JIconRender'\r\n\r\ndefineOptions({\r\n name: 'JSidebar'\r\n})\r\n\r\ninterface Props {\r\n menus: SidebarMenuItem[]\r\n activeMenu?: string\r\n title?: string\r\n collapsedTitle?: string\r\n collapsed?: boolean\r\n collapsedWidth?: number\r\n expandedWidth?: number\r\n router?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n activeMenu: '',\r\n title: '',\r\n collapsedTitle: '',\r\n collapsed: false,\r\n collapsedWidth: 80,\r\n expandedWidth: 200,\r\n router: true,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'menu-click': [item: SidebarMenuItem]\r\n}>()\r\n\r\nconst innerCollapsed = ref(props.collapsed)\r\n\r\nwatch(() => props.collapsed, (val) => {\r\n innerCollapsed.value = val\r\n})\r\n\r\nconst toggleCollapse = () => {\r\n innerCollapsed.value = !innerCollapsed.value\r\n emit('update:collapsed', innerCollapsed.value)\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.j-sidebar {\r\n position: relative;\r\n background-color: $color-white;\r\n box-shadow: 2px 0 6px rgba(0, 21, 41, 0.08);\r\n overflow-x: hidden;\r\n transition: width 0.3s ease-in-out;\r\n flex-shrink: 0;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n}\r\n\r\n.j-sidebar__logo {\r\n padding: 0 $spacing-md;\r\n display: flex;\r\n align-items: center;\r\n height: 50px;\r\n flex-shrink: 0;\r\n}\r\n\r\n\r\n.j-sidebar__title {\r\n margin: 0;\r\n font-size: $font-size-lg;\r\n font-weight: bold;\r\n color: $color-primary;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n}\r\n\r\n.j-sidebar__menu-wrapper {\r\n flex: 1;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n}\r\n\r\n.j-sidebar__menu {\r\n border: none;\r\n width: 100%;\r\n}\r\n\r\n.j-sidebar__footer {\r\n flex-shrink: 0;\r\n padding: $spacing-sm;\r\n display: flex;\r\n}\r\n\r\n// 折叠状态:覆盖 el-menu 样式,图标+文字上下排列\r\n.j-sidebar--collapsed {\r\n :deep(.el-menu--collapse) {\r\n width: 100%;\r\n\r\n .el-menu-item,\r\n .el-sub-menu__title {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n height: auto;\r\n line-height: normal;\r\n padding: $spacing-sm 0 !important;\r\n\r\n .el-icon,\r\n .iconify {\r\n margin: 0;\r\n }\r\n\r\n span {\r\n display: block !important;\r\n visibility: visible !important;\r\n overflow: visible !important;\r\n width: auto !important;\r\n height: auto !important;\r\n font-size: $font-size-xs;\r\n margin-top: $spacing-xs;\r\n }\r\n }\r\n\r\n // 隐藏子菜单展开箭头\r\n .el-sub-menu__icon-arrow {\r\n display: none;\r\n }\r\n }\r\n}\r\n\r\n.j-sidebar__collapse-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: $spacing-sm;\r\n border: none;\r\n border-radius: $border-radius-base;\r\n background-color: transparent;\r\n color: $color-text-secondary;\r\n cursor: pointer;\r\n transition: $transition-fast;\r\n\r\n &:hover {\r\n background-color: $color-bg-base;\r\n color: $color-primary;\r\n }\r\n\r\n}\r\n</style>\r\n","<template>\n <div class=\"j-admin-layout\" :style=\"layoutStyle\">\n <JSidebar\n v-if=\"sideMenus.length > 0\"\n v-model:collapsed=\"innerCollapsed\"\n :menus=\"sideMenus\"\n :active-menu=\"activeMenu\"\n :title=\"title\"\n :collapsed-title=\"collapsedTitle\"\n :collapsed-width=\"sidebarCollapsedWidth\"\n :expanded-width=\"sidebarExpandedWidth\"\n @menu-click=\"handleSideMenuClick\"\n >\n <template v-if=\"$slots.sidebarFooter\" #footer=\"{ collapsed: sidebarCollapsed, toggle }\">\n <slot name=\"sidebarFooter\" :collapsed=\"sidebarCollapsed\" :toggle=\"toggle\" />\n </template>\n </JSidebar>\n\n <div class=\"j-admin-layout__right-panel\">\n <div class=\"j-admin-layout__header\">\n <div class=\"j-admin-layout__header-left\">\n <div v-if=\"sideMenus.length === 0\" class=\"j-admin-layout__logo\">\n <slot name=\"logo\">\n <h2>{{ logoTitle || title }}</h2>\n </slot>\n </div>\n\n <el-menu\n mode=\"horizontal\"\n :default-active=\"activeTopMenu\"\n class=\"j-admin-layout__top-menu\"\n @select=\"handleTopMenuSelect\"\n >\n <el-menu-item\n v-for=\"menu in topMenus\"\n :key=\"menu.path\"\n :index=\"menu.path\"\n >\n <JIconRender v-if=\"menu.icon\" :icon=\"menu.icon\" class=\"j-admin-layout__top-menu-icon\" />\n <span>{{ menu.title || menu.name }}</span>\n </el-menu-item>\n </el-menu>\n </div>\n\n <div class=\"j-admin-layout__header-right\">\n <slot name=\"headerRight\" :username=\"username\">\n <el-dropdown v-if=\"showProfile || showLogout\">\n <span class=\"j-admin-layout__user\">\n {{ username }}\n <el-icon>\n <ArrowDown />\n </el-icon>\n </span>\n <template #dropdown>\n <el-dropdown-menu>\n <el-dropdown-item v-if=\"showProfile\" @click=\"emit('profile')\">\n {{ profileText }}\n </el-dropdown-item>\n <el-dropdown-item v-if=\"showLogout\" @click=\"emit('logout')\">\n {{ logoutText }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </slot>\n </div>\n </div>\n\n <div class=\"j-admin-layout__content-container\">\n <slot name=\"tabs\" />\n <div\n :class=\"[\n 'j-admin-layout__content',\n { 'j-admin-layout__content--scrollbar-hidden': hideContentScrollbar }\n ]\"\n >\n <slot />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ElMenu, ElMenuItem, ElDropdown, ElDropdownMenu, ElDropdownItem, ElIcon } from 'element-plus'\nimport { ArrowDown } from '@element-plus/icons-vue'\nimport { JSidebar, type SidebarMenuItem } from '../JSidebar'\nimport { JIconRender } from '../JIconRender'\nimport type { AdminLayoutMenuItem } from './types'\n\ndefineOptions({\n name: 'JAdminLayout'\n})\n\ninterface Props {\n topMenus: AdminLayoutMenuItem[]\n sideMenus: AdminLayoutMenuItem[]\n activeTopMenu?: string\n activeMenu?: string\n title?: string\n logoTitle?: string\n collapsedTitle?: string\n collapsed?: boolean\n username?: string\n profileText?: string\n logoutText?: string\n sidebarCollapsedWidth?: number\n sidebarExpandedWidth?: number\n headerHeight?: string\n contentPadding?: string\n contentBackground?: string\n headerBackground?: string\n showProfile?: boolean\n showLogout?: boolean\n hideContentScrollbar?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n activeTopMenu: '',\n activeMenu: '',\n title: '',\n logoTitle: '',\n collapsedTitle: '',\n collapsed: false,\n username: '',\n profileText: '个人资料',\n logoutText: '退出登录',\n sidebarCollapsedWidth: 80,\n sidebarExpandedWidth: 200,\n headerHeight: '50px',\n contentPadding: '10px',\n contentBackground: '#f0f2f5',\n headerBackground: '#ffffff',\n showProfile: true,\n showLogout: true,\n hideContentScrollbar: false,\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'top-menu-select': [path: string]\n 'side-menu-click': [item: SidebarMenuItem]\n profile: []\n logout: []\n}>()\n\nconst innerCollapsed = computed({\n get: () => props.collapsed,\n set: value => emit('update:collapsed', value),\n})\n\nconst layoutStyle = computed(() => ({\n '--j-admin-layout-header-height': props.headerHeight,\n '--j-admin-layout-content-padding': props.contentPadding,\n '--j-admin-layout-content-bg': props.contentBackground,\n '--j-admin-layout-header-bg': props.headerBackground,\n}))\n\nconst handleTopMenuSelect = (path: string) => {\n emit('top-menu-select', path)\n}\n\nconst handleSideMenuClick = (item: SidebarMenuItem) => {\n emit('side-menu-click', item)\n}\n</script>\n\n<style scoped lang=\"scss\">\n.j-admin-layout {\n width: 100vw;\n height: 100vh;\n display: flex;\n flex-direction: row;\n overflow: hidden;\n}\n\n.j-admin-layout__right-panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--j-admin-layout-content-bg);\n}\n\n.j-admin-layout__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n background-color: var(--j-admin-layout-header-bg);\n box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);\n height: var(--j-admin-layout-header-height);\n flex-shrink: 0;\n z-index: 100;\n}\n\n.j-admin-layout__header-left {\n display: flex;\n align-items: center;\n flex: 1;\n height: 100%;\n min-width: 0;\n}\n\n.j-admin-layout__logo {\n padding: 0 $spacing-md;\n display: flex;\n align-items: center;\n height: var(--j-admin-layout-header-height);\n flex-shrink: 0;\n}\n\n.j-admin-layout__logo h2 {\n margin: 0;\n font-size: $font-size-lg;\n font-weight: bold;\n color: $color-primary;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.j-admin-layout__top-menu {\n flex: 1;\n height: var(--j-admin-layout-header-height);\n border-bottom: none;\n min-width: 0;\n}\n\n.j-admin-layout__top-menu-icon {\n margin-right: $spacing-xs;\n}\n\n.j-admin-layout__header-right {\n display: flex;\n align-items: center;\n gap: $spacing-md;\n padding-right: $spacing-md;\n flex-shrink: 0;\n}\n\n.j-admin-layout__user {\n cursor: pointer;\n display: flex;\n align-items: center;\n color: $color-text-regular;\n}\n\n.j-admin-layout__user:hover {\n color: $color-primary;\n}\n\n.j-admin-layout__content-container {\n flex: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n background-color: var(--j-admin-layout-content-bg);\n}\n\n.j-admin-layout__content {\n padding: var(--j-admin-layout-content-padding);\n flex: 1;\n overflow: auto;\n}\n\n.j-admin-layout__content--scrollbar-hidden {\n scrollbar-width: none;\n -ms-overflow-style: none;\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n</style>\n","/**\r\n * 递归转换值(处理嵌套对象和数组)\r\n */\r\nfunction deepTransformValue(value: any, transformer: (obj: Record<string, any>) => Record<string, any>): any {\r\n if (Array.isArray(value)) {\r\n return value.map((item) => deepTransformValue(item, transformer))\r\n }\r\n if (value !== null && typeof value === 'object' && !(value instanceof Date)) {\r\n return transformer(value)\r\n }\r\n return value\r\n}\r\n\r\n/**\r\n * 将对象的 key 从小驼峰转为下划线格式(支持深度嵌套)\r\n * @param obj 需要转换的对象\r\n * @param excludeKeys 不需要转换的 key 数组(可选)\r\n */\r\nexport function toSnakeCase<T extends Record<string, any>>(\r\n obj: T,\r\n excludeKeys: string[] = []\r\n): Record<string, any> {\r\n return Object.fromEntries(\r\n Object.entries(obj).map(([key, value]) => {\r\n const newValue = deepTransformValue(value, (v) => toSnakeCase(v, excludeKeys))\r\n if (excludeKeys.includes(key)) {\r\n return [key, newValue]\r\n }\r\n const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase()\r\n return [snakeKey, newValue]\r\n })\r\n )\r\n}\r\n\r\n/**\r\n * 将对象的 key 从下划线格式转为小驼峰(支持深度嵌套)\r\n * @param obj 需要转换的对象\r\n * @param excludeKeys 不需要转换的 key 数组(可选)\r\n */\r\nexport function toCamelCase<T extends Record<string, any>>(\r\n obj: T,\r\n excludeKeys: string[] = []\r\n): Record<string, any> {\r\n return Object.fromEntries(\r\n Object.entries(obj).map(([key, value]) => {\r\n const newValue = deepTransformValue(value, (v) => toCamelCase(v, excludeKeys))\r\n if (excludeKeys.includes(key)) {\r\n return [key, newValue]\r\n }\r\n const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase())\r\n return [camelKey, newValue]\r\n })\r\n )\r\n}\r\n\r\n/**\r\n * 从对象中提取指定的 key\r\n * @param obj 源对象\r\n * @param keys 要提取的 key 数组\r\n */\r\nexport function pick<T extends Record<string, any>, K extends keyof T>(\r\n obj: T,\r\n keys: K[]\r\n): Pick<T, K> {\r\n const result = {} as Pick<T, K>\r\n for (const key of keys) {\r\n if (key in obj) {\r\n result[key] = obj[key]\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 从对象中排除指定的 key\r\n * @param obj 源对象\r\n * @param keys 要排除的 key 数组\r\n */\r\nexport function omit<T extends Record<string, any>, K extends keyof T>(\r\n obj: T,\r\n keys: K[]\r\n): Omit<T, K> {\r\n const result = { ...obj }\r\n for (const key of keys) {\r\n delete result[key]\r\n }\r\n return result as Omit<T, K>\r\n}\r\n\r\n/**\r\n * 深拷贝对象\r\n * @param obj 需要拷贝的对象\r\n */\r\nexport function deepClone<T>(obj: T): T {\r\n if (obj === null || typeof obj !== 'object') return obj\r\n if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T\r\n if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags) as unknown as T\r\n if (Array.isArray(obj)) return obj.map((item) => deepClone(item)) as unknown as T\r\n const cloned = {} as Record<string, any>\r\n for (const key of Object.keys(obj)) {\r\n cloned[key] = deepClone((obj as Record<string, any>)[key])\r\n }\r\n return cloned as T\r\n}\r\n\r\n/**\r\n * 过滤对象中的空值(null、undefined、空字符串)\r\n * @param obj 需要过滤的对象\r\n * @param deep 是否递归过滤嵌套对象,默认 false\r\n */\r\nexport function filterEmpty<T extends Record<string, any>>(\r\n obj: T,\r\n deep = false\r\n): Partial<T> {\r\n const result = {} as Record<string, any>\r\n for (const [key, value] of Object.entries(obj)) {\r\n if (value === null || value === undefined || value === '') continue\r\n if (deep && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\r\n const filtered = filterEmpty(value, true)\r\n if (Object.keys(filtered).length > 0) {\r\n result[key] = filtered\r\n }\r\n } else {\r\n result[key] = value\r\n }\r\n }\r\n return result as Partial<T>\r\n}\r\n\r\n/**\r\n * 将嵌套对象扁平化为单层对象\r\n * @param obj 需要扁平化的对象\r\n * @param prefix key 前缀,默认为空\r\n * @param separator 分隔符,默认为 '.'\r\n * @example flatten({ a: { b: 1, c: { d: 2 } } }) => { 'a.b': 1, 'a.c.d': 2 }\r\n */\r\nexport function flatten(\r\n obj: Record<string, any>,\r\n prefix = '',\r\n separator = '.'\r\n): Record<string, any> {\r\n const result: Record<string, any> = {}\r\n for (const [key, value] of Object.entries(obj)) {\r\n const newKey = prefix ? `${prefix}${separator}${key}` : key\r\n if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\r\n Object.assign(result, flatten(value, newKey, separator))\r\n } else {\r\n result[newKey] = value\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 将扁平化的对象还原为嵌套对象\r\n * @param obj 扁平化的对象\r\n * @param separator 分隔符,默认为 '.'\r\n * @example unflatten({ 'a.b': 1, 'a.c.d': 2 }) => { a: { b: 1, c: { d: 2 } } }\r\n */\r\nexport function unflatten(\r\n obj: Record<string, any>,\r\n separator = '.'\r\n): Record<string, any> {\r\n const result: Record<string, any> = {}\r\n for (const [key, value] of Object.entries(obj)) {\r\n const parts = key.split(separator)\r\n let current = result\r\n for (let i = 0; i < parts.length - 1; i++) {\r\n const k = parts[i] as string\r\n if (!(k in current)) {\r\n current[k] = {}\r\n }\r\n current = current[k]\r\n }\r\n current[parts[parts.length - 1] as string] = value\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 将 { key: label } 对象转换为 el-table-column 所需的表头数组\r\n * @param fieldMap 字段映射对象\r\n * @example toTableColumns({ id: '主键', name: '名称' }) => [{ id: 'id', label: '主键', prop: 'id' }, ...]\r\n */\r\nexport function toTableColumns(fieldMap: Record<string, string>) {\r\n return Object.entries(fieldMap).map(([key, label]) => ({\r\n id: key,\r\n label,\r\n prop: key,\r\n }))\r\n}\r\n","import type { RouteRecordRaw } from 'vue-router'\r\nimport type { BackendRoute } from './types'\r\n\r\ntype ViewModules = Record<string, () => Promise<unknown>>\r\n\r\ninterface RouteTreeNode extends BackendRoute {\r\n children?: RouteTreeNode[]\r\n}\r\n\r\nexport function getComponentImport(componentPath: string, viewModules: ViewModules): (() => Promise<unknown>) | null {\r\n if (!componentPath) return null\r\n\r\n let normalizedPath = componentPath.replace(/^@\\//, '')\r\n if (!normalizedPath.startsWith('views/')) normalizedPath = `views/${normalizedPath}`\r\n if (!normalizedPath.endsWith('.vue')) normalizedPath = `${normalizedPath}.vue`\r\n\r\n const fullPath = `/src/${normalizedPath}`\r\n\r\n return (viewModules[fullPath] ?? viewModules['/src/views/common/NotFound.vue'] ?? null) as (() => Promise<unknown>) | null\r\n}\r\n\r\nexport function buildRouteTree(routes: BackendRoute[]): RouteTreeNode[] {\r\n const routeMap = new Map<string, RouteTreeNode>()\r\n const rootRoutes: RouteTreeNode[] = []\r\n\r\n routes.forEach(route => routeMap.set(route.name, { ...route, children: [] }))\r\n\r\n routes.forEach(route => {\r\n const currentRoute = routeMap.get(route.name)!\r\n if (!route.parent_name) {\r\n rootRoutes.push(currentRoute)\r\n } else {\r\n const parentRoute = routeMap.get(route.parent_name)\r\n if (parentRoute) {\r\n parentRoute.children ??= []\r\n parentRoute.children.push(currentRoute)\r\n } else {\r\n rootRoutes.push(currentRoute)\r\n }\r\n }\r\n })\r\n return rootRoutes\r\n}\r\n\r\nexport function transformBackendRoutes(\r\n backendRoutes: RouteTreeNode[],\r\n viewModules: ViewModules,\r\n projectName?: string\r\n): RouteRecordRaw[] {\r\n return backendRoutes\r\n .filter(route => !projectName || !route.project || route.project === projectName)\r\n .map(route => {\r\n const routeConfig: Partial<RouteRecordRaw> = {\r\n path: route.path,\r\n name: route.name,\r\n meta: {\r\n requiresAuth: route.requiresAuth === 1,\r\n title: route.title,\r\n permissions: route.permissions\r\n ? route.permissions.split(',').map(p => p.trim()).filter(Boolean)\r\n : [],\r\n params: route.params\r\n ? route.params.split('&').map(p => p.trim()).filter(Boolean)\r\n : [],\r\n },\r\n }\r\n\r\n const component = getComponentImport(route.component, viewModules)\r\n if (component) routeConfig.component = component as RouteRecordRaw['component']\r\n\r\n if (route.redirect) routeConfig.redirect = route.redirect\r\n\r\n if (route.children?.length) {\r\n routeConfig.children = transformBackendRoutes(route.children, viewModules, projectName)\r\n }\r\n\r\n return routeConfig as RouteRecordRaw\r\n })\r\n}\r\n\r\nexport function findFirstAllowedRoute(\r\n backendRoutes: BackendRoute[],\r\n routePermissions: string[],\r\n layoutRouteName: string,\r\n projectName?: string\r\n): BackendRoute | undefined {\r\n const routeMap = new Map<string, BackendRoute>()\r\n backendRoutes.forEach(route => routeMap.set(route.name, route))\r\n\r\n const isDescendantOfLayout = (route: BackendRoute): boolean => {\r\n let parentName = route.parent_name\r\n const visited = new Set<string>()\r\n\r\n while (parentName) {\r\n if (parentName === layoutRouteName) return true\r\n if (visited.has(parentName)) return false\r\n\r\n visited.add(parentName)\r\n const parentRoute = routeMap.get(parentName)\r\n if (!parentRoute) return false\r\n parentName = parentRoute.parent_name\r\n }\r\n\r\n return false\r\n }\r\n\r\n return backendRoutes\r\n .filter(route =>\r\n route.name !== layoutRouteName &&\r\n route.is_show === 1 &&\r\n route.component &&\r\n routePermissions.includes(route.code) &&\r\n isDescendantOfLayout(route) &&\r\n (!projectName || !route.project || route.project === projectName)\r\n )\r\n .sort((a, b) => a.sorts - b.sorts)[0]\r\n}\r\n","import type { NavigationGuardNext, Router } from 'vue-router'\r\nimport NProgress from 'nprogress'\r\nimport 'nprogress/nprogress.css'\r\nimport { ElMessage } from 'element-plus'\r\nimport type { RouterGuardOptions } from './types'\r\nimport { buildRouteTree, transformBackendRoutes } from './helpers'\r\n\r\nNProgress.configure({ showSpinner: false })\r\n\r\nconst isDev = import.meta.env.DEV\r\nconst log = (...args: unknown[]) => { if (isDev) console.log('[Router]', ...args) }\r\nconst warn = (...args: unknown[]) => { if (isDev) console.warn('[Router]', ...args) }\r\nconst logError = (...args: unknown[]) => { console.error('[Router]', ...args) }\r\n\r\nexport function createRouterGuard(router: Router, options: RouterGuardOptions) {\r\n const {\r\n getRouteStore,\r\n getPermissionStore,\r\n getUserStore,\r\n isAuthenticated,\r\n viewModules,\r\n homeComponent,\r\n notFoundComponent,\r\n projectName,\r\n layoutRouteName = 'Layout',\r\n publicPaths = ['/login', '/register'],\r\n loginRouteName = 'login',\r\n forbiddenRouteName = 'Forbidden',\r\n homeRouteName = 'Home',\r\n homeRoutePath = 'home',\r\n homeTitle = '\\u9996\\u9875',\r\n noPermissionMessage = '\\u6682\\u65e0\\u6743\\u9650',\r\n } = options\r\n\r\n let hasAddedRoutes = false\r\n\r\n function addDynamicRoutes(backendRoutes: ReturnType<typeof getRouteStore>['backendRoutes']) {\r\n const treeRoutes = buildRouteTree(backendRoutes)\r\n const layoutRoute = treeRoutes.find(route => route.name === layoutRouteName)\r\n const childRoutes = layoutRoute?.children\r\n ? transformBackendRoutes(layoutRoute.children, viewModules, projectName)\r\n : []\r\n\r\n childRoutes.forEach(route => {\r\n if (route.name && router.hasRoute(route.name)) return\r\n router.addRoute(layoutRouteName, route)\r\n log(`Add route: ${String(route.name)} -> ${route.path}`)\r\n })\r\n\r\n if (!router.hasRoute(homeRouteName)) {\r\n router.addRoute(layoutRouteName, {\r\n path: homeRoutePath,\r\n name: homeRouteName,\r\n component: homeComponent as never,\r\n meta: { title: homeTitle },\r\n })\r\n }\r\n\r\n if (!router.hasRoute('NotFound')) {\r\n router.addRoute({\r\n path: '/:pathMatch(.*)*',\r\n name: 'NotFound',\r\n component: notFoundComponent as never,\r\n })\r\n }\r\n\r\n log(`Dynamic routes added: ${childRoutes.length}`)\r\n }\r\n\r\n function redirectToHomeOrContinue(\r\n toName: ReturnType<Router['resolve']>['name'],\r\n next: NavigationGuardNext\r\n ) {\r\n if (toName === homeRouteName) {\r\n next()\r\n return\r\n }\r\n\r\n if (router.hasRoute(homeRouteName)) {\r\n next({ name: homeRouteName, replace: true })\r\n } else {\r\n next()\r\n }\r\n }\r\n\r\n router.beforeEach(async (to, _from, next) => {\r\n NProgress.start()\r\n\r\n if (isAuthenticated() && !hasAddedRoutes) {\r\n try {\r\n const routeStore = getRouteStore()\r\n const permissionStore = getPermissionStore()\r\n\r\n await routeStore.initRoutes()\r\n await permissionStore.loadUserPermissions()\r\n\r\n addDynamicRoutes(routeStore.backendRoutes)\r\n hasAddedRoutes = true\r\n\r\n if (to.path === '/' || to.path === '') {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n if (to.name === layoutRouteName) {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n const currentRoute = routeStore.backendRoutes.find(route => route.name === to.name)\r\n if (currentRoute && !permissionStore.routePermissions.includes(currentRoute.code)) {\r\n warn('No route permission:', to.path)\r\n next({ name: forbiddenRouteName, replace: true })\r\n return\r\n }\r\n\r\n next({\r\n path: to.path,\r\n query: to.query,\r\n hash: to.hash,\r\n replace: true,\r\n })\r\n return\r\n } catch (err) {\r\n logError('Failed to load route permissions:', err)\r\n hasAddedRoutes = false\r\n await getUserStore().logout()\r\n next({ name: loginRouteName, replace: true })\r\n return\r\n }\r\n }\r\n\r\n if (publicPaths.includes(to.path)) {\r\n if (isAuthenticated()) {\r\n next({ path: '/', replace: true })\r\n } else {\r\n next()\r\n }\r\n return\r\n }\r\n\r\n if (!isAuthenticated()) {\r\n next({ name: loginRouteName, replace: true })\r\n return\r\n }\r\n\r\n const permissionStore = getPermissionStore()\r\n const routeStore = getRouteStore()\r\n\r\n if (to.name === homeRouteName) {\r\n next()\r\n return\r\n }\r\n\r\n if (permissionStore.routePermissions.length === 0) {\r\n if (to.path !== '/') {\r\n ElMessage.warning(noPermissionMessage)\r\n next({ path: '/', replace: true })\r\n } else {\r\n redirectToHomeOrContinue(to.name, next)\r\n }\r\n return\r\n }\r\n\r\n if (to.path === '/') {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n if (to.name === layoutRouteName) {\r\n redirectToHomeOrContinue(to.name, next)\r\n return\r\n }\r\n\r\n const currentRoute = routeStore.backendRoutes.find(route => route.name === to.name)\r\n if (currentRoute && !permissionStore.routePermissions.includes(currentRoute.code)) {\r\n warn('No route permission:', to.path)\r\n next({ name: forbiddenRouteName, replace: true })\r\n return\r\n }\r\n\r\n next()\r\n })\r\n\r\n router.afterEach(() => NProgress.done())\r\n\r\n return {\r\n resetRouterState: () => { hasAddedRoutes = false },\r\n }\r\n}\r\n","import type { App } from 'vue'\r\nimport { JButton } from './components/JButton'\r\nimport { JUploadImg } from './components/JUploadImg'\r\nimport { JIconRender } from './components/JIconRender'\r\nimport { JSearchHeader } from './components/JSearchHeader'\r\nimport { JImportExcel } from './components/JImportExcel'\r\nimport { JSidebar, JSideMenuItem } from './components/JSidebar'\r\nimport { JAdminLayout } from './components/JAdminLayout'\r\n\r\n// 组件列表,新增组件后在此处注册\r\nconst components = [JButton, JUploadImg, JIconRender, JSearchHeader, JImportExcel, JSidebar, JSideMenuItem, JAdminLayout]\r\n\r\n// 全量安装插件(app.use(JgyComponents))\r\nconst install = (app: App) => {\r\n components.forEach((component) => {\r\n app.component(component.name ?? component.__name ?? '', component)\r\n })\r\n}\r\n\r\n// 默认导出插件对象\r\nexport default { install }\r\n\r\n// 按需导出各组件\r\nexport { JButton, JUploadImg, JIconRender, JSearchHeader, JImportExcel, JSidebar, JSideMenuItem, JAdminLayout }\r\n\r\n// 导出工具方法\r\nexport { toSnakeCase, toCamelCase, pick, omit, deepClone, filterEmpty, flatten, unflatten, toTableColumns } from './utils'\r\n\r\n// 导出路由工具\r\nexport { createRouterGuard, buildRouteTree, transformBackendRoutes, findFirstAllowedRoute, getComponentImport } from './router'\r\nexport type { BackendRoute, RouteStoreAdapter, PermissionStoreAdapter, UserStoreAdapter, RouterGuardOptions } from './router'\r\n\r\n// 导出类型\r\nexport type { ButtonType, ButtonSize } from './components/JButton'\r\nexport type { JUploadImgProps, JUploadImgEmits, ImageItem } from './components/JUploadImg'\r\nexport type { JIconRenderProps } from './components/JIconRender'\r\nexport type { JSearchHeaderProps } from './components/JSearchHeader'\r\nexport type { JImportExcelProps, ApiFn } from './components/JImportExcel'\r\nexport type { SidebarMenuItem, JSidebarProps, JSideMenuItemProps } from './components/JSidebar'\r\nexport type { AdminLayoutMenuItem, JAdminLayoutProps } from './components/JAdminLayout'\r\n"],"names":["props","__props","emit","__emit","handleClick","event","_createElementBlock","_openBlock","_hoisted_2","$slots","_hoisted_3","_renderSlot","_ctx","imageUrl","ref","showPreview","showTipText","handlePreviewSingle","handleRemoveSingle","ElMessage","imageList","uploading","initImageList","urls","url","index","watch","val","handleDragEnd","emitMultipleUpdate","handleRemove","handleFileChange","uploadFile","file","error","item","_hoisted_1","_createVNode","_unref","draggable","$event","_withCtx","element","_createElementVNode","ElImage","img","_withModifiers","ElIcon","Delete","_createBlock","ElUpload","Loading","Plus","_Fragment","_hoisted_5","_hoisted_6","ZoomIn","ElImageViewer","_hoisted_7","_hoisted_8","_toDisplayString","_mergeProps","$attrs","formItemsRef","expanded","isAnimated","itemCount","collapsedHeight","shouldCollapse","computed","measureItems","items","lastVisibleItem","wrapperRect","lastVisibleRect","toggleExpand","el","onEnd","observer","onMounted","onBeforeUnmount","ElCard","ElForm","_normalizeClass","ElButton","Search","_cache","Refresh","_createTextVNode","ArrowDown","visible","_useModel","submitting","fieldMap","renderData","stats","statusMap","dataColumns","key","label","handleOpen","res","raw","reader","e","workbook","XLSX","wsName","ws","dataRows","row","cell","list","col","handleDel","idx","handleConfirm","data","_status","_error","rest","r","handleDownloadTemplate","wb","headers","tr","h","handleClose","ElDialog","ElDivider","ElLink","ElRow","ElCol","_hoisted_4","ElTable","_renderList","ElTableColumn","$index","ElSubMenu","JIconRender","child","_component_JSideMenuItem","ElMenuItem","innerCollapsed","toggleCollapse","_normalizeStyle","_a","ElMenu","route","JSideMenuItem","value","layoutStyle","handleTopMenuSelect","path","handleSideMenuClick","JSidebar","sidebarCollapsed","toggle","menu","ElDropdown","ElDropdownMenu","ElDropdownItem","deepTransformValue","transformer","toSnakeCase","obj","excludeKeys","newValue","v","toCamelCase","_","char","pick","keys","result","omit","deepClone","cloned","filterEmpty","deep","filtered","flatten","prefix","separator","newKey","unflatten","parts","current","i","k","toTableColumns","getComponentImport","componentPath","viewModules","normalizedPath","fullPath","buildRouteTree","routes","routeMap","rootRoutes","currentRoute","parentRoute","transformBackendRoutes","backendRoutes","projectName","routeConfig","p","component","findFirstAllowedRoute","routePermissions","layoutRouteName","isDescendantOfLayout","parentName","visited","a","b","NProgress","log","args","warn","logError","createRouterGuard","router","options","getRouteStore","getPermissionStore","getUserStore","isAuthenticated","homeComponent","notFoundComponent","publicPaths","loginRouteName","forbiddenRouteName","homeRouteName","homeRoutePath","homeTitle","noPermissionMessage","hasAddedRoutes","addDynamicRoutes","layoutRoute","childRoutes","redirectToHomeOrContinue","toName","next","to","_from","routeStore","permissionStore","err","components","JButton","JUploadImg","JSearchHeader","JImportExcel","JAdminLayout","app"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .j-button[data-v-22864824]{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:8px 16px;font-size:14px;font-weight:400;line-height:1;white-space:nowrap;cursor:pointer;border:1px solid #dcdfe6;border-radius:4px;background-color:#fff;color:#606266;transition:all .15s ease;outline:none;-webkit-user-select:none;user-select:none}.j-button[data-v-22864824]:hover{color:#409eff;border-color:#409eff;background-color:#fff}.j-button--small[data-v-22864824]{padding:5px 12px;font-size:12px}.j-button--medium[data-v-22864824]{padding:8px 16px;font-size:14px}.j-button--large[data-v-22864824]{padding:12px 20px;font-size:16px}.j-button--primary[data-v-22864824]{background-color:#409eff;border-color:#409eff;color:#fff}.j-button--primary[data-v-22864824]:hover{background-color:#73b8ff;border-color:#73b8ff;color:#fff}.j-button--success[data-v-22864824]{background-color:#67c23a;border-color:#67c23a;color:#fff}.j-button--success[data-v-22864824]:hover{background-color:#85cf60;border-color:#85cf60;color:#fff}.j-button--danger[data-v-22864824]{background-color:#f56c6c;border-color:#f56c6c;color:#fff}.j-button--danger[data-v-22864824]:hover{background-color:#f89c9c;border-color:#f89c9c;color:#fff}.j-button--warning[data-v-22864824]{background-color:#e6a23c;border-color:#e6a23c;color:#fff}.j-button--warning[data-v-22864824]:hover{background-color:#ecb869;border-color:#ecb869;color:#fff}.j-button--plain.j-button--primary[data-v-22864824]{background-color:#fff;color:#409eff}.j-button--plain.j-button--primary[data-v-22864824]:hover{background-color:#409eff;color:#fff}.j-button--round[data-v-22864824]{border-radius:20px}.j-button--disabled[data-v-22864824]{opacity:.5;cursor:not-allowed;pointer-events:none}.j-button--loading[data-v-22864824]{opacity:.7;cursor:not-allowed;pointer-events:none}.j-button__loading-icon[data-v-22864824]{display:inline-block;width:14px;height:14px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:j-spin-22864824 .6s linear infinite}@keyframes j-spin-22864824{to{transform:rotate(360deg)}}.j-upload-img-container[data-v-94fad0af]{display:inline-block}.images-wrapper[data-v-94fad0af]{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}.images-list[data-v-94fad0af]{display:flex;flex-wrap:wrap;gap:8px}.image-item[data-v-94fad0af]{position:relative;width:74px;height:74px;border:1px solid var(--el-border-color);border-radius:6px;overflow:hidden;cursor:move}.image-preview[data-v-94fad0af]{width:100%;height:100%}.delete-btn[data-v-94fad0af]{position:absolute;top:2px;right:2px;width:20px;height:20px;background-color:#0009;border-radius:50%;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .3s;cursor:pointer;z-index:1}.image-item:hover .delete-btn[data-v-94fad0af]{opacity:1}.delete-btn[data-v-94fad0af]:hover{background-color:#000c}.icon-delete[data-v-94fad0af]{font-size:12px;color:#fff}.image-uploader[data-v-94fad0af]{display:inline-block;vertical-align:top}.image-uploader[data-v-94fad0af] .el-upload{border:1px dashed var(--el-border-color);border-radius:6px;cursor:pointer;position:relative;overflow:hidden;transition:var(--el-transition-duration-fast);width:74px!important;height:74px!important}.image-uploader[data-v-94fad0af] .el-upload:hover{border-color:var(--el-color-primary)}.uploader-icon[data-v-94fad0af]{font-size:20px;color:#8c939d;width:74px;height:74px;text-align:center;display:flex;align-items:center;justify-content:center}.single-image-item[data-v-94fad0af]{cursor:default}.image-actions[data-v-94fad0af]{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;gap:12px;opacity:0;transition:opacity .3s}.single-image-item:hover .image-actions[data-v-94fad0af],.image-item:hover .image-actions[data-v-94fad0af]{opacity:1}.action-icon[data-v-94fad0af]{font-size:18px;color:#fff;cursor:pointer;transition:transform .2s}.action-icon[data-v-94fad0af]:hover{transform:scale(1.2)}.upload-tip[data-v-94fad0af]{margin-top:8px;font-size:12px;color:#909399;line-height:1.5}.j-search-header-card[data-v-d3f83c3f] .el-card__body{padding:16px 20px 12px}.search-form[data-v-d3f83c3f]{display:flex;flex-wrap:wrap;align-items:center;gap:0}.form-items-wrapper[data-v-d3f83c3f]{display:flex;flex-wrap:wrap;align-items:center;overflow:hidden}.form-items-wrapper.is-animated[data-v-d3f83c3f]{transition:max-height .3s ease-in-out}.form-items-wrapper[data-v-d3f83c3f] .el-form-item{margin-bottom:10px;margin-right:16px}.action-bar[data-v-d3f83c3f]{display:flex;align-items:center;justify-content:space-between;padding-top:10px;border-top:1px solid #ebeef5}.action-left[data-v-d3f83c3f]{display:flex;gap:8px}.toggle-icon[data-v-d3f83c3f]{margin-left:2px;transition:transform .25s}.toggle-icon.is-expanded[data-v-d3f83c3f]{transform:rotate(180deg)}.j-import-upload-card[data-v-c7bcbb68]{margin-bottom:16px}.j-import-upload-card-title[data-v-c7bcbb68]{font-size:14px;color:#606266}.j-import-upload-row[data-v-c7bcbb68]{display:flex;justify-content:space-between;align-items:center}.j-import-no-wrap[data-v-c7bcbb68]{white-space:nowrap}.j-import-stats-row[data-v-c7bcbb68]{margin:12px 0}.j-import-stats-cols[data-v-c7bcbb68]{text-align:center;line-height:1.5}.j-import-stat-item[data-v-c7bcbb68]{padding:0 8px}.j-import-stat-num[data-v-c7bcbb68]{font-size:20px}.j-import-stat-label[data-v-c7bcbb68]{font-size:12px;color:#909399}.j-import-confirm-col[data-v-c7bcbb68]{text-align:right;padding-top:10px}.j-import-data-table[data-v-c7bcbb68]{margin-top:10px}.j-import-status-fail[data-v-c7bcbb68]{color:#f56c6c;font-size:12px}.j-sidebar__menu-item-title[data-v-3787a646]{padding:0 10px}.j-sidebar__depth-1[data-v-3787a646]>.el-sub-menu__title{color:#909399;font-size:13px}[data-v-3787a646] .j-sidebar__depth-1.el-menu-item{color:#909399;font-size:13px}[data-v-3787a646] .j-sidebar__depth-2.el-menu-item{font-size:15px}.j-sidebar[data-v-2e746199]{position:relative;background-color:#fff;box-shadow:2px 0 6px #00152914;overflow-x:hidden;transition:width .3s ease-in-out;flex-shrink:0;display:flex;flex-direction:column;height:100%}.j-sidebar__logo[data-v-2e746199]{padding:0 16px;display:flex;align-items:center;height:50px;flex-shrink:0}.j-sidebar__title[data-v-2e746199]{margin:0;font-size:18px;font-weight:700;color:#409eff;white-space:nowrap;overflow:hidden}.j-sidebar__menu-wrapper[data-v-2e746199]{flex:1;overflow-y:auto;overflow-x:hidden}.j-sidebar__menu[data-v-2e746199]{border:none;width:100%}.j-sidebar__footer[data-v-2e746199]{flex-shrink:0;padding:8px;display:flex}.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse{width:100%}.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-menu-item,.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-sub-menu__title{display:flex;flex-direction:column;align-items:center;height:auto;line-height:normal;padding:8px 0!important}.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-menu-item .el-icon,.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-menu-item .iconify,.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-sub-menu__title .el-icon,.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-sub-menu__title .iconify{margin:0}.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-menu-item span,.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-sub-menu__title span{display:block!important;visibility:visible!important;overflow:visible!important;width:auto!important;height:auto!important;font-size:12px;margin-top:4px}.j-sidebar--collapsed[data-v-2e746199] .el-menu--collapse .el-sub-menu__icon-arrow{display:none}.j-sidebar__collapse-btn[data-v-2e746199]{display:flex;align-items:center;justify-content:center;padding:8px;border:none;border-radius:4px;background-color:transparent;color:#909399;cursor:pointer;transition:all .15s ease}.j-sidebar__collapse-btn[data-v-2e746199]:hover{background-color:#f5f7fa;color:#409eff}.j-admin-layout[data-v-d1b0f182]{width:100vw;height:100vh;display:flex;flex-direction:row;overflow:hidden}.j-admin-layout__right-panel[data-v-d1b0f182]{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:var(--j-admin-layout-content-bg)}.j-admin-layout__header[data-v-d1b0f182]{display:flex;align-items:center;justify-content:space-between;background-color:var(--j-admin-layout-header-bg);box-shadow:0 1px 4px #00152914;height:var(--j-admin-layout-header-height);flex-shrink:0;z-index:100}.j-admin-layout__header-left[data-v-d1b0f182]{display:flex;align-items:center;flex:1;height:100%;min-width:0}.j-admin-layout__logo[data-v-d1b0f182]{padding:0 16px;display:flex;align-items:center;height:var(--j-admin-layout-header-height);flex-shrink:0}.j-admin-layout__logo h2[data-v-d1b0f182]{margin:0;font-size:18px;font-weight:700;color:#409eff;white-space:nowrap;overflow:hidden}.j-admin-layout__top-menu[data-v-d1b0f182]{flex:1;height:var(--j-admin-layout-header-height);border-bottom:none;min-width:0}.j-admin-layout__top-menu-icon[data-v-d1b0f182]{margin-right:4px}.j-admin-layout__header-right[data-v-d1b0f182]{display:flex;align-items:center;gap:16px;padding-right:16px;flex-shrink:0}.j-admin-layout__user[data-v-d1b0f182]{cursor:pointer;display:flex;align-items:center;color:#606266}.j-admin-layout__user[data-v-d1b0f182]:hover{color:#409eff}.j-admin-layout__content-container[data-v-d1b0f182]{flex:1;overflow:hidden;display:flex;flex-direction:column;background-color:var(--j-admin-layout-content-bg)}.j-admin-layout__content[data-v-d1b0f182]{padding:var(--j-admin-layout-content-padding);flex:1;overflow:auto}.j-admin-layout__content--scrollbar-hidden[data-v-d1b0f182]{scrollbar-width:none;-ms-overflow-style:none}.j-admin-layout__content--scrollbar-hidden[data-v-d1b0f182]::-webkit-scrollbar{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .spinner,.nprogress-custom-parent #nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}to{transform:rotate(360deg)}}
1
+ .j-button[data-v-22864824]{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:8px 16px;font-size:14px;font-weight:400;line-height:1;white-space:nowrap;cursor:pointer;border:1px solid #dcdfe6;border-radius:4px;background-color:#fff;color:#606266;transition:all .15s ease;outline:none;-webkit-user-select:none;user-select:none}.j-button[data-v-22864824]:hover{color:#409eff;border-color:#409eff;background-color:#fff}.j-button--small[data-v-22864824]{padding:5px 12px;font-size:12px}.j-button--medium[data-v-22864824]{padding:8px 16px;font-size:14px}.j-button--large[data-v-22864824]{padding:12px 20px;font-size:16px}.j-button--primary[data-v-22864824]{background-color:#409eff;border-color:#409eff;color:#fff}.j-button--primary[data-v-22864824]:hover{background-color:#73b8ff;border-color:#73b8ff;color:#fff}.j-button--success[data-v-22864824]{background-color:#67c23a;border-color:#67c23a;color:#fff}.j-button--success[data-v-22864824]:hover{background-color:#85cf60;border-color:#85cf60;color:#fff}.j-button--danger[data-v-22864824]{background-color:#f56c6c;border-color:#f56c6c;color:#fff}.j-button--danger[data-v-22864824]:hover{background-color:#f89c9c;border-color:#f89c9c;color:#fff}.j-button--warning[data-v-22864824]{background-color:#e6a23c;border-color:#e6a23c;color:#fff}.j-button--warning[data-v-22864824]:hover{background-color:#ecb869;border-color:#ecb869;color:#fff}.j-button--plain.j-button--primary[data-v-22864824]{background-color:#fff;color:#409eff}.j-button--plain.j-button--primary[data-v-22864824]:hover{background-color:#409eff;color:#fff}.j-button--round[data-v-22864824]{border-radius:20px}.j-button--disabled[data-v-22864824]{opacity:.5;cursor:not-allowed;pointer-events:none}.j-button--loading[data-v-22864824]{opacity:.7;cursor:not-allowed;pointer-events:none}.j-button__loading-icon[data-v-22864824]{display:inline-block;width:14px;height:14px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:j-spin-22864824 .6s linear infinite}@keyframes j-spin-22864824{to{transform:rotate(360deg)}}.j-upload-img-container[data-v-7dc4f798]{display:inline-block}.images-wrapper[data-v-7dc4f798]{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}.images-list[data-v-7dc4f798]{display:flex;flex-wrap:wrap;gap:8px}.image-item[data-v-7dc4f798]{position:relative;width:74px;height:74px;border:1px solid var(--el-border-color);border-radius:6px;overflow:hidden;cursor:move}.image-preview[data-v-7dc4f798]{width:100%;height:100%}.delete-btn[data-v-7dc4f798]{position:absolute;top:2px;right:2px;width:20px;height:20px;background-color:#0009;border-radius:50%;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .3s;cursor:pointer;z-index:1}.image-item:hover .delete-btn[data-v-7dc4f798]{opacity:1}.delete-btn[data-v-7dc4f798]:hover{background-color:#000c}.icon-delete[data-v-7dc4f798]{font-size:12px;color:#fff}.image-uploader[data-v-7dc4f798]{display:inline-block;vertical-align:top}.image-uploader[data-v-7dc4f798] .el-upload{border:1px dashed var(--el-border-color);border-radius:6px;cursor:pointer;position:relative;overflow:hidden;transition:var(--el-transition-duration-fast);width:74px!important;height:74px!important}.image-uploader[data-v-7dc4f798] .el-upload:hover{border-color:var(--el-color-primary)}.uploader-icon[data-v-7dc4f798]{font-size:20px;color:#8c939d;width:74px;height:74px;text-align:center;display:flex;align-items:center;justify-content:center}.single-image-item[data-v-7dc4f798]{cursor:default}.image-actions[data-v-7dc4f798]{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;gap:12px;opacity:0;transition:opacity .3s}.single-image-item:hover .image-actions[data-v-7dc4f798],.image-item:hover .image-actions[data-v-7dc4f798]{opacity:1}.action-icon[data-v-7dc4f798]{font-size:18px;color:#fff;cursor:pointer;transition:transform .2s}.action-icon[data-v-7dc4f798]:hover{transform:scale(1.2)}.upload-tip[data-v-7dc4f798]{margin-top:8px;font-size:12px;color:#909399;line-height:1.5}.j-search-header-card[data-v-c1f4bf4c] .el-card__body{padding:16px 20px 12px}.search-form[data-v-c1f4bf4c]{display:flex;flex-wrap:wrap;align-items:center;gap:0}.form-items-wrapper[data-v-c1f4bf4c]{display:flex;flex-wrap:wrap;align-items:center;overflow:hidden}.form-items-wrapper.is-animated[data-v-c1f4bf4c]{transition:max-height .3s ease-in-out}.form-items-wrapper[data-v-c1f4bf4c] .el-form-item{margin-bottom:10px;margin-right:16px}.action-bar[data-v-c1f4bf4c]{display:flex;align-items:center;justify-content:space-between;padding-top:10px;border-top:1px solid #ebeef5}.action-left[data-v-c1f4bf4c]{display:flex;gap:8px}.toggle-icon[data-v-c1f4bf4c]{margin-left:2px;transition:transform .25s}.toggle-icon.is-expanded[data-v-c1f4bf4c]{transform:rotate(180deg)}.j-import-upload-card[data-v-74dcefaa]{margin-bottom:16px}.j-import-upload-card-title[data-v-74dcefaa]{font-size:14px;color:#606266}.j-import-upload-row[data-v-74dcefaa]{display:flex;justify-content:space-between;align-items:center}.j-import-no-wrap[data-v-74dcefaa]{white-space:nowrap}.j-import-stats-row[data-v-74dcefaa]{margin:12px 0}.j-import-stats-cols[data-v-74dcefaa]{text-align:center;line-height:1.5}.j-import-stat-item[data-v-74dcefaa]{padding:0 8px}.j-import-stat-num[data-v-74dcefaa]{font-size:20px}.j-import-stat-label[data-v-74dcefaa]{font-size:12px;color:#909399}.j-import-confirm-col[data-v-74dcefaa]{text-align:right;padding-top:10px}.j-import-data-table[data-v-74dcefaa]{margin-top:10px}.j-import-status-fail[data-v-74dcefaa]{color:#f56c6c;font-size:12px}.j-sidebar__menu-item-title[data-v-3a990426]{padding:0 10px}.j-sidebar__depth-1[data-v-3a990426]>.el-sub-menu__title{color:#909399;font-size:13px}[data-v-3a990426] .j-sidebar__depth-1.el-menu-item{color:#909399;font-size:13px}[data-v-3a990426] .j-sidebar__depth-2.el-menu-item{font-size:15px}.j-sidebar[data-v-a3fac9de]{position:relative;background-color:#fff;box-shadow:2px 0 6px #00152914;overflow-x:hidden;transition:width .3s ease-in-out;flex-shrink:0;display:flex;flex-direction:column;height:100%}.j-sidebar__logo[data-v-a3fac9de]{padding:0 16px;display:flex;align-items:center;height:50px;flex-shrink:0}.j-sidebar__title[data-v-a3fac9de]{margin:0;font-size:18px;font-weight:700;color:#409eff;white-space:nowrap;overflow:hidden}.j-sidebar__menu-wrapper[data-v-a3fac9de]{flex:1;overflow-y:auto;overflow-x:hidden}.j-sidebar__menu[data-v-a3fac9de]{border:none;width:100%}.j-sidebar__footer[data-v-a3fac9de]{flex-shrink:0;padding:8px;display:flex}.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse{width:100%}.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-menu-item,.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-sub-menu__title{display:flex;flex-direction:column;align-items:center;height:auto;line-height:normal;padding:8px 0!important}.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-menu-item .el-icon,.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-menu-item .iconify,.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-sub-menu__title .el-icon,.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-sub-menu__title .iconify{margin:0}.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-menu-item span,.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-sub-menu__title span{display:block!important;visibility:visible!important;overflow:visible!important;width:auto!important;height:auto!important;font-size:12px;margin-top:4px}.j-sidebar--collapsed[data-v-a3fac9de] .el-menu--collapse .el-sub-menu__icon-arrow{display:none}.j-sidebar__collapse-btn[data-v-a3fac9de]{display:flex;align-items:center;justify-content:center;padding:8px;border:none;border-radius:4px;background-color:transparent;color:#909399;cursor:pointer;transition:all .15s ease}.j-sidebar__collapse-btn[data-v-a3fac9de]:hover{background-color:#f5f7fa;color:#409eff}.j-admin-layout[data-v-51b44ebf]{width:100vw;height:100vh;display:flex;flex-direction:row;overflow:hidden}.j-admin-layout__right-panel[data-v-51b44ebf]{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:var(--j-admin-layout-content-bg)}.j-admin-layout__header[data-v-51b44ebf]{display:flex;align-items:center;justify-content:space-between;background-color:var(--j-admin-layout-header-bg);box-shadow:0 1px 4px #00152914;height:var(--j-admin-layout-header-height);flex-shrink:0;z-index:100}.j-admin-layout__header-left[data-v-51b44ebf]{display:flex;align-items:center;flex:1;height:100%;min-width:0}.j-admin-layout__logo[data-v-51b44ebf]{padding:0 16px;display:flex;align-items:center;height:var(--j-admin-layout-header-height);flex-shrink:0}.j-admin-layout__logo h2[data-v-51b44ebf]{margin:0;font-size:18px;font-weight:700;color:#409eff;white-space:nowrap;overflow:hidden}.j-admin-layout__top-menu[data-v-51b44ebf]{flex:1;height:var(--j-admin-layout-header-height);border-bottom:none;min-width:0}.j-admin-layout__top-menu-icon[data-v-51b44ebf]{margin-right:4px}.j-admin-layout__header-right[data-v-51b44ebf]{display:flex;align-items:center;gap:16px;padding-right:16px;flex-shrink:0}.j-admin-layout__user[data-v-51b44ebf]{cursor:pointer;display:flex;align-items:center;color:#606266}.j-admin-layout__user[data-v-51b44ebf]:hover{color:#409eff}.j-admin-layout__content-container[data-v-51b44ebf]{flex:1;overflow:hidden;display:flex;flex-direction:column;background-color:var(--j-admin-layout-content-bg)}.j-admin-layout__content[data-v-51b44ebf]{padding:var(--j-admin-layout-content-padding);flex:1;overflow:auto}.j-admin-layout__content--scrollbar-hidden[data-v-51b44ebf]{scrollbar-width:none;-ms-overflow-style:none}.j-admin-layout__content--scrollbar-hidden[data-v-51b44ebf]::-webkit-scrollbar{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .spinner,.nprogress-custom-parent #nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}to{transform:rotate(360deg)}}