Browse Source

feat: ✨ 新增加表格滚动,自动搜索,修复请假小时

@sun-chaoqun 2 years ago
parent
commit
c856291bd0

+ 9 - 0
components.d.ts

@@ -13,13 +13,19 @@ declare module '@vue/runtime-core' {
     Dialog: typeof import('./src/components/dialog/Dialog.vue')['default']
     Drawer: typeof import('./src/components/Drawer/index.vue')['default']
     ElAside: typeof import('element-plus/es')['ElAside']
+    ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
+    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
@@ -38,6 +44,8 @@ declare module '@vue/runtime-core' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -45,6 +53,7 @@ declare module '@vue/runtime-core' {
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
+    ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     Loading: typeof import('./src/components/Loading/index.vue')['default']

+ 27 - 83
package-lock.json

@@ -11,11 +11,11 @@
         "@element-plus/icons-vue": "^2.1.0",
         "axios": "^1.3.4",
         "dayjs": "^1.11.7",
+        "el-table-infinite-scroll": "^3.0.1",
         "element-plus": "^2.3.0",
         "js-md5": "^0.7.3",
         "nprogress": "^0.2.0",
         "pinia": "^2.0.33",
-        "qiniu-js": "^3.4.1",
         "vue": "^3.2.45",
         "vue-router": "^4.1.6"
       },
@@ -450,18 +450,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/@babel/runtime-corejs2": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs2/-/runtime-corejs2-7.21.0.tgz",
-      "integrity": "sha512-hVFDLYkuthnvQwWoOniPSq+RWyQTiimVdMXQJujoiSX8maFh/62+qRImGkRpeRflsVXXSMFS4HgNe3X9fuw5ww==",
-      "dependencies": {
-        "core-js": "^2.6.12",
-        "regenerator-runtime": "^0.13.11"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@babel/template": {
       "version": "7.20.7",
       "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.20.7.tgz",
@@ -1959,10 +1947,9 @@
       "dev": true
     },
     "node_modules/core-js": {
-      "version": "2.6.12",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz",
-      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
-      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "version": "3.30.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.30.0.tgz",
+      "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==",
       "hasInstallScript": true
     },
     "node_modules/cross-spawn": {
@@ -2167,6 +2154,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/el-table-infinite-scroll": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/el-table-infinite-scroll/-/el-table-infinite-scroll-3.0.1.tgz",
+      "integrity": "sha512-A5zeqo0us1mzAi+bvQsluex2V4BSEf/2a4FuZzkluJWsoqNCIexRVnxcgWVRl/8HaAK9nLGLnkAb//Xox+eLOg==",
+      "dependencies": {
+        "core-js": "^3.x",
+        "element-plus": "^2.x",
+        "vue": "^3.x"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.330",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.330.tgz",
@@ -3631,25 +3628,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/qiniu-js": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmmirror.com/qiniu-js/-/qiniu-js-3.4.1.tgz",
-      "integrity": "sha512-8vxrLqDPlJUk3fUAaTozh3TAT3ww9B5KqGogmGuTiFHnewXDoMxTCSY5z8Ab5UNdrCo6ZxDM07G/o++CICRUFw==",
-      "dependencies": {
-        "@babel/runtime-corejs2": "^7.10.2",
-        "querystring": "^0.2.1",
-        "spark-md5": "^3.0.0"
-      }
-    },
-    "node_modules/querystring": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.1.tgz",
-      "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
-      "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
-      "engines": {
-        "node": ">=0.4.x"
-      }
-    },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3668,11 +3646,6 @@
         "node": ">=8.10.0"
       }
     },
-    "node_modules/regenerator-runtime": {
-      "version": "0.13.11",
-      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
-      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
-    },
     "node_modules/relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz",
@@ -3852,11 +3825,6 @@
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "deprecated": "Please use @jridgewell/sourcemap-codec instead"
     },
-    "node_modules/spark-md5": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
-      "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
-    },
     "node_modules/strip-ansi": {
       "version": "6.0.1",
       "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -4863,15 +4831,6 @@
       "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.3.tgz",
       "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ=="
     },
