Browse Source

完成我的加班的部分功能,完成上传文件的功能

@sun-chaoqun 2 years ago
parent
commit
72df2b075c

+ 1 - 0
components.d.ts

@@ -51,6 +51,7 @@ declare module '@vue/runtime-core' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
     Loading: typeof import('./src/components/Loading/index.vue')['default']
     Pagination: typeof import('./src/components/TableBase/components/Pagination.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']

+ 88 - 0
package-lock.json

@@ -15,6 +15,7 @@
         "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"
       },
@@ -449,6 +450,18 @@
         "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",
@@ -1945,6 +1958,13 @@
       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
       "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.",
+      "hasInstallScript": true
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3611,6 +3631,25 @@
         "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",
@@ -3629,6 +3668,11 @@
         "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",
@@ -3808,6 +3852,11 @@
       "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",
@@ -4814,6 +4863,15 @@
       "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",
@@ -5892,6 +5950,11 @@
       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
       "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=="
+    },
     "cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -7210,6 +7273,21 @@
       "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",
@@ -7225,6 +7303,11 @@
         "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",
@@ -7353,6 +7436,11 @@
       "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 - 0
package.json

@@ -16,6 +16,7 @@
     "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"
   },

+ 4 - 0
src/api/public/index.ts

@@ -0,0 +1,4 @@
+import $http from '../index'
+
+// 文件上传
+export const UpFileToken = (params: any) => $http.post('/api/user/UpFileToken', params)

+ 2 - 2
src/api/workAttendance/index.ts

@@ -24,7 +24,7 @@ export const Leave_Del = (params: any) => $http.post('api/ams/Leave/Del', params
 // 审批列表
 export const Overtime_List = (params: any) => $http.post('api/ams/Overtime/List', params)
 // 我的加班列表
-export const Overtime_User_list = (params: any) => $http.post('api/ams/Overtime/User_list', params)
+export const Overtime_User_list = (params: any) => $http.post('/api/ams/Overtime/User_list', params)
 // 加班申请
 export const Overtime_Add = (params: any) => $http.post('api/ams/Overtime/Add', params)
 // 编辑
@@ -34,4 +34,4 @@ export const Overtime_Approval = (params: any) => $http.post('api/ams/Overtime/A
 // 删除
 export const Overtime_Del = (params: any) => $http.post('api/ams/Overtime/Del', params)
 // 统计
-export const Overtime_Stat = (params: any) => $http.post('api/ams/Overtime/Stat', params)
+export const Overtime_Stat = (params: any) => $http.post('/api/ams/Overtime/Stat', params)

+ 8 - 12
src/components/TableBase/index.vue

@@ -19,6 +19,7 @@ interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
   displayHeader?: boolean
   onResize?: () => number
   rowClick?: (row: any, column: any, event: any) => void
+  height?: number
 }
 
 // 接受父组件参数,配置默认值
@@ -34,17 +35,12 @@ const props = withDefaults(defineProps<ProTableProps>(), {
 })
 
 // 表格操作 Hooks
-const {
-  tableData,
-  pageable,
-  // searchParam,
-  // searchInitParam,
-  getTableList,
-  searchTable,
-  // reset,
-  handleSizeChange,
-  handleCurrentChange
-} = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback)
+const { tableData, pageable, getTableList, searchTable, handleSizeChange, handleCurrentChange } = useTable(
+  props.requestApi,
+  props.initParam,
+  props.pagination,
+  props.dataCallback
+)
 // 接收 columns 并设置为响应式
 const tableColumns = ref<ColumnProps[]>(props.columns)
 
