Files
admin-govern/src/views/setting/dictionary/component/add.vue
2026-01-07 21:01:28 +08:00

278 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
draggable
class="cn-operate-dialog"
v-model="dialogVisible"
width="1000px"
:title="title"
@close="cancel"
>
<div style="display: flex">
<el-form :inline="false" :model="form" label-width="auto" :rules="rules" ref="formRef" style="flex: 1">
<el-form-item class="top" label="组件名称" prop="name">
<el-input v-model="form.name" placeholder="请输入组件名称"></el-input>
</el-form-item>
<el-form-item class="top" label="父组件节点" prop="system">
<el-cascader
v-model="form.system"
:options="customDeptOption"
:props="props"
collapse-tags
collapse-tags-tooltip
clearable
placeholder="请选择父组件节点"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="组件图标">
<IconSelector v-model="form.icon" placeholder="请选择图标" />
</el-form-item>
<el-form-item class="top" label="组件标识" prop="code">
<el-input v-model="form.code" placeholder="请输入组件菜单选取"></el-input>
</el-form-item>
<el-form-item class="top" label="组件路径" prop="path">
<el-input v-model="form.path" placeholder="请输入组件路径"></el-input>
</el-form-item>
<el-form-item class="top" label="组件查询时间" prop="timeKeys">
<el-checkbox-group v-model="form.timeKeys">
<el-checkbox-button value="1"></el-checkbox-button>
<el-checkbox-button value="2"></el-checkbox-button>
<el-checkbox-button value="3"></el-checkbox-button>
<el-checkbox-button value="4"></el-checkbox-button>
<el-checkbox-button value="5"></el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item class="top" label="组件排序" prop="sort">
<el-input v-model.number="form.sort" placeholder="请输入组件排序"></el-input>
</el-form-item>
</el-form>
<div style="width: 600px; height: 390px; overflow: hidden">
<div class="ml10" style="font-weight: 600">组件展示</div>
<component
:is="registerComponent(form.path)"
v-if="registerComponent(form.path)"
class="pd10 GridLayout"
:key="form.path"
:height="'350px'"
:width="'580px'"
:timeKey="form.timeKeys"
:w="12"
:h="6"
/>
<!-- <div class="pd10">组件加载失败...</div> -->
<el-empty v-else description="未查询到组件" style="height: 350px; width: 533px" />
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit" :loading="loading">保存</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, inject, onMounted, type Component, defineAsyncComponent, h, markRaw } from 'vue'
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { useDictData } from '@/stores/dictData'
import { getFatherComponent, componentAdd, componentEdit } from '@/api/user-boot/dept'
import IconSelector from '@/components/baInput/components/iconSelector.vue'
import html2canvas from 'html2canvas'
const emit = defineEmits(['cancel', 'submit'])
const dictData = useDictData()
const dialogVisible = ref(false)
const title = ref('')
const formRef = ref()
const loading = ref(false)
// 注意不要和表单ref的命名冲突
const form = ref<anyObj>({
name: '',
sort: 100,
system: [],
timeKeys: ['1', '2', '3', '4', '5'],
code: '',
path: ''
})
const props = { label: 'name', value: 'id', multiple: true }
const rules = {
code: [{ required: true, message: '请输入组件标识', trigger: 'blur' }],
name: [{ required: true, message: '请输输入组件名称', trigger: 'blur' }],
system: [{ required: true, message: '请先择父组件节点', trigger: 'change' }],
icon: [{ required: true, message: '请先择组件图标', trigger: 'change' }],
path: [{ required: true, message: '请输入组件路径', trigger: 'blur' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
timeKeys: [{ required: true, message: '请选择组件查询时间', trigger: 'change' }]
}
const customDeptOption: any = ref([])
onMounted(() => {})
const getSystem = async () => {
// customDeptOption.value = dictData.getBasicData('System_Type')
// await customDeptOption.value.forEach((item: any) => {
// getFatherComponent({ systemType: item.id }).then(res => {
// item.children = res.data.filter(item => item.name != '无')
// })
// })
customDeptOption.value = dictData.getBasicData('System_Type')
// 创建所有异步请求的 Promise 数组
const promises = customDeptOption.value.map(async (item: any) => {
try {
const res = await getFatherComponent({ systemType: item.id })
item.children = res.data.sort((a, b) => a.sort - b.sort)
} catch (error) {
console.error('加载组件失败:', error)
item.children = []
}
})
// 等待所有请求完成
await Promise.all(promises)
// 所有 children 数据都已加载完成
return customDeptOption.value
}
const open = async (text: string, data?: anyObj) => {
title.value = text
dialogVisible.value = true
await getSystem()
if (data) {
let Data = JSON.parse(JSON.stringify(data))
form.value = Data
form.value.system = getSystemComponentPairs(customDeptOption.value, Data.componentType)
form.value.timeKeys = Data.timeKeys || []
}
}
const submit = () => {
formRef.value.validate(async (valid: boolean) => {
if (valid) {
let url = ''
ElMessage.info('正在保存请稍等!')
loading.value = true
setTimeout(async () => {
await html2canvas(document.querySelector('.GridLayout'), {
// scale: 2
scale: 1, // 降低缩放比例2倍会让像素量翻倍优先降到1
useCORS: false, // 非跨域图片关闭CORS检测减少网络请求
logging: false, // 关闭日志输出(减少控制台开销)
letterRendering: true, // 优化文字渲染性能
allowTaint: true, // 允许跨域图片污染画布(避免额外校验)
removeContainer: true, // 自动清理生成的临时容器
ignoreElements: el => {
// 忽略不可见/非关键元素(比如隐藏的弹窗、装饰性元素)
return el.style.display === 'none' || el.classList.contains('ignore-screenshot')
}
}).then(canvas => {
url = canvas.toDataURL('image/png')
})
if (title.value == '新增组件') {
await componentAdd({
...form.value,
systemType: formatFirstIdToString(form.value.system, 0),
componentType: formatFirstIdToString(form.value.system, 1),
pid: '123',
image: url
}).then(res => {
ElMessage.success('新增成功!')
emit('submit')
cancel()
})
} else {
await componentEdit({
...form.value,
systemType: formatFirstIdToString(form.value.system, 0),
componentType: formatFirstIdToString(form.value.system, 1),
pid: '123',
image: url
}).then(res => {
ElMessage.success('修改成功!')
emit('submit')
cancel()
})
}
loading.value = false
}, 500)
}
})
}
// 处理函数:提取子数组第一个元素 + 去重 + 拼接
const formatFirstIdToString = (data: any, num: number) => {
// 1. 提取每个子数组的第一个元素
const firstIds = data.map(item => item[num])
// 2. 去重利用Set特性
const uniqueFirstIds = [...new Set(firstIds)]
// 3. 拼接成逗号分隔的字符串
return uniqueFirstIds.join(',')
}
// 根据id 找二位数组回显
function getSystemComponentPairs(data: any, idsStr: string) {
const targetIds = idsStr.split(',')
const result: any[] = []
// 遍历所有数据
data.forEach(system => {
const systemId = system.id
const children = system.children || []
// 在children中查找匹配的ID
children.forEach(child => {
if (targetIds.includes(child.id)) {
result.push([systemId, child.id])
}
})
})
// 按照提供的ID顺序排序
return result.sort((a, b) => {
const indexA = targetIds.indexOf(a[1])
const indexB = targetIds.indexOf(b[1])
return indexA - indexB
})
}
// 组件映射
const componentMap = reactive(new Map<string, Component | string>())
// 动态注册组件
const registerComponent = (path: string): Component | string | null => {
if (!path) return null
if (path.slice(-4) != '.vue') return null
const cacheKey = path
// 使用缓存的组件
if (componentMap.has(cacheKey)) {
return componentMap.get(cacheKey)!
}
try {
// 动态导入组件
const modules = import.meta.glob('@/**/*.vue')
if (!modules[path]) {
console.error(`组件加载失败: ${path}`)
return null
}
const AsyncComponent = defineAsyncComponent({
loader: modules[path],
loadingComponent: () => h('div', '加载中...'),
errorComponent: ({ error }) => h('div', `加载错误: ${error.message}`),
delay: 200,
timeout: 10000
})
// 保存到映射中
componentMap.set(cacheKey, markRaw(AsyncComponent))
return AsyncComponent
} catch (error) {
console.error('注册组件失败:', error)
return null
}
}
const cancel = () => {
emit('cancel')
dialogVisible.value = false
}
defineExpose({ open })
</script>