Files
admin-govern/src/views/setting/dictionary/component/add.vue
2026-01-04 14:55:31 +08:00

224 lines
9.5 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"
placeholder="请选择父组件节点"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="组件图标" prop="icon">
<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-radio-group v-model="form.timeKeys" style="width: 100%">
<el-radio-button label="年" value="1" />
<el-radio-button label="季" value="2" />
<el-radio-button label="月" value="3" />
<el-radio-button label="周" value="4" />
<el-radio-button label="日" value="5" />
</el-radio-group> -->
<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">保存</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()
// 注意不要和表单ref的命名冲突
const form = ref<anyObj>({
name: '',
sort: 100,
system: [],
timeKeys: ['1', '2', '3', '4', '5'],
code: '',
path: ''
})
const props = { label: 'name', value: 'id' }
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(() => {
customDeptOption.value = dictData.getBasicData('System_Type')
customDeptOption.value.forEach((item: any) => {
getFatherComponent({ systemType: item.id }).then(res => {
item.children = res.data.filter(item => item.name != '无')
})
})
})
const open = (text: string, data?: anyObj) => {
console.log(data)
title.value = text
dialogVisible.value = true
if (data) {
let Data = JSON.parse(JSON.stringify(data))
form.value = Data
form.value.system = [Data.systemType, Data.pid]
// form.value.timeKeys = Data.timeKeys.split(',').map(Number)
form.value.timeKeys = Data.timeKeys || []
}
}
const submit = () => {
formRef.value.validate(async (valid: boolean) => {
if (valid) {
let url = ''
ElMessage.info('正在保存请稍等!')
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: form.value.system[0],
pid: form.value.system[1],
image: url
}).then(res => {
ElMessage.success('新增成功!')
emit('submit')
cancel()
})
} else {
await componentEdit({
...form.value,
systemType: form.value.system[0],
pid: form.value.system[1],
image: url
}).then(res => {
ElMessage.success('修改成功!')
emit('submit')
cancel()
})
}
}, 500)
}
})
}
// 组件映射
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>