工程管理
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
12 KiB

<template>
<div class="step-device-category h-full flex flex-col">
<div class="flex-1 overflow-y-auto">
<el-tabs
v-if="Object.keys(modelValue).length > 0"
v-model="activeTab"
type="card"
class="h-full flex flex-col"
>
<el-tab-pane
v-for="(categoriesMap, channelType) in modelValue"
:key="channelType"
:label="
Object.entries(ChannelEnum).find(([key, val]) => val === channelType)?.[1]
"
:name="channelType"
>
<el-scrollbar>
<div class="grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-4 p-4">
<div
class="bg-white rounded-xl shadow-sm p-12 border-2 border-dashed border-gray-200 hover:border-blue-400 transition-all duration-300 cursor-pointer flex items-center justify-center min-h-[200px]"
@click="handleAdd"
>
<div class="flex flex-col items-center gap-3">
<el-icon :size="48" class="text-gray-400">
<Plus />
</el-icon>
<span class="text-base font-medium text-gray-600">新增设备类别</span>
</div>
</div>
<div
v-for="{ fileName, name: categoryName, status } in categoriesMap"
:key="fileName"
class="bg-white rounded-xl shadow-sm p-12 border border-gray-100 hover:shadow-lg transition-all duration-300"
>
<div class="flex justify-between items-start mb-3">
<div class="flex-1 min-w-0 flex items-center justify-between">
<h3
class="font-bold text-lg text-gray-900 truncate"
:title="categoryName as string"
>
类别名称{{ categoryName }}
</h3>
<el-tag
v-if="!!status"
size="small"
type="success"
effect="light"
class="rounded-md"
>
已上传
</el-tag>
<el-tag
v-else
size="small"
type="info"
effect="light"
class="rounded-md"
>
未上传
</el-tag>
</div>
</div>
<div>
<div class="space-y-2 mb-3">
<div class="text-sm">
<div class="flex items-center justify-between">
<span class="text-gray-500 whitespace-nowrap">点表文件</span>
<el-tooltip
effect="dark"
:content="fileName"
placement="top"
:disabled="!fileName"
>
<span class="text-gray-900 font-medium truncate max-w-[200px]">
{{ fileName || '-' }}
</span>
</el-tooltip>
</div>
</div>
</div>
<div class="upload-area relative">
<div
v-if="uploadingStates[fileName]"
class="absolute inset-0 z-10 bg-white/90 flex items-center justify-center rounded-lg"
>
<el-button
type="danger"
size="small"
@click="handleCancelUpload(fileName as string)"
>
取消上传
</el-button>
</div>
<el-upload
:ref="(el) => setUploadRef(el, fileName as string)"
class="upload-demo"
drag
:auto-upload="false"
:limit="1"
:show-file-list="false"
:disabled="uploadingStates[fileName]"
:on-change="
(file: any) => handleFileChange(file, fileName as string)
"
>
<el-icon class="el-icon--upload !text-3xl !mb-2 !text-gray-400"
><upload-filled
/></el-icon>
<div class="el-upload__text text-xs text-gray-500">
拖拽文件或<em class="text-blue-500 not-italic cursor-pointer"
>点击/{{ !!status ? '重新' : '' }}上传</em
>
</div>
</el-upload>
</div>
</div>
</div>
</div>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<div v-if="Object.keys(modelValue).length === 0">
<div
class="bg-white rounded-xl w-340 shadow-sm p-12 border-2 border-dashed border-gray-200 hover:border-blue-400 transition-all duration-300 cursor-pointer flex items-center justify-center min-h-[200px]"
@click="handleAdd"
>
<div class="flex flex-col items-center gap-3">
<el-icon :size="48" class="text-gray-400">
<Plus />
</el-icon>
<span class="text-base font-medium text-gray-600">新增设备类别</span>
</div>
</div>
<el-empty description="暂无设备类别,请点击上方按钮添加" />
</div>
</div>
<el-dialog
v-model="dialogVisible"
:title="'新增设备类别'"
width="500px"
append-to-body
destroy-on-close
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="类别名称:" prop="pointName">
<el-input v-model="form.pointName" placeholder="请输入类别名称" />
</el-form-item>
</el-form>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="通道类型:" prop="groupName">
<el-select v-model="form.groupName" placeholder="请选择通道类型">
<el-option
v-for="[key, val] in Object.entries(ChannelEnum)"
:key="key"
:label="val"
:value="val"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { UploadFilled, Plus } from '@element-plus/icons-vue'
import type { IDeviceCategoryList, IDeviceCategoryOV } from '@/api/module/device/index.d'
import type { FormInstance, FormRules, UploadFile } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { createDeviceType, uploadDeviceTypeFile } from '@/api/module/device/category'
import { ChannelEnum } from '@/api/module/channel/index'
import type { IChannelOV } from '@/api/module/channel/index.d'
import { validateName } from '@/utils/validate'
const props = defineProps<{
modelValue: IDeviceCategoryList
channels: IChannelOV
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: IDeviceCategoryList): void
(e: 'on-load-device-categoty'): void
}>()
const route = useRoute()
const projectName = computed(() => route.query.name as string)
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const uploadControllers = reactive<{ [key: string]: AbortController }>({})
const uploadingStates = reactive<{ [key: string]: boolean }>({})
const activeTab = ref<string>('modbus')
watch(
() => props.modelValue,
val => {
if (
val &&
Object.keys(val).length > 0 &&
!Object.keys(val).includes(activeTab.value)
) {
activeTab.value = Object.keys(val)[0]
}
},
{ immediate: true, deep: true },
)
const form = reactive<IDeviceCategoryOV>({
pointName: '',
fileName: '',
groupName: '',
})
const rules = reactive<FormRules>({
pointName: [
{ required: true, message: '请输入类别名称', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
const error = validateName(value)
if (error) {
callback(new Error(error))
} else {
callback()
}
},
trigger: 'blur',
},
],
groupName: [{ required: true, message: '请输入通道类型', trigger: 'blur' }],
})
const handleAdd = () => {
form.pointName = ''
form.fileName = ''
form.groupName = ''
dialogVisible.value = true
}
const handleSave = async () => {
if (!formRef.value) return
await formRef.value.validate(async valid => {
if (valid) {
await addDeviceType()
}
})
}
async function addDeviceType() {
const res = await createDeviceType({
pointName: form.pointName,
projectName: projectName.value,
groupName: form.groupName,
})
if (res.code !== 0) {
ElMessage.error(res?.msg || '添加失败')
dialogVisible.value = false
return
}
emit('on-load-device-categoty')
dialogVisible.value = false
}
const uploadRefs = ref<Record<string, any>>({})
const setUploadRef = (el: any, key: string) => {
if (el) {
uploadRefs.value[key] = el
}
}
const handleFileChange = async (file: UploadFile, fileName: string) => {
if (!file.raw) return
const controller = new AbortController()
uploadControllers[fileName] = controller
uploadingStates[fileName] = true
try {
const res = await uploadDeviceTypeFile(
{
file: file.raw,
projectName: projectName.value,
fileName: fileName,
},
controller,
)
if (res) {
if (res.code === 0) {
ElMessage.success('上传成功')
emit('on-load-device-categoty')
} else if (res.code === 10001) {
ElMessage.success('取消成功')
} else {
ElMessage.error(res.msg || '上传失败')
}
}
} finally {
delete uploadControllers[fileName]
uploadingStates[fileName] = false
uploadRefs.value[fileName]?.clearFiles()
}
}
const handleCancelUpload = (fileName: string) => {
const controller = uploadControllers[fileName]
if (controller) {
controller.abort()
delete uploadControllers[fileName]
uploadingStates[fileName] = false
}
}
// Check if there are any uploading files
const hasUploadingFiles = () => {
return Object.values(uploadingStates).some(state => state === true)
}
// Cancel all uploads
const cancelAllUploads = () => {
Object.keys(uploadControllers).forEach(fileName => {
handleCancelUpload(fileName)
})
}
// Check before leaving step
const checkBeforeLeave = async (): Promise<boolean> => {
if (!hasUploadingFiles()) {
return true
}
return new Promise(resolve => {
ElMessageBox.confirm(
'当前有文件正在上传,离开将取消所有上传任务,是否确定离开?',
'提示',
{
confirmButtonText: '确定离开',
cancelButtonText: '取消',
type: 'warning',
},
)
.then(() => {
cancelAllUploads()
resolve(true)
})
.catch(() => {
resolve(false)
})
})
}
defineExpose({
checkBeforeLeave,
})
</script>
<style scoped>
:deep(.el-tabs__content) {
flex: 1;
overflow: hidden;
height: 0;
}
:deep(.el-tab-pane) {
height: 100%;
}
:deep(.el-scrollbar__view) {
height: 100%;
}
.upload-demo {
width: 100%;
}
:deep(.el-upload-dragger) {
padding: 20px;
}
:deep(.el-icon--upload) {
font-size: 40px;
color: #409eff;
margin-bottom: 8px;
}
:deep(.el-upload__text) {
font-size: 14px;
line-height: 1.5;
}
</style>