@@ -75,7 +71,7 @@ defineExpose({
 
 <template>
   <div class="table-header" :style="{ display: displayHeader ? 'none' : '' }">
-    <slot name="table-header"></slot>
+    <slot name="table-header" :pageable="pageable"></slot>
   </div>
 
   <div class="card table" :style="{ height: cardHeight + 'px' }">

+ 5 - 3
src/hooks/useTable.ts

@@ -3,6 +3,7 @@ interface Pageable {
   pageNum: number
   pageSize: number
   total: number
+  RemainingTime?: number
   small: boolean
   disabled: boolean
 }
@@ -39,6 +40,7 @@ export const useTable = (
       pageSize: 10,
       // 总条数
       total: 0,
+      RemainingTime: 0,
       small: false,
       disabled: false
     },
@@ -60,6 +62,9 @@ export const useTable = (
     dataCallback && dataCallback(res)
     state.tableData = res.Data.Data
     state.pageable.total = res.Data.Num
+    if (res.Data.RemainingTime) {
+      state.pageable.RemainingTime = res.Data.RemainingTime
+    }
   }
 
   if (isPagination) {
@@ -82,11 +87,8 @@ export const useTable = (
 
   return {
     ...toRefs(state),
-    // searchParam,
-    // searchInitParam,
     getTableList,
     searchTable,
-    // reset,
     handleSizeChange,
     handleCurrentChange
   }

+ 7 - 2
src/layouts/Header/index.vue

@@ -27,12 +27,12 @@ const logOut = () => {
       </el-col>
       <el-col :span="4">
         <el-dropdown>
-          <el-button link>
+          <div class="avatar">
             <el-avatar>
               <img src="@/assets/images/avatar.jpg" />
             </el-avatar>
             <span style="padding-left: 16px">{{ globalStore.GET_User_Info.T_name }}</span>
-          </el-button>
+          </div>
           <template #dropdown>
             <el-dropdown-menu>
               <el-dropdown-item @click="logOut">退出登录</el-dropdown-item>
@@ -55,5 +55,10 @@ const logOut = () => {
       font-size: 1.35rem;
     }
   }
+  .avatar {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+  }
 }
 </style>

+ 15 - 2
src/layouts/Main/index.scss

@@ -14,12 +14,25 @@
   transition: all 0.2s;
 }
 .fade-transform-enter-from {
-  // opacity: 0;
+  opacity: 0;
   transition: all 0.2s;
   transform: translateX(-30px);
 }
 .fade-transform-leave-to {
-  // opacity: 0;
+  opacity: 0;
   transition: all 0.2s;
   transform: translateX(30px);
 }
+
+.slide-left-enter,
+.slide-right-leave-active {
+  opacity: 0;
+  -webkit-transform: translate(50px, 0);
+  transform: translate(50px, 0);
+}
+.slide-left-leave-active,
+.slide-right-enter {
+  opacity: 0;
+  -webkit-transform: translate(-50px, 0);
+  transform: translate(-50px, 0);
+}

+ 14 - 1
src/layouts/Main/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-// import { ref, provide } from 'vue'
+import { ref, provide, beforeRouteUpdate } from 'vue'
 // import { KeepAliveStore } from '@/stores/modules/keepAlive'
 // const isRouterShow = ref(true)
 // const refreshCurrentPage = (val: boolean) => {
@@ -9,6 +9,19 @@
 
 // const keepAliveStore = KeepAliveStore()
 // console.log(keepAliveStore)
+// const transitionName = ''
+// beforeRouteUpdate (to, from, next) {
+//  // 如果isBack为true时,证明是用户点击了回退,执行slide-right动画
+//   let isBack = this.$router.isBack
+//   if (isBack) {
+//    this.transitionName = 'slide-right'
+//   } else {
+//    this.transitionName = 'slide-left'
+//   }
+//   // 做完回退动画后,要设置成前进动画,否则下次打开页面动画将还是回退
+//   this.$router.isBack = false
+//   next()
+// }
 </script>
 
 <template>

+ 3 - 0
src/style.scss

@@ -19,6 +19,9 @@ body,
   height: 100%;
   width: 100%;
 }
+// body {
+//   background: #000;
+// }
 
 .margin-left-0 {
   margin-left: 0 !important;

+ 94 - 15
src/utils/loginBg.ts

@@ -1,12 +1,77 @@
+// class StarrySky {
+//   canvas: HTMLCanvasElement
+//   ctx: CanvasRenderingContext2D
+//   particles: Particle[]
+//   count: number
+//   // actions: string[]
+//   // action: number
+//   constructor() {
+//     this.canvas = document.createElement('canvas') as HTMLCanvasElement
+//     this.canvas.width = innerWidth
+//     this.canvas.height = innerHeight
+//     this.canvas.style.zIndex = '-1'
+//     this.canvas.style.position = 'fixed'
+//     this.canvas.style.top = '0'
+//     this.canvas.style.left = '0'
+//     this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D
+//     document.body.appendChild(this.canvas)
+
+//     this.particles = []
+//     this.count = 1000
+
+//     // this.animate()
+//   }
+//   draw() {
+//     this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
+//     if (this.particles.length < this.count) {
+//       this.particles.push(new Particle(this.canvas.width, this.canvas.height, this.ctx))
+//     }
+//     for (let i in this.particles) {
+//       const p = this.particles[i]
+//       p.update()
+//       p.draw()
+//     }
+//   }
+// }
+
+// class Particle {
+//   x: number
+//   y: number
+//   vx: number
+//   w: number
+//   h: number
+//   ctx: CanvasRenderingContext2D
+//   constructor(width: number, height: number, ctx: CanvasRenderingContext2D) {
+//     this.w = width
+//     this.h = height
+//     this.ctx = ctx
+//     this.x = Math.random() * width
+//     this.y = Math.random() * height
+//     this.vx = Math.random()
+//   }
+//   update() {
+//     this.x += this.vx * 3
+//     if (this.x > this.w) {
+//       this.x = 0
+//     }
+//   }
+//   draw() {
+//     this.ctx.beginPath()
+//     this.ctx.arc(this.x, this.y, 1 + this.vx, 0, Math.PI * 2)
+//     this.ctx.fillStyle = `rgba(255, 255, 255, ${this.vx})`
+//     this.ctx.fill()
+//   }
+// }
+
+// export default StarrySky
+
 class StarrySky {
   canvas: HTMLCanvasElement
   ctx: CanvasRenderingContext2D
   particles: Particle[]
   count: number
-  actions: string[]
-  action: number
   constructor() {
-    this.canvas = document.createElement('canvas') as HTMLCanvasElement
+    this.canvas = document.createElement('canvas')
     this.canvas.width = innerWidth
     this.canvas.height = innerHeight
     this.canvas.style.zIndex = '-1'
@@ -19,26 +84,37 @@ class StarrySky {
     this.particles = []
     this.count = 1000
 
-    // this.animate()
+    this.animate()
+    this.draw()
   }
   draw() {
     this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
-    if (this.particles.length < this.count) {
+    for (let i = 0; i < this.count; i++) {
       this.particles.push(new Particle(this.canvas.width, this.canvas.height, this.ctx))
-    }
-    for (let i in this.particles) {
       const p = this.particles[i]
-      p.update()
       p.draw()
     }
   }
-  // onresize(){
-
-  // }
-  // animate() {
-  //   requestAnimationFrame(() => this.animate())
-  //   this.draw()
-  // }
+  onresize() {
+    window.onresize = () => {
+      this.canvas.width = window.innerWidth
+      this.canvas.height = window.innerHeight
+      this.draw()
+    }
+  }
+  shanshuo() {
+    let index = Math.ceil(Math.random() * this.count)
+    this.particles.splice(0, index + 5)
+    if (this.particles.length < this.count) {
+      let particle = new Particle(this.canvas.width, this.canvas.height, this.ctx)
+      this.particles.push(particle)
+      particle.draw()
+    }
+  }
+  animate() {
+    requestAnimationFrame(() => this.animate())
+    this.shanshuo()
+  }
 }
 
 class Particle {
@@ -70,4 +146,7 @@ class Particle {
   }
 }
 
+// const star = new StarrySky()
+// star.onresize()
+
 export default StarrySky

+ 3 - 1
src/views/Index.vue

@@ -257,7 +257,9 @@ const handleClose = (key: string, keyPath: string[]) => {
   .el-header {
     display: flex;
     align-items: center;
-    border-bottom: solid 2px var(--el-menu-border-color);
+    // border-bottom: solid 2px var(--el-menu-border-color);
+    z-index: 10;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
   }
 }
 </style>

+ 5 - 4
src/views/Login.vue

@@ -57,9 +57,10 @@ const changeType = () => {
   passType.value = passType.value === 'password' ? 'text' : 'password'
 }
 
-onMounted(() => {
-  new StarrySky()
-})
+// onMounted(() => {
+//   const star = new StarrySky()
+//   star.onresize()
+// })
 </script>
 
 <template>
@@ -85,7 +86,7 @@ onMounted(() => {
           </el-input>
         </el-form-item>
         <el-form-item>
-          <el-button class="submit" type="primary" @click="submitForm(ruleFormRef)">登录</el-button>
+          <el-button class="submit" type="primary" @click.enter="submitForm(ruleFormRef)">登录</el-button>
         </el-form-item>
       </el-form>
     </div>

+ 1 - 2
src/views/account/roles/Roles.vue

@@ -5,7 +5,6 @@ import type { ColumnProps } from '@/components/TableBase/interface/index'
 import {
   User_Power_List,
   User_Sys_List,
-  Menu_List,
   User_Power_Get,
   User_Power_Add,
   User_Power_Del,
@@ -198,7 +197,7 @@ const searchHandle = () => {
     <TableBase ref="TableRef" :columns="columns" :requestApi="User_Power_List" :initParam="initParam">
       <template #table-header>
         <div class="input-suffix">
-          <el-row :gutter="20">
+          <el-row :gutter="20" style="margin-bottom: 0">
             <el-col :span="12">
               <span class="inline-flex items-center">角色名:</span>
               <el-input v-model="search" type="text" class="w-50 m-2" />

+ 26 - 0
src/views/salary/Descriptions-item.vue

@@ -0,0 +1,26 @@
+<template>
+  <td class="el-descriptions__cell el-descriptions__label is-bordered-label" colspan="1">
+    <div class="cell-item">
+      <i class="iconfont">{{ '\ue7d1' }}</i>
+      {{ name }}
+    </div>
+  </td>
+  <td class="el-descriptions__cell el-descriptions__content is-bordered-content" colspan="0">
+    <el-tag type="danger"
+      >{{ salary }}{{ ['T_user_name', 'T_user_post', 'T_user_dept'].includes(field) ? '' : '¥' }}</el-tag
+    >
+  </td>
+</template>
+
+<script setup lang="ts">
+import { toRefs } from 'vue'
+interface PropsType {
+  name: string
+  salary: string
+  field: string
+}
+const props = defineProps<PropsType>()
+const { name, salary, field } = toRefs(props)
+</script>
+
+<style scoped></style>

+ 1 - 3
src/views/salary/SalaryCount.vue

@@ -83,16 +83,14 @@ const initParam = reactive({
   page: 1,
   page_z: 999
 })
-// const total = ref(0)
 const getSalary_List = async () => {
   const res: any = await Salary_List(initParam)
   tableData.value = res.Data.Data
-  // total.value = res.Data.Num
 }
 getSalary_List()
 
 const exportSalaryExcel = async () => {
-  const res: any = await Salary_Excel()
+  const res: any = await Salary_Excel({ T_date: `${salaryFromData.year}-${salaryFromData.month}` })
   if (res.Code === 200) {
     window.open(res.Data)
   }

+ 67 - 20
src/views/salary/SalaryMy.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import { reactive } from 'vue'
 import { Salary_User_Get } from '@/api/salary/index'
+import DescriptionsItem from './descriptions-item.vue'
 let date = new Date()
 const year = date.getFullYear()
 const month = date.getMonth()
@@ -14,28 +15,36 @@ const salaryData = reactive([
   { name: '部门', field: 'T_user_dept', salary: '' },
   { name: '岗位', field: 'T_user_post', salary: '' },
   { name: '基础工资', field: 'T_base', salary: '' },
+  { name: '需缴养老保险:', field: 'T_pension_insurance', salary: '' },
   { name: '岗位工资', field: 'T_post', salary: '' },
+  { name: '需缴医疗保险:', field: 'T_unemployment_insurance', salary: '' },
   { name: '工龄工资:', field: 'T_seniority', salary: '' },
+  { name: '需缴住房公积金:', field: 'T_medical_insurance', salary: '' },
   { name: '绩效金额:', field: 'T_perf', salary: '' },
+  { name: '需缴纳失业保险:', field: 'T_large_medical_insurance', salary: '' },
   { name: '绩效得分:', field: 'T_perf_score', salary: '' },
+  { name: '需缴大额医疗保险:', field: 'T_housing_fund', salary: '' },
   { name: '实发绩效', field: 'T_actual_Perf', salary: '' },
+  { name: '个税扣款', field: 'T_tax', salary: '' },
   { name: '其他补款:', field: 'T_back_payment', salary: '' },
-  { name: '需缴个人所得税:', field: 'T_tax', salary: '' },
+  { name: '扣款合计', field: 'T_laborage', salary: '' },
   { name: '考勤扣款:', field: 'T_attendance', salary: '' },
   { name: '其他扣款', field: 'T_cut_payment', salary: '' },
   { name: '应发合计', field: 'T_laballot', salary: '' },
-  { name: '需缴养老保险:', field: 'T_pension_insurance', salary: '' },
-  { name: '需缴医疗保险:', field: 'T_unemployment_insurance', salary: '' },
-  { name: '需缴住房公积金:', field: 'T_medical_insurance', salary: '' },
-  { name: '需缴纳失业保险:', field: 'T_large_medical_insurance', salary: '' },
-  { name: '需缴大额医疗保险:', field: 'T_housing_fund', salary: '' },
-  { name: '个税扣款', field: 'T_tax', salary: '' },
-  { name: '扣款合计', field: 'T_laborage', salary: '' },
   { name: '实发合计', field: 'T_total', salary: '' }
 ])
+const isSalary = ['T_user_name', 'T_user_post', 'T_user_dept', 'T_attendance', 'T_cut_payment', 'T_laballot', 'T_total']
 const getMySalary = async () => {
   let T_date = salaryFromData.year + '-' + salaryFromData.month
-  const res: any = await Salary_User_Get({ T_date })
+  const res: any = await Salary_User_Get({ T_date }).catch(error => {
+    if (error.Code === 202) {
+      for (let item of salaryData) {
+        if (!['T_user_name', 'T_user_dept', 'T_user_post'].includes(item.field)) {
+          item.salary = '0'
+        }
+      }
+    }
+  })
   if (res.Code === 200) {
     for (let item of salaryData) {
       item.salary = res.Data[item.field]
@@ -91,20 +100,54 @@ getMySalary()
         </el-col>
       </el-row>
     </el-card>
-    <el-card>
+    <el-card class="salary-content">
       <el-descriptions title="我的薪资" :column="3" border>
-        <el-descriptions-item v-for="item in salaryData" :key="item.field">
+        <el-descriptions-item>
           <template #label>
-            <div class="cell-item">
-              <i class="iconfont">{{ '\ue7d1' }}</i>
-              {{ item.name }}
-            </div>
+            <h4>姓名:</h4>
           </template>
-          <el-tag type="danger"
-            >{{ item.salary
-            }}{{ ['T_user_name', 'T_user_post', 'T_user_dept'].includes(item.field) ? '' : '¥' }}</el-tag
-          >
-          <!-- <span style="color: #ffd04b">{{ item.salary }}</span> -->
+          <h4>{{ salaryData[0].salary }}</h4>
+        </el-descriptions-item>
+        <el-descriptions-item
+          ><template #label>
+            <h4>部门:</h4>
+          </template>
+          <h4>{{ salaryData[1].salary }}</h4></el-descriptions-item
+        >
+        <el-descriptions-item
+          ><template #label>
+            <h4>岗位:</h4>
+          </template>
+          <h4>{{ salaryData[2].salary }}</h4></el-descriptions-item
+        >
+      </el-descriptions>
+      <el-descriptions :column="2" border>
+        <template v-for="item in salaryData" :key="item.field">
+          <el-descriptions-item v-if="!isSalary.includes(item.field)">
+            <template #label>
+              <div class="cell-item">
+                <i class="iconfont">{{ '\ue7d1' }}</i>
+                {{ item.name }}
+              </div>
+            </template>
+            <el-tag type="danger">{{ item.salary }}¥</el-tag>
+          </el-descriptions-item>
+        </template>
+        <el-descriptions-item :span="2">
+          <template #label>考勤扣款:</template>
+          <el-tag type="danger">{{ salaryData[17].salary }}¥</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item :span="2">
+          <template #label>其他扣款:</template>
+          <el-tag type="danger">{{ salaryData[18].salary }}¥</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item :span="2">
+          <template #label>应发合计:</template>
+          <el-tag type="danger">{{ salaryData[19].salary }}¥</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item :span="2">
+          <template #label>实发合计:</template>
+          <el-tag type="danger">{{ salaryData[20].salary }}¥</el-tag>
         </el-descriptions-item>
       </el-descriptions>
     </el-card>
@@ -118,4 +161,8 @@ getMySalary()
   align-items: center;
   flex-wrap: nowrap;
 }
+.salary-content {
+  overflow-y: auto;
+  height: 800px;
+}
 </style>

+ 1 - 1
src/views/salary/salary/relus.ts

@@ -1,6 +1,6 @@
 import type { FormRules } from 'element-plus'
 
-const floatReg = /^[-\+]?\d+(\.\d+)?$/
+export const floatReg = /^[-\+]?\d+(\.\d+)?$/
 
 const validate_float = (rule: any, value: any, callback: any) => {
   if (value === '') {

+ 3 - 2
src/views/workAttendance/MyLeave.vue

@@ -10,7 +10,6 @@ import { GlobalStore } from '@/stores/index'
 import { Edit, Delete } from '@element-plus/icons-vue'
 import type { FormInstance, FormRules } from 'element-plus'
 import { ElMessageBox, ElMessage } from 'element-plus'
-import { dayJs } from '@/utils/common'
 const globalStore = GlobalStore()
 const TableRef = ref()
 const columns: ColumnProps[] = [
@@ -180,7 +179,9 @@ onMounted(() => {
         <el-tag v-else-if="row.T_State === 2" type="warning">未通过</el-tag>
         <el-tag v-else type="danger">待审核</el-tag>
       </template>
-      <template #T_duration="{ row }"> {{ row.T_duration }}分钟 </template>
+      <template #T_duration="{ row }">
+        <el-tag effect="dark"> {{ row.T_duration }}分钟 </el-tag>
+      </template>
       <template #right="{ row }">
         <el-button link type="primary" size="small" :icon="Edit" @click="openDrawerLeave('edit', row)">编辑</el-button>
         <el-button link type="danger" size="small" :icon="Delete" @click="LeaveDelete(row)">删除</el-button>

+ 374 - 4
src/views/workAttendance/MyOvertime.vue

@@ -1,7 +1,377 @@
+<script setup lang="ts">
+import { reactive, ref, nextTick } from 'vue'
+import { ColumnProps } from '@/components/TableBase/interface/index'
+import {
+  Overtime_User_list,
+  Overtime_Stat,
+  Overtime_Add,
+  Overtime_Del,
+  Overtime_Edit
+} from '@/api/workAttendance/index'
+import { User_List } from '@/api/user/index'
+import { UpFileToken } from '@/api/public/index'
+import Dialog from '@/components/dialog/Dialog.vue'
+import Drawer from '@/components/Drawer/index.vue'
+import { Edit, Delete, ZoomIn, Download, Plus } from '@element-plus/icons-vue'
+import type {
+  FormInstance,
+  FormRules,
+  UploadFile,
+  UploadInstance,
+  UploadProps,
+  UploadRawFile,
+  UploadFiles
+} from 'element-plus'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import { floatReg } from '@/views/salary/salary/relus'
+import { GlobalStore } from '@/stores/index'
+const globalStore = GlobalStore()
+
+const TableRef = ref()
+const columns: ColumnProps[] = [
+  { prop: 'T_start_time', label: '开始时间' },
+  { prop: 'T_end_time', label: '结束时间' },
+  { prop: 'T_duration', label: '时长', name: 'T_duration' },
+  { prop: 'T_State', label: '审核', name: 'T_State' },
+  { prop: 'operation', label: '操作', width: 200, fixed: 'right' }
+]
+const columns_Stat: ColumnProps[] = [
+  { prop: 'T_duration', label: '时长' },
+  { prop: 'RemainingTime', label: '剩余时长' },
+  { prop: 'T_type_name', label: '事项' },
+  { prop: 'T_approver_name', label: '处理人' },
+  { prop: 'UpdateTime', label: '时间' }
+]
+const initParam = {
+  User_tokey: globalStore.GET_User_tokey
+}
+const openDrawerOvertime = (str: string, row: any) => {
+  drawerRef.value.openDrawer()
+  if (str === 'edit') {
+    // isNew = false
+    // nextTick(() => {
+    //   form.value = { ...row }
+    //   uuid = row.T_approver
+    //   form.value.T_approver = row.T_user_name
+    //   form.value.T_id = row.Id
+    // })
+  }
+}
+const OvertimeDelete = (row: any) => {}
+const AddOvertime = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async valid => {
+    if (valid) {
+      let res: any = {}
+      form.value.T_duration = form.value.T_duration * 60
+      if (isNew) {
+        res = await Overtime_Add({ ...form.value, T_approver: uuid })
+      } else {
+        res = await Overtime_Edit({ ...form.value, T_approver: uuid })
+      }
+      if (res.Code === 200) {
+        ElMessage({
+          type: 'success',
+          message: `${isNew ? '申请' : '修改'}成功!`
+        })
+        nextTick(() => {
+          drawerRef.value.closeDrawer()
+          TableRef.value.getTableList()
+          resetForm(ruleFormRef.value)
+          isNew = true
+        })
+      }
+    } else {
+      return false
+    }
+  })
+}
+// Drawer
+const drawerRef = ref()
+const ruleFormRef = ref<FormInstance>()
+type Fn = () => void
+const callbackDrawer = (done: Fn) => {
+  resetForm(ruleFormRef.value)
+  done()
+}
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+}
+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, validator: validate_float, trigger: 'blur' }],
+  T_approver: [{ required: true, message: '请选择审批人', trigger: 'blur' }]
+})
+let uuid = ''
+const form = ref({
+  T_type: '',
+  T_start_time: '',
+  T_end_time: '',
+  T_duration: 0,
+  T_text: '',
+  T_approver: '',
+  T_id: ''
+})
+const formLabelWidth = ref('100px')
+let isNew = true
+const selectApprover = () => {
+  dialog.value.DialogOpen()
+}
+
+// dialog
+const dialog = ref()
+const search = ref('')
+const tableApproverRef = ref()
+const Dialogcolumns: ColumnProps[] = [{ prop: 'T_name', label: '名字', name: 'T_name' }]
+const approverInitParam = {
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: '',
+  T_dept_leader: 1
+}
+const searchHandle = () => {
+  approverInitParam.T_name = search.value
+  tableApproverRef.value.searchTable()
+}
+const getApproverInfo = (row: any) => {
+  uuid = row.T_uuid
+  form.value.T_approver = row.T_name
+  dialog.value.DialogClose()
+}
+
+const onResize = () => {
+  const height = document.documentElement.clientHeight
+  return height / 2
+}
+
+// upload file
+let uploadData = { token: '' }
+const dialogImageUrl = ref('')
+const dialogVisible = ref(false)
+const disabled = ref(false)
+// const fileList = ref<UploadUserFile[]>([])
+const handlePictureCardPreview = (file: UploadFile) => {
+  console.log(file)
+  dialogImageUrl.value = file.url as string
+  dialogVisible.value = true
+}
+const beforeUpload = async (file: any) => {
+  let reg = /^image/g
+  if (!reg.test(file.type)) {
+    ElMessage({
+      type: 'error',
+      message: '必须上传图片!!'
+    })
+    return
+  }
+  const res: any = await UpFileToken({ User_tokey: globalStore.GET_User_tokey })
+  uploadData.token = res.Data
+}
+const handleExceed: UploadProps['onExceed'] = (files: any) => {
+  console.log(files)
+  // upload.value!.clearFiles()
+  // const file = files[0] as UploadRawFile
+  // file.uid = genFileId()
+  // upload.value!.handleStart(file)
+}
+const onSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+  console.log(response, uploadFile, uploadFiles)
+}
+const onError = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+  console.log(error, uploadFile, uploadFiles)
+}
+// const handleRemove = (file: UploadFile) => {
+//   console.log(file)
+// }
+const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
+  console.log(uploadFile, uploadFiles)
+}
+</script>
+
 <template>
-  <div>我的加班</div>
-</template>
+  <el-row :gutter="24" class="h-100">
+    <el-col :span="12" class="padding-right-0 h-100">
+      <TableBase
+        ref="TableRef"
+        :columns="columns"
+        :requestApi="Overtime_User_list"
+        :initParam="initParam"
+        layout="total,sizes,prev, pager, next"
+      >
+        <template #table-header>
+          <div class="table-header">
+            <h4>我的加班</h4>
+            <el-button type="primary" @click="openDrawerOvertime">申请加班</el-button>
+          </div>
+        </template>
+        <template #T_duration="{ row }"> {{ row.T_duration }}分钟 </template>
+        <template #T_State="{ row }">
+          <el-tag v-if="row.T_State === 1" type="success">通过</el-tag>
+          <el-tag v-else-if="row.T_State === 2" type="warning">未通过</el-tag>
+          <el-tag v-else type="danger">待审核</el-tag>
+        </template>
+        <template #right="{ row }">
+          <el-button link type="primary" size="small" :icon="Edit" @click="openDrawerOvertime('edit', row)"
+            >编辑</el-button
+          >
+          <el-button link type="danger" size="small" :icon="Delete" @click="OvertimeDelete(row)">删除</el-button>
+        </template>
+      </TableBase></el-col
+    >
+    <el-col :span="12" class="h-100" style="overflow: hidden">
+      <TableBase
+        ref="TableRef"
+        :columns="columns_Stat"
+        :requestApi="Overtime_Stat"
+        :initParam="initParam"
+        layout="total,sizes,prev, pager, next"
+      >
+        <template #table-header="{ pageable }">
+          <el-button text
+            ><h4>剩余总时长:{{ pageable.RemainingTime }}</h4></el-button
+          ></template
+        >
+        <template #T_name="{ row }">
+          <el-button type="primary" text>{{ row.T_name }}</el-button>
+        </template>
+      </TableBase>
+    </el-col>
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">{{ isNew ? '添加' : '编辑' }} - 角色</h4>
+      </template>
+      <el-form ref="ruleFormRef" :model="form" :rules="rules">
+        <el-form-item label="开始时间:" :label-width="formLabelWidth" prop="T_start_time">
+          <el-date-picker
+            style="width: 100%"
+            class="my-date-picker"
+            v-model="form.T_start_time"
+            type="datetime"
+            placeholder="选择开始时间"
+            format="YYYY-MM-DD hh:mm:ss"
+            value-format="YYYY-MM-DD hh:mm:ss"
+          />
+        </el-form-item>
+        <el-form-item label="结束时间:" :label-width="formLabelWidth" prop="T_end_time">
+          <el-date-picker
+            style="width: 100%"
+            class="my-date-picker"
+            v-model="form.T_end_time"
+            type="datetime"
+            placeholder="选择结束时间"
+            format="YYYY-MM-DD hh:mm:ss"
+            value-format="YYYY-MM-DD hh:mm:ss"
+          />
+        </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-form-item>
+        <el-form-item label="取证:" :label-width="formLabelWidth" prop="T_duration">
+          <el-upload
+            action="https://up-z2.qiniup.com"
+            list-type="picture-card"
+            :limit="1"
+            :auto-upload="true"
+            :before-upload="beforeUpload"
+            :on-success="onSuccess"
+            :on-error="onError"
+            :on-exceed="handleExceed"
+            :on-remove="handleRemove"
+            :on-preview="handlePictureCardPreview"
+            :data="uploadData"
+          >
+            <el-icon><Plus /></el-icon>
 
-<script setup lang="ts"></script>
+            <!-- <template #file="{ file }">
+              <div>
+                <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
+                <span class="el-upload-list__item-actions">
+                  <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
+                    <el-icon><zoom-in /></el-icon>
+                  </span>
+                  <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
+                    <el-icon><Download /></el-icon>
+                  </span>
+                  <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
+                    <el-icon><Delete /></el-icon>
+                  </span>
+                </span>
+              </div>
+            </template> -->
+          </el-upload>
+          <el-dialog v-model="dialogVisible">
+            <img w-full :src="dialogImageUrl" class="full-img" alt="Preview Image" />
+          </el-dialog>
+        </el-form-item>
+        <el-form-item label="内容:" :label-width="formLabelWidth">
+          <el-input
+            v-model="form.T_text"
+            autocomplete="off"
+            type="textarea"
+            :autosize="{ minRows: 4, maxRows: 6 }"
+            placeholder="请输入内容"
+          />
+        </el-form-item>
+        <el-form-item label="审批:" :label-width="formLabelWidth" prop="T_approver">
+          <el-input v-model="form.T_approver" autocomplete="off" placeholder="审批人" @focus="selectApprover" />
+        </el-form-item>
+        <el-form-item :label-width="formLabelWidth">
+          <el-button v-if="isNew" color="#626aef" @click="AddOvertime(ruleFormRef)">添加</el-button>
+          <el-button v-else color="#626aef" @click="AddOvertime(ruleFormRef)">修改</el-button>
+        </el-form-item>
+      </el-form>
+    </Drawer>
+    <Dialog ref="dialog" width="30%">
+      <template #header>
+        <h3>选择审批人</h3>
+      </template>
+      <TableBase
+        ref="tableApproverRef"
+        :columns="Dialogcolumns"
+        :initParam="approverInitParam"
+        :requestApi="User_List"
+        layout="total, prev, pager, next"
+        :onResize="onResize"
+      >
+        <template #table-header>
+          <el-row :gutter="20">
+            <el-col :span="20" class="d-flex">
+              <span class="inline-flex">账户查询:</span>
+              <el-input v-model="search" type="text" class="w-50 m-2" />
+              <el-button type="primary" @click="searchHandle">搜索</el-button>
+            </el-col>
+          </el-row>
+        </template>
+        <template #T_name="{ row }">
+          <el-button type="primary" link @click="getApproverInfo(row)">{{ row.T_name }}</el-button>
+        </template>
+      </TableBase>
+    </Dialog>
+  </el-row>
+</template>
 
-<style scoped></style>
+<style scoped lang="scss">
+.table-header {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.full-img {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 14 - 14
vite.config.ts

@@ -61,20 +61,20 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     // * 打包去除 console.log && debugger
     esbuild: {
       pure: env.VITE_DROP_CONSOLE ? ['console.log', 'debugger'] : []
-    },
-    build: {
-      rollupOptions: {
-        output: {
-          // 打包后文件命名
-          assetFileNames: '[hash].[name].[ext]'
-        }
-      },
-      // 图片大小分界线:大于 4kb,图片正常打包。小于 4kb,图片会转化为 base64。
-      assetsInlineLimit: 4096, // 4kb
-      // 打包后名称,默认是 dist
-      outDir: 'testDir',
-      // 打包后静态目录名称
-      assetsDir: 'static'
     }
+    // build: {
+    //   rollupOptions: {
+    //     output: {
+    //       // 打包后文件命名
+    //       assetFileNames: '[hash].[name].[ext]'
+    //     }
+    //   },
+    //   // 图片大小分界线:大于 4kb,图片正常打包。小于 4kb,图片会转化为 base64。
+    //   assetsInlineLimit: 4096, // 4kb
+    //   // 打包后名称,默认是 dist
+    //   outDir: 'testDir',
+    //   // 打包后静态目录名称
+    //   assetsDir: 'static'
+    // }
   }
 })