-    "@babel/runtime-corejs2": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs2/-/runtime-corejs2-7.21.0.tgz",
-      "integrity": "sha512-hVFDLYkuthnvQwWoOniPSq+RWyQTiimVdMXQJujoiSX8maFh/62+qRImGkRpeRflsVXXSMFS4HgNe3X9fuw5ww==",
-      "requires": {
-        "core-js": "^2.6.12",
-        "regenerator-runtime": "^0.13.11"
-      }
-    },
     "@babel/template": {
       "version": "7.20.7",
       "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.20.7.tgz",
@@ -5951,9 +5910,9 @@
       "dev": true
     },
     "core-js": {
-      "version": "2.6.12",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz",
-      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
+      "version": "3.30.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.30.0.tgz",
+      "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg=="
     },
     "cross-spawn": {
       "version": "7.0.3",
@@ -6113,6 +6072,16 @@
         "jake": "^10.8.5"
       }
     },
+    "el-table-infinite-scroll": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/el-table-infinite-scroll/-/el-table-infinite-scroll-3.0.1.tgz",
+      "integrity": "sha512-A5zeqo0us1mzAi+bvQsluex2V4BSEf/2a4FuZzkluJWsoqNCIexRVnxcgWVRl/8HaAK9nLGLnkAb//Xox+eLOg==",
+      "requires": {
+        "core-js": "^3.x",
+        "element-plus": "^2.x",
+        "vue": "^3.x"
+      }
+    },
     "electron-to-chromium": {
       "version": "1.4.330",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.330.tgz",
@@ -7273,21 +7242,6 @@
       "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
       "dev": true
     },
-    "qiniu-js": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmmirror.com/qiniu-js/-/qiniu-js-3.4.1.tgz",
-      "integrity": "sha512-8vxrLqDPlJUk3fUAaTozh3TAT3ww9B5KqGogmGuTiFHnewXDoMxTCSY5z8Ab5UNdrCo6ZxDM07G/o++CICRUFw==",
-      "requires": {
-        "@babel/runtime-corejs2": "^7.10.2",
-        "querystring": "^0.2.1",
-        "spark-md5": "^3.0.0"
-      }
-    },
-    "querystring": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.1.tgz",
-      "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg=="
-    },
     "queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -7303,11 +7257,6 @@
         "picomatch": "^2.2.1"
       }
     },
-    "regenerator-runtime": {
-      "version": "0.13.11",
-      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
-      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
-    },
     "relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz",
@@ -7436,11 +7385,6 @@
       "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
     },
-    "spark-md5": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
-      "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
-    },
     "strip-ansi": {
       "version": "6.0.1",
       "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",

+ 1 - 1
package.json

@@ -12,11 +12,11 @@
     "@element-plus/icons-vue": "^2.1.0",
     "axios": "^1.3.4",
     "dayjs": "^1.11.7",
+    "el-table-infinite-scroll": "^3.0.1",
     "element-plus": "^2.3.0",
     "js-md5": "^0.7.3",
     "nprogress": "^0.2.0",
     "pinia": "^2.0.33",
-    "qiniu-js": "^3.4.1",
     "vue": "^3.2.45",
     "vue-router": "^4.1.6"
   },

+ 6 - 2
src/components/Drawer/index.vue

@@ -37,11 +37,15 @@ defineExpose({ closeDrawer, openDrawer })
       <template #header="{ close, titleId, titleClass }">
         <slot name="header" :params="{ close, titleId, titleClass }"></slot>
       </template>
-      <div class="demo-drawer__content">
+      <div class="drawer__content">
         <slot></slot>
       </div>
     </el-drawer>
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.drawer__content {
+  height: 100%;
+}
+</style>

+ 11 - 4
src/components/TableBase/index.vue

@@ -16,10 +16,11 @@ interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
   border?: boolean // 是否带有纵向边框 ==> 非必传(默认为true)
   toolButton?: boolean // 是否显示表格功能按钮 ==> 非必传(默认为true)
   selectId?: string // 当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
-  displayHeader?: boolean
+  displayHeader?: boolean // 是否隐藏头部
   onResize?: () => number
-  rowClick?: (row: any, column: any, event: any) => void
-  height?: number
+  rowClick?: (row: any, column: any, event: any) => void // 点击行
+  selectionChange?: (row: any) => void // 选择函数
+  getRowKey?: ((row: any) => string) | string // 用于优化select勾选框
 }
 
 // 接受父组件参数,配置默认值
