Files
mom-web/src/views/iot/check/Execute.vue
2026-03-05 16:52:12 +08:00

245 lines
6.3 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 v-model="visibleInner" :title="dialogTitle" width="820px" :close-on-click-modal="false">
<div class="sub mb-10px">分类{{ categoryName || '-' }}</div>
<el-card shadow="never">
<template #header>
<div class="card-header flex items-center justify-between">
<span>检查项目</span>
<div class="flex items-center gap-8px">
<el-button size="small" type="primary" plain @click="addItem">新增项目</el-button>
</div>
</div>
</template>
<div v-if="localItems.length === 0" class="text-gray-500">暂无项目点击"新增项目"添加</div>
<div v-for="(item, idx) in localItems" :key="item.code" class="item-row">
<div class="item-left">
<div class="item-name flex items-center">
<span class="mr-8px">{{ idx + 1 }}.</span>
<el-input v-model="item.name" placeholder="检查项名称" size="small" style="width: 240px; margin-right: 12px;" />
<el-input v-model="item.desc" placeholder="检查项说明(可选)" size="small" style="width: 480px;" />
</div>
</div>
<div class="item-right">
<el-radio-group v-model="item.result" @change="onResultChange(item)">
<el-radio label="ok">正常</el-radio>
<el-radio label="bad">异常</el-radio>
</el-radio-group>
<div class="inline-actions ml-12px">
<el-button size="small" text @click="moveUp(idx)" :disabled="idx === 0">上移</el-button>
<el-button size="small" text @click="moveDown(idx)" :disabled="idx === localItems.length - 1">下移</el-button>
<el-button size="small" text type="danger" @click="removeItem(idx)">删除</el-button>
</div>
</div>
<div v-if="item.result === 'bad'" class="issue-area">
<el-input v-model="item.issueDesc" type="textarea" :rows="2" placeholder="请描述异常" />
<el-upload
class="mt-8px"
action=""
:http-request="handleUpload"
list-type="picture-card"
:file-list="item.photos"
:on-remove="(file, list) => onRemove(file, list, item)"
:on-preview="(file) => onPreview(file)"
:limit="10"
multiple
accept="image/*">
<el-icon>
<Plus />
</el-icon>
</el-upload>
</div>
</div>
</el-card>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</div>
</template>
</el-dialog>
<!-- 图片预览对话框 -->
<el-dialog v-model="previewVisible" title="图片预览" width="80%" center>
<img :src="previewImageUrl" style="width: 100%; max-height: 70vh; object-fit: contain;" />
</el-dialog>
</template>
<script lang="ts" setup>
import { Plus } from '@element-plus/icons-vue'
import { CheckLogApi } from '@/api/iot/check/log'
const props = defineProps<{
modelValue?: boolean
categoryName?: string
items?: Array<{ name: string; desc?: string }>
title?: string
}>()
const emit = defineEmits(['update:modelValue', 'save'])
const visibleInner = computed({
get: () => !!props.modelValue,
set: (v: boolean) => emit('update:modelValue', v)
})
const dialogTitle = computed(() => props.title || '新增模板')
interface ExecItem {
code: string
name: string
desc?: string
result?: 'ok' | 'bad'
issueDesc?: string
photos: any[]
}
const localItems = ref<ExecItem[]>([])
// 图片预览相关
const previewVisible = ref(false)
const previewImageUrl = ref('')
// 初始化本地数据
const initLocalItems = () => {
const defs = (props.items || []).length ? props.items! : []
localItems.value = defs.map((def, idx) => ({
code: `CHK-${String(idx + 1).padStart(3, '0')}`,
name: def.name,
desc: def.desc,
result: 'ok',
issueDesc: '',
photos: []
}))
}
onMounted(() => {
initLocalItems()
})
// 监听props.items变化用于编辑时回显数据
watch(() => props.items, () => {
initLocalItems()
}, { deep: true })
function addItem() {
const nextIndex = localItems.value.length + 1
localItems.value.push({
code: `CHK-${String(nextIndex).padStart(3, '0')}`,
name: '',
desc: '',
result: 'ok',
issueDesc: '',
photos: []
})
}
function removeItem(idx: number) {
localItems.value.splice(idx, 1)
// 重排 code
localItems.value.forEach((it, i) => (it.code = `CHK-${String(i + 1).padStart(3, '0')}`))
}
function moveUp(i: number) {
if (i <= 0) return
const t = localItems.value[i - 1]
localItems.value[i - 1] = localItems.value[i]
localItems.value[i] = t
}
function moveDown(i: number) {
if (i >= localItems.value.length - 1) return
const t = localItems.value[i + 1]
localItems.value[i + 1] = localItems.value[i]
localItems.value[i] = t
}
function onResultChange(item: ExecItem) {
if (item.result !== 'bad') {
item.issueDesc = ''
item.photos = []
}
}
async function handleUpload(options: any) {
const { file, onSuccess, onError } = options
try {
const res = await CheckLogApi.uploadImage(file as File)
onSuccess({ url: res.url, name: res.name })
} catch (e) {
onError(e)
}
}
function onRemove(_: any, list: any[], item: ExecItem) {
item.photos = list
}
// 图片预览
function onPreview(file: any) {
previewImageUrl.value = file.url || file.response?.url
previewVisible.value = true
}
function save() {
const payload = localItems.value.map(it => ({ name: (it.name || '').trim(), desc: (it.desc || '').trim() }))
if (!payload.length) {
ElMessage.warning('请至少新增一个检查项')
return
}
emit('save', payload)
visibleInner.value = false
}
function close() {
visibleInner.value = false
}
</script>
<style scoped>
.sub {
color: #666;
font-size: 12px;
}
.card-header {
font-weight: 600;
}
.item-row {
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
}
.item-left {
margin-bottom: 8px;
}
.item-name {
font-weight: 600;
}
.item-desc {
color: #666;
font-size: 12px;
margin-top: 4px;
}
.item-right {
margin: 8px 0;
}
.issue-area {
margin-top: 8px;
}
.text-gray-500 {
color: #909399;
}
.hint {
color: #909399;
font-size: 12px;
}
.mt-8px {
margin-top: 8px;
}
</style>