소스 검색

提交临时设备管理

Lijy 8 달 전
부모
커밋
ed0a58e230

+ 178 - 14
fhKeeper/formulahousekeeper/timesheet/package-lock.json

@@ -2624,8 +2624,7 @@
     "decamelize": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
-      "dev": true
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
     },
     "decode-uri-component": {
       "version": "0.2.2",
@@ -2798,6 +2797,11 @@
         }
       }
     },
+    "dijkstrajs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+      "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+    },
     "dingtalk-jsapi": {
       "version": "2.15.4",
       "resolved": "https://registry.npmmirror.com/dingtalk-jsapi/-/dingtalk-jsapi-2.15.4.tgz",
@@ -3579,8 +3583,7 @@
     "get-caller-file": {
       "version": "2.0.5",
       "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
-      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
     },
     "get-intrinsic": {
       "version": "1.2.1",
@@ -5926,6 +5929,11 @@
         "find-up": "^1.0.0"
       }
     },
+    "pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
+    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -7128,6 +7136,167 @@
       "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
       "dev": true
     },
+    "qrcode": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
+      "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+      "requires": {
+        "dijkstrajs": "^1.0.1",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "camelcase": {
+          "version": "5.3.1",
+          "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
+          "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+        },
+        "cliui": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
+          "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.0",
+            "wrap-ansi": "^6.2.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+        },
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+        },
+        "string-width": {
+          "version": "4.2.3",
+          "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+          "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        },
+        "wrap-ansi": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+          "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "yargs": {
+          "version": "15.4.1",
+          "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
+          "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+          "requires": {
+            "cliui": "^6.0.0",
+            "decamelize": "^1.2.0",
+            "find-up": "^4.1.0",
+            "get-caller-file": "^2.0.1",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^4.2.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^18.1.2"
+          }
+        },
+        "yargs-parser": {
+          "version": "18.1.3",
+          "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
+          "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
     "qs": {
       "version": "6.11.2",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz",
@@ -7553,8 +7722,7 @@
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
     },
     "require-from-string": {
       "version": "1.2.1",
@@ -7565,8 +7733,7 @@
     "require-main-filename": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
-      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
-      "dev": true
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
     },
     "requires-port": {
       "version": "1.0.0",
@@ -7796,8 +7963,7 @@
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
-      "dev": true
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
     },
     "set-value": {
       "version": "2.0.1",
@@ -9869,8 +10035,7 @@
     "which-module": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
-      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
-      "dev": true
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
     },
     "wide-align": {
       "version": "1.1.5",
@@ -9986,8 +10151,7 @@
     "y18n": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
-      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
-      "dev": true
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
     },
     "yallist": {
       "version": "2.1.2",

+ 1 - 0
fhKeeper/formulahousekeeper/timesheet/package.json

@@ -21,6 +21,7 @@
     "font-awesome": "^4.7.0",
     "jquery": "^3.4.1",
     "nprogress": "^0.2.0",
+    "qrcode": "^1.5.4",
     "tinymce": "^5.7.1",
     "v-distpicker": "^1.2.13",
     "v-viewer": "^1.6.4",

+ 2 - 1
fhKeeper/formulahousekeeper/timesheet/src/i18n/en.json

@@ -30,7 +30,8 @@
     "basicDataManagement": "Basic data management",
     "budgetReview": "Estimated working hours review",
     "projectFormSettings": "Project Form Settings",
-    "userGroupManagement": "User group management"
+    "userGroupManagement": "User group management",
+    "deviceManagement": "deviceManagement"
   },
   "role": {
     "ordinaryEmployees": "Ordinary employees",

+ 2 - 1
fhKeeper/formulahousekeeper/timesheet/src/i18n/zh.json

@@ -30,7 +30,8 @@
     "budgetReview": "预估工时审核",
     "gongshitongji": "工时统计表",
     "caiwushenhe": "财务审核",
-    "userGroupManagement": "用户分组管理"
+    "userGroupManagement": "用户分组管理",
+    "deviceManagement": "设备管理"
   },
   "role": {
     "ordinaryEmployees": "普通员工",

+ 10 - 0
fhKeeper/formulahousekeeper/timesheet/src/main.js

@@ -119,6 +119,16 @@ router.beforeEach((to, from, next) => {
     }
 
     let user = JSON.parse(sessionStorage.getItem('user'));
+
+    // 添加临时路由
+    if(user && user.moduleList && user.companyId == 10 && user.moduleList.length > 0) {
+        const modulOne = JSON.parse(JSON.stringify(user.moduleList[0] || [])) 
+        console.log(modulOne, '<===== modulOne')
+        modulOne.name = '设备管理'
+        modulOne.path = '/deviceManagement'
+        user.moduleList.splice(2, 0, modulOne);
+    }
+
     if (!user && to.path != '/login') {
         next({ path: '/login' })
     } else {

+ 16 - 0
fhKeeper/formulahousekeeper/timesheet/src/routes.js

@@ -99,6 +99,9 @@ import financeAudit from './views/financeAudit/financeAudit.vue'
 // 用户分组管理
 import userGrouping from './views/userGrouping/userGrouping.vue'
 
+// 设备管理
+import deviceManagement from './views/deviceManagement/deviceManagement'
+
 Vue.use(Router)
 
 export const fixedRouter = [
@@ -514,6 +517,19 @@ export const allRouters = [//组织架构
         // 其他信息
         meta: { text: 'navigation.basicDataManagement' }
     },
+    // 设备管理
+    {
+        path: '/',
+        component: Home,
+        name: '设备管理',
+        iconCls: 'iconfont firerock-icondaibanshixiang',
+        leaf: true,//只有一个节点
+        children: [
+            { path: '/deviceManagement', component: deviceManagement, name: '设备管理' },
+        ],
+        // 其他信息
+        meta: { text: 'navigation.deviceManagement' } 
+    },
     {
         path: '/404',
         component: NotFound,

+ 243 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/deviceManagement/deviceManagement.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="deviceManagement">
+    <div class="deviceManagement-title">
+      <div class="deviceManagement-title-left">
+        <div>设备管理</div>
+        <el-input placeholder="请输入设备编码" v-model.trim="equipmentCode" size="small" style="width: 300px;" clearable>
+          <template slot="append">
+            <el-button @click="searchForDevices">搜索</el-button>
+          </template>
+        </el-input>
+      </div>
+
+      <div class="deviceManagement-title-right">
+        <el-button type="primary" size="small" @click="editDevice({}, true)">添加设备</el-button>
+      </div>
+    </div>
+
+    <div class="deviceManagement-table">
+      <el-table :data="deviceManagementList" border style="width: 100%" height="calc(100% - 10px)">
+        <el-table-column prop="equipmentNumber" label="设备编号"></el-table-column>
+        <el-table-column prop="equipmentName" label="设备名称"></el-table-column>
+        <el-table-column prop="equipmentModel" label="设备型号"></el-table-column>
+        <el-table-column prop="manufacturer" label="制造商"></el-table-column>
+        <el-table-column prop="purchaseDate" label="购置日期" width="200"></el-table-column>
+        <el-table-column prop="maintenanceDate" label="维保日期" width="200"></el-table-column>
+        <el-table-column prop="operation" label="操作" width="200">
+          <template slot-scope="scope">
+            <el-button type="text" @click="editDevice(scope.row, true)">编辑</el-button>
+            <el-button type="text" @click="deleteDevice(scope.$index, scope.row)">删除</el-button>
+            <el-button type="text" @click="seeDevice(scope.$index, scope.row)">查看二维码</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 新增编辑 -->
+    <el-dialog title="新增/修改设备" :visible.sync="equipmentVisable" width="780px" top="7.8vh" :before-close="handleClose">
+      <div class="modifyEquipment">
+        <el-form :model="equipmentForm" :rules="equipmentRules" ref="equipmentruleForm" label-width="100px"
+          class="demo-ruleForm">
+          <el-form-item label="设备编号" prop="equipmentNumber">
+            <el-input v-model.trim="equipmentForm.equipmentNumber" placeholder="请输入设备编码"></el-input>
+          </el-form-item>
+          <el-form-item label="设备名称" prop="equipmentName">
+            <el-input v-model.trim="equipmentForm.equipmentName" placeholder="请输入设备名称"></el-input>
+          </el-form-item>
+          <el-form-item label="设备型号" prop="equipmentModel">
+            <el-input v-model.trim="equipmentForm.equipmentModel" placeholder="请输入设备型号"></el-input>
+          </el-form-item>
+          <el-form-item label="制造商" prop="manufacturer">
+            <el-input v-model.trim="equipmentForm.manufacturer" placeholder="请输入制造商"></el-input>
+          </el-form-item>
+          <el-form-item label="购置日期" prop="purchaseDate">
+            <el-date-picker v-model="equipmentForm.purchaseDate" value-format="yyyy-MM-dd" type="date"
+              placeholder="选择日期" style="width: 100%;"></el-date-picker>
+          </el-form-item>
+          <el-form-item label="维保日期" prop="maintenanceDate">
+            <el-date-picker v-model="equipmentForm.maintenanceDate" value-format="yyyy-MM-dd" type="date"
+              placeholder="选择日期" style="width: 100%;"></el-date-picker>
+          </el-form-item>
+        </el-form>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="equipmentVisable = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm('equipmentruleForm')">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 查看二维码 -->
+    <el-dialog :title="`${equipmentItem.equipmentNumber} - ${equipmentItem.equipmentName}`"
+      :visible.sync="equipmentQrCodeVisable" width="600px" top="7.8vh" :before-close="handleClose">
+      <div class="equipmentQrCode">
+        <div class="equipmentQrCodeImg">
+          <img :src="qrCodeURL" alt="二维码" />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import QRCode from 'qrcode';
+export default {
+  data() {
+    return {
+      equipmentCode: '',
+      equipmentVisable: false,
+      equipmentQrCodeVisable: false,
+      equipmentForm: {},
+      equipmentItem: {},
+      qrCodeURL: '',
+      equipmentRules: {
+        equipmentNumber: [{ required: true, message: '请输入设备编码', trigger: 'blur' }],
+        equipmentName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
+        equipmentModel: [{ required: true, message: '请输入设备型号', trigger: 'blur' }],
+        manufacturer: [{ required: true, message: '请输入制造商', trigger: 'blur' }],
+        purchaseDate: [{ required: true, message: '请选择购置日期', trigger: 'blur' }],
+        maintenanceDate: [{ required: true, message: '请选择维保日期', trigger: 'blur' }]
+      },
+      deviceManagementList: [
+        { equipmentNumber: '123456789', equipmentName: '设备名称1(在使用)', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01', status: 1, projectName: '展示项目', durationOfUse: 1, usageTime: '2024-09-03 10:11:11' },
+        { equipmentNumber: '987654321', equipmentName: '设备名称2', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01' },
+        { equipmentNumber: 'aabbccas', equipmentName: '设备名称3', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01' },
+        { equipmentNumber: 'jkjkjkjkjk', equipmentName: '设备名称4(在使用)', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01', status: 1, projectName: '小小神射手', durationOfUse: 15, usageTime: '2024-08-07 08:09:11' },
+      ],
+      deviceManagementListTwo: [
+        { equipmentNumber: '123456789', equipmentName: '设备名称1(在使用)', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01', status: 1, projectName: '展示项目', durationOfUse: 1, usageTime: '2024-09-03 10:11:11' },
+        { equipmentNumber: '987654321', equipmentName: '设备名称2', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01' },
+        { equipmentNumber: 'aabbccas', equipmentName: '设备名称3', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01' },
+        { equipmentNumber: 'jkjkjkjkjk', equipmentName: '设备名称4(在使用)', equipmentModel: '设备型号', manufacturer: '制造商', purchaseDate: '2019-01-01', maintenanceDate: '2020-01-01', status: 1, projectName: '小小神射手', durationOfUse: 15, usageTime: '2024-08-07 08:09:11' },
+      ]
+    };
+  },
+  mounted() {
+  },
+  methods: {
+    searchForDevices() {
+      console.log('点击搜索')
+      const newList = JSON.parse(JSON.stringify(this.deviceManagementListTwo))
+      this.deviceManagementList = newList.filter(item => item.equipmentNumber.indexOf(this.equipmentCode) > -1)
+    },
+    seeDevice(i, row) {
+      QRCode.toDataURL(JSON.stringify(row))
+        .then(url => {
+          this.qrCodeURL = url;
+        })
+        .catch(err => {
+          console.error(err);
+        });
+      this.equipmentItem = row
+      this.equipmentQrCodeVisable = true
+    },
+    editDevice(row, flag) {
+      this.equipmentForm = row
+      this.equipmentVisable = flag
+    },
+    deleteDevice(i, row) {
+      const { equipmentNumber } = row
+      this.$confirm(`此操作将删除设备编码为【${equipmentNumber}】的设备, 是否继续?`, '删除设备', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.deviceManagementList.splice(i, 1)
+        this.deviceManagementListTwo.splice(i, 1)
+        this.$message({
+          type: 'success',
+          message: '删除成功!'
+        });
+      }).catch(() => { });
+    },
+    submitForm(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          console.log('submit!');
+          const { equipmentNumber } = this.equipmentForm
+          const numverList = this.deviceManagementList.map(item => item.equipmentNumber == equipmentNumber)
+          if (numverList.length > 0) {
+            this.$message({
+              message: '设备编号重复',
+              type: 'error'
+            })
+            return
+          }
+          this.deviceManagementList.push(this.equipmentForm)
+          this.deviceManagementListTwo.push(this.equipmentForm)
+          this.$message({
+            message: '提交成功',
+            type: 'success'
+          });
+          this.equipmentVisable = false
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    }
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.deviceManagement {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .deviceManagement-title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    background: #F2F2F2;
+    padding: 10px 10px;
+
+    .deviceManagement-title-left {
+      display: flex;
+      align-items: center;
+      margin-right: 20px;
+
+      &>div {
+        width: 80px;
+      }
+    }
+  }
+
+  .deviceManagement-table {
+    flex: 1;
+    overflow: auto;
+  }
+
+  .modifyEquipment {
+    height: 378px;
+    overflow: auto;
+    padding: 0 20px;
+  }
+
+  .equipmentQrCodeImg {
+    width: 400px;
+    height: 400px;
+    background: red;
+
+    img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .equipmentQrCode {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-wrap: wrap;
+    width: 100%;
+    height: 500px;
+  }
+}
+</style>
+
+<style lang="scss">
+.deviceManagement .el-dialog__body {
+  padding: 0;
+}
+</style>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 16140 - 572
fhKeeper/formulahousekeeper/timesheet_h5/package-lock.json


+ 1 - 0
fhKeeper/formulahousekeeper/timesheet_h5/package.json

@@ -12,6 +12,7 @@
   },
   "dependencies": {
     "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+    "@zxing/library": "^0.21.3",
     "babel-plugin-import": "^1.13.3",
     "core-js": "^2.6.12",
     "css-loader": "^3.6.0",

+ 259 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/components/qrcode.vue

@@ -0,0 +1,259 @@
+<template>
+	<div class="page-scan">
+		<div class="scan-box">
+			<video ref="video" id="video" class="scan-video" autoplay></video>
+			<div class="qr-scanner">
+				<div class="box">
+					<div class="line"></div>
+					<div class="angle"></div>
+				</div>
+			</div>
+			<div class="scan-tip">{{ scanTextData.tipMsg }}</div>
+		</div>
+		<!-- <div @click.stop="destroyed">重置摄像头</div> -->
+     <div class="clonsIcon" @click="cloneQrcde()"><van-icon name="clear" /></div>
+	</div>
+</template>
+
+<script>
+import { BrowserMultiFormatReader } from '@zxing/library';
+// import { $verification } from '@/api';
+export default {
+	name: 'scanCodePage',
+	data() {
+		return {
+			scanTextData: {
+				codeReader: null,
+				tipMsg: '扫描设备码',
+				// 这个,就是当前调用的摄像头的索引,为什么是6,我会在后面说的 华为手机是鸿蒙系统有8个摄像头
+				num: 5,
+				// 这个就是扫描到的摄像头的数量
+				videoLength: '',
+			},
+			hasBind: false,
+		};
+	},
+	props: {
+		show: {
+			type: Boolean,
+			default: false,
+		},
+	},
+	mounted() {
+		this.scanTextData.codeReader = new BrowserMultiFormatReader();
+		this.openScan(); // 打开摄像头
+	},
+	watch: {
+		show(val) {
+			if (!val) {
+				// 关闭摄像头
+				if (!document.getElementById('video')) {
+					alert('请授权');
+					return;
+				}
+				let thisVideo = document.getElementById('video');
+				thisVideo.srcObject.getTracks()[0].stop();
+				this.scanTextData.codeReader.reset();
+			} else {
+				if (this.scanTextData.codeReader === null) {
+					this.scanTextData.codeReader = new BrowserMultiFormatReader();
+				}
+				this.openScan();
+			}
+		},
+	},
+	methods: {
+    cloneQrcde() {
+      this.$emit('closeQrcode')
+    },
+		async openScan() {
+			this.scanTextData.codeReader
+				.getVideoInputDevices()
+				.then((videoInputDevices) => {
+					// 默认获取第一个摄像头设备id
+					let firstDeviceId = videoInputDevices[0].deviceId;
+					console.log('手机摄像头的数量', videoInputDevices.length, videoInputDevices);
+					// 获取第一个摄像头设备的名称
+					const videoInputDeviceslablestr = JSON.stringify(videoInputDevices[0].label);
+					if (videoInputDevices.length > 1) {
+						// 华为手机有6个摄像头,前三个是前置,后三个是后置,第6个摄像头最清晰
+						if (videoInputDevices.length > 5) {
+							firstDeviceId = videoInputDevices[5].deviceId;
+						} else {
+							// 判断是否后置摄像头
+							if (videoInputDeviceslablestr.indexOf('back') > -1) {
+								firstDeviceId = videoInputDevices[0].deviceId;
+							} else {
+								firstDeviceId = videoInputDevices[1].deviceId;
+							}
+						}
+					}
+					this.decodeFromInputVideoFunc(firstDeviceId);
+				})
+				.catch((err) => {
+					console.error(err);
+				});
+		},
+		// 解析 二维码
+		decodeFromInputVideoFunc(firstDeviceId) {
+			this.scanTextData.codeReader.reset();
+			this.scanTextData.codeReader.decodeFromInputVideoDeviceContinuously(
+				firstDeviceId,
+				'video',
+				(result, err) => {
+					if (result && result.text) {
+						this.handleResult(result.text);
+					}
+					if (err && !err) {
+						console.log(err);
+						this.$toast.fail(err);
+					}
+				}
+			);
+		},
+		// 解析二维码成功后 执行的回调 (逻辑处理或直接返回扫码结果)
+		async handleResult(scanResult) {
+			console.log('逻辑处理或直接返回扫码结果', scanResult);
+      this.$emit('determineQrcode', scanResult)
+		},
+	},
+	destroyed() {
+		this.scanTextData.codeReader.reset(); // 重置摄像头
+	},
+};
+</script>
+
+<style lang="less" scoped>
+.scan-box {
+	position: fixed;
+	top: 0;
+	left: 0;
+	height: 100%;
+	width: 100vw;
+	background-image: linear-gradient(
+			0deg,
+			transparent 24%,
+			rgba(32, 255, 77, 0.1) 25%,
+			rgba(32, 255, 77, 0.1) 26%,
+			transparent 27%,
+			transparent 74%,
+			rgba(32, 255, 77, 0.1) 75%,
+			rgba(32, 255, 77, 0.1) 76%,
+			transparent 77%,
+			transparent
+		),
+		linear-gradient(
+			90deg,
+			transparent 24%,
+			rgba(32, 255, 77, 0.1) 25%,
+			rgba(32, 255, 77, 0.1) 26%,
+			transparent 27%,
+			transparent 74%,
+			rgba(32, 255, 77, 0.1) 75%,
+			rgba(32, 255, 77, 0.1) 76%,
+			transparent 77%,
+			transparent
+		);
+	background-size: 3rem 3rem;
+	background-position: -1rem -1rem;
+}
+
+.scan-video {
+	height: 100vh;
+	width: 100vw;
+	object-fit: cover;
+}
+
+.qr-scanner .box {
+	width: 213px;
+	height: 213px;
+	position: absolute;
+	left: 50%;
+	top: 50%;
+	transform: translate(-50%, -50%);
+	overflow: hidden;
+	border: 0.1rem solid rgba(0, 255, 51, 0.2);
+	/* background: url('http://resource.beige.world/imgs/gongconghao.png') no-repeat center center; */
+}
+
+.qr-scanner .line {
+	height: calc(100% - 2px);
+	width: 100%;
+	background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);
+	border-bottom: 3px solid #00ff33;
+	transform: translateY(-100%);
+	animation: radar-beam 2s infinite alternate;
+	animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
+	animation-delay: 1.4s;
+}
+
+.qr-scanner .box:after,
+.qr-scanner .box:before,
+.qr-scanner .angle:after,
+.qr-scanner .angle:before {
+	content: '';
+	display: block;
+	position: absolute;
+	width: 3vw;
+	height: 3vw;
+	border: 0.2rem solid transparent;
+}
+
+.qr-scanner .box:after,
+.qr-scanner .box:before {
+	top: 0;
+	border-top-color: #00ff33;
+}
+
+.qr-scanner .angle:after,
+.qr-scanner .angle:before {
+	bottom: 0;
+	border-bottom-color: #00ff33;
+}
+
+.qr-scanner .box:before,
+.qr-scanner .angle:before {
+	left: 0;
+	border-left-color: #00ff33;
+}
+
+.qr-scanner .box:after,
+.qr-scanner .angle:after {
+	right: 0;
+	border-right-color: #00ff33;
+}
+
+@keyframes radar-beam {
+	0% {
+		transform: translateY(-100%);
+	}
+
+	100% {
+		transform: translateY(0);
+	}
+}
+
+.scan-tip {
+	width: 100vw;
+	text-align: center;
+	margin-bottom: 5vh;
+	color: white;
+	font-size: 5vw;
+	position: absolute;
+	bottom: 50px;
+	left: 0;
+	color: #fff;
+}
+
+.page-scan {
+	overflow-y: hidden;
+}
+
+.clonsIcon {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  z-index: 999;
+  font-size: 26px;
+}
+</style>

+ 14 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/router/index.js

@@ -259,6 +259,20 @@ const router = new Router({
             title: "成本"
         }
     },
+    {
+        path: "/useRegistration",
+        component: () => import("@/views/deviceManagement/useRegistration"),
+        meta: {
+            title: "设备使用登记"
+        }
+    },
+    {
+        path: "/usageHistory",
+        component: () => import("@/views/deviceManagement/usageHistory"),
+        meta: {
+            title: "设备使用记录"
+        }
+    },
     {
         path: "/pdf",
         meta: {

+ 25 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/views/deviceManagement/usageHistory.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="useRegistration">
+    <van-nav-bar title="设备使用记录" left-text="返回" @click-left="back" fixed left-arrow style="z-index:1000" />
+
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+
+    };
+  },
+  mounted() {
+  },
+  methods: {
+    back() {
+      history.back();
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 230 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/views/deviceManagement/useRegistration.vue

@@ -0,0 +1,230 @@
+<template>
+  <div class="useRegistration">
+    <van-nav-bar title="设备使用登记" left-text="返回" @click-left="back()" left-arrow style="z-index:1000">
+      <van-icon name="scan" slot="right" class="tabIcon" @click="scanClick()" />
+    </van-nav-bar>
+    <div class="useRegistration-content">
+      <van-cell-group>
+        <van-field v-model="equipmentForm.code" label="设备编号" placeholder="请扫码" readonly clearable :label-width="100" />
+        <van-field v-model="equipmentForm.code" label="设备名称" placeholder="请扫码" readonly clearable :label-width="100" />
+        <van-field readonly v-model="equipmentForm.projectName" clickable type="textarea" autosize :label="'选择项目'"
+          :placeholder="'请选择项目'" @click="showPickerUserddp = true" />
+        <van-field v-model="equipmentForm.time" label="开始使用时间" placeholder="请选择使用时间" @click="endDateShow = true"
+          readonly clickable></van-field>
+        <van-field v-model="durationOfUse" label="已使用时长(小时)" placeholder="请扫码" readonly clearable :label-width="120"
+          v-if="status == 1" />
+      </van-cell-group>
+      <!-- 选择时间 -->
+      <van-popup v-model="endDateShow" position="bottom">
+        <van-datetime-picker type="datetime" title="选择截止时间" v-model="currentDate2" @confirm="endDateChange"
+          @cancel="endDateShow = false; $forceUpdate();" />
+      </van-popup>
+      <!-- 选择项目 -->
+      <van-popup v-model="showPickerUserddp" position="bottom" style="height: 84%">
+        <div class="popupDiv">
+          <div class="popupSearch">
+            <van-search v-model="userName" shape="round" background="#F4F4F4" placeholder="请输入项目名称/编号" @clear="sea()"
+              @blur="sea()" @search="sea()" @input="sea()" />
+          </div>
+
+          <div class="popupCon">
+            <div class="popupTitle">近期选择项目</div>
+
+            <div v-for="(item, index) in integrationProjectList" :key="index" class="popupItem paddingDiv"
+              @click="fZr(item, index)">
+              <div class="popupItemOne" v-if="item.projectName">{{ item.projectName }}</div>
+              <p class="popupItemTwo" v-if="item.projectCode">{{ item.projectCode }}</p>
+            </div>
+
+            <div class="popupTitle marginTop">全部项目</div>
+
+            <div v-for="(item, index) in projectss" :key="item.id" class="popupItem paddingDiv"
+              @click="fZr(item, index)">
+              <div class="popupItemOne" v-if="item.projectName">{{ item.projectName }}</div>
+              <p class="popupItemTwo" v-if="item.projectCode">{{ item.projectCode }}</p>
+            </div>
+
+            <!-- <div class="popupTitle paddingDiv">全部项目</div> -->
+          </div>
+        </div>
+      </van-popup>
+    </div>
+    <div class="useRegistration-footer">
+      <van-button type="primary" size="info" round block @click="operation" :disabled="!this.equipmentForm.code">{{
+        status == 0 ? '确定开始使用' : '确定结束使用' }}</van-button>
+    </div>
+    <!-- 扫码 -->
+    <div class="qrcodeClass" v-if="showQrcode">
+      <Qrcode @closeQrcode="closeQrcode" @determineQrcode="determineQrcode" />
+    </div>
+  </div>
+</template>
+
+<script>
+import Qrcode from '../../components/qrcode.vue'
+export default {
+  components: { Qrcode },
+  data() {
+    return {
+      equipmentForm: {
+        code: '',
+        name: '',
+        projectName: '',
+        time: this.getToday()
+      },
+      endDateShow: false,
+      currentDate2: this.getToday(),
+      showPickerUserddp: false,
+      userName: '',
+      integrationProjectList: [],
+      projectss: [],
+      project: [],
+      proads: [],
+      showQrcode: false,
+      status: 0, // 0: 新增,1:编辑
+      durationOfUse: 0
+    };
+  },
+  mounted() {
+    this.reset()
+    this.getRecentlyProject()
+    this.getPeoject()
+  },
+  methods: {
+    operation() {
+      this.equipmentForm = {
+        code: '',
+        name: '',
+        projectName: '',
+        time: this.getToday()
+      },
+        this.status = 0
+      this.$toast.success('操作成功')
+    },
+    closeQrcode() {
+      this.showQrcode = false
+    },
+    scanClick() {
+      this.showQrcode = true
+    },
+    determineQrcode(val) {
+      console.log(val, '<=========== val')
+      if (val != '') {
+        const newVal = JSON.parse(val)
+        this.equipmentForm = {
+          code: newVal.equipmentNumber,
+          name: newVal.equipmentName,
+          projectName: newVal.projectName,
+          time: newVal.usageTime
+        }
+        this.durationOfUse = newVal.durationOfUse || 0
+        this.status = newVal.status || 0
+      } else {
+        this.$toast.fail('无效的二维码')
+      }
+      this.closeQrcode()
+    },
+    reset() {
+      this.equipmentForm = {
+        code: '',
+        name: '',
+        projectName: '',
+        time: this.getToday()
+      }
+    },
+    fZr(row, index) {
+      this.equipmentForm.projectName = row.projectName
+      this.showPickerUserddp = false
+    },
+    sea() {
+      if (this.userName.length > 0) {
+        let data = this.proads.filter(item => { return item.projectName.indexOf(this.userName.toUpperCase()) != '-1' });
+        let dataList = this.proads.filter(item => { return item.projectCode.indexOf(this.userName.toUpperCase()) != '-1' });
+        let dataTree = data.concat(dataList)
+        let arrList = Array.from(new Set(dataTree))
+        this.projectss = arrList
+      } else {
+        this.projectss = this.proads
+      }
+    },
+    getToday() {
+      return this.formatDate(new Date())
+    },
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0'); // 获取月份,并确保两位数
+      const day = String(date.getDate()).padStart(2, '0'); // 获取日期,并确保两位数
+      const hours = String(date.getHours()).padStart(2, '0'); // 获取小时,并确保两位数
+      const minutes = String(date.getMinutes()).padStart(2, '0'); // 获取分钟,并确保两位数
+      const seconds = String(date.getSeconds()).padStart(2, '0'); // 获取秒钟,并确保两位数
+
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    },
+    endDateChange(date) {
+      this.equipmentForm.time = this.formatDate(date)
+      this.endDateShow = false
+    },
+    back() {
+      history.back();
+    },
+    getRecentlyProject() {
+      this.$axios.post('/project/nearProject', {})
+        .then(res => {
+          if (res.code == 'ok') {
+            this.integrationProjectList = res.data;
+          }
+        }).catch(err => { this.$toast.clear(); this.cardRefLoading = false; })
+    },
+    getPeoject() {
+      this.$axios.post("/project/getProjectList", { forReport: 1 })
+        .then(res => {
+          if (res.code == "ok") {
+            for (var i in res.data) {
+              if (res.data[i].projectCode == 'null' || res.data[i].projectCode == null) {
+                res.data[i].projectCode = ' '
+              }
+            }
+            this.projectss = res.data;
+            this.project = res.data;
+            this.projectss = this.projectss.filter(p => p.status == 1 || p.status == 4);
+            this.proads = res.data
+          } else {
+            this.$toast.fail('获取失败:' + res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.useRegistration {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+
+  .useRegistration-content {
+    flex: 1;
+    overflow: auto;
+  }
+
+  .useRegistration-footer {
+    width: 100%;
+    padding: 20px 20px 20px 20px;
+  }
+
+  .tabIcon {
+    font-size: 20px;
+    font-weight: bold;
+  }
+
+  .qrcodeClass {
+    position: absolute;
+    width: 100%;
+    height: 100vh;
+    z-index: 9999;
+  }
+}
+</style>

+ 10 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/views/index/index.vue

@@ -299,6 +299,16 @@
                         icon: 'todo-list-o'
                     });
                 }
+
+                // 设备管理
+                if(this.user.companyId == 10) {
+                    this.routers.push(
+                    {
+                        name: '设备管理',
+                        url: '/useRegistration',
+                        icon: 'label-o'
+                    });
+                }
             },
 
             // 获取企业微信参数