浏览代码

feat: ✨ 完成ERP 消息通知,修复 加班审批bug

@sun-chaoqun 2 年之前
父节点
当前提交
d622f2d0ae

+ 0 - 10
README.md

@@ -25,13 +25,3 @@ https://erp.baozhida.cn/testapi/storage
 https://58ftdz.axshare.com
 
 https://6b87x3.axshare.com/#id=s9uw1m&p=%E6%88%91%E7%9A%84%E5%8A%A0%E7%8F%AD&g=1
-
-### 待优化部分
-
-请假选择完开始结束自动算出小时
-
-销售合同(销售): 通过后屏蔽掉编辑、删除
-
-合同详情:查看 SN 未做
-
-合同详情,等动态路由做完还要测试一下

+ 3 - 0
components.d.ts

@@ -15,6 +15,7 @@ declare module '@vue/runtime-core' {
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    ElBadge: typeof import('element-plus/es')['ElBadge']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
@@ -52,6 +53,8 @@ declare module '@vue/runtime-core' {
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']

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

@@ -17,3 +17,7 @@ export const User_Post_List = (params?: any) => $http.post('/api/user/Post/List'
 export const User_Info = (params?: any) => $http.post('/api/user/User/Info', params)
 // 修改密码
 export const User_Post = (params?: any) => $http.post('/api/user/User/Post', params)
+// 消息列表
+export const User_News_List = (params?: any) => $http.post('/api/user/News/List', params)
+// 查看消息
+export const User_News_See = (params?: any) => $http.post('/api/user/News/See', params)

+ 143 - 43
src/layouts/Header/Notice.vue

@@ -1,61 +1,161 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
+import { GlobalStore } from '@/stores/index'
+import { User_News_List, User_News_See } from '@/api/user/index'
 import type { TabsPaneContext } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+interface NoticeType {
+  CreateTime: string
+  Id: number
+  T_Tag: number
+  T_Title: string
+  T_uuid: string
+}
+
+const page = ref(1)
+let total = 0
+const globalStore = GlobalStore()
 const activeName = ref('first')
+const newsList = ref<NoticeType[]>([])
 
-const handleClick = (tab: TabsPaneContext, event: Event) => {
-  console.log(tab, event)
+const getUserNewsList = async (tag?: number) => {
+  const params = {
+    User_tokey: globalStore.GET_User_tokey,
+    T_Tag: tag,
+    page: page.value,
+    page_z: 10
+  }
+  const res: any = await User_News_List(params)
+  newsList.value.push(...res.Data.Data)
+  total = res.Data.Num
 }
+/**
+ * 查看通知消息
+ */
+const previewNotice = (item: NoticeType) => {
+  ElMessageBox.confirm(item.T_Title, '消息通知', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'success',
+    draggable: true
+  })
+    .then(async () => {
+      const res: any = await User_News_See({ User_tokey: globalStore.GET_User_tokey, Id: item.Id })
+      if (res.Code) {
+        ElMessage({
+          type: 'success',
+          message: '确认查看消息!'
+        })
+        newsList.value.filter((notice: NoticeType) => item.Id !== notice.Id)
+      }
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: '取消确认消息!'
+      })
+    })
+}
+
+const handleClick = (tab: TabsPaneContext) => {
+  const { props } = tab
+  newsList.value = []
+  page.value = 1
+  switch (props.name) {
+    case 'first':
+      getUserNewsList()
+      break
+    case 'second':
+      getUserNewsList(1)
+      break
+  }
+}
+
+onMounted(() => {
+  const tabs__content = document.getElementsByClassName('el-tabs__content')[0]
+  tabs__content?.addEventListener('scroll', (e: any) => {
+    const { target } = e
+    if (target.scrollHeight - target.scrollTop === target.clientHeight && total !== newsList.value.length) {
+      page.value++
+      getUserNewsList()
+    }
+  })
+
+  getUserNewsList()
+})
+
+defineExpose({
+  newsList: newsList.value
+})
 </script>
 <template>
   <div class="notice">
-    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
-      <el-tab-pane label="未读" name="first">
-        <div class="list">
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-        </div>
-      </el-tab-pane>
-      <el-tab-pane label="全部" name="second">
-        <div class="list">
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-          <p class="scrollbar-item">
-            <el-badge is-dot class="item">query</el-badge>
-          </p>
-        </div>
-      </el-tab-pane>
+    <el-tabs v-model="activeName" @tab-click="handleClick">
+      <transition-group
+        appear
+        leave-active-class="animate__animated animate__fadeInLeft"
+        enter-active-class="animate__animated animate__fadeInLeft"
+      >
+        <el-tab-pane label="未读" name="first" key="first">
+          <el-dropdown-menu>
+            <el-dropdown-item v-for="item in newsList" :key="item.T_uuid" @click="previewNotice(item)">
+              <div class="item">
+                <div class="item-box">
+                  <el-badge :is-dot="item.T_Tag === 0">{{ item.T_Title }}</el-badge>
+                </div>
+                <span>{{ item.CreateTime.split(' ')[0] }}</span>
+              </div>
+            </el-dropdown-item>
+          </el-dropdown-menu>
+        </el-tab-pane>
+        <el-tab-pane label="全部" name="second" key="second">
+          <el-dropdown-menu>
+            <el-dropdown-item v-for="item in newsList" :key="item.T_uuid" @click="previewNotice(item)">
+              <div class="item">
+                <div class="item-box">
+                  <el-badge :is-dot="item.T_Tag === 0">{{ item.T_Title }}</el-badge>
+                </div>
+                <span>{{ item.CreateTime.split(' ')[0] }}</span>
+              </div>
+            </el-dropdown-item>
+          </el-dropdown-menu>
+        </el-tab-pane>
+      </transition-group>
     </el-tabs>
   </div>
 </template>
 
 <style scoped lang="scss">
 .notice {
-  .list {
+  padding: 20px;
+  width: 500px;
+  height: 270px;
+  overflow: hidden;
+  :deep(.el-tabs__content) {
+    height: 180px;
+    overflow-y: scroll;
+  }
+  .item {
+    width: 100%;
     display: flex;
-    flex-direction: column;
-    .scrollbar-item {
-      font-size: 16px;
-      display: flex;
-      align-items: center;
-      justify-content: start;
-      // height: 50px;
-      margin: 10px;
-      text-align: center;
-      border-radius: 4px;
-      // background: var(--el-color-primary-light-9);
-      // color: var(--el-color-primary);
+    justify-content: space-between;
+    .item-box {
+      width: 330px;
+      .el-badge {
+        max-width: 100%;
+        :deep(.el-badge__content.is-fixed.is-dot) {
+          left: 0px;
+          margin-left: -12px;
+        }
+        .item-content {
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          max-width: 100%;
+          display: inline-block;
+        }
+      }
     }
   }
 }

+ 23 - 12
src/layouts/Header/index.vue

@@ -3,10 +3,11 @@ import { ref, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { GlobalStore } from '@/stores/index'
 import Breadcrumb from './Breadcrumb.vue'
-// import Notice from './Notice.vue'
+import Notice from './Notice.vue'
 import { Expand, Fold, List, SwitchButton, UserFilled } from '@element-plus/icons-vue'
 import 'element-plus/theme-chalk/src/dropdown.scss'
 
+const noticeRef = ref<InstanceType<typeof Notice> | null>(null)
 const globalStore = GlobalStore()
 const router = useRouter()
 const logOut = () => {
@@ -33,6 +34,17 @@ let time = ref(getTime())
 setInterval(() => {
   time.value = getTime()
 }, 1000 * 60)
+
+const computedNotice = computed(() => {
+  if (noticeRef.value?.newsList) {
+    const notice = noticeRef.value?.newsList.find((item: any) => item.T_Tag === 0)
+    if (!notice) {
+      return false
+    }
+    return true
+  }
+  return false
+})
 </script>
 
 <template>
@@ -51,16 +63,16 @@ setInterval(() => {
       <el-link type="primary" class="mr-2" @click="goTask"
         ><el-icon> <List /> </el-icon>任务管理</el-link
       >
-      <el-dropdown class="mr-2">
-        <el-button type="primary" circle>
-          <i class="iconfont el-icon">&#xe63e;</i>
-        </el-button>
-        <template #dropdown>
-          <el-dropdown-menu>
-            <el-dropdown-item>action</el-dropdown-item>
-          </el-dropdown-menu>
-        </template>
-      </el-dropdown>
+      <el-badge :is-dot="computedNotice" class="mr-2">
+        <el-dropdown trigger="click">
+          <el-button type="primary" circle>
+            <i class="iconfont el-icon">&#xe63e;</i>
+          </el-button>
+          <template #dropdown>
+            <Notice ref="noticeRef" />
+          </template>
+        </el-dropdown>
+      </el-badge>
       <el-dropdown class="mr-2">
         <div class="avatar">
           <el-avatar> <img src="@/assets/images/avatar.jpg" /> </el-avatar>
@@ -87,7 +99,6 @@ setInterval(() => {
   color: #fff;
   display: flex;
   flex-wrap: wrap;
-  // background-color: rgba(18, 21, 39, 0.86);
   .header-left {
     flex: 1;
     display: flex;

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

@@ -31,7 +31,7 @@ const isNew = ref(true)
 let currentVal: any = {}
 const SysList = ref<InSys[]>([])
 const globalStore = GlobalStore()
-const formLabelWidth = ref('80px')
+const formLabelWidth = ref('100px')
 const ruleFormRef = ref<FormInstance>()
 const dialog = ref<InstanceType<typeof Dialog> | null>(null)
 const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
@@ -99,7 +99,7 @@ const getPermissionArr = (menu: any) => {
   getCurrentFlatMenu(Children, Menu_checked, T_permission)
 }
 
-const getCurrentFlatMenu = (children: any[], arr: number[], permission: strng) => {
+const getCurrentFlatMenu = (children: any[], arr: number[], permission: string) => {
   const fatherMenu = menuMap.get(permission)
 
   children.forEach((item: any) => {

+ 4 - 4
src/views/account/users/Users.vue

@@ -21,15 +21,15 @@ const columns: ColumnProps[] = [
   { type: 'index', label: '#', width: 80, fixed: 'left' },
   { prop: 'T_name', label: '姓名', width: 120, fixed: 'left', name: 'T_name' },
   { prop: 'T_post_name', label: '职位', ellipsis: true },
-  { prop: 'T_dept_name', label: '部门', ellipsis: true },
-  { prop: 'T_phone', label: '联系电话', ellipsis: true, width: 100 },
+  { prop: 'T_dept_name', label: '部门', width: 120 },
+  { prop: 'T_phone', label: '联系电话', width: 120 },
   { prop: 'T_nation', label: '民族' },
   { prop: 'T_sex', label: '性别', name: 'T_sex' },
   { prop: 'T_school', label: '毕业院校', ellipsis: true, width: 100 },
   { prop: 'T_major', label: '专业', ellipsis: true },
   { prop: 'T_education', label: '学历' },
-  { prop: 'T_entry_time', label: '入职时间', ellipsis: true, width: 100 },
-  { prop: 'T_positive_time', label: '转正时间', ellipsis: true, width: 100 },
+  { prop: 'T_entry_time', label: '入职时间', width: 120 },
+  { prop: 'T_positive_time', label: '转正时间', width: 120 },
   { prop: 'T_entry_type', label: '入职类型', name: 'T_entry_type', width: 100 },
   { prop: 'T_contract_start_time', label: '劳动合同开始时间', width: 160 },
   { prop: 'T_contract_end_time', label: '劳动合同结束时间', width: 160 },

+ 10 - 0
src/views/storehouse/InventoryStatistics.vue

@@ -290,6 +290,7 @@ onMounted(() => {
         <h4 :id="params.titleId" :class="params.titleClass">库存明细</h4>
       </template>
       <TableBase
+        border
         ref="TableDetailRef"
         :pagination="false"
         :columns="detailColumns"
@@ -328,6 +329,15 @@ onMounted(() => {
 :deep(.drawer__content) {
   @include f-direction;
 }
+:deep(.el-drawer__header) {
+  margin-bottom: 0;
+}
+:deep(.table-header) {
+  border: none;
+}
+:deep(.card) {
+  border: none;
+}
 .inventory-statistics {
   @include f-direction;
   .head-search {

+ 12 - 3
src/views/workAttendance/Overtime.vue

@@ -116,7 +116,7 @@ const tableRowClassName = (data: any): any => {
               </div>
             </div>
           </el-card>
-          <el-card class="m-b-3 b-show-0" style="flxe: 1">
+          <el-card class="b-show-0 f-1">
             <el-row>
               <el-col :span="12"
                 ><div>
@@ -152,12 +152,13 @@ const tableRowClassName = (data: any): any => {
             <el-row>
               <el-col>
                 <span class="ml-3 w-35 text-gray-600 inline-flex items-center">内容:</span>
-                <el-input
+                <!-- <el-input
                   v-model="userInfo.T_text"
                   :autosize="{ minRows: 4, maxRows: 6 }"
                   type="textarea"
                   placeholder="加班内容"
-                />
+                /> -->
+                {{ userInfo.T_text }}
               </el-col>
             </el-row>
             <div class="btn">
@@ -174,6 +175,7 @@ const tableRowClassName = (data: any): any => {
 <style scoped lang="scss">
 @import '@/styles/var.scss';
 .Overtime {
+  display: flex;
   height: 100%;
   .Overtime-table {
     @include f-direction;
@@ -193,6 +195,13 @@ const tableRowClassName = (data: any): any => {
       }
     }
   }
+  :deep(.el-card .el-card__body) {
+    font-weight: bold;
+    color: var(--el-text-color-secondary);
+  }
+  .f-1 {
+    flex: 1;
+  }
   .title {
     width: 100%;
     text-align: center;

+ 11 - 15
src/views/workAttendance/records/Records.vue

@@ -95,7 +95,6 @@ onMounted(() => {
   window.onresize = onContentResize
 })
 onUnmounted(() => (window.onresize = null))
-const onResizeItem = () => 336
 // 点击行
 const getSalaryParams = (row: any) => {
   if (row.T_uuid === userInitParam.T_uuid) return
@@ -198,14 +197,13 @@ const searchHandle = () => TableRef.value?.searchTable()
           <div class="content" :style="{ height: clientHeight + 'px' }">
             <div>
               <div class="content-table-item">
-                <el-tag class="mx-1 font-large" type="success" effect="dark">加班记录:</el-tag>
+                <el-tag font-large type="success" effect="dark">加班记录:</el-tag>
                 <TableBase
                   ref="overtimeRef"
                   v-if="userInitParam.T_uuid"
                   :columns="overtimeColums"
                   :requestApi="Overtime_User_list"
                   :initParam="userInitParam"
-                  :onResize="onResizeItem"
                   :displayHeader="true"
                   layout="prev, pager, next"
                 >
@@ -227,11 +225,10 @@ const searchHandle = () => TableRef.value?.searchTable()
                   :columns="remainingTimeColums"
                   :requestApi="Overtime_Stat"
                   :initParam="userInitParam"
-                  :onResize="onResizeItem"
                   layout="prev, pager, next"
                 >
                   <template #table-header="{ pageable }">
-                    <el-tag class="mx-1" effect="dark">
+                    <el-tag effect="dark">
                       剩余总时长:{{ getFormatDuration(pageable.RemainingTime as number) }}
                     </el-tag>
                   </template>
@@ -240,14 +237,13 @@ const searchHandle = () => TableRef.value?.searchTable()
                 </TableBase>
               </div>
               <div class="content-table-item">
-                <el-tag class="mx-1 font-large" type="danger" effect="dark">请假记录:</el-tag>
+                <el-tag font-large type="danger" effect="dark">请假记录:</el-tag>
                 <TableBase
                   ref="leaveRef"
                   v-if="userInitParam.T_uuid"
                   :columns="leaveColums"
                   :requestApi="Leave_User_list"
                   :initParam="userInitParam"
-                  :onResize="onResizeItem"
                   :displayHeader="true"
                   layout="prev, pager, next"
                 >
@@ -274,10 +270,10 @@ const searchHandle = () => TableRef.value?.searchTable()
       <div>
         <el-descriptions :column="1" size="large" border>
           <el-descriptions-item label="开始时间:"
-            ><el-text class="mx-1" type="primary">{{ OvertimeInfo.T_start_time }}</el-text></el-descriptions-item
+            ><el-text type="primary">{{ OvertimeInfo.T_start_time }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="结束时间:"
-            ><el-text class="mx-1" type="primary">{{ OvertimeInfo.T_end_time }}</el-text></el-descriptions-item
+            ><el-text type="primary">{{ OvertimeInfo.T_end_time }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="取证:" :span="2">
             <el-image
@@ -289,7 +285,7 @@ const searchHandle = () => TableRef.value?.searchTable()
               fit="cover"
           /></el-descriptions-item>
           <el-descriptions-item label="内容:">
-            <el-text class="mx-1" type="primary">{{ OvertimeInfo.T_text }}</el-text>
+            <el-text type="primary">{{ OvertimeInfo.T_text }}</el-text>
           </el-descriptions-item>
         </el-descriptions>
         <div class="btn">
@@ -304,19 +300,19 @@ const searchHandle = () => TableRef.value?.searchTable()
       <div>
         <el-descriptions :column="1" size="large" border>
           <el-descriptions-item label="请假类型:"
-            ><el-text class="mx-1" type="danger">{{ LeaveInfo.T_type_name }}</el-text></el-descriptions-item
+            ><el-text type="danger">{{ LeaveInfo.T_type_name }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="开始时间:"
-            ><el-text class="mx-1" type="primary">{{ LeaveInfo.T_start_time }}</el-text></el-descriptions-item
+            ><el-text type="primary">{{ LeaveInfo.T_start_time }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="结束时间:"
-            ><el-text class="mx-1" type="primary">{{ LeaveInfo.T_end_time }}</el-text></el-descriptions-item
+            ><el-text type="primary">{{ LeaveInfo.T_end_time }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="请假时长:"
-            ><el-text class="mx-1" type="primary">{{ LeaveInfo.T_duration }}</el-text></el-descriptions-item
+            ><el-text type="primary">{{ LeaveInfo.T_duration }}</el-text></el-descriptions-item
           >
           <el-descriptions-item label="内容:">
-            <el-text class="mx-1" type="primary">{{ LeaveInfo.T_text }}</el-text>
+            <el-text type="primary">{{ LeaveInfo.T_text }}</el-text>
           </el-descriptions-item>
         </el-descriptions>
         <div class="btn">