zhengnaiwen_citu пре 6 месеци
родитељ
комит
fead8e6e72

+ 23 - 12
package-lock.json

@@ -2784,6 +2784,15 @@
           "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
           "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
+        },
+        "qs": {
+          "version": "6.13.0",
+          "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
+          "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.6"
+          }
         }
       }
     },
@@ -2929,7 +2938,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.3.tgz",
       "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
-      "dev": true,
       "requires": {
         "call-bind-apply-helpers": "^1.0.1",
         "get-intrinsic": "^1.2.6"
@@ -4937,6 +4945,15 @@
           "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
           "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
+        },
+        "qs": {
+          "version": "6.13.0",
+          "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
+          "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.6"
+          }
         }
       }
     },
@@ -7227,8 +7244,7 @@
     "object-inspect": {
       "version": "1.13.4",
       "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
-      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
-      "dev": true
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
     },
     "object-keys": {
       "version": "1.1.1",
@@ -8427,12 +8443,11 @@
       "dev": true
     },
     "qs": {
-      "version": "6.13.0",
-      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
-      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
-      "dev": true,
+      "version": "6.14.0",
+      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
       "requires": {
-        "side-channel": "^1.0.6"
+        "side-channel": "^1.1.0"
       }
     },
     "queue-microtask": {
@@ -9115,7 +9130,6 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
       "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
-      "dev": true,
       "requires": {
         "es-errors": "^1.3.0",
         "object-inspect": "^1.13.3",
@@ -9128,7 +9142,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
       "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
-      "dev": true,
       "requires": {
         "es-errors": "^1.3.0",
         "object-inspect": "^1.13.3"
@@ -9138,7 +9151,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
       "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
-      "dev": true,
       "requires": {
         "call-bound": "^1.0.2",
         "es-errors": "^1.3.0",
@@ -9150,7 +9162,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
       "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
-      "dev": true,
       "requires": {
         "call-bound": "^1.0.2",
         "es-errors": "^1.3.0",

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
     "element-ui": "^2.15.14",
     "fs": "0.0.1-security",
     "lodash": "^4.17.21",
+    "qs": "^6.14.0",
     "scrolling-element": "^1.0.2",
     "vue": "^2.6.14",
     "vue-router": "^3.5.1",

+ 6 - 0
src/api/panorama.js

@@ -0,0 +1,6 @@
+import http from '@/utils/request'
+
+// 工资单 - 根据组织编码查询,当前组织及下级组织的员工工资单数据
+export function getSalaryPayrollPage (data) {
+  return http.post('/employee/payroll/organization/page', data)
+}

+ 15 - 0
src/api/salary.js

@@ -70,3 +70,18 @@ export function getPayrollPage (data) {
 export function downloadPayroll (data) {
   return http.download('/employee/payroll/download/export', data)
 }
+
+// 业绩分配 列表查询
+export function getAllocationPage (data) {
+  return http.post('/employee/performance/page', data)
+}
+
+// 业绩分配 绩效分配
+export function saveAllocationGrant (data) {
+  return http.post('/employee/performance/grant', data)
+}
+
+// 业绩分配 绩效分配
+export function getAllocationEmployeeCategory (data) {
+  return http.post('/employee/performance/employee/category/dict', data)
+}

+ 59 - 56
src/components/AutoComponents/MSearch/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <m-card>
+  <m-card :shadow="shadow">
     <el-form
       :inline="true"
       :model="query"
@@ -9,65 +9,67 @@
       :show-message="false"
       inline-message
     >
-      <el-form-item
-        v-for="(item, index) in items"
-        :key="index"
-        v-bind="item"
-      >
-        <!-- 输入框 -->
-        <el-input
-          v-if="item.type === 'input'"
-          v-model="query[item.prop]"
-          @keydown.enter.native="onSubmit"
-          :clearable="item.option?.clearable ?? true"
-          v-bind="item.option"
-          v-on="item.handles"
-        ></el-input>
-
-        <!-- 下拉框 -->
-        <el-select
-          v-if="item.type === 'select'"
-          v-model="query[item.prop]"
-          :clearable="item.option?.clearable ?? true"
-          v-bind="item.option"
-          v-on="item.handles"
+    <template v-for="(item, index) in items">
+        <el-form-item
+          v-if="!item.hidden"
+          :key="index"
+          v-bind="item"
         >
-          <el-option
-            v-for="option in item.option.items"
-            :key="option.value"
-            :label="option[item.option.labelText ?? option.label]"
-            :value="option[item.option.labelValue ?? option.value]"
-          ></el-option>
-        </el-select>
+          <!-- 输入框 -->
+          <el-input
+            v-if="item.type === 'input'"
+            v-model="query[item.prop]"
+            @keydown.enter.native="onSubmit"
+            :clearable="item.option?.clearable ?? true"
+            v-bind="item.option"
+            v-on="item.handles"
+          ></el-input>
 
-        <el-autocomplete
-          v-if="item.type === 'autocomplete'"
-          v-model="query[item.prop]"
-          v-bind="item.option"
-          v-on="item.handles"
-        >
-          <template v-for="slot in item.slots" :slot="scope">
-            <slot :name="`${item.prop}.${slot}`" :scope="scope"></slot>
-          </template>
-        </el-autocomplete>
+          <!-- 下拉框 -->
+          <el-select
+            v-if="item.type === 'select'"
+            v-model="query[item.prop]"
+            :clearable="item.option?.clearable ?? true"
+            v-bind="item.option"
+            v-on="item.handles"
+          >
+            <el-option
+              v-for="option in item.option.items"
+              :key="option.value"
+              :label="option[item.option.labelText ?? option.label]"
+              :value="option[item.option.labelValue ?? option.value]"
+            ></el-option>
+          </el-select>
 
-        <!-- 时间选择器 -->
-        <el-date-picker
-          v-if="item.type === 'date'"
-          v-model="query[item.prop]"
-          v-bind="item.option"
-          v-on="item.handles"
-        ></el-date-picker>
+          <el-autocomplete
+            v-if="item.type === 'autocomplete'"
+            v-model="query[item.prop]"
+            v-bind="item.option"
+            v-on="item.handles"
+          >
+            <template v-for="slot in item.slots" :slot="scope">
+              <slot :name="`${item.prop}.${slot}`" :scope="scope"></slot>
+            </template>
+          </el-autocomplete>
 
-        <!-- 其他类型可以根据需要扩展 -->
-      </el-form-item>
-      <el-form-item class="flex">
-        <template v-if="items.length">
-          <m-button class="ml-3" icon="el-icon-search" @click="onSubmit">查询</m-button>
-          <m-button class="ml-3" icon="el-icon-refresh" @click="onReset">重置</m-button>
-        </template>
-        <slot name="button"></slot>
-      </el-form-item>
+          <!-- 时间选择器 -->
+          <el-date-picker
+            v-if="item.type === 'datePicker'"
+            v-model="query[item.prop]"
+            v-bind="item.option"
+            v-on="item.handles"
+          ></el-date-picker>
+
+          <!-- 其他类型可以根据需要扩展 -->
+        </el-form-item>
+      </template>
+        <el-form-item class="flex">
+          <template v-if="items.length">
+            <m-button class="ml-3" icon="el-icon-search" @click="onSubmit">查询</m-button>
+            <m-button class="ml-3" icon="el-icon-refresh" @click="onReset">重置</m-button>
+          </template>
+          <slot name="button"></slot>
+        </el-form-item>
     </el-form>
   </m-card>
 </template>
@@ -76,6 +78,7 @@
 export default {
   name: 'm-search',
   props: {
+    shadow: String,
     items: {
       type: Array,
       default: () => []

+ 1 - 1
src/store/modules/menu.js

@@ -71,7 +71,7 @@ const actions = {
         if (!response.data) {
           return resolve()
         }
-        commit('SET_ROUTES', response.data.routes)
+        commit('SET_ROUTES', JSON.parse(JSON.stringify(response.data.routes)))
         commit('SET_PERMISSION', response.data.permission)
         // 路由重新配置触发更新
         commit('SET_REFRESH', true)

+ 25 - 0
src/utils/dict.js

@@ -0,0 +1,25 @@
+import Vue from 'vue'
+
+import {
+  getOrganizationTree as getOrganizationTreeApi
+} from '@/api/system'
+
+export const MENU_TYPE = {
+  DIRECTORY: 0,
+  MENU: 1,
+  BUTTON: 2,
+  COMPONENT: 3
+}
+
+const organizationTree = []
+export const getOrganizationTree = async (data) => {
+  try {
+    if (!organizationTree.length) {
+      const { data } = await getOrganizationTreeApi()
+      return [data]
+    }
+    return organizationTree
+  } catch (error) {
+    Vue.$message.error(error.message)
+  }
+}

+ 27 - 0
src/utils/panorama.js

@@ -0,0 +1,27 @@
+// 涉及全景相关配置信息
+
+export const PAYROLL_HEADER = [
+  { label: '月份', prop: 'month' },
+  { label: '机构', prop: 'organizationName', width: 200 },
+  { label: '姓名', prop: 'employeeName', align: 'center' },
+  { label: '统一认证号', prop: 'unifiedCertificationNumber', align: 'center', width: 120 },
+  { label: '起薪类型', prop: 'payrollCategory' },
+  { label: '绩效工资', prop: 'meritPay', align: 'center' },
+  { label: '加班工资', prop: 'workOvertime', align: 'center' },
+  { label: '交通补贴', prop: 'commuteSubsidy' },
+  { label: '通讯补贴', prop: 'communicationSubsidy' },
+  { label: '午餐费补贴', prop: 'lunchSubsidy', width: 120 },
+  { label: '岗位贡献补贴', prop: 'jobContributionSubsidy', width: 120 },
+  { label: '交流干部补贴', prop: 'exchangeCadresSubsidy', width: 120 },
+  { label: '网点岗位津贴', prop: 'networkJobSubsidy', width: 120 },
+  { label: '网讯稿酬', prop: 'newsSubsidy' },
+  { label: '独生子女费', prop: 'onlyChild', width: 120 },
+  { label: '养老保险', prop: 'pensionInsurance' },
+  { label: '医疗保险', prop: 'medicalInsurance' },
+  { label: '失业保险', prop: 'unemploymentInsurance' },
+  { label: '住房公积金', prop: 'housingProvidentFund', width: 120 },
+  { label: '企业年金', prop: 'enterpriseAnnuity' },
+  { label: '个人所得税', prop: 'individualIncomeTax', width: 120 },
+  { label: '工资扣款', prop: 'payrollDeduction' },
+  { label: '合计', prop: 'totalSalary', align: 'center' }
+]

+ 9 - 10
src/views/humanResources/organizationStructure/index.vue

@@ -17,9 +17,11 @@
 
 <script>
 import {
-  getOrganizationTree,
   organizationDrill
 } from '@/api/system'
+import {
+  getOrganizationTree
+} from '@/utils/dict'
 export default {
   name: 'organization-structure',
   data () {
@@ -38,16 +40,13 @@ export default {
   },
   methods: {
     async init () {
-      try {
-        const { data } = await getOrganizationTree()
-        this.expandRowKeys = [data.uuid]
-        this.items = [
-          data
-        ]
-        this.total = this.items.length
-      } catch (error) {
-        this.$message.error(error)
+      const data = await getOrganizationTree()
+      if (!data) {
+        return
       }
+      this.expandRowKeys = [data[0].uuid]
+      this.items = data
+      this.total = this.items.length
     },
     async load (tree, treeNode, resolve) {
       try {

+ 116 - 0
src/views/humanResources/panorama/dynamic/salary/payroll.vue

@@ -0,0 +1,116 @@
+<template>
+  <div>
+    <m-search class="mb-3" shadow="never" v-model="searchValues" :items="searchItems" @search="onSearch"></m-search>
+    <m-table
+      shadow="never"
+      v-loading="loading"
+      :headers="headers"
+      :items="items"
+      :page-size="pageInfo.size"
+      :total="total"
+      :page-current="pageInfo.current"
+      @page-change="onPageChange"
+    ></m-table>
+  </div>
+</template>
+
+<script>
+import {
+  getSalaryPayrollPage
+} from '@/api/panorama'
+import {
+  PAYROLL_HEADER
+} from '@/utils/panorama'
+export default {
+  name: 'panorama-salary-payroll',
+  data () {
+    return {
+      pageInfo: {
+        current: 1,
+        size: 10
+      },
+      total: 0,
+      query: {
+        organizationNo: this.$route.query.organizationNo
+      },
+      headers: PAYROLL_HEADER,
+      items: [],
+      searchValues: {
+        employeeName: null,
+        month: null
+      }
+    }
+  },
+  created () {
+    const query = this.$route.query
+    if (query.certification) {
+      Object.assign(this.query, {
+        unifiedCertificationNumber: query.certification
+      })
+    }
+    this.onInit()
+  },
+  computed: {
+    searchItems () {
+      return [
+        {
+          label: '姓名',
+          prop: 'employeeName',
+          type: 'input',
+          hidden: this.$route.query.certification,
+          option: {
+            placeholder: '请输入姓名'
+          }
+        },
+        {
+          label: '月份',
+          prop: 'month',
+          type: 'datePicker',
+          option: {
+            type: 'month',
+            format: 'yyyy-MM',
+            valueFormat: 'yyyy-MM',
+            placeholder: '请选择更新月份'
+          }
+        }
+      ]
+    }
+  },
+  methods: {
+    async onInit () {
+      this.loading = true
+      try {
+        const { data } = await getSalaryPayrollPage({
+          page: {
+            ...this.pageInfo
+          },
+          ...this.query,
+          ...this.searchValues
+          // organizationNo: '123',
+          // employeeName: '',
+          // month: ''
+
+        })
+        this.items = data.records
+        this.total = data.total
+      } catch (error) {
+        this.$message.error(error.message)
+      } finally {
+        this.loading = false
+      }
+    },
+    onSearch () {
+      this.pageInfo.current = 1
+      this.onInit()
+    },
+    onPageChange (index) {
+      this.pageInfo.current = index
+      this.onInit()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 28 - 21
src/views/humanResources/panorama/index.vue

@@ -14,7 +14,7 @@
         <template v-slot="{ data }">
           <div class="text">
             <div>{{ data.organizationName }}</div>
-            <div class="eye link" @click.stop="onJump(data)">
+            <div class="eye link" @click.stop="onJump(data.organizationNo)">
               <span class="mdi mdi-eye"></span>
               <span class="min ml-1">
                 详情
@@ -35,17 +35,17 @@
       @page-change="onPageChange"
     >
       <template #parentOrganizationName="{ row }">
-        <span class="link" @click="onJump(row)">
+        <span class="link" @click="onJump(row.parentOrganizationNo)">
           {{ row.parentOrganizationName || '--' }}
         </span>
       </template>
       <template #deptName="{ row }">
-        <span class="link" @click="onJump(row)">
+        <span class="link" @click="onJump(row.organizationNo)">
           {{ row.deptName || '--' }}
         </span>
       </template>
       <template #employeeName="{ row }">
-        <span class="link" @click="onJump(row, true)">
+        <span class="link" @click="onJump(row.organizationNo, row)">
           {{ row.employeeName || '--' }}
         </span>
       </template>
@@ -63,9 +63,13 @@
 
 <script>
 import {
-  getOrganizationTree,
   getOrganizationDetails
 } from '@/api/system'
+
+import {
+  getOrganizationTree
+} from '@/utils/dict'
+import qs from 'qs'
 export default {
   name: 'human-resources-panorama',
   data () {
@@ -102,19 +106,16 @@ export default {
   },
   methods: {
     async getDept () {
-      try {
-        const { data } = await getOrganizationTree()
-        this.expandExpandedKeys = data.organizationNo
-        this.treeItems = [
-          data
-        ]
-        this.handleNodeClick({ organizationNo: data.organizationNo })
-        this.$nextTick(() => {
-          this.$refs.tree.setCurrentKey(data.organizationNo)
-        })
-      } catch (error) {
-        this.$message.error(error)
+      const data = await getOrganizationTree()
+      if (!data) {
+        return
       }
+      this.expandExpandedKeys = data[0].organizationNo
+      this.treeItems = data
+      this.handleNodeClick({ organizationNo: data[0].organizationNo })
+      this.$nextTick(() => {
+        this.$refs.tree.setCurrentKey(data[0].organizationNo)
+      })
     },
     async onInit () {
       this.loading = true
@@ -138,12 +139,18 @@ export default {
       this.searchQuery.organizationNo = organizationNo
       this.onInit()
     },
-    onJump (data, employee) {
+    onJump (organizationNo, employee) {
+      const _ROOT = '/human-resources/panorama/details'
+      const query = {
+        organizationNo: organizationNo
+      }
       if (employee) {
-        window.open('/human-resources/panorama/details?passes=' + data.passes)
-      } else {
-        window.open('/human-resources/panorama/details?organizationNo=' + data.organizationNo)
+        query.employeeNo = employee.personnelCode
+        query.employeeName = employee.employeeName
       }
+      console.log(query)
+      console.log(`${_ROOT}?${qs.stringify(query)}`)
+      window.open(`${_ROOT}?${qs.stringify(query)}`)
     },
     onPageChange (index) {
       this.pageInfo.current = index

+ 109 - 18
src/views/humanResources/panorama/panoramaDetails.vue

@@ -1,35 +1,120 @@
 <template>
   <div class="white pa-3 content">
-    <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
-      <el-menu-item index="1">处理中心</el-menu-item>
-      <el-submenu index="2">
-        <template slot="title">薪酬核算</template>
-        <el-menu-item index="2-1">选项1</el-menu-item>
-        <el-menu-item index="2-2">选项2</el-menu-item>
-        <el-menu-item index="2-3">选项3</el-menu-item>
-        <el-submenu index="2-4">
-          <template slot="title">选项4</template>
-          <el-menu-item index="2-4-1">选项1</el-menu-item>
-          <el-menu-item index="2-4-2">选项2</el-menu-item>
-          <el-menu-item index="2-4-3">选项3</el-menu-item>
-        </el-submenu>
-      </el-submenu>
-      <el-menu-item index="3" disabled>消息中心</el-menu-item>
-      <el-menu-item index="4"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
+    <el-menu :default-active="activeIndex" unique-opened class="el-menu-demo" mode="horizontal" @select="onSelect">
+      <PanoramaDetailsMenu v-for="_path in pathList" :key="_path.id" :item="_path"></PanoramaDetailsMenu>
     </el-menu>
+    <div class="py-5">
+      <el-breadcrumb>
+        <el-breadcrumb-item
+          v-for="(_path, i) in paths"
+          :key="_path.value"
+          :to="i < paths.length - 1 ? $route.path + '?organizationNo=' + _path.value : undefined"
+        >
+          {{ _path.label }}
+        </el-breadcrumb-item>
+        <el-breadcrumb-item>薪酬核算</el-breadcrumb-item>
+        <el-breadcrumb-item>工资单管理</el-breadcrumb-item>
+      </el-breadcrumb>
+    </div>
+    <div class="content-body">
+      <component :is="path" :key="$route.fullPath"></component>
+    </div>
   </div>
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
+import PanoramaDetailsMenu from './panoramaDetailsMenu.vue'
+
+import { getOrganizationTree } from '@/utils/dict'
+
 export default {
   name: 'panorama-details',
+  components: { PanoramaDetailsMenu },
   data () {
     return {
-      activeName: 'first'
+      activeIndex: 'salary/payroll',
+      paths: []
+    }
+  },
+  computed: {
+    ...mapGetters(['routes']),
+    pathList () {
+      return this.filterRoute(JSON.parse(JSON.stringify(this.routes)))
+    }
+  },
+  watch: {
+    '$route.fullPath': function () {
+      this.getDept()
     }
   },
   created () {
-    console.log(this.$route.query)
+    this.getDept()
+  },
+  methods: {
+    async getDept () {
+      const data = await getOrganizationTree()
+      if (!data) {
+        return
+      }
+      this.paths = findPath(data, this.$route.query.organizationNo)
+
+      // 递归查找机构编码,返回从根节点开始的路径数组{ value, label }
+      function findPath (tree, targetCode, path = []) {
+        for (const node of tree) {
+          // 将当前节点加入路径
+          const currentPath = [...path, { value: node.organizationNo, label: node.organizationName }]
+
+          // 如果当前节点是目标节点,返回路径
+          if (node.organizationNo === targetCode) {
+            return currentPath
+          }
+
+          // 如果当前节点有子节点,递归查找
+          if (node.child && node.child.length > 0) {
+            const result = findPath(node.child, targetCode, currentPath)
+            if (result) {
+              return result
+            }
+          }
+        }
+
+        // 如果没有找到目标节点,返回 null
+        return null
+      }
+    },
+    path () {
+      return import('./dynamic/salary/payroll.vue')
+    },
+    filterRoute (items) {
+      return items.filter(item => {
+        if (item.children && item.children.length) {
+          item.children = this.filterRoute(item.children)
+        }
+        return item.meta.panorama
+      })
+    },
+    // findFirst (items, path = []) {
+    //   const _first = items[0]
+    //   path.push(_first.label)
+    //   console.log(_first, MENU_TYPE.MENU, _first.children.length)
+    //   if (_first.type === 1) {
+    //     return path.join('/')
+    //   }
+    //   if (_first.children && _first.children.length) {
+    //     const _items = _first.children
+    //     const res = this.firstShow(_items, path)
+    //     if (res) {
+    //       return res
+    //     }
+    //   }
+    //   path.pop()
+    //   return null
+    // },
+    onSelect () {},
+    onRun (path) {
+      window.open(this.$route.path + '?organizationNo=' + path)
+    }
   }
 }
 </script>
@@ -39,5 +124,11 @@ export default {
   width: 100%;
   height: 100%;
   box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  &-body {
+    height: 0;
+    flex: 1;
+  }
 }
 </style>

+ 27 - 0
src/views/humanResources/panorama/panoramaDetailsMenu.vue

@@ -0,0 +1,27 @@
+<template>
+  <el-submenu v-if="item.children && item.children.length" :index="item.component">
+    <template slot="title">{{ item.label }}</template>
+    <panorama-details-menu
+      v-for="(child, index) in item.children"
+      :key="index"
+      :item="child"
+    />
+  </el-submenu>
+  <el-menu-item v-else :index="item.component">{{ item.label }}</el-menu-item>
+</template>
+
+<script>
+export default {
+  name: 'panorama-details-menu',
+  props: {
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 17 - 17
src/views/humanResources/welfare/welfareHistory.vue

@@ -1,21 +1,18 @@
 <template>
   <DrawerHistory ref="drawerHistoryRefs" :get-page="getPage">
     <template #panel="{ item }">
-      <h3 class="mb-3 title">{{ item.name }}</h3>
-        <el-form label-position="left" class="m-form">
-          <el-form-item label="配置分类">
-            <span>{{ item.category }}</span>
-          </el-form-item>
-          <el-form-item label="数值">
-            <span>{{ item.value }}</span>
-          </el-form-item>
-          <el-form-item label="计算月份">
-            <span>{{ item.month }}</span>
-          </el-form-item>
-          <el-form-item label="描述">
-            <span>{{ item.tag }}</span>
-          </el-form-item>
-        </el-form>
+      <h3 class="mb-3 title">{{ item.subsidyName }}</h3>
+      <el-form label-position="left" class="m-form">
+        <el-form-item label="机构名称">
+          <span>{{ item.subsidyOrganizationNames }}</span>
+        </el-form-item>
+        <el-form-item label="福利薪资">
+          <span>{{ item.subsidySalary }}</span>
+        </el-form-item>
+        <el-form-item label="福利条件描述">
+          <span>{{ item.subsidyCheck }}</span>
+        </el-form-item>
+      </el-form>
     </template>
   </DrawerHistory>
 </template>
@@ -32,11 +29,14 @@ export default {
     DrawerHistory
   },
   data () {
-    return {}
+    return {
+      uuid: null
+    }
   },
   methods: {
     async open (item) {
-      this.$refs.drawerHistoryRefs.open(item.name)
+      this.uuid = item.uuid
+      this.$refs.drawerHistoryRefs.open(`历史记录 - ${item.subsidyName}`)
     },
     getPage (pageInfo) {
       return new Promise((resolve, reject) => {

+ 5 - 6
src/views/humanResources/welfare/welfareRules.vue

@@ -73,7 +73,7 @@ import {
 
 import {
   getOrganizationTree
-} from '@/api/system'
+} from '@/utils/dict'
 export default {
   name: 'welfare-rules',
   data () {
@@ -122,12 +122,11 @@ export default {
       }
     },
     async getDeptTree () {
-      try {
-        const { data } = await getOrganizationTree()
-        this.options = this.assignData([data])
-      } catch (error) {
-        this.$message.error(error)
+      const data = await getOrganizationTree()
+      if (!data) {
+        return
       }
+      this.options = this.assignData(data)
     },
     assignData (data) {
       return data.map(res => {

+ 129 - 13
src/views/salary/allocation/index.vue

@@ -1,6 +1,24 @@
 <template>
   <div class="pa-3 white">
     <m-search :items="searchItems" v-model="searchValues" class="mb-3" @search="onSearch">
+      <template #button>
+        <m-button
+          :type="isEdit ? 'success' : 'primary'"
+          size="small"
+          :icon="isEdit ? 'el-icon-check' : 'el-icon-edit'"
+          @click="onDistribute"
+        >
+          {{ isEdit ? '保存分配' : '编辑分配'}}
+        </m-button>
+        <m-button
+          type="primary"
+          size="small"
+          icon="el-icon-finished"
+          @click="onDistribute"
+        >
+          确认分配
+        </m-button>
+      </template>
     </m-search>
     <m-table
       :items="items"
@@ -11,21 +29,54 @@
       :page-current="pageInfo.current"
       @page-change="onPageChange"
       @expand-change="onExpandChange"
-    ></m-table>
+    >
+      <template #status="{ row }">
+        <el-tag :type="statusList[row.status].color">{{ statusList[row.status].text }}</el-tag>
+      </template>
+      <template #allocationPerformanceSalary="{ row }">
+        <template v-if="isEdit">
+          <el-input-number v-model="values[row.employeePerformanceId]" placeholder="分配绩效" size="small"></el-input-number>
+        </template>
+        <template v-else>
+          {{ row.allocationPerformanceSalary }}
+        </template>
+      </template>
+    </m-table>
   </div>
 </template>
 
 <script>
 import { dateFormat } from '@/utils/date'
+import {
+  getAllocationPage,
+  saveAllocationGrant
+  // getAllocationEmployeeCategory
+} from '@/api/salary'
 export default {
   name: 'salary-allocation',
   data () {
     return {
+      values: {},
+      isEdit: false,
+      statusList: {
+        0: {
+          text: '未分配',
+          color: 'warning'
+        },
+        1: {
+          text: '已分配',
+          color: 'success'
+        },
+        2: {
+          text: '已确认',
+          color: 'info'
+        }
+      },
       searchItems: [
         {
           label: '月份',
           prop: 'month',
-          type: 'date',
+          type: 'datePicker',
           option: {
             placeholder: '请选择月份',
             type: 'month',
@@ -37,17 +88,23 @@ export default {
       searchValues: {
         month: dateFormat('YYYY-mm', new Date())
       },
+      query: {
+        month: dateFormat('YYYY-mm', new Date())
+      },
       headers: [
         { label: '月份', prop: 'month' },
-        { label: '状态', prop: 'state' },
-        { label: '机构', prop: 'organizationName' },
-        { label: '姓名', prop: 'employeeName' },
-        { label: '统一认证号', prop: 'unifiedCertificationNumber' },
-        { label: '岗位', prop: 'unifiedCertificationNumber' },
-        { label: '当月薪资', prop: 'unifiedCertificationNumber' },
-        { label: '可分配绩效金额', prop: 'unifiedCertificationNumber' },
-        { label: '已分配绩效金额', prop: 'unifiedCertificationNumber' },
-        { label: '操作', prop: 'action', align: 'center', width: 100 }
+        { label: '机构名称', prop: 'organizationName' },
+        { label: '员工姓名', prop: 'employeeName' },
+        { label: '分配状态', prop: 'status' },
+        { label: '员工类型', prop: 'employeeCategory' },
+        { label: '可分配绩效薪资', prop: 'assignablePerformanceSalary', width: 150 },
+        { label: '领导分配绩效薪资', prop: 'allocationPerformanceSalary', width: 150 },
+        { label: '基础绩效薪资', prop: 'basicPerformanceSalary', width: 120 },
+        { label: '总绩效薪资', prop: 'performanceSalary' },
+        { label: '数据来源', prop: 'dataType' },
+        { label: '数据版本', prop: 'version', width: 160 },
+        { label: '创建时间', prop: 'createDate', width: 160 },
+        { label: '操作', prop: 'actions', align: 'center', width: 100, fixed: 'right' }
       ],
       items: [
       ],
@@ -57,10 +114,69 @@ export default {
         current: 1,
         size: 10
       },
-      method: {
-        onExpandChange () {}
+      orders: []
+
+    }
+  },
+  created () {
+    this.onInit()
+  },
+  methods: {
+    async onInit () {
+      try {
+        const { data } = await getAllocationPage({
+          page: {
+            ...this.pageInfo,
+            orders: this.orders
+          },
+          entity: {
+            ...this.searchValues
+          }
+        })
+        this.items = data.records.map(e => {
+          e.createDate = dateFormat('YYYY-mm-dd HH:MM:SS', new Date(e.createDate))
+          return e
+        })
+        this.total = data.total
+      } catch (error) {
+        this.$snackbar.error(error)
       }
+    },
+    onSearch () {
+      this.pageInfo.current = 1
+      this.query = { ...this.searchValues }
+      this.onInit()
+    },
+    // 领导分配绩效薪资
+    onDistribute () {
+      if (this.isEdit) {
+        this.$confirm('确定保存当前数据?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.onSave()
+        }).catch((error) => {
+          this.$message.error(error)
+        })
+      }
+      this.isEdit = !this.isEdit
+    },
+    async onSave () {
+      try {
+        const { data } = await saveAllocationGrant({
 
+        })
+        console.log(data)
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    onDetails () {},
+    onExpandChange () {},
+    onPageChange (index) {
+      this.pageInfo.current = index
+      this.onInit()
     }
   }
 }

+ 1 - 1
src/views/salary/comparison/index.vue

@@ -85,7 +85,7 @@ export default {
         {
           label: '月份',
           prop: 'month',
-          type: 'date',
+          type: 'datePicker',
           option: {
             editable: false,
             clearable: false,

+ 5 - 21
src/views/salary/payroll/index.vue

@@ -14,11 +14,7 @@
       :page-current="pageInfo.current"
       @page-change="onPageChange"
     >
-      <template #actions="{ row }">
-        <m-button text type="primary" @click="onDetail(row)">明细</m-button>
-      </template>
     </m-table>
-    <payrollDetails ref="payrollDetailsRefs"></payrollDetails>
   </div>
 </template>
 
@@ -28,12 +24,11 @@ import {
   downloadPayroll
 } from '@/api/salary'
 import { downloadFile } from '@/utils'
-import payrollDetails from './payrollDetails.vue'
+import {
+  PAYROLL_HEADER
+} from '@/utils/panorama'
 export default {
   name: 'salary-payroll',
-  components: {
-    payrollDetails
-  },
   data () {
     return {
       exportLoading: false,
@@ -41,7 +36,7 @@ export default {
         {
           label: '月份',
           prop: 'month',
-          type: 'date',
+          type: 'datePicker',
           option: {
             placeholder: '请选择月份',
             type: 'month',
@@ -57,18 +52,7 @@ export default {
         organizationName: null,
         employeeName: null
       },
-      headers: [
-        { label: '月份', prop: 'month' },
-        { label: '机构', prop: 'organizationName' },
-        { label: '姓名', prop: 'employeeName', align: 'center' },
-        { label: '统一认证号', prop: 'unifiedCertificationNumber', align: 'center' },
-        { label: '绩效工资', prop: 'meritPay', align: 'center' },
-        // { label: '企业年金', prop: 'enterpriseAnnuity', align: 'center' },
-        // { label: '住房公积金', prop: 'housingProvidentFund', align: 'center' },
-        { label: '加班工资', prop: 'workOvertime', align: 'center' },
-        { label: '合计', prop: 'totalSalary', align: 'center' },
-        { label: '操作', prop: 'actions', width: 100, fixed: 'right' }
-      ],
+      headers: PAYROLL_HEADER,
       items: [],
       loading: false,
       total: 0,

+ 0 - 88
src/views/salary/payroll/payrollDetails.vue

@@ -1,88 +0,0 @@
-<template>
-  <m-dialog ref="dialog" :title="`${itemData.month} 工资单明细`" v-bind="$attrs" >
-    <div v-loading="loading"></div>
-    <!-- <el-descriptions :column="2" :border="true">
-      <el-descriptions-item :labelStyle="{'width': '200px'}">
-        <template slot="label">
-          用户名
-        </template>
-        {{ itemData.employeeName }}
-      </el-descriptions-item>
-      <el-descriptions-item :labelStyle="{'width': '200px'}">
-        <template slot="label">
-          机构
-        </template>
-        {{ itemData.organizationName }}
-      </el-descriptions-item>
-      <el-descriptions-item :labelStyle="{'width': '200px'}">
-        <template slot="label">
-          统一认证号
-        </template>
-        {{ itemData.unifiedCertificationNumber }}
-      </el-descriptions-item>
-      <el-descriptions-item :labelStyle="{'width': '200px'}">
-        <template slot="label">
-          起薪类型
-        </template>
-        {{ itemData.payrollCategory }}
-      </el-descriptions-item>
-    </el-descriptions> -->
-    <el-descriptions :column="2" :border="true" class="mt-3">
-      <el-descriptions-item v-for="item in items" :key="item.label" :labelStyle="{'width': '150px'}">
-        <template slot="label">
-          {{ item.label }}
-        </template>
-        {{ itemData[item.value] }}
-      </el-descriptions-item>
-    </el-descriptions>
-  </m-dialog>
-</template>
-
-<script>
-// import {
-//   getPayrollDetails
-// } from '@/api/salary'
-export default {
-  name: 'payroll-details',
-  data () {
-    return {
-      loading: false,
-      itemData: {},
-      items: [
-        { label: '用户名', value: 'employeeName' },
-        { label: '机构', value: 'organizationName' },
-        { label: '统一认证号', value: 'unifiedCertificationNumber' },
-        { label: '起薪类型', value: 'payrollCategory' },
-        { label: '交通补贴', value: 'commuteSubsidy' },
-        { label: '通讯补贴', value: 'communicationSubsidy' },
-        { label: '午餐费补贴', value: 'lunchSubsidy' },
-        { label: '岗位贡献补贴', value: 'jobContributionSubsidy' },
-        { label: '交流干部补贴', value: 'exchangeCadresSubsidy' },
-        { label: '网点岗位津贴', value: 'networkJobSubsidy' },
-        { label: '绩效工资', value: 'meritPay' },
-        { label: '网讯稿酬', value: 'newsSubsidy' },
-        { label: '加班工资', value: 'workOvertime' },
-        { label: '独生子女费', value: 'onlyChild' },
-        { label: '养老保险', value: 'pensionInsurance' },
-        { label: '医疗保险', value: 'medicalInsurance' },
-        { label: '失业保险', value: 'unemploymentInsurance' },
-        { label: '住房公积金', value: 'housingProvidentFund' },
-        { label: '企业年金', value: 'enterpriseAnnuity' },
-        { label: '个人所得税', value: 'individualIncomeTax' },
-        { label: '工资扣款', value: 'payrollDeduction' },
-        { label: '合计', value: 'totalSalary' }
-      ]
-    }
-  },
-  methods: {
-    async open (item) {
-      this.itemData = item
-      this.$refs.dialog.open()
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-
-</style>

+ 13 - 12
src/views/system/menu/menuEdit.vue

@@ -36,6 +36,7 @@
 </template>
 
 <script>
+import { MENU_TYPE } from '@/utils/dict'
 export default {
   name: 'menu-edit',
   data () {
@@ -70,24 +71,24 @@ export default {
           options: {
             size: 'small',
             items: [
-              { text: '目录', label: 0 },
-              { text: '菜单', label: 1 },
-              { text: '按钮', label: 2 },
-              { text: '组件', label: 3 }
+              { text: '目录', label: MENU_TYPE.DIRECTORY },
+              { text: '菜单', label: MENU_TYPE.MENU },
+              { text: '按钮', label: MENU_TYPE.BUTTON },
+              { text: '组件', label: MENU_TYPE.COMPONENT }
             ]
           }
         },
         {
           label: '菜单图标',
           prop: 'icon',
-          hidden: [2].includes(type),
+          hidden: [MENU_TYPE.BUTTON].includes(type),
           type: 'input',
           options: { placeholder: '请输入菜单图标' }
         },
         {
           label: '路由地址',
           prop: 'path',
-          hidden: [2, 3].includes(type),
+          hidden: [MENU_TYPE.BUTTON, MENU_TYPE.COMPONENT].includes(type),
           type: 'input',
           options: { placeholder: '请输入路由地址' },
           rules: [{ required: true, message: '请输入路由地址', trigger: 'change' }]
@@ -95,7 +96,7 @@ export default {
         {
           label: '组件地址',
           prop: 'component',
-          hidden: [2].includes(type),
+          hidden: [MENU_TYPE.BUTTON].includes(type),
           type: 'input',
           options: { placeholder: '请输入组件地址' },
           rules: [{ required: type !== 0, message: '请输入组件地址', trigger: 'change' }]
@@ -103,7 +104,7 @@ export default {
         {
           label: '组件名称',
           prop: 'name',
-          hidden: [2].includes(type),
+          hidden: [MENU_TYPE.BUTTON].includes(type),
           type: 'input',
           options: { placeholder: '请输入组件名称' },
           rules: [{ required: true, message: '请输入组件名称', trigger: 'change' }]
@@ -111,7 +112,7 @@ export default {
         {
           label: '权限标识',
           prop: 'code',
-          hidden: [0, 1, 3].includes(type),
+          hidden: [MENU_TYPE.DIRECTORY, MENU_TYPE.MENU, MENU_TYPE.COMPONENT].includes(type),
           type: 'input',
           options: { placeholder: '请输入权限标识' },
           rules: [{ required: true, message: '请输入权限标识', trigger: 'change' }]
@@ -138,7 +139,7 @@ export default {
           label: '缓存状态',
           prop: 'keepAlive',
           type: 'radioGroup',
-          hidden: [0, 2, 3].includes(type),
+          hidden: [MENU_TYPE.DIRECTORY, MENU_TYPE.BUTTON, MENU_TYPE.COMPONENT].includes(type),
           options: {
             items: [
               { text: '缓存', label: true },
@@ -150,7 +151,7 @@ export default {
           label: '开启新窗口',
           prop: 'window',
           type: 'radioGroup',
-          hidden: [0, 2, 3].includes(type),
+          hidden: [MENU_TYPE.DIRECTORY, MENU_TYPE.BUTTON, MENU_TYPE.COMPONENT].includes(type),
           options: {
             items: [
               { text: '开启', label: true },
@@ -162,7 +163,7 @@ export default {
           label: '全景视图',
           prop: 'panorama',
           type: 'radioGroup',
-          hidden: [2].includes(type),
+          hidden: [MENU_TYPE.BUTTON].includes(type),
           options: {
             items: [
               { text: '加入', label: true },

+ 4 - 10
src/views/system/role/roleData.vue

@@ -58,7 +58,7 @@ import { getRoleDataPermissionList } from '@/api/user'
 
 import {
   getOrganizationTree
-} from '@/api/system'
+} from '@/utils/dict'
 export default {
   name: 'role-data',
   data () {
@@ -139,15 +139,9 @@ export default {
       data.items = []
     },
     async getOrganization () {
-      try {
-        const { data } = await getOrganizationTree()
-        this.expandRowKeys = [data.organizationNo]
-        this.treeItems = [
-          data
-        ]
-      } catch (error) {
-        this.$message.error(error)
-      }
+      const data = await getOrganizationTree()
+      this.expandRowKeys = [data[0].organizationNo]
+      this.treeItems = data
     }
   }
 }