@@ -76,7 +77,13 @@ defineExpose({
   </div>
 
   <div class="card table" :style="{ height: cardHeight + 'px' }">
-    <el-table :data="tableData" @row-click="props.rowClick" :border="border" row-key="prop">
+    <el-table
+      :data="tableData"
+      @row-click="props.rowClick"
+      @selection-change="props.selectionChange"
+      :border="border"
+      :row-key="props.getRowKey"
+    >
       <!-- 默认插槽 -->
       <slot></slot>
       <template v-for="item in tableColumns" :key="item">

+ 1 - 1
src/layouts/Header/Breadcrumb.vue

@@ -10,7 +10,7 @@ const router = useRouter()
 const breadcrumbList: any = computed(() => {
   return route.matched
 })
-console.log(breadcrumbList)
+// console.log(breadcrumbList)
 const isUnicode = (val: string | undefined) => {
   if (!val) return false
   return !/ue/g.test(val)

+ 11 - 0
src/styles/element.scss

@@ -30,3 +30,14 @@
 .el-table .cell {
   white-space: nowrap !important;
 }
+
+.el-popper.is-customized {
+  /* Set padding to ensure the height is 32px */
+  padding: 6px 12px;
+  background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
+}
+
+.el-popper.is-customized .el-popper__arrow::before {
+  background: linear-gradient(45deg, #b2e68d, #bce689);
+  right: 0;
+}

+ 69 - 45
src/utils/common.ts

@@ -1,6 +1,5 @@
 import md5 from 'js-md5'
 import dayjs from 'dayjs'
-import * as qiniu from 'qiniu-js'
 
 /**
  * md5 加密
@@ -141,50 +140,50 @@ export const getFormatDuration = (time: number) => {
  * @param {*} file 上传的文件
  * @param {*} token 上传需要的token
  */
-export const qiniuUpLoadFun = (file: any, token: any) => {
-  //七牛上传方法
-  return new Promise(function (resolve) {
-    let timestamp = new Date().valueOf()
-    let params = {
-      file: file, //要上传的文件
-      key: 'paper_file/' + timestamp + file.name, //自定义文件地址
-      token: token,
-      config: {
-        useCdnDomain: false,
-        // region: undefined,
-        region: qiniu.region.z2,
-        domin: 'https://qiniu.region.z2',
-        chunkSize: 1000
-      },
-      putExtra: {
-        fname: file.name,
-        params: {},
-        mimeType: [] || null
-      }
-    }
-    console.log('domin')
-    resolve(params)
-  })
-}
-export function upLoadQiniu(params: any) {
-  let observable = qiniu.upload(params.file, params.key, params.token, params.putExtra, params.config)
-  let observer = {
-    next(res: any) {
-      // console.log(res);
-      console.log('上传中', res)
-    },
-    error(err: any) {
-      console.log(err)
-    },
-    complete(res: any) {
-      // let url = 'http://xxxxxx.cn/' + res.key
-      // layer.close(loading) //关闭loading效果
-      console.log(res)
-      // resolve(url)
-    }
-  }
-  observable.subscribe(observer)
-}
+// export const qiniuUpLoadFun = (file: any, token: any) => {
+//   //七牛上传方法
+//   return new Promise(function (resolve) {
+//     let timestamp = new Date().valueOf()
+//     let params = {
+//       file: file, //要上传的文件
+//       key: 'paper_file/' + timestamp + file.name, //自定义文件地址
+//       token: token,
+//       config: {
+//         useCdnDomain: false,
+//         // region: undefined,
+//         region: qiniu.region.z2,
+//         domin: 'https://qiniu.region.z2',
+//         chunkSize: 1000
+//       },
+//       putExtra: {
+//         fname: file.name,
+//         params: {},
+//         mimeType: [] || null
+//       }
+//     }
+//     console.log('domin')
+//     resolve(params)
+//   })
+// }
+// export function upLoadQiniu(params: any) {
+//   let observable = qiniu.upload(params.file, params.key, params.token, params.putExtra, params.config)
+//   let observer = {
+//     next(res: any) {
+//       // console.log(res);
+//       console.log('上传中', res)
+//     },
+//     error(err: any) {
+//       console.log(err)
+//     },
+//     complete(res: any) {
+//       // let url = 'http://xxxxxx.cn/' + res.key
+//       // layer.close(loading) //关闭loading效果
+//       console.log(res)
+//       // resolve(url)
+//     }
+//   }
+//   observable.subscribe(observer)
+// }
 
 interface iconsType {
   [propName: string]: string | number
@@ -208,3 +207,28 @@ export const icons: iconsType = {
   ue7cd: '\ue7cd',
   ue831: '\ue831'
 }
+
+// export const debounce = (fn: () => void, duration: number) => {
+//   let time: NodeJS.Timeout = 0
+//   return () => {
+//     // let arg =
+//     clearTimeout(time)
+//     time = setTimeout(() => {
+//       fn && fn()
+//     }, duration)
+//   }
+// }
+
+export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
+  let timerId: number | undefined
+
+  return (...args: Parameters<T>) => {
+    if (timerId) {
+      clearTimeout(timerId)
+    }
+    timerId = window.setTimeout(() => {
+      fn(...args)
+      timerId = undefined
+    }, delay)
+  }
+}

+ 5 - 3
src/views/storehouse/ProductionList.vue

@@ -15,7 +15,7 @@ import Dialog from '@/components/dialog/Dialog.vue'
 import TableBase from '@/components/TableBase/index.vue'
 import { ElMessageBox, ElMessage } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
-import { Edit, Delete, Picture } from '@element-plus/icons-vue'
+import { Edit, Delete, Picture, Plus } from '@element-plus/icons-vue'
 import type { ColumnProps } from '@/components/TableBase/interface/index'
 
 const isNew = ref(true)
@@ -33,7 +33,7 @@ const columns: ColumnProps[] = [
   { prop: 'T_model', label: '产品型号', ellipsis: true },
   { prop: 'T_spec', label: '产品规格' },
   { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
-  { prop: 'T_name', label: '更新时间' },
+  { prop: 'T_remark', label: '备注' },
   { prop: 'operation', label: '操作', width: 150, fixed: 'right' }
 ]
 
@@ -253,7 +253,9 @@ onMounted(() => {
         </el-form-item>
         <el-form-item label="产品图片:" class="m-b-6" :label-width="formLabelWidth" prop="T_img">
           <!-- <Upload ref="uploadRef" v-if="isNew || form.T_img" :isImg="true" :limit="1" v-model="form.T_img"></Upload> -->
-          <Upload ref="uploadRef" :isImg="true" :limit="1" v-model="form.T_img" accept="image/*"></Upload>
+          <Upload ref="uploadRef" :isImg="true" :limit="1" v-model="form.T_img" accept="image/*">
+            <el-icon><Plus /></el-icon>
+          </Upload>
         </el-form-item>
         <el-form-item label="备注:" class="m-b-6" :label-width="formLabelWidth" prop="T_remark">
           <el-input

+ 267 - 95
src/views/storehouse/sales/ContractForm.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref, reactive, nextTick } from 'vue'
 import Drawer from '@/components/Drawer/index.vue'
 import Dialog from '@/components/dialog/Dialog.vue'
 import type { FormInstance, FormRules } from 'element-plus'
@@ -8,8 +8,17 @@ import { Delete } from '@element-plus/icons-vue'
 import { GlobalStore } from '@/stores/index'
 import TableBase from '@/components/TableBase/index.vue'
 import Upload from '@/components/Upload/index.vue'
-import { Storehouse_Product_List } from '@/api/storehouse/index'
-// import { Storehouse_IotCard_Add, Storehouse_IotCard_Edit } from '@/api/storehouse/index'
+import {
+  Storehouse_Product_List,
+  Storehouse_IotCard_Edit,
+  Storehouse_ProductClass_List,
+  Storehouse_Product_Model_List,
+  Storehouse_Product_Name_List
+} from '@/api/storehouse/index'
+import { debounce } from '@/utils/common'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import { default as vElTableInfiniteScroll } from 'el-table-infinite-scroll'
+import { log } from 'console'
 
 const isNew = ref(true)
 const globalStore = GlobalStore()
@@ -19,10 +28,15 @@ const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
 const dialogRef = ref<InstanceType<typeof Dialog> | null>(null)
 const uploadRef = ref<InstanceType<typeof Upload> | null>(null)
 const TableProductRef = ref<InstanceType<typeof TableBase> | null>(null)
+const drawerProductRef = ref<InstanceType<typeof Drawer> | null>(null)
 
 const validate_T_product = (rule: any, value: any, callback: any) => {
+  console.log(value)
+
   if (form.T_type === 1 && value === '') {
     callback(new Error('请选择产品明细'))
+  } else if (value.includes(undefined)) {
+    callback(new Error('请填写产品数量'))
   } else {
     callback()
   }
@@ -41,6 +55,7 @@ const callbackDrawer = (done: Fn) => {
   resetForm(ruleFormRef.value)
   done()
 }
+const callbackProductDrawer = (done: Fn) => done()
 
 const resetForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
@@ -48,12 +63,23 @@ const resetForm = (formEl: FormInstance | undefined) => {
 }
 
 // 添加仓库名称
+interface FormType {
+  T_id: string
+  T_number: string
+  T_customer: string
+  T_type: any
+  T_product: any
+  T_money: string
+  T_date: string
+  T_remark: string
+  T_pdf: string
+}
 type Fn = () => void
-const form = reactive({
+const form = reactive<FormType>({
   T_id: '',
   T_number: '',
   T_customer: '',
-  T_type: null,
+  T_type: '',
   T_product: '',
   T_money: '',
   T_date: '',
@@ -74,66 +100,128 @@ const openDrawer = (type: string, row?: any) => {
   drawerRef.value?.openDrawer()
 }
 
-// const AddUserName = (formEl: FormInstance | undefined) => {
-//   if (!formEl) return
-//   formEl.validate(async valid => {
-//     if (valid) {
-//       let res: any = {}
-//       if (isNew.value) {
-//         console.log(form)
-//         res = await Storehouse_IotCard_Add({ User_tokey: globalStore.GET_User_tokey, ...form })
-//       } else {
-//         res = await Storehouse_IotCard_Edit({
-//           User_tokey: globalStore.GET_User_tokey,
-//           ...form
-//         })
-//       }
-//       if (res.Code === 200) {
-//         ElMessage.success(`${isNew.value ? '添加' : '修改'}物联网卡成功!!`)
-//         nextTick(() => {
-//           drawerRef.value?.closeDrawer()
-//           TableRef.value?.getTableList()
-//           resetForm(ruleFormRef.value)
-//           isNew.value = true
-//         })
-//       }
-//     } else {
-//       return false
-//     }
-//   })
-// }
+const AddContract = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  form.T_product = tableData.value.map(item => {
+    if (!item.count) return undefined
+    return `${item.Id},${item.count}`
+  })
+
+  formEl.validate(async valid => {
+    if (valid) {
+      let res: any = {}
+      if (isNew.value) {
+        console.log(form)
+        // res = await Storehouse_IotCard_Add({ User_tokey: globalStore.GET_User_tokey, ...form })
+      } else {
+        res = await Storehouse_IotCard_Edit({
+          User_tokey: globalStore.GET_User_tokey,
+          ...form
+        })
+      }
+      if (res.Code === 200) {
+        ElMessage.success(`${isNew.value ? '添加' : '修改'}物联网卡成功!!`)
+        nextTick(() => {
+          drawerRef.value?.closeDrawer()
+          // TableRef.value?.getTableList()
+          resetForm(ruleFormRef.value)
+          isNew.value = true
+        })
+      }
+    } else {
+      return false
+    }
+  })
+}
 
-// const AddUserName = () => {
-//   console.log(tableData)
-// }
 // 增加产品
 // dialog
 const initParam = reactive({
   User_tokey: globalStore.GET_User_tokey,
   T_name: '',
   T_model: '',
-  T_class: ''
+  T_class: '',
+  page: 0,
+  page_z: 20
 })
-const classOptions = reactive<any[]>([])
-const modelOptions = reactive<any[]>([])
+const classOptions = ref<any[]>([])
+const modelOptions = ref<any[]>([])
+// 获取产品分类
+const getProductClassList = async () => {
+  const res: any = await Storehouse_ProductClass_List({ page: 1, page_z: 999 })
+  classOptions.value = res.Data.Data
+}
+// 获取产品型号
+const getProductModelList = async () => {
+  const res: any = await Storehouse_Product_Model_List({ T_name: initParam.T_name })
+  console.log(res)
+  modelOptions.value = res.Data.map((item: any, index: number) => {
+    return {
+      value: item,
+      index: index
+    }
+  })
+}
 const AddProductionDetailed = () => {
-  dialogRef.value?.DialogOpen()
+  // getProductList()
+  !classOptions.value.length && getProductClassList()
+  drawerProductRef.value?.openDrawer()
+}
+// 搜索名称
+const searchNameHandle = async () => {
+  console.log(initParam)
+  const res = await Storehouse_Product_Name_List({ T_name: initParam.T_name, T_class: initParam.T_class })
+  console.log(res)
+}
+// 搜索模型
+const searchModelHandle = () => {
+  total = 0
+  initParam.page = 1
+  tableProductData.value = []
+  getProductList()
+  // console.log(initParam)
 }
 const searchHandle = () => {
   //
 }
+// 保存选中的数据id,row-key就是要指定一个key标识这一行的数据
+const getRowKey = (row: any) => {
+  return row.Id
+}
+// 选中的产品
+const ProductselectionChange = (row: any[]) => {
+  tableData.value = row
+}
 
-const tableData = reactive<any[]>([])
+// 加载第二个抽屉数据
+const load = () => {
+  if (initParam.page && total === tableProductData.value.length) {
+    ElMessage.warning('没有更多数据了!!')
+    return
+  }
+  initParam.page++
+  getProductList()
+}
+let total = 0
+const tableProductData = ref<any[]>([])
+const getProductList = async () => {
+  const res: any = await Storehouse_Product_List({ ...initParam })
+  console.log(res)
+  tableProductData.value.push(...res.Data.Data)
+  total = res.Data.Num
+}
+
+const tableData = ref<any[]>([])
 const columns = [
   { type: 'index', label: '序号', width: 80, align: 'center ' },
-  { label: '产品图片', prop: 'T_img', align: 'center ' },
+  { label: '产品图片', prop: 'T_img', align: 'center ', name: 'T_img' },
   { label: '产品名称', prop: 'T_name', align: 'center ' },
   { label: '产品分类', prop: 'T_class_name', align: 'center ' },
   { label: '产品型号', prop: 'T_model', align: 'center ' },
   { label: '产品规格', prop: 'T_spec', align: 'center ' },
-  { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120 },
+  { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120, name: 'T_relation_sn' },
   { label: '*数量', prop: 'count', align: 'center ', name: 'count' },
-  { label: '备注', prop: 'id', align: 'center ' },
+  { label: '备注', prop: 'T_remark', align: 'center ' },
   { prop: 'operation', label: '操作', width: 80, fixed: 'right' }
 ]
 
@@ -142,12 +230,41 @@ const productColumns = [
   { prop: 'T_img', label: '产品图片', name: 'T_img' },
   { prop: 'T_name', label: '产品名称' },
   { prop: 'T_class_name', label: '产品分类' },
-  { prop: 'T_model', label: '产品型号' },
+  { prop: 'T_model', label: '产品型号', ellipsis: true },
   { prop: 'T_spec', label: '产品规格' },
   { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
-  { prop: 'T_name', label: '更新时间' }
+  { prop: 'T_remark', label: '备注', ellipsis: true }
 ]
 
+// 自动搜索
+const autoSelect = ref('')
+let timeout: NodeJS.Timeout
+const querySearchAsync = async (queryString: string, cb: (arg: any) => void) => {
+  clearTimeout(timeout)
+  timeout = setTimeout(async () => {
+    const results = await getNameAsync(queryString)
+    console.log(autoSelect, queryString)
+    cb(results)
+  }, 2000)
+}
+
+const getNameAsync = async (str: string): Promise<any> => {
+  const res: any = await Storehouse_Product_Name_List({ T_name: str, T_class: initParam.T_class })
+  if (!res.Data) return
+  return res.Data.map((item: any, index: number) => {
+    return {
+      value: item,
+      index: index
+    }
+  })
+}
+
+const handleSelect = (item: any) => {
+  console.log(item)
+  initParam.T_name = item.value
+  getProductModelList()
+}
+
 defineExpose({
   openDrawer
 })
@@ -196,8 +313,13 @@ defineExpose({
                 <template #header v-if="item.prop === 'count'">
                   <span style="color: red">*数量</span>
                 </template>
-                <template #default="{ row }" v-if="item.prop === item.name && item.prop">
-                  <el-input v-model.number="row.count" type="text" autocomplete="off" />
+                <template #default="{ row }" v-if="item.prop === item.name">
+                  <el-input v-if="item.prop === 'count'" v-model.number="row.count" type="text" autocomplete="off" />
+                  <span v-if="item.prop === 'T_relation_sn'">
+                    <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                    <el-tag v-else type="success" effect="dark">否</el-tag>
+                  </span>
+                  <el-image v-if="item.prop === 'T_img'" style="height: 50px" :src="row.T_img" fit="cover" />
                 </template>
               </el-table-column>
               <el-table-column v-bind="item" v-if="item.fixed === 'right'">
@@ -234,7 +356,7 @@ defineExpose({
             placeholder="请输入备注信息"
           />
         </el-form-item>
-        <el-form-item label="上传附件:" :label-width="formLabelWidth" prop="T_type">
+        <el-form-item label="上传附件:" :label-width="formLabelWidth" prop="T_pdf">
           <Upload
             class="w-50"
             ref="uploadRef"
@@ -252,61 +374,104 @@ defineExpose({
         <div class="btn">
           <el-divider>
             <el-button>取消</el-button>
-            <el-button v-if="isNew" color="#626aef">提交</el-button>
+            <el-button v-if="isNew" color="#626aef" @click="AddContract(ruleFormRef)">提交</el-button>
             <el-button v-else color="#626aef">修改</el-button>
           </el-divider>
         </div>
+        <Drawer ref="drawerProductRef" :handleClose="callbackProductDrawer" size="70%">
+          <template #header="{ params }">
+            <h4 :id="params.titleId" :class="params.titleClass">选择产品</h4>
+          </template>
+          <el-card class="box-card">
+            <template #header>
+              <div class="input-suffix">
+                <el-row :gutter="20" style="margin-bottom: 0">
+                  <el-col :xl="5" :lg="8" :md="10" class="d-flex">
+                    <span class="inline-flex items-center">产品分类:</span>
+                    <el-select v-model="initParam.T_class" clearable placeholder="请选择分类~">
+                      <el-option v-for="item in classOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
+                    </el-select>
+                  </el-col>
+                  <el-col :xl="7" :lg="8" :md="10" class="d-flex">
+                    <span class="inline-flex items-center">产品名称:</span>
+                    <el-autocomplete
+                      v-model="autoSelect"
+                      clearable
+                      :fetch-suggestions="querySearchAsync"
+                      placeholder="Please input"
+                      :debounce="2000"
+                      @select="handleSelect"
+                    />
+                  </el-col>
+                  <el-col :xl="7" :lg="8" :md="12" class="d-flex">
+                    <span class="inline-flex items-center">产品型号:</span>
+                    <el-select v-model="initParam.T_model" clearable placeholder="请选择型号~">
+                      <el-option
+                        v-for="item in modelOptions"
+                        :key="item.index"
+                        :label="item.value"
+                        :value="item.index"
+                      />
+                    </el-select>
+                    <el-button type="primary" @click="searchModelHandle">搜索</el-button>
+                  </el-col>
+                </el-row>
+              </div>
+            </template>
+            <el-table
+              border
+              :row-key="getRowKey"
+              :data="tableProductData"
+              style="width: 100%; height: 99%"
+              v-el-table-infinite-scroll="load"
+              @selection-change="ProductselectionChange"
+            >
+              <template v-for="item in productColumns" :key="item">
+                <el-table-column
+                  v-if="item.type === 'index' || item.type === 'selection'"
+                  align="center"
+                  v-bind="item"
+                />
+                <el-table-column v-if="!item.ellipsis && item.prop" v-bind="item">
+                  <template #default="{ row }">
+                    <span v-if="item.prop === 'T_relation_sn'">
+                      <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                      <el-tag v-else type="success" effect="dark">否</el-tag>
+                    </span>
+                    <el-image v-if="item.prop === 'T_img'" style="height: 50px" :src="row.T_img" fit="cover" />
+                  </template>
+                </el-table-column>
+                <el-table-column v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">
+                  <template #default="{ row }">
+                    <el-tooltip effect="dark" :content="row.T_model" placement="bottom">
+                      {{ row.T_model }}
+                    </el-tooltip>
+                  </template>
+                </el-table-column>
+                <el-table-column v-if="item.ellipsis && item.prop === 'T_remark'" align="center" v-bind="item">
+                  <template #default="{ row }">
+                    <el-tooltip effect="customized" placement="left">
+                      <template #content>
+                        <div class="tooltip-content">{{ row.T_remark }}</div>
+                      </template>
+                      {{ row.T_remark }}
+                    </el-tooltip>
+                  </template>
+                </el-table-column>
+              </template>
+            </el-table>
+          </el-card>
+        </Drawer>
       </el-form>
     </Drawer>
-    <Dialog ref="dialogRef" width="80%">
-      <template #header> 选择产品 </template>
-      <TableBase
-        ref="TableProductRef"
-        :columns="productColumns"
-        :requestApi="Storehouse_Product_List"
-        :initParam="initParam"
-      >
-        <template #table-header>
-          <div class="input-suffix">
-            <el-row :gutter="20" style="margin-bottom: 0">
-              <el-col :xl="6" :lg="8" :md="10" class="d-flex">
-                <span class="inline-flex items-center">产品分类:</span>
-                <el-select v-model="initParam.T_class" clearable placeholder="请选择分类~">
-                  <el-option v-for="item in classOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
-                </el-select>
-              </el-col>
-              <el-col :xl="6" :lg="8" :md="10" class="d-flex">
-                <span class="inline-flex items-center">产品名称:</span>
-                <el-input
-                  v-model="initParam.T_name"
-                  type="text"
-                  placeholder="按产品名称、产品型号搜索"
-                  @change="searchHandle"
-                />
-              </el-col>
-              <el-col :xl="7" :lg="8" :md="12" class="d-flex">
-                <span class="inline-flex items-center">产品型号:</span>
-                <el-select v-model="initParam.T_model" clearable placeholder="请选择型号~">
-                  <el-option v-for="item in modelOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
-                </el-select>
-                <el-button type="primary" @click="searchHandle">搜索</el-button>
-              </el-col>
-            </el-row>
-          </div>
-        </template>
-        <template #T_img="{ row }">
-          <el-image style="height: 50px" :src="row.T_img" fit="cover" />
-        </template>
-        <template #T_relation_sn="{ row }">
-          <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
-          <el-tag v-else type="success" effect="dark">否</el-tag>
-        </template>
-      </TableBase>
-    </Dialog>
   </div>
 </template>
 
 <style scoped lang="scss">
+.tooltip-content {
+  max-width: 500px;
+  overflow-y: auto;
+}
 .contract-form {
   :deep(.el-table--border .el-table__cell) {
     border-right: 0;
@@ -315,6 +480,13 @@ defineExpose({
   :deep(.card) {
     border: 0;
   }
+  .box-card {
+    height: 100%;
+    :deep(.el-card__body) {
+      height: calc(100% - 70px);
+    }
+  }
+
   .btn {
     margin-top: 32px;
     display: flex;

+ 20 - 9
src/views/workAttendance/MyLeave.vue

@@ -17,6 +17,7 @@ import TableBase from '@/components/TableBase/index.vue'
 import { getFormatDuration } from '@/utils/common'
 import type { FormInstance, FormRules } from 'element-plus'
 import { ref, reactive, onMounted, nextTick } from 'vue'
+import { floatReg } from '@/views/salary/salary/relus'
 import { ColumnProps } from '@/components/TableBase/interface/index'
 
 let uuid = ''
@@ -86,15 +87,16 @@ const AddLeave = (formEl: FormInstance | undefined) => {
   formEl.validate(async valid => {
     if (valid) {
       let res: any = {}
-      form.value.T_duration = form.value.T_duration * 60
-      if (form.value.T_duration > RemainingTime.value) {
+      let time = form.value.T_duration * 60
+      if (time > RemainingTime.value && form.value.T_type == '3') {
         ElMessage.warning('请假时长不得大于剩余时长,请重新选择时间!!')
         return
       }
+      console.log(form)
       if (isNew) {
-        res = await Leave_Add({ ...form.value, T_approver: uuid })
+        res = await Leave_Add({ ...form.value, T_approver: uuid, T_duration: time })
       } else {
-        res = await Leave_Edit({ ...form.value, T_approver: uuid })
+        res = await Leave_Edit({ ...form.value, T_approver: uuid, T_duration: time })
       }
       if (res.Code === 200) {
         ElMessage.success(`${isNew ? '申请' : '修改'}成功!`)
@@ -121,14 +123,23 @@ const form = ref({
   T_start_time: ''
 })
 
+const validate_float = (rule: any, value: any, callback: any) => {
+  if (value === '') {
+    callback(new Error('请输入请假时长'))
+  } else {
+    if (floatReg.test(value) || /\d+/.test(value)) {
+      callback()
+    } else {
+      callback(new Error('时间必须是数字或小数'))
+    }
+  }
+}
+
 const rules = reactive<FormRules>({
   T_type: [{ required: true, message: '请选择请假类型', trigger: 'blur' }],
   T_start_time: [{ required: true, message: '请选择开始时间', trigger: 'blur' }],
   T_end_time: [{ required: true, message: '请选择结束时间', trigger: 'blur' }],
-  T_duration: [
-    { required: true, message: '请输入请假时长', trigger: 'blur' },
-    { type: 'number', message: '必须是整数' }
-  ],
+  T_duration: [{ required: true, validator: validate_float, trigger: 'blur' }],
   T_approver: [{ required: true, message: '请选择审批人', trigger: 'blur' }]
 })
 
@@ -237,7 +248,7 @@ onMounted(() => {
           />
         </el-form-item>
         <el-form-item label="请假时长:" :label-width="formLabelWidth" prop="T_duration">
-          <el-input v-model.number="form.T_duration" autocomplete="off" placeholder="请假时长" />
+          <el-input v-model="form.T_duration" autocomplete="off" placeholder="请假时长" />
         </el-form-item>
         <el-form-item label="内容:" :label-width="formLabelWidth">
           <el-input