245 lines
6.3 KiB
Vue
245 lines
6.3 KiB
Vue
|
|
<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>
|