<template>
|
<view class="inspection-upload-page">
|
<!-- 页面头部 -->
|
<PageHeader title="巡检上传" />
|
|
<!-- 标签页 -->
|
<view class="tabs-container">
|
<view class="custom-tabs">
|
<view
|
v-for="(tab, index) in tabs"
|
:key="index"
|
class="tab-item"
|
:class="{ 'tab-active': currentTabIndex === index }"
|
@click="handleTabChange(index)"
|
>
|
{{ tab.name }}
|
</view>
|
<view class="tab-line" :style="{ left: currentTabIndex * 50 + '%' }"></view>
|
</view>
|
</view>
|
|
<!-- 扫码模块 -->
|
<view v-if="activeTab === 'qrCode'" class="scan-section">
|
<view class="scan-controls">
|
<u-button
|
:type="isScanning ? 'error' : 'primary'"
|
:loading="scanLoading"
|
@click="toggleScan"
|
>
|
{{ scanButtonText }}
|
</u-button>
|
</view>
|
|
<!-- 扫码区域 -->
|
<view v-show="isScanning" class="qr-scan-container">
|
<camera
|
class="qr-camera"
|
device-position="back"
|
flash="off"
|
@scancode="handleScanCode"
|
@error="handleCameraError"
|
></camera>
|
<view class="scan-overlay">
|
<view class="scan-frame"></view>
|
<view class="scan-tip">请将二维码放入框内</view>
|
</view>
|
</view>
|
|
<!-- 状态提示 -->
|
<view class="status-info">
|
<u-alert
|
v-if="cameraError"
|
:title="cameraError"
|
type="error"
|
:showIcon="true"
|
:closable="true"
|
@close="cameraError = ''"
|
></u-alert>
|
<view v-if="isScanning" class="scanning-text">
|
<u-loading-icon mode="circle" color="#1890ff" size="20"></u-loading-icon>
|
<text class="scanning-label">正在扫描二维码...</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 数据列表 -->
|
<view class="table-section">
|
<!-- 生产巡检列表 -->
|
<view v-if="activeTab === 'task'" class="task-list">
|
<view
|
v-for="(item, index) in tableData"
|
:key="index"
|
class="task-item"
|
@click="handleAdd(item)"
|
>
|
<view class="task-header">
|
<view class="task-info">
|
<text class="task-name">{{ item.taskName }}</text>
|
<text class="task-location">{{ item.inspectionLocation }}</text>
|
</view>
|
<view class="task-actions">
|
<u-button
|
type="primary"
|
size="small"
|
@click.stop="handleAdd(item)"
|
:customStyle="{
|
borderRadius: '15px',
|
height: '30px',
|
fontSize: '12px'
|
}"
|
>
|
上传
|
</u-button>
|
</view>
|
</view>
|
<view class="task-details">
|
<view class="detail-item">
|
<text class="detail-label">备注:</text>
|
<text class="detail-value">{{ item.remarks || '无' }}</text>
|
</view>
|
<view class="detail-item">
|
<text class="detail-label">执行人:</text>
|
<text class="detail-value">{{ item.inspector }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 现场巡检列表 -->
|
<view v-if="activeTab === 'qrCode'" class="qr-list">
|
<view
|
v-for="(item, index) in tableData"
|
:key="index"
|
class="qr-item"
|
@click="viewFile(item)"
|
>
|
<view class="qr-header">
|
<view class="qr-info">
|
<text class="device-name">{{ item.qrCode?.deviceName }}</text>
|
<text class="device-location">{{ item.qrCode?.location }}</text>
|
</view>
|
<view class="qr-actions">
|
<u-button
|
type="primary"
|
size="small"
|
@click.stop="viewFile(item)"
|
:customStyle="{
|
borderRadius: '15px',
|
height: '30px',
|
fontSize: '12px'
|
}"
|
>
|
查看附件
|
</u-button>
|
</view>
|
</view>
|
<view class="qr-details">
|
<view class="detail-item">
|
<text class="detail-label">巡检人:</text>
|
<text class="detail-value">{{ item.scanner }}</text>
|
</view>
|
<view class="detail-item">
|
<text class="detail-label">巡检时间:</text>
|
<text class="detail-value">{{ item.scanTime }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 空状态 -->
|
<view v-if="tableData.length === 0 && !tableLoading" class="empty-state">
|
<u-empty
|
mode="data"
|
text="暂无数据"
|
:iconSize="80"
|
></u-empty>
|
</view>
|
|
<!-- 加载状态 -->
|
<view v-if="tableLoading" class="loading-state">
|
<u-loading-icon mode="circle" color="#1890ff" size="40"></u-loading-icon>
|
<text class="loading-text">加载中...</text>
|
</view>
|
</view>
|
|
<!-- 分页 -->
|
<view v-if="total > 0" class="pagination-container">
|
<u-pagination
|
:total="total"
|
:current="pageNum"
|
:pageSize="pageSize"
|
@change="handlePageChange"
|
:showTotal="true"
|
:showSizer="false"
|
:showJumper="false"
|
></u-pagination>
|
</view>
|
|
<!-- 弹窗组件 -->
|
<form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
|
<qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia>
|
</view>
|
</template>
|
|
<script setup>
|
import { onMounted, ref, reactive, computed, nextTick } from 'vue'
|
import { onShow } from '@dcloudio/uni-app'
|
import PageHeader from '@/components/PageHeader.vue'
|
import FormDia from './components/formDia.vue'
|
import QrCodeFormDia from './components/qrCodeFormDia.vue'
|
import { qrCodeScanRecordList } from '@/api/inspectionUpload/index.js'
|
import { getInspectionTaskList } from '@/api/equipmentManagement/inspection.js'
|
import {inspectionTaskList} from "@/api/inspectionManagement";
|
// import ViewQrCodeFiles from '@/pages/inspectionManagement/components/viewQrCodeFiles.vue'
|
|
// 组件引用
|
const formDia = ref()
|
const qrCodeFormDia = ref()
|
|
// 当前标签
|
const activeTab = ref('task')
|
|
const tabName = ref('task')
|
const currentTabIndex = ref(0)
|
|
// 标签页数据
|
const tabs = reactive([
|
{ name: '生产巡检' },
|
{ name: '现场巡检' }
|
])
|
|
// 表格数据
|
const tableData = ref([])
|
const tableLoading = ref(false)
|
const total = ref(0)
|
const pageNum = ref(1)
|
const pageSize = ref(10)
|
|
// 扫码相关状态
|
const isScanning = ref(false)
|
const scanLoading = ref(false)
|
const cameraError = ref('')
|
|
// 计算属性
|
const scanButtonText = computed(() => {
|
if (scanLoading.value) return '正在初始化...'
|
return isScanning.value ? '停止扫码' : '开始扫码'
|
})
|
|
// 生命周期
|
onMounted(() => {
|
// 延迟初始化,确保DOM已渲染
|
nextTick(() => {
|
handleTabClick({ props: { name: 'task' } })
|
})
|
})
|
|
onShow(() => {
|
// 页面显示时刷新数据
|
getList()
|
})
|
|
// 标签页切换
|
const handleTabChange = (index) => {
|
currentTabIndex.value = index
|
const tabNames = ['task', 'qrCode']
|
activeTab.value = tabNames[index]
|
tabName.value = tabNames[index]
|
tableData.value = []
|
pageNum.value = 1
|
getList()
|
}
|
|
// 标签页点击(兼容旧方法)
|
const handleTabClick = (tab) => {
|
tabName.value = tab.props.name
|
activeTab.value = tab.props.name
|
tableData.value = []
|
getList()
|
}
|
|
// 查询数据
|
const handleQuery = () => {
|
pageNum.value = 1
|
pageSize.value = 10
|
getList()
|
}
|
|
// 获取列表数据
|
const getList = () => {
|
tableLoading.value = true
|
if (tabName.value === "task") {
|
inspectionTaskList({size: pageSize.value, current: pageNum.value}).then(res => {
|
tableLoading.value = false;
|
tableData.value = res.data.records;
|
total.value = res.data.total;
|
})
|
} else {
|
qrCodeScanRecordList({size: pageSize.value, current: pageNum.value}).then(res => {
|
tableLoading.value = false;
|
tableData.value = res.data.records;
|
total.value = res.data.total;
|
})
|
}
|
}
|
|
// 分页变化
|
const handlePageChange = (page) => {
|
pageNum.value = page
|
getList()
|
}
|
|
// 上传
|
const handleAdd = (row) => {
|
nextTick(() => {
|
formDia.value?.openDialog(row)
|
})
|
}
|
|
// 查看附件
|
const viewFile = (row) => {
|
console.log('查看附件:', row)
|
uni.showToast({
|
title: '查看附件功能开发中',
|
icon: 'none'
|
})
|
}
|
|
// 扫码相关方法
|
const toggleScan = async () => {
|
if (isScanning.value) {
|
await stopScan()
|
} else {
|
await startScan()
|
}
|
}
|
|
const startScan = async () => {
|
try {
|
// 使用uniapp的扫码API
|
uni.scanCode({
|
success: (res) => {
|
handleScanSuccess(res)
|
},
|
fail: (err) => {
|
console.error('扫码失败:', err)
|
uni.showToast({
|
title: '扫码失败',
|
icon: 'error'
|
})
|
}
|
})
|
} catch (e) {
|
console.error('启动扫码失败:', e)
|
uni.showToast({
|
title: '启动扫码失败',
|
icon: 'error'
|
})
|
}
|
}
|
|
const stopScan = async () => {
|
isScanning.value = false
|
}
|
|
// 扫码成功处理
|
const handleScanSuccess = async (result) => {
|
try {
|
uni.showToast({
|
title: '识别成功',
|
icon: 'success'
|
})
|
|
// 解析二维码数据
|
let qrData
|
try {
|
qrData = JSON.parse(result.result)
|
} catch (e) {
|
qrData = { deviceName: result.result, location: '' }
|
}
|
|
callBackendAPI(qrData)
|
} catch (error) {
|
uni.showToast({
|
title: error.message || '数据解析失败',
|
icon: 'error'
|
})
|
}
|
}
|
|
const callBackendAPI = (result) => {
|
nextTick(() => {
|
qrCodeFormDia.value?.openDialog(result)
|
})
|
}
|
|
// 扫码处理
|
const handleScanCode = (result) => {
|
console.log('扫码结果:', result)
|
handleScanSuccess(result)
|
}
|
|
// 摄像头错误处理
|
const handleCameraError = (error) => {
|
console.error('摄像头错误:', error)
|
cameraError.value = '摄像头访问失败,请检查权限设置'
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.inspection-upload-page {
|
min-height: 100vh;
|
background-color: #f5f5f5;
|
}
|
|
.tabs-container {
|
background-color: #fff;
|
margin: 0;
|
border-bottom: 1px solid #e8e8e8;
|
}
|
|
.custom-tabs {
|
display: flex;
|
position: relative;
|
background-color: #fff;
|
width: 100%;
|
}
|
|
.tab-item {
|
flex: 1;
|
text-align: center;
|
padding: 20px 0;
|
font-size: 16px;
|
font-weight: 500;
|
color: #606266;
|
transition: all 0.3s ease;
|
cursor: pointer;
|
position: relative;
|
z-index: 2;
|
}
|
|
.tab-item.tab-active {
|
color: #1890ff;
|
font-weight: 600;
|
}
|
|
.tab-line {
|
position: absolute;
|
bottom: 0;
|
width: 50%;
|
height: 3px;
|
background-color: #1890ff;
|
transition: left 0.3s ease;
|
}
|
|
.scan-section {
|
background-color: #fff;
|
padding: 10px;
|
}
|
|
.scan-controls {
|
display: flex;
|
justify-content: center;
|
}
|
|
.qr-scan-container {
|
position: relative;
|
width: 100%;
|
max-width: 500px;
|
margin: 0 auto;
|
background: #000;
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
.qr-camera {
|
width: 100%;
|
height: 300px;
|
}
|
|
.scan-overlay {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
width: 70%;
|
height: 70%;
|
border: 3px solid #1890ff;
|
border-radius: 8px;
|
box-shadow: 0 0 20px rgba(24, 144, 255, 0.3);
|
animation: pulse 2s infinite;
|
}
|
|
.scan-frame {
|
width: 100%;
|
height: 100%;
|
border: 2px solid #fff;
|
border-radius: 4px;
|
}
|
|
.scan-tip {
|
position: absolute;
|
bottom: -30px;
|
left: 50%;
|
transform: translateX(-50%);
|
color: #fff;
|
font-size: 14px;
|
text-align: center;
|
}
|
|
@keyframes pulse {
|
0% { opacity: 0.8; }
|
50% { opacity: 0.4; }
|
100% { opacity: 0.8; }
|
}
|
|
.status-info {
|
margin-top: 16px;
|
text-align: center;
|
}
|
|
.scanning-text {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
color: #1890ff;
|
margin-top: 8px;
|
}
|
|
.scanning-label {
|
margin-left: 8px;
|
font-size: 14px;
|
}
|
|
.table-section {
|
padding: 0 15px;
|
}
|
|
.task-list, .qr-list {
|
.task-item, .qr-item {
|
background-color: #fff;
|
border-radius: 8px;
|
margin-bottom: 10px;
|
padding: 15px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
}
|
|
.task-header, .qr-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 10px;
|
}
|
|
.task-info, .qr-info {
|
flex: 1;
|
}
|
|
.task-name, .device-name {
|
font-size: 16px;
|
font-weight: 600;
|
color: #333;
|
margin-bottom: 4px;
|
}
|
|
.task-location, .device-location {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.task-actions, .qr-actions {
|
margin-left: 10px;
|
}
|
|
.task-details, .qr-details {
|
.detail-item {
|
display: flex;
|
margin-bottom: 6px;
|
|
.detail-label {
|
font-size: 14px;
|
color: #666;
|
min-width: 60px;
|
}
|
|
.detail-value {
|
font-size: 14px;
|
color: #333;
|
flex: 1;
|
}
|
}
|
}
|
|
.empty-state, .loading-state {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 40px 20px;
|
background-color: #fff;
|
border-radius: 8px;
|
margin: 10px 15px;
|
}
|
|
.loading-text {
|
margin-top: 10px;
|
font-size: 14px;
|
color: #666;
|
}
|
|
.pagination-container {
|
padding: 20px 15px;
|
background-color: #fff;
|
margin-top: 10px;
|
}
|
</style>
|