| | |
| | | "nprogress": "0.2.0", |
| | | "pinia": "2.1.7", |
| | | "print-js": "^1.6.0", |
| | | "qr-scanner": "^1.4.2", |
| | | "qrcode": "^1.5.4", |
| | | "splitpanes": "3.1.5", |
| | | "vue": "3.4.31", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // å·¡æ£ä¸ä¼ |
| | | import request from '@/utils/request' |
| | | |
| | | // äºç»´ç 管ç表æ¥è¯¢ |
| | | export function qrCodeList(query) { |
| | | return request({ |
| | | url: '/qrCode/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ¥è¯¢ |
| | | export function qrCodeScanRecordList(query) { |
| | | return request({ |
| | | url: '/qrCodeScanRecord/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // äºç»´ç 管ç表æ°å¢ä¿®æ¹ |
| | | export function addOrEditQrCode(query) { |
| | | return request({ |
| | | url: '/qrCode/addOrEditQrCode', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ°å¢ä¿®æ¹ |
| | | export function addOrEditQrCodeRecord(query) { |
| | | return request({ |
| | | url: '/qrCodeScanRecord/addOrEditQrCodeRecord', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ°å¢ä¿®æ¹ |
| | | export function delQrCode(query) { |
| | | return request({ |
| | | url: '/qrCode/delQrCode', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <div ref="chartRef" :style="chartStyle"></div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, watchEffect } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // Props |
| | | const props = defineProps({ |
| | | options: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | chartStyle: { |
| | | type: Object, |
| | | default: () => ({ |
| | | height: '80%', |
| | | width: '100%' |
| | | }) |
| | | }, |
| | | dataset: { |
| | | type: Object, |
| | | default: () => {} |
| | | }, |
| | | xAxis: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | yAxis: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | series: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | grid: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | legend: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | tooltip: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | lineColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | barColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | pieColors: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | loadingOption: { |
| | | type: Object, |
| | | default: () => ({ |
| | | text: 'æ°æ®å è½½ä¸...', |
| | | color: '#00BAFF', |
| | | textColor: '#000', |
| | | maskColor: 'rgba(255, 255, 255, 0.8)', |
| | | zlevel: 0 |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | import { watch } from 'vue' |
| | | |
| | | // Refs |
| | | const chartRef = ref(null) |
| | | let chartInstance = null |
| | | |
| | | // Methods |
| | | function generateChart(option) { |
| | | const copiedOption = JSON.parse(JSON.stringify(option)) // â
æ·±æ·è´ |
| | | |
| | | // if (copiedOption.series && copiedOption.series.length > 0) { |
| | | // copiedOption.series.forEach((s, index) => { |
| | | // if (s.type === 'line') { |
| | | // s.itemStyle = { |
| | | // color: props.lineColors[index] || props.lineColors[0] |
| | | // } |
| | | // s.lineStyle = { |
| | | // color: props.lineColors[index] || props.lineColors[0] |
| | | // } |
| | | // } else if (s.type === 'bar') { |
| | | // s.itemStyle = { |
| | | // color: props.barColors[index] || props.barColors[0] |
| | | // } |
| | | // } |
| | | // }) |
| | | // } |
| | | |
| | | chartInstance.setOption(copiedOption) |
| | | } |
| | | |
| | | function renderChart() { |
| | | const option = { |
| | | backgroundColor: props.options.backgroundColor || '#fff', |
| | | xAxis: props.xAxis, |
| | | yAxis: props.yAxis, |
| | | dataset: props.dataset, |
| | | series: props.series, |
| | | grid: props.grid, |
| | | legend: props.legend, |
| | | tooltip: props.tooltip |
| | | } |
| | | |
| | | chartInstance.clear() |
| | | generateChart(option) |
| | | } |
| | | |
| | | function windowResizeListener() { |
| | | if (!chartInstance) return |
| | | chartInstance.resize() |
| | | } |
| | | |
| | | // Lifecycle hooks |
| | | onMounted(() => { |
| | | chartInstance = echarts.init(chartRef.value) |
| | | renderChart() |
| | | window.addEventListener('resize', windowResizeListener) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (chartInstance) { |
| | | window.removeEventListener('resize', windowResizeListener) |
| | | chartInstance.dispose() |
| | | chartInstance = null |
| | | } |
| | | }) |
| | | |
| | | // Watch all reactive props that affect the chart |
| | | watch( |
| | | () => [props.xAxis, props.series], |
| | | () => { |
| | | if (chartInstance) { |
| | | renderChart() |
| | | } |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |
| | | </script> |
| | |
| | | <div class="card-header"> |
| | | <h3>é宿°æ®</h3> |
| | | </div> |
| | | <el-table |
| | | :data="salesData" |
| | | <el-table |
| | | :data="salesData" |
| | | style="width: 100%" |
| | | :header-cell-style="tableHeaderStyle" |
| | | > |
| | |
| | | <el-table-column prop="amount" label="éé¢" width="90"></el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="70"> |
| | | <template #default="scope"> |
| | | <el-tag |
| | | <el-tag |
| | | :type="scope.row.status === '已宿' ? 'success' : 'warning'" |
| | | size="small" |
| | | > |
| | |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="设å¤åç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥è®¾å¤åç§°" maxlength="30" /> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="form.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å°ç¹" prop="taxTrans"> |
| | | <el-input v-model="form.taxTrans" placeholder="请è¾å
¥å°ç¹" maxlength="30"/> |
| | | <el-form-item label="æå¨ä½ç½®æè¿°" prop="location"> |
| | | <el-input v-model="form.location" placeholder="请è¾å
¥æå¨ä½ç½®æè¿°" maxlength="30"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <script setup> |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {reactive, ref} from "vue"; |
| | | import printJS from 'print-js'; // å¼å
¥ print.js |
| | | import printJS from 'print-js'; |
| | | import {addOrEditQrCode} from "@/api/inspectionUpload/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits() |
| | |
| | | const isShowQrCode = ref(false); |
| | | const operationType = ref('add'); |
| | | |
| | | const qrCodeValue = ref('https://example.com'); |
| | | const qrCodeValue = ref(''); |
| | | const qrCodeSize = ref(100); |
| | | const data = reactive({ |
| | | form: { |
| | | name: '', |
| | | taxTrans: '', |
| | | deviceName: '', |
| | | location: '', |
| | | qrCodeId: '', |
| | | id: '' |
| | | }, |
| | | rules: { |
| | | name: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | taxTrans: [{ required: true, message: '请è¾å
¥å°ç¹', trigger: 'blur' }] |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | location: [{ required: true, message: '请è¾å
¥å°ç¹', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | qrCodeValue.value = '' |
| | | isShowQrCode.value = false; |
| | | if (type === 'edit') { |
| | | form.value.id = row.id |
| | | form.value.qrCodeId = row.id |
| | | form.value.deviceName = row.deviceName |
| | | form.value.location = row.location |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | } |
| | | } |
| | | // æäº¤å并表å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | addOrEditQrCode(form.value).then((res) => { |
| | | form.value.qrCodeId = res.data |
| | | }) |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | |
| | | // å»¶è¿æ§è¡æå°ï¼é¿å
DOM æ´æ°åå°±è°ç¨æå° |
| | | setTimeout(() => { |
| | | printJS({ |
| | | printable: 'qrCodeContainer',//é¡µé¢ |
| | | type: "html",//ææ¡£ç±»å |
| | | maxWidth: 360, |
| | | style: `@page { |
| | | showQrCode() |
| | | } |
| | | }) |
| | | } |
| | | const showQrCode = () => { |
| | | // å»¶è¿æ§è¡æå°ï¼é¿å
DOM æ´æ°åå°±è°ç¨æå° |
| | | setTimeout(() => { |
| | | printJS({ |
| | | printable: 'qrCodeContainer',//é¡µé¢ |
| | | type: "html",//ææ¡£ç±»å |
| | | maxWidth: 360, |
| | | style: `@page { |
| | | margin:0; |
| | | size: 400px 75px collapse; |
| | | margin-top:3px; |
| | |
| | | height: 75px; |
| | | margin:0; |
| | | }`, |
| | | targetStyles: ["*"], // 使ç¨domçæææ ·å¼ï¼å¾éè¦ |
| | | font_size: '0.20cm', |
| | | }); |
| | | }, 300); |
| | | } |
| | | }) |
| | | targetStyles: ["*"], // 使ç¨domçæææ ·å¼ï¼å¾éè¦ |
| | | font_size: '0.20cm', |
| | | }); |
| | | }, 300); |
| | | } |
| | | // å
³éå并表å |
| | | const cancel = () => { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="æ¥çéä»¶" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <div class="upload-container"> |
| | | <div class="form-container"> |
| | | <div class="title">å·¡æ£éä»¶</div> |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- ç»ä¸åªä½æ¥çå¨ --> |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <!-- å¾ç --> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <Video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | // å¾çæ°ç» |
| | | const beforeProductionImgs = ref([]); |
| | | // è§é¢æ°ç» |
| | | const beforeProductionVideos = ref([]); |
| | | // åªä½æ¥çå¨ç¶æ |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | // æ¾ç¤ºåªä½ï¼å¾ç or è§é¢ï¼ |
| | | function showMedia(mediaArray, index, type) { |
| | | mediaList.value = mediaArray; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | } |
| | | // å
³éåªä½æ¥çå¨ |
| | | function closeMediaViewer() { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | } |
| | | // 表åå
³éæ¹æ³ |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | const images = []; |
| | | const videos = []; |
| | | items.forEach(item => { |
| | | if (item.contentType?.startsWith('image/')) { |
| | | images.push(item.url); |
| | | } else if (item.contentType?.startsWith('video/')) { |
| | | videos.push(item.url); |
| | | } |
| | | }); |
| | | return { images, videos }; |
| | | } |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | } |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | </el-tabs> |
| | | <!-- æä½æé®åº --> |
| | | <el-space> |
| | | <el-space v-if="tabName !== 'qrCodeScanRecord'"> |
| | | <el-button type="primary" :icon="Plus" @click="handleAdd">æ°å»º</el-button> |
| | | <el-button type="danger" :icon="Delete" @click="handleDelete">å é¤</el-button> |
| | | <el-button type="info" plain :icon="Download">导åº</el-button> |
| | |
| | | <div> |
| | | <ETable :loading="tableLoading" |
| | | :table-data="tableData" |
| | | :columns="columns" |
| | | :columns="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | :show-selection="true" |
| | | :border="true" |
| | | :maxHeight="480" |
| | | operationsWidth="130" |
| | | :operations="['edit', 'viewFile']" |
| | | :operations="operationsArr" |
| | | @edit="handleAdd" |
| | | @viewFile="viewFile" |
| | | v-if="tabName !== 'qrCodeScanRecord'" |
| | | ></ETable> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" v-else> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.deviceName}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="æå¨ä½ç½®æè¿°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.location}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="scanner" label="å·¡æ£äºº"></el-table-column> |
| | | <el-table-column prop="scanTime" label="å·¡æ£æ¶é´"></el-table-column> |
| | | <el-table-column fixed="right" label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleAdd(scope.row)">æ¥çéä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <pagination |
| | | v-if="total>0" |
| | |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue"; |
| | | import {delInspectionTask, inspectionTaskList} from "@/api/inspectionManagement/index.js"; |
| | | import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue"; |
| | | import {delQrCode, qrCodeList, qrCodeScanRecordList} from "@/api/inspectionUpload/index.js"; |
| | | import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue"; |
| | | |
| | | const formDia = ref() |
| | | const qrCodeDia = ref() |
| | | const viewFiles = ref() |
| | | const viewQrCodeFiles = ref() |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | supplierName: "", |
| | |
| | | const tabs = reactive([ |
| | | { name: "task", label: "ä»»å¡ä¸å" }, |
| | | { name: "qrCode", label: "äºç»´ç 管ç" }, |
| | | { name: "qrCodeScanRecord", label: "ç°åºå·¡æ£è®°å½" }, |
| | | ]); |
| | | // è¡¨æ ¼ |
| | | const selectedRows = ref([]); |
| | | const tableData = ref([]); |
| | | const operationsArr = ref([]); |
| | | const tableColumns = ref([]); |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | ]); |
| | | const columns1 = ref([ |
| | | { prop: "deviceName", label: "设å¤åç§°", minWidth: 160 }, |
| | | { prop: "location", label: "æå¨ä½ç½®æè¿°", minWidth: 120 }, |
| | | { prop: "createBy", label: "å建è
", minWidth: 100 }, |
| | | { prop: "createTime", label: "å建æ¶é´", minWidth: 100 }, |
| | | ]); |
| | | |
| | | onMounted(() => { |
| | | handleTabClick({ props: { name: "task" } }); |
| | |
| | | const handleTabClick = (tab) => { |
| | | tabName.value = tab.props.name; |
| | | tableData.value = []; |
| | | if (tabName.value === "task") { |
| | | tableColumns.value = columns.value; |
| | | operationsArr.value = ['edit', 'viewFile'] |
| | | } else { |
| | | tableColumns.value = columns1.value; |
| | | operationsArr.value = ['edit'] |
| | | } |
| | | getList(); |
| | | }; |
| | | // ç¹å»æ¥è¯¢ |
| | |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | inspectionTaskList({...queryParams, size: pageSize.value, current: pageNum.value}).then(res => { |
| | | console.log(res) |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | if (tabName.value === "task") { |
| | | inspectionTaskList({...queryParams, size: pageSize.value, current: pageNum.value}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | } else if (tabName.value === "qrCode") { |
| | | qrCodeList({...queryParams, 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 resetQuery = () => { |
| | |
| | | nextTick(() => { |
| | | if (tabName.value === "task") { |
| | | formDia.value?.openDialog(type, row) |
| | | } else { |
| | | } else if (tabName.value === "qrCode") { |
| | | qrCodeDia.value?.openDialog(type, row) |
| | | } else { |
| | | viewQrCodeFiles.value?.openDialog(row) |
| | | } |
| | | }) |
| | | }; |
| | |
| | | } |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | proxy.$modal.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼').then(function() { |
| | | return delInspectionTask(deleteIds) |
| | | return delQrCode(deleteIds) |
| | | }).then(() => { |
| | | handleQuery() |
| | | proxy.$modal.msgSuccess("å 餿å") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | title="å·¡æ£" |
| | | v-model="dialogVisitable" |
| | | width="400px" |
| | | @close="cancel" |
| | | > |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="form.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" maxlength="30" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å°ç¹" prop="location"> |
| | | <el-input v-model="form.location" placeholder="请è¾å
¥å°ç¹" maxlength="30" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶" prop="taxTrans"> |
| | | <fileUpload |
| | | :statusType="0" |
| | | ref="beforeProductionRef" |
| | | :fileSize="1024" |
| | | :fileType="['mp3', 'mp4', 'avi', 'mov', 'mkv']" |
| | | :limit="10" |
| | | :drag="false" |
| | | v-model:modelValue="form.storageBlobDTO" |
| | | > |
| | | </fileUpload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å·¡æ£äºº" prop="scannerName"> |
| | | <el-input v-model="form.scannerName" disabled placeholder="请è¾å
¥" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å·¡æ£æ¶é´" prop="scanTime"> |
| | | <el-input v-model="form.scanTime" disabled placeholder="请è¾å
¥" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref } from "vue"; |
| | | import {ElMessage} from "element-plus"; |
| | | import fileUpload from "@/components/FileUpload/index.vue"; |
| | | import {uploadInspectionTask} from "@/api/inspectionManagement/index.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {addOrEditQrCodeRecord} from "@/api/inspectionUpload/index.js"; |
| | | |
| | | const emit = defineEmits(['closeDia']); |
| | | const dialogVisitable = ref(false); |
| | | const { proxy } = getCurrentInstance() |
| | | const storageBlobDTO = ref([]); |
| | | const beforeProductionRef = ref(null); |
| | | const userStore = useUserStore(); |
| | | const userInfo = ref({}); |
| | | |
| | | function getCurrentDateTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(now.getDate()).padStart(2, '0'); |
| | | const hours = String(now.getHours()).padStart(2, '0'); |
| | | const minutes = String(now.getMinutes()).padStart(2, '0'); |
| | | const seconds = String(now.getSeconds()).padStart(2, '0'); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | const data = reactive({ |
| | | form: { |
| | | deviceName: '', |
| | | location: '', |
| | | scannerName: '', |
| | | scannerId: '', |
| | | scanTime: '', |
| | | qrCode: { |
| | | id: '' |
| | | } |
| | | }, |
| | | rules: { |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | location: [{ required: true, message: '请è¾å
¥å°ç¹', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // è°ç¨å½æ° |
| | | const currentDateTime = getCurrentDateTime(); |
| | | |
| | | // è·åç¨æ·ä¿¡æ¯ |
| | | onMounted(async () => { |
| | | let res = await userStore.getInfo(); |
| | | userInfo.value = res.user; |
| | | form.value.scannerName = userInfo.value.nickName |
| | | form.value.scannerId = userInfo.value.userId |
| | | form.value.scanTime = currentDateTime |
| | | }); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (row) => { |
| | | dialogVisitable.value = true; |
| | | form.value.deviceName = row.deviceName |
| | | form.value.location = row.location |
| | | form.value.qrCodeId = row.qrCodeId |
| | | }; |
| | | const submitForm = async () => { |
| | | form.value.qrCode.id = form.value.qrCodeId |
| | | await addOrEditQrCodeRecord({...form.value}); |
| | | cancel() |
| | | ElMessage.success("æäº¤æå"); |
| | | }; |
| | | // å
³éå并表å |
| | | const cancel = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogVisitable.value = false; |
| | | emit("closeDia"); |
| | | }; |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | } |
| | | .title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; /* è°æ´åç´ä½ç½® */ |
| | | width: 4px; /* å°æ°æ¡å®½åº¦ */ |
| | | height: 14px; /* å°æ°æ¡é«åº¦ */ |
| | | background-color: #165dff; /* èè² */ |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | </el-tabs> |
| | | <div> |
| | | <!-- æ«ç 模å --> |
| | | <div v-if="activeTab === 'qrCode'" class="scan-section"> |
| | | <div class="scan-controls"> |
| | | <el-button |
| | | type="primary" |
| | | :loading="scanLoading" |
| | | @click="toggleScan" |
| | | > |
| | | {{ scanButtonText }} |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æ«ç è§é¢å®¹å¨ --> |
| | | <div v-show="isScanning" class="qr-video-container"> |
| | | <video |
| | | ref="qrVideo" |
| | | class="qr-video" |
| | | playsinline |
| | | webkit-playsinline |
| | | ></video> |
| | | <div class="scan-overlay"></div> |
| | | </div> |
| | | |
| | | <!-- ç¶ææç¤º --> |
| | | <div class="status-info"> |
| | | <el-alert |
| | | v-if="cameraError" |
| | | :title="cameraError" |
| | | type="error" |
| | | show-icon |
| | | closable |
| | | /> |
| | | <div v-if="isScanning" class="scanning-text"> |
| | | <el-icon :color="statusColor"><Loading /></el-icon> |
| | | æ£å¨æ«æäºç»´ç ... |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading"> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" v-if="activeTab !== 'qrCode'"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column prop="taskName" label="å·¡æ£ä»»å¡åç§°" :show-overflow-tooltip="true"></el-table-column> |
| | | <el-table-column prop="port" label="å°ç¹" :show-overflow-tooltip="true"></el-table-column> |
| | |
| | | <el-table-column fixed="right" label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleAdd(scope.row)">ä¸ä¼ </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" v-if="activeTab === 'qrCode'"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.deviceName}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="æå¨ä½ç½®æè¿°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.location}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="scanner" label="å·¡æ£äºº"></el-table-column> |
| | | <el-table-column prop="scanTime" label="å·¡æ£æ¶é´"></el-table-column> |
| | | <el-table-column fixed="right" label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="viewFile(scope.row)">æ¥çéä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia> |
| | | <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import {inspectionTaskList} from "@/api/inspectionManagement/index.js"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/inspectionUpload/components/formDia.vue"; |
| | | import {ElMessage} from "element-plus"; |
| | | import QrScanner from 'qr-scanner' |
| | | import QrCodeFormDia from "@/views/inspectionUpload/components/qrCodeFormDia.vue"; |
| | | import {qrCodeList, qrCodeScanRecordList} from "@/api/inspectionUpload/index.js"; |
| | | import {inspectionTaskList} from "@/api/inspectionManagement/index.js"; |
| | | import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue"; |
| | | const formDia = ref() |
| | | const qrCodeFormDia = ref() |
| | | const viewQrCodeFiles = ref() |
| | | // å½åæ ç¾ |
| | | const activeTab = ref("task"); |
| | | const tabName = ref("task"); |
| | | // æ ç¾é¡µæ°æ® |
| | | const tabs = reactive([ |
| | | { name: "task", label: "ä»»å¡ä¸å" }, |
| | | { name: "qrCode", label: "äºç»´ç 管ç" }, |
| | | { name: "task", label: "ç产巡æ£" }, |
| | | { name: "qrCode", label: "ç°åºå·¡æ£" }, |
| | | ]); |
| | | // è¡¨æ ¼ |
| | | const tableData = ref([]); |
| | |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | // æ«ç ç¸å
³ç¶æ |
| | | const qrVideo = ref(null) |
| | | const isScanning = ref(false) |
| | | const scanLoading = ref(false) |
| | | const cameraError = ref(null) |
| | | const scanner = ref(null) |
| | | const hasInit = ref(false) |
| | | |
| | | onMounted(() => { |
| | | const statusColor = computed(() => { |
| | | return isScanning.value ? '#67C23A' : '#F56C6C' |
| | | }) |
| | | // çå½å¨æç®¡çä¼å |
| | | onMounted(async () => { |
| | | handleTabClick({ props: { name: "task" } }); |
| | | }); |
| | | if (!import.meta.env.SSR && QrScanner) { // [!code focus] |
| | | await initScanner() |
| | | } |
| | | }) |
| | | |
| | | onBeforeUnmount(async () => { |
| | | if (scanner.value) { |
| | | await scanner.value.destroy() |
| | | scanner.value = null |
| | | } |
| | | hasInit.value = false |
| | | }) |
| | | // æ ç¾é¡µç¹å» |
| | | const handleTabClick = (tab) => { |
| | | tabName.value = tab.props.name; |
| | |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | inspectionTaskList({size: pageSize.value, current: pageNum.value}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | 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 handleAdd = (row) => { |
| | |
| | | formDia.value?.openDialog(row) |
| | | }) |
| | | } |
| | | // æ¥çéä»¶ |
| | | const viewFile = (row) => { |
| | | nextTick(() => { |
| | | viewQrCodeFiles.value?.openDialog(row) |
| | | }) |
| | | } |
| | | // æ«ç æé®ææ¬ |
| | | const scanButtonText = computed(() => { |
| | | if (scanLoading.value) return 'æ£å¨åå§å...' |
| | | return isScanning.value ? '忢æ«ç ' : 'å¼å§æ«ç ' |
| | | }) |
| | | |
| | | // å¢å¼ºååå§å |
| | | const initScanner = async () => { |
| | | try { |
| | | await nextTick() // ç¡®ä¿DOMæ´æ° |
| | | // æ°å¢å¤éç©ºå¼æ ¡éª |
| | | if (!qrVideo.value || !QrScanner) { |
| | | throw new Error('ä¾èµæªæ£ç¡®åå§å') |
| | | } |
| | | // å¢å æå头æé颿£æ¥ |
| | | const hasCamera = await QrScanner.hasCamera() |
| | | if (!hasCamera) { |
| | | throw new Error('æªæ£æµå°å¯ç¨æå头') |
| | | } |
| | | // æ¾å¼éæ¯æ§å®ä¾ |
| | | if (scanner.value) { |
| | | await scanner.value.destroy() |
| | | } |
| | | // å建æ°å®ä¾ |
| | | scanner.value = new QrScanner( |
| | | qrVideo.value, |
| | | result => { |
| | | handleScanSuccess(result) |
| | | // stopScan() |
| | | }, |
| | | { |
| | | preferredCamera: 'environment', |
| | | maxScansPerSecond: 5, |
| | | returnDetailedScanResult: true |
| | | } |
| | | ) |
| | | // æ°å¢ç¡¬ä»¶å éæ£æµ |
| | | if (!scanner.value._qrWorker) { |
| | | throw new Error('硬件å éä¸å¯ç¨') |
| | | } |
| | | hasInit.value = true |
| | | } catch (e) { |
| | | // handleInitError(e) |
| | | } |
| | | } |
| | | // æ«ææåå¤ç |
| | | const handleScanSuccess = async (result) => { |
| | | try { |
| | | // æ·»å æ°æ®æ ¡éª |
| | | ElMessage.success('è¯å«æå') |
| | | callBackendAPI(JSON.parse(result.data)) |
| | | await stopScan() |
| | | } catch (error) { |
| | | ElMessage.warning(error.message) |
| | | await startScan() // æ°æ®æ ææ¶ç»§ç»æ«æ |
| | | } |
| | | } |
| | | const callBackendAPI = (result) => { |
| | | nextTick(() => { |
| | | qrCodeFormDia.value?.openDialog(result) |
| | | }) |
| | | } |
| | | // 忢æ«ç ç¶æ |
| | | const toggleScan = async () => { |
| | | if (isScanning.value) { |
| | | await stopScan() |
| | | } else { |
| | | await startScan() |
| | | } |
| | | } |
| | | |
| | | // å¢å¼ºå¯å¨æ¹æ³ |
| | | const startScan = async () => { |
| | | if (!scanner.value || !hasInit.value) { // æ°å¢ç¶ææ£æ¥ |
| | | await initScanner() |
| | | } |
| | | |
| | | try { |
| | | await scanner.value.start() |
| | | isScanning.value = true |
| | | } catch (e) { |
| | | ElMessage.error(`å¯å¨å¤±è´¥: ${e.message}`) |
| | | hasInit.value = false |
| | | } |
| | | } |
| | | |
| | | // 忢æ«ç |
| | | const stopScan = async () => { |
| | | try { |
| | | await scanner.value.stop() |
| | | isScanning.value = false |
| | | } catch (err) { |
| | | console.error('忢æå头失败:', err) |
| | | } |
| | | } |
| | | |
| | | |
| | | // é误å¤çå¢å¼º |
| | | const handleInitError = (error) => { |
| | | console.error('åå§å失败:', error) |
| | | const msg = { |
| | | 'NotAllowedError': '请å
许æå头æé', |
| | | 'NotFoundError': 'æªæ¾å°æå头设å¤', |
| | | 'NotSupportedError': 'æµè§å¨ä¸æ¯ææ«ç åè½' |
| | | }[error.name] || error.message |
| | | |
| | | ElMessage.error(`åå§å失败: ${msg}`) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .qr-video-container { |
| | | position: relative; |
| | | width: 100%; |
| | | max-width: 500px; |
| | | margin: 0 auto; |
| | | background: #000; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .qr-video { |
| | | width: 100%; |
| | | height: auto; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .scan-overlay { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 70%; |
| | | height: 70%; |
| | | border: 3px solid #409eff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 0 20px rgba(64, 158, 255, 0.3); |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { opacity: 0.8; } |
| | | 50% { opacity: 0.4; } |
| | | 100% { opacity: 0.8; } |
| | | } |
| | | |
| | | .status-info { |
| | | margin-top: 16px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .scanning-text { |
| | | color: #409eff; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .table-section { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | /* ç§»å¨ç«¯ä¼å */ |
| | | @media (max-width: 768px) { |
| | | .qr-video-container { |
| | | height: 60vh; |
| | | } |
| | | |
| | | .el-table { |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | </style> |